/** * svg路径绘制。 * 作者:tmzdy * url:https://jx2d.cn */ function 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 = null; 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 = 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 = convertEndpointToCenterParameterization(x1, y1, cpx, cpy, fa, fs, rx, ry, psi); break; } ca.push({ command: cmd || c, points: points, start: { x: startX, y: startY, }, pathLength: calcLength(startX, startY, cmd || c, points), }); } if (c === 'z' || c === 'Z') { ca.push({ command: 'z', points: [], start: undefined, pathLength: 0, }); } } return ca; } function calcLength(x, y, cmd, points) { var len, p1, p2, t; switch (cmd) { case 'L': return getLineLength(x, y, points[0], points[1]); case 'C': len = 0.0; p1 = getPointOnCubicBezier(0, x, y, points[0], points[1], points[2], points[3], points[4], points[5]); for (t = 0.01; t <= 1; t += 0.01) { p2 = getPointOnCubicBezier(t, x, y, points[0], points[1], points[2], points[3], points[4], points[ 5]); len += getLineLength(p1.x, p1.y, p2.x, p2.y); p1 = p2; } return len; case 'Q': len = 0.0; p1 = getPointOnQuadraticBezier(0, x, y, points[0], points[1], points[2], points[3]); for (t = 0.01; t <= 1; t += 0.01) { p2 = getPointOnQuadraticBezier(t, x, y, points[0], points[1], points[2], points[3]); len += getLineLength(p1.x, p1.y, p2.x, p2.y); p1 = p2; } return len; 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 = getPointOnEllipticalArc(points[0], points[1], points[2], points[3], start, 0); if (dTheta < 0) { for (t = start - inc; t > end; t -= inc) { p2 = getPointOnEllipticalArc(points[0], points[1], points[2], points[3], t, 0); len += getLineLength(p1.x, p1.y, p2.x, p2.y); p1 = p2; } } else { for (t = start + inc; t < end; t += inc) { p2 = getPointOnEllipticalArc(points[0], points[1], points[2], points[3], t, 0); len += getLineLength(p1.x, p1.y, p2.x, p2.y); p1 = p2; } } p2 = getPointOnEllipticalArc(points[0], points[1], points[2], points[3], end, 0); len += getLineLength(p1.x, p1.y, p2.x, p2.y); return len; } return 0; } function 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]; } function 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: Math.round(minX), y: Math.round(minY), width: Math.round(maxX - minX), height: Math.round(maxY - minY), }; } function getPointAtLength(length) { var point, i = 0, ii = this.dataArray.length; if (!ii) { return null; } while (i < ii && length > this.dataArray[i].pathLength) { length -= this.dataArray[i].pathLength; ++i; } if (i === ii) { point = this.dataArray[i - 1].points.slice(-2); return { x: point[0], y: point[1], }; } if (length < 0.01) { point = this.dataArray[i].points.slice(0, 2); return { x: point[0], y: point[1], }; } var cp = this.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(length / cp.pathLength, cp.start.x, cp.start.y, p[0], p[1], p[2], p[3], p[ 4], p[5]); case 'Q': return Path.getPointOnQuadraticBezier(length / cp.pathLength, 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; } function getLineLength(x1, y1, x2, y2) { return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); } function getPointOnLine(dist, P1x, P1y, P2x, P2y, fromX, fromY) { if (fromX === undefined) { fromX = P1x; } if (fromY === undefined) { fromY = P1y; } var m = (P2y - P1y) / (P2x - P1x + 0.00000001); var run = Math.sqrt((dist * dist) / (1 + m * m)); if (P2x < P1x) { run *= -1; } var rise = m * run; var pt; if (P2x === P1x) { pt = { x: fromX, y: fromY + rise, }; } else if ((fromY - P1y) / (fromX - P1x + 0.00000001) === m) { pt = { x: fromX + run, y: fromY + rise, }; } else { var ix, iy; var len = this.getLineLength(P1x, P1y, P2x, P2y); var u = (fromX - P1x) * (P2x - P1x) + (fromY - P1y) * (P2y - P1y); u = u / (len * len); ix = P1x + u * (P2x - P1x); iy = P1y + u * (P2y - P1y); var pRise = this.getLineLength(fromX, fromY, ix, iy); var pRun = Math.sqrt(dist * dist - pRise * pRise); run = Math.sqrt((pRun * pRun) / (1 + m * m)); if (P2x < P1x) { run *= -1; } rise = m * run; pt = { x: ix + run, y: iy + rise, }; } return pt; } function 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, }; } function 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, }; } function 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), }; } let path2d = function(render, config = {}) { const [w, h] = render.area; let cfg = { name: 'path', animationCurve: 'easeOutBack', hover: true, drag: true, shape: { path: '', close:true, points:[] }, style: { stroke: '#000', fill:'#000', lineWidth: 1, hoverCursor: 'pointer', }, ...config, draw({ ctx }, { shape, style: { lineWidth } }){ let { points, close,x,y ,path} = shape var ca=[] if(this.shape['points'].length>0&&this.shape['points']){ ca = this.shape['svg'] }else{ ca = parsePathData(path); // ca = ca.map(el=>{ // if(el.points.length){ // return {...el,points:[el.points[0]+x,el.points[1]+y]} // } // return el // }) let ar = ca.map(el=> el.points) this.shape['points'] = ar.filter((el)=>el.length==2) this.shape['svg'] = ca; } const context=ctx context.beginPath(); 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': close = true; context.closePath(); break; } } if (close) { ctx.closePath() ctx.fill() ctx.stroke() } else { ctx.stroke() } }, }; return cfg; } export default path2d;