| import Path from '../graphic/Path'; |
| import PathProxy from '../core/PathProxy'; |
| import transformPath from './transformPath'; // command chars |
| // var cc = [ |
| // 'm', 'M', 'l', 'L', 'v', 'V', 'h', 'H', 'z', 'Z', |
| // 'c', 'C', 'q', 'Q', 't', 'T', 's', 'S', 'a', 'A' |
| // ]; |
| |
| var mathSqrt = Math.sqrt; |
| var mathSin = Math.sin; |
| var mathCos = Math.cos; |
| var PI = Math.PI; |
| |
| var vMag = function (v) { |
| return Math.sqrt(v[0] * v[0] + v[1] * v[1]); |
| }; |
| |
| var vRatio = function (u, v) { |
| return (u[0] * v[0] + u[1] * v[1]) / (vMag(u) * vMag(v)); |
| }; |
| |
| var vAngle = function (u, v) { |
| return (u[0] * v[1] < u[1] * v[0] ? -1 : 1) * Math.acos(vRatio(u, v)); |
| }; |
| |
| function processArc(x1, y1, x2, y2, fa, fs, rx, ry, psiDeg, cmd, path) { |
| var psi = psiDeg * (PI / 180.0); |
| var xp = mathCos(psi) * (x1 - x2) / 2.0 + mathSin(psi) * (y1 - y2) / 2.0; |
| var yp = -1 * mathSin(psi) * (x1 - x2) / 2.0 + mathCos(psi) * (y1 - y2) / 2.0; |
| var lambda = xp * xp / (rx * rx) + yp * yp / (ry * ry); |
| |
| if (lambda > 1) { |
| rx *= mathSqrt(lambda); |
| ry *= mathSqrt(lambda); |
| } |
| |
| var f = (fa === fs ? -1 : 1) * mathSqrt((rx * rx * (ry * ry) - rx * rx * (yp * yp) - ry * ry * (xp * xp)) / (rx * rx * (yp * yp) + ry * ry * (xp * xp))) || 0; |
| var cxp = f * rx * yp / ry; |
| var cyp = f * -ry * xp / rx; |
| var cx = (x1 + x2) / 2.0 + mathCos(psi) * cxp - mathSin(psi) * cyp; |
| var cy = (y1 + y2) / 2.0 + mathSin(psi) * cxp + mathCos(psi) * cyp; |
| var theta = vAngle([1, 0], [(xp - cxp) / rx, (yp - cyp) / ry]); |
| var u = [(xp - cxp) / rx, (yp - cyp) / ry]; |
| var v = [(-1 * xp - cxp) / rx, (-1 * yp - cyp) / ry]; |
| var dTheta = vAngle(u, v); |
| |
| if (vRatio(u, v) <= -1) { |
| dTheta = PI; |
| } |
| |
| if (vRatio(u, v) >= 1) { |
| dTheta = 0; |
| } |
| |
| if (fs === 0 && dTheta > 0) { |
| dTheta = dTheta - 2 * PI; |
| } |
| |
| if (fs === 1 && dTheta < 0) { |
| dTheta = dTheta + 2 * PI; |
| } |
| |
| path.addData(cmd, cx, cy, rx, ry, theta, dTheta, psi, fs); |
| } |
| |
| var commandReg = /([mlvhzcqtsa])([^mlvhzcqtsa]*)/ig; // Consider case: |
| // (1) delimiter can be comma or space, where continuous commas |
| // or spaces should be seen as one comma. |
| // (2) value can be like: |
| // '2e-4', 'l.5.9' (ignore 0), 'M-10-10', 'l-2.43e-1,34.9983', |
| // 'l-.5E1,54', '121-23-44-11' (no delimiter) |
| |
| var numberReg = /-?([0-9]*\.)?[0-9]+([eE]-?[0-9]+)?/g; // var valueSplitReg = /[\s,]+/; |
| |
| function createPathProxyFromString(data) { |
| if (!data) { |
| return new PathProxy(); |
| } // var data = data.replace(/-/g, ' -') |
| // .replace(/ /g, ' ') |
| // .replace(/ /g, ',') |
| // .replace(/,,/g, ','); |
| // var n; |
| // create pipes so that we can split the data |
| // for (n = 0; n < cc.length; n++) { |
| // cs = cs.replace(new RegExp(cc[n], 'g'), '|' + cc[n]); |
| // } |
| // data = data.replace(/-/g, ',-'); |
| // create array |
| // var arr = cs.split('|'); |
| // init context point |
| |
| |
| var cpx = 0; |
| var cpy = 0; |
| var subpathX = cpx; |
| var subpathY = cpy; |
| var prevCmd; |
| var path = new PathProxy(); |
| var CMD = PathProxy.CMD; // commandReg.lastIndex = 0; |
| // var cmdResult; |
| // while ((cmdResult = commandReg.exec(data)) != null) { |
| // var cmdStr = cmdResult[1]; |
| // var cmdContent = cmdResult[2]; |
| |
| var cmdList = data.match(commandReg); |
| |
| for (var l = 0; l < cmdList.length; l++) { |
| var cmdText = cmdList[l]; |
| var cmdStr = cmdText.charAt(0); |
| var cmd; // String#split is faster a little bit than String#replace or RegExp#exec. |
| // var p = cmdContent.split(valueSplitReg); |
| // var pLen = 0; |
| // for (var i = 0; i < p.length; i++) { |
| // // '' and other invalid str => NaN |
| // var val = parseFloat(p[i]); |
| // !isNaN(val) && (p[pLen++] = val); |
| // } |
| |
| var p = cmdText.match(numberReg) || []; |
| var pLen = p.length; |
| |
| for (var i = 0; i < pLen; i++) { |
| p[i] = parseFloat(p[i]); |
| } |
| |
| var off = 0; |
| |
| while (off < pLen) { |
| var ctlPtx; |
| var ctlPty; |
| var rx; |
| var ry; |
| var psi; |
| var fa; |
| var fs; |
| var x1 = cpx; |
| var y1 = cpy; // convert l, H, h, V, and v to L |
| |
| switch (cmdStr) { |
| case 'l': |
| cpx += p[off++]; |
| cpy += p[off++]; |
| cmd = CMD.L; |
| path.addData(cmd, cpx, cpy); |
| break; |
| |
| case 'L': |
| cpx = p[off++]; |
| cpy = p[off++]; |
| cmd = CMD.L; |
| path.addData(cmd, cpx, cpy); |
| break; |
| |
| case 'm': |
| cpx += p[off++]; |
| cpy += p[off++]; |
| cmd = CMD.M; |
| path.addData(cmd, cpx, cpy); |
| subpathX = cpx; |
| subpathY = cpy; |
| cmdStr = 'l'; |
| break; |
| |
| case 'M': |
| cpx = p[off++]; |
| cpy = p[off++]; |
| cmd = CMD.M; |
| path.addData(cmd, cpx, cpy); |
| subpathX = cpx; |
| subpathY = cpy; |
| cmdStr = 'L'; |
| break; |
| |
| case 'h': |
| cpx += p[off++]; |
| cmd = CMD.L; |
| path.addData(cmd, cpx, cpy); |
| break; |
| |
| case 'H': |
| cpx = p[off++]; |
| cmd = CMD.L; |
| path.addData(cmd, cpx, cpy); |
| break; |
| |
| case 'v': |
| cpy += p[off++]; |
| cmd = CMD.L; |
| path.addData(cmd, cpx, cpy); |
| break; |
| |
| case 'V': |
| cpy = p[off++]; |
| cmd = CMD.L; |
| path.addData(cmd, cpx, cpy); |
| break; |
| |
| case 'C': |
| cmd = CMD.C; |
| path.addData(cmd, p[off++], p[off++], p[off++], p[off++], p[off++], p[off++]); |
| cpx = p[off - 2]; |
| cpy = p[off - 1]; |
| break; |
| |
| case 'c': |
| cmd = CMD.C; |
| path.addData(cmd, p[off++] + cpx, p[off++] + cpy, p[off++] + cpx, p[off++] + cpy, p[off++] + cpx, p[off++] + cpy); |
| cpx += p[off - 2]; |
| cpy += p[off - 1]; |
| break; |
| |
| case 'S': |
| ctlPtx = cpx; |
| ctlPty = cpy; |
| var len = path.len(); |
| var pathData = path.data; |
| |
| if (prevCmd === CMD.C) { |
| ctlPtx += cpx - pathData[len - 4]; |
| ctlPty += cpy - pathData[len - 3]; |
| } |
| |
| cmd = CMD.C; |
| x1 = p[off++]; |
| y1 = p[off++]; |
| cpx = p[off++]; |
| cpy = p[off++]; |
| path.addData(cmd, ctlPtx, ctlPty, x1, y1, cpx, cpy); |
| break; |
| |
| case 's': |
| ctlPtx = cpx; |
| ctlPty = cpy; |
| var len = path.len(); |
| var pathData = path.data; |
| |
| if (prevCmd === CMD.C) { |
| ctlPtx += cpx - pathData[len - 4]; |
| ctlPty += cpy - pathData[len - 3]; |
| } |
| |
| cmd = CMD.C; |
| x1 = cpx + p[off++]; |
| y1 = cpy + p[off++]; |
| cpx += p[off++]; |
| cpy += p[off++]; |
| path.addData(cmd, ctlPtx, ctlPty, x1, y1, cpx, cpy); |
| break; |
| |
| case 'Q': |
| x1 = p[off++]; |
| y1 = p[off++]; |
| cpx = p[off++]; |
| cpy = p[off++]; |
| cmd = CMD.Q; |
| path.addData(cmd, x1, y1, cpx, cpy); |
| break; |
| |
| case 'q': |
| x1 = p[off++] + cpx; |
| y1 = p[off++] + cpy; |
| cpx += p[off++]; |
| cpy += p[off++]; |
| cmd = CMD.Q; |
| path.addData(cmd, x1, y1, cpx, cpy); |
| break; |
| |
| case 'T': |
| ctlPtx = cpx; |
| ctlPty = cpy; |
| var len = path.len(); |
| var pathData = path.data; |
| |
| if (prevCmd === CMD.Q) { |
| ctlPtx += cpx - pathData[len - 4]; |
| ctlPty += cpy - pathData[len - 3]; |
| } |
| |
| cpx = p[off++]; |
| cpy = p[off++]; |
| cmd = CMD.Q; |
| path.addData(cmd, ctlPtx, ctlPty, cpx, cpy); |
| break; |
| |
| case 't': |
| ctlPtx = cpx; |
| ctlPty = cpy; |
| var len = path.len(); |
| var pathData = path.data; |
| |
| if (prevCmd === CMD.Q) { |
| ctlPtx += cpx - pathData[len - 4]; |
| ctlPty += cpy - pathData[len - 3]; |
| } |
| |
| cpx += p[off++]; |
| cpy += p[off++]; |
| cmd = CMD.Q; |
| path.addData(cmd, ctlPtx, ctlPty, cpx, cpy); |
| break; |
| |
| case 'A': |
| rx = p[off++]; |
| ry = p[off++]; |
| psi = p[off++]; |
| fa = p[off++]; |
| fs = p[off++]; |
| x1 = cpx, y1 = cpy; |
| cpx = p[off++]; |
| cpy = p[off++]; |
| cmd = CMD.A; |
| processArc(x1, y1, cpx, cpy, fa, fs, rx, ry, psi, cmd, path); |
| break; |
| |
| case 'a': |
| rx = p[off++]; |
| ry = p[off++]; |
| psi = p[off++]; |
| fa = p[off++]; |
| fs = p[off++]; |
| x1 = cpx, y1 = cpy; |
| cpx += p[off++]; |
| cpy += p[off++]; |
| cmd = CMD.A; |
| processArc(x1, y1, cpx, cpy, fa, fs, rx, ry, psi, cmd, path); |
| break; |
| } |
| } |
| |
| if (cmdStr === 'z' || cmdStr === 'Z') { |
| cmd = CMD.Z; |
| path.addData(cmd); // z may be in the middle of the path. |
| |
| cpx = subpathX; |
| cpy = subpathY; |
| } |
| |
| prevCmd = cmd; |
| } |
| |
| path.toStatic(); |
| return path; |
| } // TODO Optimize double memory cost problem |
| |
| |
| function createPathOptions(str, opts) { |
| var pathProxy = createPathProxyFromString(str); |
| opts = opts || {}; |
| |
| opts.buildPath = function (path) { |
| if (path.setData) { |
| path.setData(pathProxy.data); // Svg and vml renderer don't have context |
| |
| var ctx = path.getContext(); |
| |
| if (ctx) { |
| path.rebuildPath(ctx); |
| } |
| } else { |
| var ctx = path; |
| pathProxy.rebuildPath(ctx); |
| } |
| }; |
| |
| opts.applyTransform = function (m) { |
| transformPath(pathProxy, m); |
| this.dirty(true); |
| }; |
| |
| return opts; |
| } |
| /** |
| * Create a Path object from path string data |
| * http://www.w3.org/TR/SVG/paths.html#PathData |
| * @param {Object} opts Other options |
| */ |
| |
| |
| export function createFromString(str, opts) { |
| return new Path(createPathOptions(str, opts)); |
| } |
| /** |
| * Create a Path class from path string data |
| * @param {string} str |
| * @param {Object} opts Other options |
| */ |
| |
| export function extendFromString(str, opts) { |
| return Path.extend(createPathOptions(str, opts)); |
| } |
| /** |
| * Merge multiple paths |
| */ |
| // TODO Apply transform |
| // TODO stroke dash |
| // TODO Optimize double memory cost problem |
| |
| export function mergePath(pathEls, opts) { |
| var pathList = []; |
| var len = pathEls.length; |
| |
| for (var i = 0; i < len; i++) { |
| var pathEl = pathEls[i]; |
| |
| if (!pathEl.path) { |
| pathEl.createPathProxy(); |
| } |
| |
| if (pathEl.__dirtyPath) { |
| pathEl.buildPath(pathEl.path, pathEl.shape, true); |
| } |
| |
| pathList.push(pathEl.path); |
| } |
| |
| var pathBundle = new Path(opts); // Need path proxy. |
| |
| pathBundle.createPathProxy(); |
| |
| pathBundle.buildPath = function (path) { |
| path.appendPath(pathList); // Svg and vml renderer don't have context |
| |
| var ctx = path.getContext(); |
| |
| if (ctx) { |
| path.rebuildPath(ctx); |
| } |
| }; |
| |
| return pathBundle; |
| } |