From 4a7ae3b67196367d498c4626a9005958411c443d Mon Sep 17 00:00:00 2001 From: Anton Lavrevov Date: Sat, 23 Aug 2025 14:22:39 -0500 Subject: [PATCH] Better svg parsing. fix #1549 --- src/shapes/Path.ts | 70 ++++++++++++++++++++++++++++++++++++++---- test/unit/Path-test.ts | 24 +++++++++++++++ 2 files changed, 88 insertions(+), 6 deletions(-) diff --git a/src/shapes/Path.ts b/src/shapes/Path.ts index a80b02de..7180bb74 100644 --- a/src/shapes/Path.ts +++ b/src/shapes/Path.ts @@ -522,19 +522,77 @@ export class Path extends Shape { // while ((match = re.exec(str))) { // coords.push(match[0]); // } - const p: number[] = []; + let p: number[] = []; + // Track param position for A/a commands: 0..6 => rx, ry, psi, fa, fs, x, y + let arcParamIndex = c === 'A' || c === 'a' ? 0 : -1; for (let j = 0, jlen = coords.length; j < jlen; j++) { + const token = coords[j]; // extra case for merged flags - if (coords[j] === '00') { + if (token === '00') { p.push(0, 0); + if (arcParamIndex >= 0) { + arcParamIndex += 2; + if (arcParamIndex >= 7) arcParamIndex -= 7; + } continue; } - const parsed = parseFloat(coords[j]); - if (!isNaN(parsed)) { - p.push(parsed); + if (arcParamIndex >= 0) { + // index-aware minimal handling for merged flags + if (arcParamIndex === 3) { + // expecting large-arc-flag; token may contain fa+fs(+x) + if (/^[01]{2}\d+(?:\.\d+)?$/.test(token)) { + p.push(parseInt(token[0], 10)); + p.push(parseInt(token[1], 10)); + p.push(parseFloat(token.slice(2))); + arcParamIndex += 3; + if (arcParamIndex >= 7) arcParamIndex -= 7; + continue; + } + if (token === '11' || token === '10' || token === '01') { + p.push(parseInt(token[0], 10)); + p.push(parseInt(token[1], 10)); + arcParamIndex += 2; + if (arcParamIndex >= 7) arcParamIndex -= 7; + continue; + } + if (token === '0' || token === '1') { + p.push(parseInt(token, 10)); + arcParamIndex += 1; + if (arcParamIndex >= 7) arcParamIndex -= 7; + continue; + } + } else if (arcParamIndex === 4) { + // expecting sweep-flag; token may contain fs(+x) + if (/^[01]\d+(?:\.\d+)?$/.test(token)) { + p.push(parseInt(token[0], 10)); + p.push(parseFloat(token.slice(1))); + arcParamIndex += 2; + if (arcParamIndex >= 7) arcParamIndex -= 7; + continue; + } + if (token === '0' || token === '1') { + p.push(parseInt(token, 10)); + arcParamIndex += 1; + if (arcParamIndex >= 7) arcParamIndex -= 7; + continue; + } + } + const parsedArc = parseFloat(token); + if (!isNaN(parsedArc)) { + p.push(parsedArc); + } else { + p.push(0); + } + arcParamIndex += 1; + if (arcParamIndex >= 7) arcParamIndex -= 7; } else { - p.push(0); + const parsed = parseFloat(token); + if (!isNaN(parsed)) { + p.push(parsed); + } else { + p.push(0); + } } } diff --git a/test/unit/Path-test.ts b/test/unit/Path-test.ts index dbeb19eb..77a677d5 100644 --- a/test/unit/Path-test.ts +++ b/test/unit/Path-test.ts @@ -487,6 +487,30 @@ describe('Path', function () { ); }); + it.only('parses arc without separators after flags', function () { + const stage = addStage(); + const layer = new Konva.Layer(); + stage.add(layer); + + const path = new Konva.Path({ + data: 'M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1115 0z', + stroke: 'red', + }); + layer.add(path); + layer.draw(); + + const arc = path.dataArray[3]; + assert.equal(arc.command, 'A'); + assert.closeTo(arc.points[0], 12, 0.001); + assert.closeTo(arc.points[1], 10.5, 0.001); + assert.closeTo(arc.points[2], 7.5, 0.001); + assert.closeTo(arc.points[3], 7.5, 0.001); + assert.closeTo(arc.points[4], Math.PI, 0.001); + assert.closeTo(arc.points[5], Math.PI, 0.001); + assert.equal(arc.points[6], 0); + assert.equal(arc.points[7], 1); + }); + // ====================================================== it('Tiger (RAWR!)', function () { this.timeout(5000);