Better svg parsing. fix #1549

This commit is contained in:
Anton Lavrevov
2025-08-23 14:22:39 -05:00
parent e2c0a719b9
commit 4a7ae3b671
2 changed files with 88 additions and 6 deletions

View File

@@ -522,21 +522,79 @@ export class Path extends Shape<PathConfig> {
// while ((match = re.exec(str))) { // while ((match = re.exec(str))) {
// coords.push(match[0]); // 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++) { for (let j = 0, jlen = coords.length; j < jlen; j++) {
const token = coords[j];
// extra case for merged flags // extra case for merged flags
if (coords[j] === '00') { if (token === '00') {
p.push(0, 0); p.push(0, 0);
if (arcParamIndex >= 0) {
arcParamIndex += 2;
if (arcParamIndex >= 7) arcParamIndex -= 7;
}
continue; continue;
} }
const parsed = parseFloat(coords[j]); 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 {
const parsed = parseFloat(token);
if (!isNaN(parsed)) { if (!isNaN(parsed)) {
p.push(parsed); p.push(parsed);
} else { } else {
p.push(0); p.push(0);
} }
} }
}
while (p.length > 0) { while (p.length > 0) {
if (isNaN(p[0])) { if (isNaN(p[0])) {

View File

@@ -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 () { it('Tiger (RAWR!)', function () {
this.timeout(5000); this.timeout(5000);