diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ece64959..0913d35e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +- Fix svg `path.getPointAtLength()` calculations in some cases - Fix `shape.getClientRect()` when any of parents is cached ### 9.3.11 (2024-05-23) diff --git a/src/shapes/Path.ts b/src/shapes/Path.ts index 1c8e08410..e91f54005 100644 --- a/src/shapes/Path.ts +++ b/src/shapes/Path.ts @@ -320,61 +320,38 @@ export class Path extends Shape { } static getPointOnLine(dist, P1x, P1y, P2x, P2y, fromX?, fromY?) { - if (fromX === undefined) { - fromX = P1x; - } - if (fromY === undefined) { - fromY = P1y; - } + fromX = fromX ?? P1x; + fromY = fromY ?? P1y; - var m = (P2y - P1y) / (P2x - P1x + 0.00000001); - var run = Math.sqrt((dist * dist) / (1 + m * m)); - if (P2x < P1x) { - run *= -1; + const len = this.getLineLength(P1x, P1y, P2x, P2y); + if (len < 1e-10) { + return { x: P1x, y: P1y }; } - var rise = m * run; - var pt; if (P2x === P1x) { - // vertical line - 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); - // if (len < 0.00000001) { - // return { - // x: P1x, - // y: P1y, - // }; - // } - 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, - }; + // Vertical line + 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 }; } - return pt; + 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) { diff --git a/test/unit/Path-test.ts b/test/unit/Path-test.ts index e8cdff824..4b3501f67 100644 --- a/test/unit/Path-test.ts +++ b/test/unit/Path-test.ts @@ -1134,11 +1134,11 @@ describe('Path', function () { assert.deepEqual(points, [ { x: 300, y: 10 }, - { x: 290.2871413779118, y: 27.48314552325543 }, - { x: 280.57428275582356, y: 44.96629104651086 }, - { x: 270.86142413373534, y: 62.4494365697663 }, - { x: 261.1485655116471, y: 79.93258209302172 }, - { x: 251.43570688955887, y: 97.41572761627717 }, + { x: 290.28714137642737, y: 27.483145522430753 }, + { x: 280.57428275285474, y: 44.96629104486151 }, + { x: 270.86142412928206, y: 62.44943656729226 }, + { x: 261.1485655057094, y: 79.93258208972301 }, + { x: 251.4357068821368, y: 97.41572761215377 }, { x: 230.89220826660141, y: 87.23996356219386 }, { x: 207.0639321224534, y: 74.08466390481559 }, { x: 182.87529785963875, y: 63.52674972743341 }, @@ -1168,6 +1168,47 @@ describe('Path', function () { stage.add(layer); }); + it('get point at path with float attrs', function () { + var stage = addStage(); + var layer = new Konva.Layer(); + + const data = + 'M419.0000314094981 342.88624187900973 L419.00003140949804 577.0038889378335 L465.014001082264 577.0038889378336 Z'; + var path = new Konva.Path({ + stroke: 'red', + strokeWidth: 3, + data, + }); + layer.add(path); + if (isBrowser) { + const SVGPath = document.createElementNS( + 'http://www.w3.org/2000/svg', + 'path' + ) as SVGPathElement; + SVGPath.setAttribute('d', data); + for (var i = 0; i < path.getLength(); i += 1) { + var p = path.getPointAtLength(i); + var circle = new Konva.Circle({ + x: p.x, + y: p.y, + radius: 2, + fill: 'black', + stroke: 'black', + }); + layer.add(circle); + const position = SVGPath.getPointAtLength(i); + assert( + Math.abs(p.x - position.x) <= 1, + 'error for x should be smaller than 10% for i = ' + i + ); + assert( + Math.abs(p.y - position.y) <= 1, + 'error for y should be smaller than 10% for i = ' + i + ); + } + } + }); + it('get point at path - bezier', function () { var stage = addStage(); var layer = new Konva.Layer(); @@ -1618,8 +1659,11 @@ describe('Path', function () { layer.add(path); stage.add(layer); - const trace = layer.getContext().getTrace() + const trace = layer.getContext().getTrace(); - assert.equal(trace, 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(200,100);lineTo(300,100);lineTo(300,150);closePath();fillStyle=#ccc;fill(evenodd);restore();'); + assert.equal( + trace, + 'clearRect(0,0,578,200);save();transform(1,0,0,1,0,0);beginPath();moveTo(200,100);lineTo(300,100);lineTo(300,150);closePath();fillStyle=#ccc;fill(evenodd);restore();' + ); }); });