"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Path = void 0; const Factory_1 = require("../Factory"); const Shape_1 = require("../Shape"); const Global_1 = require("../Global"); const BezierFunctions_1 = require("../BezierFunctions"); class Path extends Shape_1.Shape { constructor(config) { super(config); this.dataArray = []; this.pathLength = 0; this._readDataAttribute(); this.on('dataChange.konva', function () { this._readDataAttribute(); }); } _readDataAttribute() { this.dataArray = Path.parsePathData(this.data()); this.pathLength = Path.getPathLength(this.dataArray); } _sceneFunc(context) { var ca = this.dataArray; context.beginPath(); var isClosed = false; for (var n = 0; n < ca.length; n++) { var c = ca[n].command; var p = ca[n].points; switch (c) { case 'L': context.lineTo(p[0], p[1]); break; case 'M': context.moveTo(p[0], p[1]); break; case 'C': context.bezierCurveTo(p[0], p[1], p[2], p[3], p[4], p[5]); break; case 'Q': context.quadraticCurveTo(p[0], p[1], p[2], p[3]); break; case 'A': var cx = p[0], cy = p[1], rx = p[2], ry = p[3], theta = p[4], dTheta = p[5], psi = p[6], fs = p[7]; var r = rx > ry ? rx : ry; var scaleX = rx > ry ? 1 : rx / ry; var scaleY = rx > ry ? ry / rx : 1; context.translate(cx, cy); context.rotate(psi); context.scale(scaleX, scaleY); context.arc(0, 0, r, theta, theta + dTheta, 1 - fs); context.scale(1 / scaleX, 1 / scaleY); context.rotate(-psi); context.translate(-cx, -cy); break; case 'z': isClosed = true; context.closePath(); break; } } if (!isClosed && !this.hasFill()) { context.strokeShape(this); } else { context.fillStrokeShape(this); } } getSelfRect() { var points = []; this.dataArray.forEach(function (data) { if (data.command === 'A') { var start = data.points[4]; var dTheta = data.points[5]; var end = data.points[4] + dTheta; var inc = Math.PI / 180.0; if (Math.abs(start - end) < inc) { inc = Math.abs(start - end); } if (dTheta < 0) { for (let t = start - inc; t > end; t -= inc) { const point = Path.getPointOnEllipticalArc(data.points[0], data.points[1], data.points[2], data.points[3], t, 0); points.push(point.x, point.y); } } else { for (let t = start + inc; t < end; t += inc) { const point = Path.getPointOnEllipticalArc(data.points[0], data.points[1], data.points[2], data.points[3], t, 0); points.push(point.x, point.y); } } } else if (data.command === 'C') { for (let t = 0.0; t <= 1; t += 0.01) { const point = Path.getPointOnCubicBezier(t, data.start.x, data.start.y, data.points[0], data.points[1], data.points[2], data.points[3], data.points[4], data.points[5]); points.push(point.x, point.y); } } else { points = points.concat(data.points); } }); var minX = points[0]; var maxX = points[0]; var minY = points[1]; var maxY = points[1]; var x, y; for (var i = 0; i < points.length / 2; i++) { x = points[i * 2]; y = points[i * 2 + 1]; if (!isNaN(x)) { minX = Math.min(minX, x); maxX = Math.max(maxX, x); } if (!isNaN(y)) { minY = Math.min(minY, y); maxY = Math.max(maxY, y); } } return { x: minX, y: minY, width: maxX - minX, height: maxY - minY, }; } getLength() { return this.pathLength; } getPointAtLength(length) { return Path.getPointAtLengthOfDataArray(length, this.dataArray); } static getLineLength(x1, y1, x2, y2) { return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); } static getPathLength(dataArray) { let pathLength = 0; for (var i = 0; i < dataArray.length; ++i) { pathLength += dataArray[i].pathLength; } return pathLength; } static getPointAtLengthOfDataArray(length, dataArray) { var point, i = 0, ii = dataArray.length; if (!ii) { return null; } while (i < ii && length > dataArray[i].pathLength) { length -= dataArray[i].pathLength; ++i; } if (i === ii) { point = dataArray[i - 1].points.slice(-2); return { x: point[0], y: point[1], }; } if (length < 0.01) { point = dataArray[i].points.slice(0, 2); return { x: point[0], y: point[1], }; } var cp = dataArray[i]; var p = cp.points; switch (cp.command) { case 'L': return Path.getPointOnLine(length, cp.start.x, cp.start.y, p[0], p[1]); case 'C': return Path.getPointOnCubicBezier((0, BezierFunctions_1.t2length)(length, Path.getPathLength(dataArray), (i) => { return (0, BezierFunctions_1.getCubicArcLength)([cp.start.x, p[0], p[2], p[4]], [cp.start.y, p[1], p[3], p[5]], i); }), cp.start.x, cp.start.y, p[0], p[1], p[2], p[3], p[4], p[5]); case 'Q': return Path.getPointOnQuadraticBezier((0, BezierFunctions_1.t2length)(length, Path.getPathLength(dataArray), (i) => { return (0, BezierFunctions_1.getQuadraticArcLength)([cp.start.x, p[0], p[2]], [cp.start.y, p[1], p[3]], i); }), cp.start.x, cp.start.y, p[0], p[1], p[2], p[3]); case 'A': var cx = p[0], cy = p[1], rx = p[2], ry = p[3], theta = p[4], dTheta = p[5], psi = p[6]; theta += (dTheta * length) / cp.pathLength; return Path.getPointOnEllipticalArc(cx, cy, rx, ry, theta, psi); } return null; } static getPointOnLine(dist, P1x, P1y, P2x, P2y, fromX, fromY) { fromX = fromX !== null && fromX !== void 0 ? fromX : P1x; fromY = fromY !== null && fromY !== void 0 ? fromY : P1y; const len = this.getLineLength(P1x, P1y, P2x, P2y); if (len < 1e-10) { return { x: P1x, y: P1y }; } if (P2x === P1x) { return { x: fromX, y: fromY + (P2y > P1y ? dist : -dist) }; } const m = (P2y - P1y) / (P2x - P1x); const run = Math.sqrt((dist * dist) / (1 + m * m)) * (P2x < P1x ? -1 : 1); const rise = m * run; if (Math.abs(fromY - P1y - m * (fromX - P1x)) < 1e-10) { return { x: fromX + run, y: fromY + rise }; } const u = ((fromX - P1x) * (P2x - P1x) + (fromY - P1y) * (P2y - P1y)) / (len * len); const ix = P1x + u * (P2x - P1x); const iy = P1y + u * (P2y - P1y); const pRise = this.getLineLength(fromX, fromY, ix, iy); const pRun = Math.sqrt(dist * dist - pRise * pRise); const adjustedRun = Math.sqrt((pRun * pRun) / (1 + m * m)) * (P2x < P1x ? -1 : 1); const adjustedRise = m * adjustedRun; return { x: ix + adjustedRun, y: iy + adjustedRise }; } static getPointOnCubicBezier(pct, P1x, P1y, P2x, P2y, P3x, P3y, P4x, P4y) { function CB1(t) { return t * t * t; } function CB2(t) { return 3 * t * t * (1 - t); } function CB3(t) { return 3 * t * (1 - t) * (1 - t); } function CB4(t) { return (1 - t) * (1 - t) * (1 - t); } var x = P4x * CB1(pct) + P3x * CB2(pct) + P2x * CB3(pct) + P1x * CB4(pct); var y = P4y * CB1(pct) + P3y * CB2(pct) + P2y * CB3(pct) + P1y * CB4(pct); return { x: x, y: y, }; } static getPointOnQuadraticBezier(pct, P1x, P1y, P2x, P2y, P3x, P3y) { function QB1(t) { return t * t; } function QB2(t) { return 2 * t * (1 - t); } function QB3(t) { return (1 - t) * (1 - t); } var x = P3x * QB1(pct) + P2x * QB2(pct) + P1x * QB3(pct); var y = P3y * QB1(pct) + P2y * QB2(pct) + P1y * QB3(pct); return { x: x, y: y, }; } static getPointOnEllipticalArc(cx, cy, rx, ry, theta, psi) { var cosPsi = Math.cos(psi), sinPsi = Math.sin(psi); var pt = { x: rx * Math.cos(theta), y: ry * Math.sin(theta), }; return { x: cx + (pt.x * cosPsi - pt.y * sinPsi), y: cy + (pt.x * sinPsi + pt.y * cosPsi), }; } static parsePathData(data) { if (!data) { return []; } var cs = data; var cc = [ 'm', 'M', 'l', 'L', 'v', 'V', 'h', 'H', 'z', 'Z', 'c', 'C', 'q', 'Q', 't', 'T', 's', 'S', 'a', 'A', ]; cs = cs.replace(new RegExp(' ', 'g'), ','); for (var n = 0; n < cc.length; n++) { cs = cs.replace(new RegExp(cc[n], 'g'), '|' + cc[n]); } var arr = cs.split('|'); var ca = []; var coords = []; var cpx = 0; var cpy = 0; var re = /([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/gi; var match; for (n = 1; n < arr.length; n++) { var str = arr[n]; var c = str.charAt(0); str = str.slice(1); coords.length = 0; while ((match = re.exec(str))) { coords.push(match[0]); } var p = []; for (var j = 0, jlen = coords.length; j < jlen; j++) { if (coords[j] === '00') { p.push(0, 0); continue; } var parsed = parseFloat(coords[j]); if (!isNaN(parsed)) { p.push(parsed); } else { p.push(0); } } while (p.length > 0) { if (isNaN(p[0])) { break; } var cmd = ''; var points = []; var startX = cpx, startY = cpy; var prevCmd, ctlPtx, ctlPty; var rx, ry, psi, fa, fs, x1, y1; switch (c) { case 'l': cpx += p.shift(); cpy += p.shift(); cmd = 'L'; points.push(cpx, cpy); break; case 'L': cpx = p.shift(); cpy = p.shift(); points.push(cpx, cpy); break; case 'm': var dx = p.shift(); var dy = p.shift(); cpx += dx; cpy += dy; cmd = 'M'; if (ca.length > 2 && ca[ca.length - 1].command === 'z') { for (var idx = ca.length - 2; idx >= 0; idx--) { if (ca[idx].command === 'M') { cpx = ca[idx].points[0] + dx; cpy = ca[idx].points[1] + dy; break; } } } points.push(cpx, cpy); c = 'l'; break; case 'M': cpx = p.shift(); cpy = p.shift(); cmd = 'M'; points.push(cpx, cpy); c = 'L'; break; case 'h': cpx += p.shift(); cmd = 'L'; points.push(cpx, cpy); break; case 'H': cpx = p.shift(); cmd = 'L'; points.push(cpx, cpy); break; case 'v': cpy += p.shift(); cmd = 'L'; points.push(cpx, cpy); break; case 'V': cpy = p.shift(); cmd = 'L'; points.push(cpx, cpy); break; case 'C': points.push(p.shift(), p.shift(), p.shift(), p.shift()); cpx = p.shift(); cpy = p.shift(); points.push(cpx, cpy); break; case 'c': points.push(cpx + p.shift(), cpy + p.shift(), cpx + p.shift(), cpy + p.shift()); cpx += p.shift(); cpy += p.shift(); cmd = 'C'; points.push(cpx, cpy); break; case 'S': ctlPtx = cpx; ctlPty = cpy; prevCmd = ca[ca.length - 1]; if (prevCmd.command === 'C') { ctlPtx = cpx + (cpx - prevCmd.points[2]); ctlPty = cpy + (cpy - prevCmd.points[3]); } points.push(ctlPtx, ctlPty, p.shift(), p.shift()); cpx = p.shift(); cpy = p.shift(); cmd = 'C'; points.push(cpx, cpy); break; case 's': ctlPtx = cpx; ctlPty = cpy; prevCmd = ca[ca.length - 1]; if (prevCmd.command === 'C') { ctlPtx = cpx + (cpx - prevCmd.points[2]); ctlPty = cpy + (cpy - prevCmd.points[3]); } points.push(ctlPtx, ctlPty, cpx + p.shift(), cpy + p.shift()); cpx += p.shift(); cpy += p.shift(); cmd = 'C'; points.push(cpx, cpy); break; case 'Q': points.push(p.shift(), p.shift()); cpx = p.shift(); cpy = p.shift(); points.push(cpx, cpy); break; case 'q': points.push(cpx + p.shift(), cpy + p.shift()); cpx += p.shift(); cpy += p.shift(); cmd = 'Q'; points.push(cpx, cpy); break; case 'T': ctlPtx = cpx; ctlPty = cpy; prevCmd = ca[ca.length - 1]; if (prevCmd.command === 'Q') { ctlPtx = cpx + (cpx - prevCmd.points[0]); ctlPty = cpy + (cpy - prevCmd.points[1]); } cpx = p.shift(); cpy = p.shift(); cmd = 'Q'; points.push(ctlPtx, ctlPty, cpx, cpy); break; case 't': ctlPtx = cpx; ctlPty = cpy; prevCmd = ca[ca.length - 1]; if (prevCmd.command === 'Q') { ctlPtx = cpx + (cpx - prevCmd.points[0]); ctlPty = cpy + (cpy - prevCmd.points[1]); } cpx += p.shift(); cpy += p.shift(); cmd = 'Q'; points.push(ctlPtx, ctlPty, cpx, cpy); break; case 'A': rx = p.shift(); ry = p.shift(); psi = p.shift(); fa = p.shift(); fs = p.shift(); x1 = cpx; y1 = cpy; cpx = p.shift(); cpy = p.shift(); cmd = 'A'; points = this.convertEndpointToCenterParameterization(x1, y1, cpx, cpy, fa, fs, rx, ry, psi); break; case 'a': rx = p.shift(); ry = p.shift(); psi = p.shift(); fa = p.shift(); fs = p.shift(); x1 = cpx; y1 = cpy; cpx += p.shift(); cpy += p.shift(); cmd = 'A'; points = this.convertEndpointToCenterParameterization(x1, y1, cpx, cpy, fa, fs, rx, ry, psi); break; } ca.push({ command: cmd || c, points: points, start: { x: startX, y: startY, }, pathLength: this.calcLength(startX, startY, cmd || c, points), }); } if (c === 'z' || c === 'Z') { ca.push({ command: 'z', points: [], start: undefined, pathLength: 0, }); } } return ca; } static calcLength(x, y, cmd, points) { var len, p1, p2, t; var path = Path; switch (cmd) { case 'L': return path.getLineLength(x, y, points[0], points[1]); case 'C': return (0, BezierFunctions_1.getCubicArcLength)([x, points[0], points[2], points[4]], [y, points[1], points[3], points[5]], 1); case 'Q': return (0, BezierFunctions_1.getQuadraticArcLength)([x, points[0], points[2]], [y, points[1], points[3]], 1); case 'A': len = 0.0; var start = points[4]; var dTheta = points[5]; var end = points[4] + dTheta; var inc = Math.PI / 180.0; if (Math.abs(start - end) < inc) { inc = Math.abs(start - end); } p1 = path.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], start, 0); if (dTheta < 0) { for (t = start - inc; t > end; t -= inc) { p2 = path.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], t, 0); len += path.getLineLength(p1.x, p1.y, p2.x, p2.y); p1 = p2; } } else { for (t = start + inc; t < end; t += inc) { p2 = path.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], t, 0); len += path.getLineLength(p1.x, p1.y, p2.x, p2.y); p1 = p2; } } p2 = path.getPointOnEllipticalArc(points[0], points[1], points[2], points[3], end, 0); len += path.getLineLength(p1.x, p1.y, p2.x, p2.y); return len; } return 0; } static convertEndpointToCenterParameterization(x1, y1, x2, y2, fa, fs, rx, ry, psiDeg) { var psi = psiDeg * (Math.PI / 180.0); var xp = (Math.cos(psi) * (x1 - x2)) / 2.0 + (Math.sin(psi) * (y1 - y2)) / 2.0; var yp = (-1 * Math.sin(psi) * (x1 - x2)) / 2.0 + (Math.cos(psi) * (y1 - y2)) / 2.0; var lambda = (xp * xp) / (rx * rx) + (yp * yp) / (ry * ry); if (lambda > 1) { rx *= Math.sqrt(lambda); ry *= Math.sqrt(lambda); } var f = Math.sqrt((rx * rx * (ry * ry) - rx * rx * (yp * yp) - ry * ry * (xp * xp)) / (rx * rx * (yp * yp) + ry * ry * (xp * xp))); if (fa === fs) { f *= -1; } if (isNaN(f)) { f = 0; } var cxp = (f * rx * yp) / ry; var cyp = (f * -ry * xp) / rx; var cx = (x1 + x2) / 2.0 + Math.cos(psi) * cxp - Math.sin(psi) * cyp; var cy = (y1 + y2) / 2.0 + Math.sin(psi) * cxp + Math.cos(psi) * cyp; 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)); }; 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 = Math.PI; } if (vRatio(u, v) >= 1) { dTheta = 0; } if (fs === 0 && dTheta > 0) { dTheta = dTheta - 2 * Math.PI; } if (fs === 1 && dTheta < 0) { dTheta = dTheta + 2 * Math.PI; } return [cx, cy, rx, ry, theta, dTheta, psi, fs]; } } exports.Path = Path; Path.prototype.className = 'Path'; Path.prototype._attrsAffectingSize = ['data']; (0, Global_1._registerNode)(Path); Factory_1.Factory.addGetterSetter(Path, 'data');