@emasoft/svg-matrix 1.0.27 → 1.0.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +325 -0
- package/bin/svg-matrix.js +994 -378
- package/bin/svglinter.cjs +4172 -433
- package/bin/svgm.js +744 -184
- package/package.json +16 -4
- package/src/animation-references.js +71 -52
- package/src/arc-length.js +160 -96
- package/src/bezier-analysis.js +257 -117
- package/src/bezier-intersections.js +411 -148
- package/src/browser-verify.js +240 -100
- package/src/clip-path-resolver.js +350 -142
- package/src/convert-path-data.js +279 -134
- package/src/css-specificity.js +78 -70
- package/src/flatten-pipeline.js +751 -263
- package/src/geometry-to-path.js +511 -182
- package/src/index.js +191 -46
- package/src/inkscape-support.js +404 -0
- package/src/marker-resolver.js +278 -164
- package/src/mask-resolver.js +209 -98
- package/src/matrix.js +147 -67
- package/src/mesh-gradient.js +187 -96
- package/src/off-canvas-detection.js +201 -104
- package/src/path-analysis.js +187 -107
- package/src/path-data-plugins.js +628 -167
- package/src/path-simplification.js +0 -1
- package/src/pattern-resolver.js +125 -88
- package/src/polygon-clip.js +111 -66
- package/src/svg-boolean-ops.js +194 -118
- package/src/svg-collections.js +48 -19
- package/src/svg-flatten.js +282 -164
- package/src/svg-parser.js +427 -200
- package/src/svg-rendering-context.js +147 -104
- package/src/svg-toolbox.js +16411 -3298
- package/src/svg2-polyfills.js +114 -245
- package/src/transform-decomposition.js +46 -41
- package/src/transform-optimization.js +89 -68
- package/src/transforms2d.js +49 -16
- package/src/transforms3d.js +58 -22
- package/src/use-symbol-resolver.js +150 -110
- package/src/vector.js +67 -15
- package/src/vendor/README.md +110 -0
- package/src/vendor/inkscape-hatch-polyfill.js +401 -0
- package/src/vendor/inkscape-hatch-polyfill.min.js +8 -0
- package/src/vendor/inkscape-mesh-polyfill.js +843 -0
- package/src/vendor/inkscape-mesh-polyfill.min.js +8 -0
- package/src/verification.js +288 -124
package/src/svg-flatten.js
CHANGED
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
* @module svg-flatten
|
|
32
32
|
*/
|
|
33
33
|
|
|
34
|
-
import Decimal from
|
|
35
|
-
import { Matrix } from
|
|
36
|
-
import * as Transforms2D from
|
|
34
|
+
import Decimal from "decimal.js";
|
|
35
|
+
import { Matrix } from "./matrix.js";
|
|
36
|
+
import * as Transforms2D from "./transforms2d.js";
|
|
37
37
|
|
|
38
38
|
// Set high precision for all calculations
|
|
39
39
|
Decimal.set({ precision: 80 });
|
|
@@ -69,11 +69,14 @@ Decimal.set({ precision: 80 });
|
|
|
69
69
|
* // Shows region from (-50,-50) to (150,150) in user space
|
|
70
70
|
*/
|
|
71
71
|
export function parseViewBox(viewBoxStr) {
|
|
72
|
-
if (!viewBoxStr || viewBoxStr.trim() ===
|
|
72
|
+
if (!viewBoxStr || viewBoxStr.trim() === "") {
|
|
73
73
|
return null;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
const parts = viewBoxStr
|
|
76
|
+
const parts = viewBoxStr
|
|
77
|
+
.trim()
|
|
78
|
+
.split(/[\s,]+/)
|
|
79
|
+
.map((s) => new Decimal(s));
|
|
77
80
|
if (parts.length !== 4) {
|
|
78
81
|
console.warn(`Invalid viewBox: ${viewBoxStr}`);
|
|
79
82
|
return null;
|
|
@@ -83,7 +86,7 @@ export function parseViewBox(viewBoxStr) {
|
|
|
83
86
|
minX: parts[0],
|
|
84
87
|
minY: parts[1],
|
|
85
88
|
width: parts[2],
|
|
86
|
-
height: parts[3]
|
|
89
|
+
height: parts[3],
|
|
87
90
|
};
|
|
88
91
|
}
|
|
89
92
|
|
|
@@ -123,11 +126,11 @@ export function parseViewBox(viewBoxStr) {
|
|
|
123
126
|
export function parsePreserveAspectRatio(parStr) {
|
|
124
127
|
const result = {
|
|
125
128
|
defer: false,
|
|
126
|
-
align:
|
|
127
|
-
meetOrSlice:
|
|
129
|
+
align: "xMidYMid", // default
|
|
130
|
+
meetOrSlice: "meet", // default
|
|
128
131
|
};
|
|
129
132
|
|
|
130
|
-
if (!parStr || parStr.trim() ===
|
|
133
|
+
if (!parStr || parStr.trim() === "") {
|
|
131
134
|
return result;
|
|
132
135
|
}
|
|
133
136
|
|
|
@@ -135,7 +138,7 @@ export function parsePreserveAspectRatio(parStr) {
|
|
|
135
138
|
let idx = 0;
|
|
136
139
|
|
|
137
140
|
// Check for 'defer' (only applies to <image>)
|
|
138
|
-
if (parts[idx] ===
|
|
141
|
+
if (parts[idx] === "defer") {
|
|
139
142
|
result.defer = true;
|
|
140
143
|
idx++;
|
|
141
144
|
}
|
|
@@ -206,8 +209,13 @@ export function parsePreserveAspectRatio(parStr) {
|
|
|
206
209
|
* const matrix = computeViewBoxTransform(vb, 800, 400, par);
|
|
207
210
|
* // Uniform scale of 8 (max(800/100, 400/100)), centered, top/bottom cropped
|
|
208
211
|
*/
|
|
209
|
-
export function computeViewBoxTransform(
|
|
210
|
-
|
|
212
|
+
export function computeViewBoxTransform(
|
|
213
|
+
viewBox,
|
|
214
|
+
viewportWidth,
|
|
215
|
+
viewportHeight,
|
|
216
|
+
par = null,
|
|
217
|
+
) {
|
|
218
|
+
const D = (x) => new Decimal(x);
|
|
211
219
|
|
|
212
220
|
if (!viewBox) {
|
|
213
221
|
return Matrix.identity(3);
|
|
@@ -221,12 +229,10 @@ export function computeViewBoxTransform(viewBox, viewportWidth, viewportHeight,
|
|
|
221
229
|
const vpH = D(viewportHeight);
|
|
222
230
|
|
|
223
231
|
// Default preserveAspectRatio
|
|
224
|
-
|
|
225
|
-
par = { align: 'xMidYMid', meetOrSlice: 'meet' };
|
|
226
|
-
}
|
|
232
|
+
const parValue = par || { align: "xMidYMid", meetOrSlice: "meet" };
|
|
227
233
|
|
|
228
234
|
// Handle 'none' - stretch to fill
|
|
229
|
-
if (
|
|
235
|
+
if (parValue.align === "none") {
|
|
230
236
|
const scaleX = vpW.div(vbW);
|
|
231
237
|
const scaleY = vpH.div(vbH);
|
|
232
238
|
// translate(-minX, -minY) then scale
|
|
@@ -236,11 +242,11 @@ export function computeViewBoxTransform(viewBox, viewportWidth, viewportHeight,
|
|
|
236
242
|
}
|
|
237
243
|
|
|
238
244
|
// Compute uniform scale factor
|
|
239
|
-
|
|
240
|
-
|
|
245
|
+
const scaleX = vpW.div(vbW);
|
|
246
|
+
const scaleY = vpH.div(vbH);
|
|
241
247
|
let scale;
|
|
242
248
|
|
|
243
|
-
if (
|
|
249
|
+
if (parValue.meetOrSlice === "slice") {
|
|
244
250
|
// Use larger scale (content may overflow)
|
|
245
251
|
scale = Decimal.max(scaleX, scaleY);
|
|
246
252
|
} else {
|
|
@@ -256,20 +262,20 @@ export function computeViewBoxTransform(viewBox, viewportWidth, viewportHeight,
|
|
|
256
262
|
let translateY = D(0);
|
|
257
263
|
|
|
258
264
|
// Parse alignment string (e.g., 'xMidYMid', 'xMinYMax')
|
|
259
|
-
const align =
|
|
265
|
+
const align = parValue.align;
|
|
260
266
|
|
|
261
267
|
// X alignment
|
|
262
|
-
if (align.includes(
|
|
268
|
+
if (align.includes("xMid")) {
|
|
263
269
|
translateX = vpW.minus(scaledW).div(2);
|
|
264
|
-
} else if (align.includes(
|
|
270
|
+
} else if (align.includes("xMax")) {
|
|
265
271
|
translateX = vpW.minus(scaledW);
|
|
266
272
|
}
|
|
267
273
|
// xMin is default (translateX = 0)
|
|
268
274
|
|
|
269
275
|
// Y alignment
|
|
270
|
-
if (align.includes(
|
|
276
|
+
if (align.includes("YMid")) {
|
|
271
277
|
translateY = vpH.minus(scaledH).div(2);
|
|
272
|
-
} else if (align.includes(
|
|
278
|
+
} else if (align.includes("YMax")) {
|
|
273
279
|
translateY = vpH.minus(scaledH);
|
|
274
280
|
}
|
|
275
281
|
// YMin is default (translateY = 0)
|
|
@@ -327,7 +333,13 @@ export class SVGViewport {
|
|
|
327
333
|
* "rotate(45 50 25)"
|
|
328
334
|
* );
|
|
329
335
|
*/
|
|
330
|
-
constructor(
|
|
336
|
+
constructor(
|
|
337
|
+
width,
|
|
338
|
+
height,
|
|
339
|
+
viewBox = null,
|
|
340
|
+
preserveAspectRatio = null,
|
|
341
|
+
transform = null,
|
|
342
|
+
) {
|
|
331
343
|
this.width = new Decimal(width);
|
|
332
344
|
this.height = new Decimal(height);
|
|
333
345
|
this.viewBox = viewBox ? parseViewBox(viewBox) : null;
|
|
@@ -361,7 +373,7 @@ export class SVGViewport {
|
|
|
361
373
|
this.viewBox,
|
|
362
374
|
this.width,
|
|
363
375
|
this.height,
|
|
364
|
-
this.preserveAspectRatio
|
|
376
|
+
this.preserveAspectRatio,
|
|
365
377
|
);
|
|
366
378
|
result = result.mul(vbTransform);
|
|
367
379
|
}
|
|
@@ -429,23 +441,23 @@ export function buildFullCTM(hierarchy) {
|
|
|
429
441
|
let ctm = Matrix.identity(3);
|
|
430
442
|
|
|
431
443
|
for (const item of hierarchy) {
|
|
432
|
-
if (typeof item ===
|
|
444
|
+
if (typeof item === "string") {
|
|
433
445
|
// Backwards compatibility: treat string as transform attribute
|
|
434
446
|
if (item) {
|
|
435
447
|
const matrix = parseTransformAttribute(item);
|
|
436
448
|
ctm = ctm.mul(matrix);
|
|
437
449
|
}
|
|
438
|
-
} else if (item.type ===
|
|
450
|
+
} else if (item.type === "svg") {
|
|
439
451
|
// SVG viewport with potential viewBox
|
|
440
452
|
const viewport = new SVGViewport(
|
|
441
453
|
item.width,
|
|
442
454
|
item.height,
|
|
443
455
|
item.viewBox || null,
|
|
444
456
|
item.preserveAspectRatio || null,
|
|
445
|
-
item.transform || null
|
|
457
|
+
item.transform || null,
|
|
446
458
|
);
|
|
447
459
|
ctm = ctm.mul(viewport.getTransformMatrix());
|
|
448
|
-
} else if (item.type ===
|
|
460
|
+
} else if (item.type === "g" || item.type === "element") {
|
|
449
461
|
// Group or element with optional transform
|
|
450
462
|
if (item.transform) {
|
|
451
463
|
const matrix = parseTransformAttribute(item.transform);
|
|
@@ -506,16 +518,16 @@ export function buildFullCTM(hierarchy) {
|
|
|
506
518
|
* // Returns: Decimal(32) // 2 × 16px
|
|
507
519
|
*/
|
|
508
520
|
export function resolveLength(value, referenceSize, dpi = 96) {
|
|
509
|
-
const D = x => new Decimal(x);
|
|
521
|
+
const D = (x) => new Decimal(x);
|
|
510
522
|
|
|
511
|
-
if (typeof value ===
|
|
523
|
+
if (typeof value === "number") {
|
|
512
524
|
return D(value);
|
|
513
525
|
}
|
|
514
526
|
|
|
515
527
|
const str = String(value).trim();
|
|
516
528
|
|
|
517
529
|
// Percentage
|
|
518
|
-
if (str.endsWith(
|
|
530
|
+
if (str.endsWith("%")) {
|
|
519
531
|
const pct = D(str.slice(0, -1));
|
|
520
532
|
return pct.div(100).mul(referenceSize);
|
|
521
533
|
}
|
|
@@ -527,26 +539,26 @@ export function resolveLength(value, referenceSize, dpi = 96) {
|
|
|
527
539
|
}
|
|
528
540
|
|
|
529
541
|
const num = D(match[1]);
|
|
530
|
-
const unit = (match[2] ||
|
|
542
|
+
const unit = (match[2] || "").toLowerCase().trim();
|
|
531
543
|
|
|
532
544
|
// Convert to user units (px)
|
|
533
545
|
switch (unit) {
|
|
534
|
-
case
|
|
535
|
-
case
|
|
546
|
+
case "":
|
|
547
|
+
case "px":
|
|
536
548
|
return num;
|
|
537
|
-
case
|
|
549
|
+
case "em":
|
|
538
550
|
return num.mul(16); // Assume 16px font-size
|
|
539
|
-
case
|
|
551
|
+
case "rem":
|
|
540
552
|
return num.mul(16);
|
|
541
|
-
case
|
|
553
|
+
case "pt":
|
|
542
554
|
return num.mul(dpi).div(72);
|
|
543
|
-
case
|
|
555
|
+
case "pc":
|
|
544
556
|
return num.mul(dpi).div(6);
|
|
545
|
-
case
|
|
557
|
+
case "in":
|
|
546
558
|
return num.mul(dpi);
|
|
547
|
-
case
|
|
559
|
+
case "cm":
|
|
548
560
|
return num.mul(dpi).div(2.54);
|
|
549
|
-
case
|
|
561
|
+
case "mm":
|
|
550
562
|
return num.mul(dpi).div(25.4);
|
|
551
563
|
default:
|
|
552
564
|
return num; // Unknown unit, treat as px
|
|
@@ -563,10 +575,15 @@ export function resolveLength(value, referenceSize, dpi = 96) {
|
|
|
563
575
|
* @param {Decimal} viewportHeight - Viewport height for reference
|
|
564
576
|
* @returns {{x: Decimal, y: Decimal}} Resolved coordinates
|
|
565
577
|
*/
|
|
566
|
-
export function resolvePercentages(
|
|
578
|
+
export function resolvePercentages(
|
|
579
|
+
xOrWidth,
|
|
580
|
+
yOrHeight,
|
|
581
|
+
viewportWidth,
|
|
582
|
+
viewportHeight,
|
|
583
|
+
) {
|
|
567
584
|
return {
|
|
568
585
|
x: resolveLength(xOrWidth, viewportWidth),
|
|
569
|
-
y: resolveLength(yOrHeight, viewportHeight)
|
|
586
|
+
y: resolveLength(yOrHeight, viewportHeight),
|
|
570
587
|
};
|
|
571
588
|
}
|
|
572
589
|
|
|
@@ -641,8 +658,13 @@ export function normalizedDiagonal(width, height) {
|
|
|
641
658
|
* const transform = objectBoundingBoxTransform(bbox.x, bbox.y, bbox.width, bbox.height);
|
|
642
659
|
* // Gradient with x1="0" y1="0" x2="1" y2="1" spans from (0,0) to (100,100)
|
|
643
660
|
*/
|
|
644
|
-
export function objectBoundingBoxTransform(
|
|
645
|
-
|
|
661
|
+
export function objectBoundingBoxTransform(
|
|
662
|
+
bboxX,
|
|
663
|
+
bboxY,
|
|
664
|
+
bboxWidth,
|
|
665
|
+
bboxHeight,
|
|
666
|
+
) {
|
|
667
|
+
const _D = (x) => new Decimal(x);
|
|
646
668
|
// Transform: scale(bboxWidth, bboxHeight) then translate(bboxX, bboxY)
|
|
647
669
|
const scaleM = Transforms2D.scale(bboxWidth, bboxHeight);
|
|
648
670
|
const translateM = Transforms2D.translation(bboxX, bboxY);
|
|
@@ -708,20 +730,27 @@ export function circleToPath(cx, cy, r) {
|
|
|
708
730
|
* // Equivalent to circleToPath(50, 50, 25)
|
|
709
731
|
*/
|
|
710
732
|
export function ellipseToPath(cx, cy, rx, ry) {
|
|
711
|
-
const D = x => new Decimal(x);
|
|
712
|
-
const cxD = D(cx),
|
|
733
|
+
const D = (x) => new Decimal(x);
|
|
734
|
+
const cxD = D(cx),
|
|
735
|
+
cyD = D(cy),
|
|
736
|
+
rxD = D(rx),
|
|
737
|
+
ryD = D(ry);
|
|
713
738
|
|
|
714
739
|
// Kappa for bezier approximation of circle/ellipse: 4 * (sqrt(2) - 1) / 3
|
|
715
|
-
const kappa = D(
|
|
740
|
+
const kappa = D("0.5522847498307936");
|
|
716
741
|
const kx = rxD.mul(kappa);
|
|
717
742
|
const ky = ryD.mul(kappa);
|
|
718
743
|
|
|
719
744
|
// Four bezier curves forming the ellipse
|
|
720
745
|
// Start at (cx + rx, cy) and go counterclockwise
|
|
721
|
-
const x1 = cxD.plus(rxD),
|
|
722
|
-
|
|
723
|
-
const
|
|
724
|
-
|
|
746
|
+
const x1 = cxD.plus(rxD),
|
|
747
|
+
y1 = cyD;
|
|
748
|
+
const x2 = cxD,
|
|
749
|
+
y2 = cyD.minus(ryD);
|
|
750
|
+
const x3 = cxD.minus(rxD),
|
|
751
|
+
y3 = cyD;
|
|
752
|
+
const x4 = cxD,
|
|
753
|
+
y4 = cyD.plus(ryD);
|
|
725
754
|
|
|
726
755
|
return [
|
|
727
756
|
`M ${x1.toFixed(6)} ${y1.toFixed(6)}`,
|
|
@@ -729,8 +758,8 @@ export function ellipseToPath(cx, cy, rx, ry) {
|
|
|
729
758
|
`C ${x2.minus(kx).toFixed(6)} ${y2.toFixed(6)} ${x3.toFixed(6)} ${y3.minus(ky).toFixed(6)} ${x3.toFixed(6)} ${y3.toFixed(6)}`,
|
|
730
759
|
`C ${x3.toFixed(6)} ${y3.plus(ky).toFixed(6)} ${x4.minus(kx).toFixed(6)} ${y4.toFixed(6)} ${x4.toFixed(6)} ${y4.toFixed(6)}`,
|
|
731
760
|
`C ${x4.plus(kx).toFixed(6)} ${y4.toFixed(6)} ${x1.toFixed(6)} ${y1.plus(ky).toFixed(6)} ${x1.toFixed(6)} ${y1.toFixed(6)}`,
|
|
732
|
-
|
|
733
|
-
].join(
|
|
761
|
+
"Z",
|
|
762
|
+
].join(" ");
|
|
734
763
|
}
|
|
735
764
|
|
|
736
765
|
/**
|
|
@@ -776,8 +805,11 @@ export function ellipseToPath(cx, cy, rx, ry) {
|
|
|
776
805
|
* // rx is clamped to 10 (half of 20)
|
|
777
806
|
*/
|
|
778
807
|
export function rectToPath(x, y, width, height, rx = 0, ry = null) {
|
|
779
|
-
const D = n => new Decimal(n);
|
|
780
|
-
const xD = D(x),
|
|
808
|
+
const D = (n) => new Decimal(n);
|
|
809
|
+
const xD = D(x),
|
|
810
|
+
yD = D(y),
|
|
811
|
+
wD = D(width),
|
|
812
|
+
hD = D(height);
|
|
781
813
|
let rxD = D(rx);
|
|
782
814
|
let ryD = ry !== null ? D(ry) : rxD;
|
|
783
815
|
|
|
@@ -796,8 +828,8 @@ export function rectToPath(x, y, width, height, rx = 0, ry = null) {
|
|
|
796
828
|
`L ${xD.plus(wD).toFixed(6)} ${yD.toFixed(6)}`,
|
|
797
829
|
`L ${xD.plus(wD).toFixed(6)} ${yD.plus(hD).toFixed(6)}`,
|
|
798
830
|
`L ${xD.toFixed(6)} ${yD.plus(hD).toFixed(6)}`,
|
|
799
|
-
|
|
800
|
-
].join(
|
|
831
|
+
"Z",
|
|
832
|
+
].join(" ");
|
|
801
833
|
}
|
|
802
834
|
|
|
803
835
|
// Rounded rectangle using arcs
|
|
@@ -810,8 +842,8 @@ export function rectToPath(x, y, width, height, rx = 0, ry = null) {
|
|
|
810
842
|
`L ${xD.plus(rxD).toFixed(6)} ${yD.plus(hD).toFixed(6)}`,
|
|
811
843
|
`A ${rxD.toFixed(6)} ${ryD.toFixed(6)} 0 0 1 ${xD.toFixed(6)} ${yD.plus(hD).minus(ryD).toFixed(6)}`,
|
|
812
844
|
`L ${xD.toFixed(6)} ${yD.plus(ryD).toFixed(6)}`,
|
|
813
|
-
`A ${rxD.toFixed(6)} ${ryD.toFixed(6)} 0 0 1 ${xD.plus(rxD).toFixed(6)} ${yD.toFixed(6)}
|
|
814
|
-
].join(
|
|
845
|
+
`A ${rxD.toFixed(6)} ${ryD.toFixed(6)} 0 0 1 ${xD.plus(rxD).toFixed(6)} ${yD.toFixed(6)}`,
|
|
846
|
+
].join(" ");
|
|
815
847
|
}
|
|
816
848
|
|
|
817
849
|
/**
|
|
@@ -824,7 +856,7 @@ export function rectToPath(x, y, width, height, rx = 0, ry = null) {
|
|
|
824
856
|
* @returns {string} Path data string
|
|
825
857
|
*/
|
|
826
858
|
export function lineToPath(x1, y1, x2, y2) {
|
|
827
|
-
const D = n => new Decimal(n);
|
|
859
|
+
const D = (n) => new Decimal(n);
|
|
828
860
|
return `M ${D(x1).toFixed(6)} ${D(y1).toFixed(6)} L ${D(x2).toFixed(6)} ${D(y2).toFixed(6)}`;
|
|
829
861
|
}
|
|
830
862
|
|
|
@@ -856,12 +888,12 @@ export function lineToPath(x1, y1, x2, y2) {
|
|
|
856
888
|
*/
|
|
857
889
|
export function polygonToPath(points) {
|
|
858
890
|
const pairs = parsePointPairs(points);
|
|
859
|
-
if (pairs.length === 0) return
|
|
891
|
+
if (pairs.length === 0) return "";
|
|
860
892
|
let d = `M ${pairs[0][0]} ${pairs[0][1]}`;
|
|
861
893
|
for (let i = 1; i < pairs.length; i++) {
|
|
862
894
|
d += ` L ${pairs[i][0]} ${pairs[i][1]}`;
|
|
863
895
|
}
|
|
864
|
-
return d +
|
|
896
|
+
return d + " Z";
|
|
865
897
|
}
|
|
866
898
|
|
|
867
899
|
/**
|
|
@@ -889,7 +921,7 @@ export function polygonToPath(points) {
|
|
|
889
921
|
*/
|
|
890
922
|
export function polylineToPath(points) {
|
|
891
923
|
const pairs = parsePointPairs(points);
|
|
892
|
-
if (pairs.length === 0) return
|
|
924
|
+
if (pairs.length === 0) return "";
|
|
893
925
|
let d = `M ${pairs[0][0]} ${pairs[0][1]}`;
|
|
894
926
|
for (let i = 1; i < pairs.length; i++) {
|
|
895
927
|
d += ` L ${pairs[i][0]} ${pairs[i][1]}`;
|
|
@@ -916,9 +948,12 @@ export function polylineToPath(points) {
|
|
|
916
948
|
function parsePointPairs(points) {
|
|
917
949
|
let coords;
|
|
918
950
|
if (Array.isArray(points)) {
|
|
919
|
-
coords = points.flat().map(n => new Decimal(n).toFixed(6));
|
|
951
|
+
coords = points.flat().map((n) => new Decimal(n).toFixed(6));
|
|
920
952
|
} else {
|
|
921
|
-
coords = points
|
|
953
|
+
coords = points
|
|
954
|
+
.trim()
|
|
955
|
+
.split(/[\s,]+/)
|
|
956
|
+
.map((s) => new Decimal(s).toFixed(6));
|
|
922
957
|
}
|
|
923
958
|
const pairs = [];
|
|
924
959
|
for (let i = 0; i < coords.length - 1; i += 2) {
|
|
@@ -999,9 +1034,18 @@ function parsePointPairs(points) {
|
|
|
999
1034
|
* const arc = transformArc(10, 10, 0, 0, 1, 100, 0, matrix);
|
|
1000
1035
|
* // Result: sweepFlag flipped from 1 to 0 (direction reversed)
|
|
1001
1036
|
*/
|
|
1002
|
-
export function transformArc(
|
|
1003
|
-
|
|
1004
|
-
|
|
1037
|
+
export function transformArc(
|
|
1038
|
+
rx,
|
|
1039
|
+
ry,
|
|
1040
|
+
xAxisRotation,
|
|
1041
|
+
largeArcFlag,
|
|
1042
|
+
sweepFlag,
|
|
1043
|
+
x,
|
|
1044
|
+
y,
|
|
1045
|
+
matrix,
|
|
1046
|
+
) {
|
|
1047
|
+
const D = (n) => new Decimal(n);
|
|
1048
|
+
const NEAR_ZERO = D("1e-16");
|
|
1005
1049
|
|
|
1006
1050
|
// Get matrix components
|
|
1007
1051
|
const a = matrix.data[0][0];
|
|
@@ -1012,7 +1056,8 @@ export function transformArc(rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x,
|
|
|
1012
1056
|
const f = matrix.data[1][2];
|
|
1013
1057
|
|
|
1014
1058
|
// Transform the endpoint
|
|
1015
|
-
const xD = D(x),
|
|
1059
|
+
const xD = D(x),
|
|
1060
|
+
yD = D(y);
|
|
1016
1061
|
const newX = a.mul(xD).plus(c.mul(yD)).plus(e);
|
|
1017
1062
|
const newY = b.mul(xD).plus(d.mul(yD)).plus(f);
|
|
1018
1063
|
|
|
@@ -1021,7 +1066,8 @@ export function transformArc(rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x,
|
|
|
1021
1066
|
const sinRot = Decimal.sin(rotRad);
|
|
1022
1067
|
const cosRot = Decimal.cos(rotRad);
|
|
1023
1068
|
|
|
1024
|
-
const rxD = D(rx),
|
|
1069
|
+
const rxD = D(rx),
|
|
1070
|
+
ryD = D(ry);
|
|
1025
1071
|
|
|
1026
1072
|
// Transform the ellipse axes using the algorithm from lean-svg
|
|
1027
1073
|
// m0, m1 represent the transformed X-axis direction of the ellipse
|
|
@@ -1049,12 +1095,14 @@ export function transformArc(rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x,
|
|
|
1049
1095
|
C2 = C;
|
|
1050
1096
|
} else if (AC.abs().lt(NEAR_ZERO)) {
|
|
1051
1097
|
// 45 degree case
|
|
1052
|
-
A2 = A.plus(B.mul(
|
|
1053
|
-
C2 = A.minus(B.mul(
|
|
1098
|
+
A2 = A.plus(B.mul("0.5"));
|
|
1099
|
+
C2 = A.minus(B.mul("0.5"));
|
|
1054
1100
|
newRotRad = D(Math.PI).div(4);
|
|
1055
1101
|
} else {
|
|
1056
1102
|
// General case - compute eigenvalues
|
|
1057
|
-
const K = D(1)
|
|
1103
|
+
const K = D(1)
|
|
1104
|
+
.plus(B.mul(B).div(AC.mul(AC)))
|
|
1105
|
+
.sqrt();
|
|
1058
1106
|
A2 = A.plus(C).plus(K.mul(AC)).div(2);
|
|
1059
1107
|
C2 = A.plus(C).minus(K.mul(AC)).div(2);
|
|
1060
1108
|
newRotRad = Decimal.atan(B.div(AC)).div(2);
|
|
@@ -1102,7 +1150,7 @@ export function transformArc(rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x,
|
|
|
1102
1150
|
largeArcFlag: largeArcFlag,
|
|
1103
1151
|
sweepFlag: newSweepFlag,
|
|
1104
1152
|
x: newX.toNumber(),
|
|
1105
|
-
y: newY.toNumber()
|
|
1153
|
+
y: newY.toNumber(),
|
|
1106
1154
|
};
|
|
1107
1155
|
}
|
|
1108
1156
|
|
|
@@ -1162,22 +1210,22 @@ export function transformArc(rx, ry, xAxisRotation, largeArcFlag, sweepFlag, x,
|
|
|
1162
1210
|
* // Translation by (50, 50) specified in matrix form
|
|
1163
1211
|
*/
|
|
1164
1212
|
export function parseTransformFunction(func, args) {
|
|
1165
|
-
const D = x => new Decimal(x);
|
|
1213
|
+
const D = (x) => new Decimal(x);
|
|
1166
1214
|
|
|
1167
1215
|
switch (func.toLowerCase()) {
|
|
1168
|
-
case
|
|
1216
|
+
case "translate": {
|
|
1169
1217
|
const tx = args[0] || 0;
|
|
1170
1218
|
const ty = args[1] || 0;
|
|
1171
1219
|
return Transforms2D.translation(tx, ty);
|
|
1172
1220
|
}
|
|
1173
1221
|
|
|
1174
|
-
case
|
|
1222
|
+
case "scale": {
|
|
1175
1223
|
const sx = args[0] || 1;
|
|
1176
1224
|
const sy = args[1] !== undefined ? args[1] : sx;
|
|
1177
1225
|
return Transforms2D.scale(sx, sy);
|
|
1178
1226
|
}
|
|
1179
1227
|
|
|
1180
|
-
case
|
|
1228
|
+
case "rotate": {
|
|
1181
1229
|
// SVG rotate is in degrees, can have optional cx, cy
|
|
1182
1230
|
const angleDeg = args[0] || 0;
|
|
1183
1231
|
const angleRad = D(angleDeg).mul(D(Math.PI)).div(180);
|
|
@@ -1191,29 +1239,29 @@ export function parseTransformFunction(func, args) {
|
|
|
1191
1239
|
return Transforms2D.rotate(angleRad);
|
|
1192
1240
|
}
|
|
1193
1241
|
|
|
1194
|
-
case
|
|
1242
|
+
case "skewx": {
|
|
1195
1243
|
const angleDeg = args[0] || 0;
|
|
1196
1244
|
const angleRad = D(angleDeg).mul(D(Math.PI)).div(180);
|
|
1197
1245
|
const tanVal = Decimal.tan(angleRad);
|
|
1198
1246
|
return Transforms2D.skew(tanVal, 0);
|
|
1199
1247
|
}
|
|
1200
1248
|
|
|
1201
|
-
case
|
|
1249
|
+
case "skewy": {
|
|
1202
1250
|
const angleDeg = args[0] || 0;
|
|
1203
1251
|
const angleRad = D(angleDeg).mul(D(Math.PI)).div(180);
|
|
1204
1252
|
const tanVal = Decimal.tan(angleRad);
|
|
1205
1253
|
return Transforms2D.skew(0, tanVal);
|
|
1206
1254
|
}
|
|
1207
1255
|
|
|
1208
|
-
case
|
|
1256
|
+
case "matrix": {
|
|
1209
1257
|
// matrix(a, b, c, d, e, f) -> | a c e |
|
|
1210
1258
|
// | b d f |
|
|
1211
1259
|
// | 0 0 1 |
|
|
1212
|
-
const [a, b, c, d, e, f] = args.map(x => D(x || 0));
|
|
1260
|
+
const [a, b, c, d, e, f] = args.map((x) => D(x || 0));
|
|
1213
1261
|
return Matrix.from([
|
|
1214
1262
|
[a, c, e],
|
|
1215
1263
|
[b, d, f],
|
|
1216
|
-
[D(0), D(0), D(1)]
|
|
1264
|
+
[D(0), D(0), D(1)],
|
|
1217
1265
|
]);
|
|
1218
1266
|
}
|
|
1219
1267
|
|
|
@@ -1267,7 +1315,7 @@ export function parseTransformFunction(func, args) {
|
|
|
1267
1315
|
* // Returns: Identity matrix (no transformation)
|
|
1268
1316
|
*/
|
|
1269
1317
|
export function parseTransformAttribute(transformStr) {
|
|
1270
|
-
if (!transformStr || transformStr.trim() ===
|
|
1318
|
+
if (!transformStr || transformStr.trim() === "") {
|
|
1271
1319
|
return Matrix.identity(3);
|
|
1272
1320
|
}
|
|
1273
1321
|
|
|
@@ -1283,8 +1331,8 @@ export function parseTransformAttribute(transformStr) {
|
|
|
1283
1331
|
// Parse arguments (comma or space separated)
|
|
1284
1332
|
const args = argsStr
|
|
1285
1333
|
.split(/[\s,]+/)
|
|
1286
|
-
.filter(s => s.length > 0)
|
|
1287
|
-
.map(s => parseFloat(s));
|
|
1334
|
+
.filter((s) => s.length > 0)
|
|
1335
|
+
.map((s) => parseFloat(s));
|
|
1288
1336
|
|
|
1289
1337
|
const matrix = parseTransformFunction(func, args);
|
|
1290
1338
|
// Transforms are applied left-to-right in SVG, so we multiply in order
|
|
@@ -1470,7 +1518,7 @@ export function toSVGMatrix(ctm, precision = 6) {
|
|
|
1470
1518
|
* const result = isIdentity(translation);
|
|
1471
1519
|
* // Returns: false
|
|
1472
1520
|
*/
|
|
1473
|
-
export function isIdentity(m, tolerance =
|
|
1521
|
+
export function isIdentity(m, tolerance = "1e-10") {
|
|
1474
1522
|
const identity = Matrix.identity(3);
|
|
1475
1523
|
return m.equals(identity, tolerance);
|
|
1476
1524
|
}
|
|
@@ -1538,89 +1586,118 @@ export function isIdentity(m, tolerance = '1e-10') {
|
|
|
1538
1586
|
*/
|
|
1539
1587
|
export function transformPathData(pathData, ctm, options = {}) {
|
|
1540
1588
|
const { toAbsolute = true, precision = 6 } = options;
|
|
1541
|
-
const D = x => new Decimal(x);
|
|
1589
|
+
const D = (x) => new Decimal(x);
|
|
1542
1590
|
|
|
1543
1591
|
// Parse path into commands
|
|
1544
1592
|
const commands = parsePathCommands(pathData);
|
|
1545
1593
|
const result = [];
|
|
1546
1594
|
|
|
1547
1595
|
// Track current position for relative commands
|
|
1548
|
-
let curX = D(0),
|
|
1549
|
-
|
|
1596
|
+
let curX = D(0),
|
|
1597
|
+
curY = D(0);
|
|
1598
|
+
let subpathStartX = D(0),
|
|
1599
|
+
subpathStartY = D(0);
|
|
1550
1600
|
|
|
1551
1601
|
for (const { cmd, args } of commands) {
|
|
1552
1602
|
const isRelative = cmd === cmd.toLowerCase();
|
|
1553
1603
|
const cmdUpper = cmd.toUpperCase();
|
|
1554
1604
|
|
|
1555
1605
|
switch (cmdUpper) {
|
|
1556
|
-
case
|
|
1606
|
+
case "M": {
|
|
1557
1607
|
const transformed = [];
|
|
1558
1608
|
for (let i = 0; i < args.length; i += 2) {
|
|
1559
|
-
let x = D(args[i]),
|
|
1560
|
-
|
|
1609
|
+
let x = D(args[i]),
|
|
1610
|
+
y = D(args[i + 1]);
|
|
1611
|
+
if (isRelative) {
|
|
1612
|
+
x = x.plus(curX);
|
|
1613
|
+
y = y.plus(curY);
|
|
1614
|
+
}
|
|
1561
1615
|
|
|
1562
1616
|
const pt = applyToPoint(ctm, x, y);
|
|
1563
1617
|
transformed.push(pt.x.toFixed(precision), pt.y.toFixed(precision));
|
|
1564
1618
|
|
|
1565
|
-
curX = x;
|
|
1566
|
-
|
|
1619
|
+
curX = x;
|
|
1620
|
+
curY = y;
|
|
1621
|
+
if (i === 0) {
|
|
1622
|
+
subpathStartX = x;
|
|
1623
|
+
subpathStartY = y;
|
|
1624
|
+
}
|
|
1567
1625
|
}
|
|
1568
|
-
result.push((toAbsolute ?
|
|
1626
|
+
result.push((toAbsolute ? "M" : cmd) + " " + transformed.join(" "));
|
|
1569
1627
|
break;
|
|
1570
1628
|
}
|
|
1571
1629
|
|
|
1572
|
-
case
|
|
1630
|
+
case "L": {
|
|
1573
1631
|
const transformed = [];
|
|
1574
1632
|
for (let i = 0; i < args.length; i += 2) {
|
|
1575
|
-
let x = D(args[i]),
|
|
1576
|
-
|
|
1633
|
+
let x = D(args[i]),
|
|
1634
|
+
y = D(args[i + 1]);
|
|
1635
|
+
if (isRelative) {
|
|
1636
|
+
x = x.plus(curX);
|
|
1637
|
+
y = y.plus(curY);
|
|
1638
|
+
}
|
|
1577
1639
|
|
|
1578
1640
|
const pt = applyToPoint(ctm, x, y);
|
|
1579
1641
|
transformed.push(pt.x.toFixed(precision), pt.y.toFixed(precision));
|
|
1580
1642
|
|
|
1581
|
-
curX = x;
|
|
1643
|
+
curX = x;
|
|
1644
|
+
curY = y;
|
|
1582
1645
|
}
|
|
1583
|
-
result.push((toAbsolute ?
|
|
1646
|
+
result.push((toAbsolute ? "L" : cmd) + " " + transformed.join(" "));
|
|
1584
1647
|
break;
|
|
1585
1648
|
}
|
|
1586
1649
|
|
|
1587
|
-
case
|
|
1650
|
+
case "H": {
|
|
1588
1651
|
// Horizontal line becomes L after transform (may have Y component)
|
|
1589
1652
|
let x = D(args[0]);
|
|
1590
|
-
if (isRelative) {
|
|
1653
|
+
if (isRelative) {
|
|
1654
|
+
x = x.plus(curX);
|
|
1655
|
+
}
|
|
1591
1656
|
const y = curY;
|
|
1592
1657
|
|
|
1593
1658
|
const pt = applyToPoint(ctm, x, y);
|
|
1594
|
-
result.push(
|
|
1659
|
+
result.push(
|
|
1660
|
+
"L " + pt.x.toFixed(precision) + " " + pt.y.toFixed(precision),
|
|
1661
|
+
);
|
|
1595
1662
|
|
|
1596
1663
|
curX = x;
|
|
1597
1664
|
break;
|
|
1598
1665
|
}
|
|
1599
1666
|
|
|
1600
|
-
case
|
|
1667
|
+
case "V": {
|
|
1601
1668
|
// Vertical line becomes L after transform (may have X component)
|
|
1602
1669
|
const x = curX;
|
|
1603
1670
|
let y = D(args[0]);
|
|
1604
|
-
if (isRelative) {
|
|
1671
|
+
if (isRelative) {
|
|
1672
|
+
y = y.plus(curY);
|
|
1673
|
+
}
|
|
1605
1674
|
|
|
1606
1675
|
const pt = applyToPoint(ctm, x, y);
|
|
1607
|
-
result.push(
|
|
1676
|
+
result.push(
|
|
1677
|
+
"L " + pt.x.toFixed(precision) + " " + pt.y.toFixed(precision),
|
|
1678
|
+
);
|
|
1608
1679
|
|
|
1609
1680
|
curY = y;
|
|
1610
1681
|
break;
|
|
1611
1682
|
}
|
|
1612
1683
|
|
|
1613
|
-
case
|
|
1684
|
+
case "C": {
|
|
1614
1685
|
const transformed = [];
|
|
1615
1686
|
for (let i = 0; i < args.length; i += 6) {
|
|
1616
|
-
let x1 = D(args[i]),
|
|
1617
|
-
|
|
1618
|
-
let
|
|
1687
|
+
let x1 = D(args[i]),
|
|
1688
|
+
y1 = D(args[i + 1]);
|
|
1689
|
+
let x2 = D(args[i + 2]),
|
|
1690
|
+
y2 = D(args[i + 3]);
|
|
1691
|
+
let x = D(args[i + 4]),
|
|
1692
|
+
y = D(args[i + 5]);
|
|
1619
1693
|
|
|
1620
1694
|
if (isRelative) {
|
|
1621
|
-
x1 = x1.plus(curX);
|
|
1622
|
-
|
|
1623
|
-
|
|
1695
|
+
x1 = x1.plus(curX);
|
|
1696
|
+
y1 = y1.plus(curY);
|
|
1697
|
+
x2 = x2.plus(curX);
|
|
1698
|
+
y2 = y2.plus(curY);
|
|
1699
|
+
x = x.plus(curX);
|
|
1700
|
+
y = y.plus(curY);
|
|
1624
1701
|
}
|
|
1625
1702
|
|
|
1626
1703
|
const p1 = applyToPoint(ctm, x1, y1);
|
|
@@ -1628,83 +1705,106 @@ export function transformPathData(pathData, ctm, options = {}) {
|
|
|
1628
1705
|
const p = applyToPoint(ctm, x, y);
|
|
1629
1706
|
|
|
1630
1707
|
transformed.push(
|
|
1631
|
-
p1.x.toFixed(precision),
|
|
1632
|
-
|
|
1633
|
-
|
|
1708
|
+
p1.x.toFixed(precision),
|
|
1709
|
+
p1.y.toFixed(precision),
|
|
1710
|
+
p2.x.toFixed(precision),
|
|
1711
|
+
p2.y.toFixed(precision),
|
|
1712
|
+
p.x.toFixed(precision),
|
|
1713
|
+
p.y.toFixed(precision),
|
|
1634
1714
|
);
|
|
1635
1715
|
|
|
1636
|
-
curX = x;
|
|
1716
|
+
curX = x;
|
|
1717
|
+
curY = y;
|
|
1637
1718
|
}
|
|
1638
|
-
result.push((toAbsolute ?
|
|
1719
|
+
result.push((toAbsolute ? "C" : cmd) + " " + transformed.join(" "));
|
|
1639
1720
|
break;
|
|
1640
1721
|
}
|
|
1641
1722
|
|
|
1642
|
-
case
|
|
1723
|
+
case "S": {
|
|
1643
1724
|
const transformed = [];
|
|
1644
1725
|
for (let i = 0; i < args.length; i += 4) {
|
|
1645
|
-
let x2 = D(args[i]),
|
|
1646
|
-
|
|
1726
|
+
let x2 = D(args[i]),
|
|
1727
|
+
y2 = D(args[i + 1]);
|
|
1728
|
+
let x = D(args[i + 2]),
|
|
1729
|
+
y = D(args[i + 3]);
|
|
1647
1730
|
|
|
1648
1731
|
if (isRelative) {
|
|
1649
|
-
x2 = x2.plus(curX);
|
|
1650
|
-
|
|
1732
|
+
x2 = x2.plus(curX);
|
|
1733
|
+
y2 = y2.plus(curY);
|
|
1734
|
+
x = x.plus(curX);
|
|
1735
|
+
y = y.plus(curY);
|
|
1651
1736
|
}
|
|
1652
1737
|
|
|
1653
1738
|
const p2 = applyToPoint(ctm, x2, y2);
|
|
1654
1739
|
const p = applyToPoint(ctm, x, y);
|
|
1655
1740
|
|
|
1656
1741
|
transformed.push(
|
|
1657
|
-
p2.x.toFixed(precision),
|
|
1658
|
-
|
|
1742
|
+
p2.x.toFixed(precision),
|
|
1743
|
+
p2.y.toFixed(precision),
|
|
1744
|
+
p.x.toFixed(precision),
|
|
1745
|
+
p.y.toFixed(precision),
|
|
1659
1746
|
);
|
|
1660
1747
|
|
|
1661
|
-
curX = x;
|
|
1748
|
+
curX = x;
|
|
1749
|
+
curY = y;
|
|
1662
1750
|
}
|
|
1663
|
-
result.push((toAbsolute ?
|
|
1751
|
+
result.push((toAbsolute ? "S" : cmd) + " " + transformed.join(" "));
|
|
1664
1752
|
break;
|
|
1665
1753
|
}
|
|
1666
1754
|
|
|
1667
|
-
case
|
|
1755
|
+
case "Q": {
|
|
1668
1756
|
const transformed = [];
|
|
1669
1757
|
for (let i = 0; i < args.length; i += 4) {
|
|
1670
|
-
let x1 = D(args[i]),
|
|
1671
|
-
|
|
1758
|
+
let x1 = D(args[i]),
|
|
1759
|
+
y1 = D(args[i + 1]);
|
|
1760
|
+
let x = D(args[i + 2]),
|
|
1761
|
+
y = D(args[i + 3]);
|
|
1672
1762
|
|
|
1673
1763
|
if (isRelative) {
|
|
1674
|
-
x1 = x1.plus(curX);
|
|
1675
|
-
|
|
1764
|
+
x1 = x1.plus(curX);
|
|
1765
|
+
y1 = y1.plus(curY);
|
|
1766
|
+
x = x.plus(curX);
|
|
1767
|
+
y = y.plus(curY);
|
|
1676
1768
|
}
|
|
1677
1769
|
|
|
1678
1770
|
const p1 = applyToPoint(ctm, x1, y1);
|
|
1679
1771
|
const p = applyToPoint(ctm, x, y);
|
|
1680
1772
|
|
|
1681
1773
|
transformed.push(
|
|
1682
|
-
p1.x.toFixed(precision),
|
|
1683
|
-
|
|
1774
|
+
p1.x.toFixed(precision),
|
|
1775
|
+
p1.y.toFixed(precision),
|
|
1776
|
+
p.x.toFixed(precision),
|
|
1777
|
+
p.y.toFixed(precision),
|
|
1684
1778
|
);
|
|
1685
1779
|
|
|
1686
|
-
curX = x;
|
|
1780
|
+
curX = x;
|
|
1781
|
+
curY = y;
|
|
1687
1782
|
}
|
|
1688
|
-
result.push((toAbsolute ?
|
|
1783
|
+
result.push((toAbsolute ? "Q" : cmd) + " " + transformed.join(" "));
|
|
1689
1784
|
break;
|
|
1690
1785
|
}
|
|
1691
1786
|
|
|
1692
|
-
case
|
|
1787
|
+
case "T": {
|
|
1693
1788
|
const transformed = [];
|
|
1694
1789
|
for (let i = 0; i < args.length; i += 2) {
|
|
1695
|
-
let x = D(args[i]),
|
|
1696
|
-
|
|
1790
|
+
let x = D(args[i]),
|
|
1791
|
+
y = D(args[i + 1]);
|
|
1792
|
+
if (isRelative) {
|
|
1793
|
+
x = x.plus(curX);
|
|
1794
|
+
y = y.plus(curY);
|
|
1795
|
+
}
|
|
1697
1796
|
|
|
1698
1797
|
const pt = applyToPoint(ctm, x, y);
|
|
1699
1798
|
transformed.push(pt.x.toFixed(precision), pt.y.toFixed(precision));
|
|
1700
1799
|
|
|
1701
|
-
curX = x;
|
|
1800
|
+
curX = x;
|
|
1801
|
+
curY = y;
|
|
1702
1802
|
}
|
|
1703
|
-
result.push((toAbsolute ?
|
|
1803
|
+
result.push((toAbsolute ? "T" : cmd) + " " + transformed.join(" "));
|
|
1704
1804
|
break;
|
|
1705
1805
|
}
|
|
1706
1806
|
|
|
1707
|
-
case
|
|
1807
|
+
case "A": {
|
|
1708
1808
|
// Use proper arc transformation
|
|
1709
1809
|
const transformed = [];
|
|
1710
1810
|
for (let i = 0; i < args.length; i += 7) {
|
|
@@ -1713,11 +1813,24 @@ export function transformPathData(pathData, ctm, options = {}) {
|
|
|
1713
1813
|
const rotation = args[i + 2];
|
|
1714
1814
|
const largeArc = args[i + 3];
|
|
1715
1815
|
const sweep = args[i + 4];
|
|
1716
|
-
let x = D(args[i + 5]),
|
|
1816
|
+
let x = D(args[i + 5]),
|
|
1817
|
+
y = D(args[i + 6]);
|
|
1717
1818
|
|
|
1718
|
-
if (isRelative) {
|
|
1819
|
+
if (isRelative) {
|
|
1820
|
+
x = x.plus(curX);
|
|
1821
|
+
y = y.plus(curY);
|
|
1822
|
+
}
|
|
1719
1823
|
|
|
1720
|
-
const arc = transformArc(
|
|
1824
|
+
const arc = transformArc(
|
|
1825
|
+
rx,
|
|
1826
|
+
ry,
|
|
1827
|
+
rotation,
|
|
1828
|
+
largeArc,
|
|
1829
|
+
sweep,
|
|
1830
|
+
x.toNumber(),
|
|
1831
|
+
y.toNumber(),
|
|
1832
|
+
ctm,
|
|
1833
|
+
);
|
|
1721
1834
|
|
|
1722
1835
|
transformed.push(
|
|
1723
1836
|
arc.rx.toFixed(precision),
|
|
@@ -1726,17 +1839,18 @@ export function transformPathData(pathData, ctm, options = {}) {
|
|
|
1726
1839
|
arc.largeArcFlag,
|
|
1727
1840
|
arc.sweepFlag,
|
|
1728
1841
|
arc.x.toFixed(precision),
|
|
1729
|
-
arc.y.toFixed(precision)
|
|
1842
|
+
arc.y.toFixed(precision),
|
|
1730
1843
|
);
|
|
1731
1844
|
|
|
1732
|
-
curX = x;
|
|
1845
|
+
curX = x;
|
|
1846
|
+
curY = y;
|
|
1733
1847
|
}
|
|
1734
|
-
result.push((toAbsolute ?
|
|
1848
|
+
result.push((toAbsolute ? "A" : cmd) + " " + transformed.join(" "));
|
|
1735
1849
|
break;
|
|
1736
1850
|
}
|
|
1737
1851
|
|
|
1738
|
-
case
|
|
1739
|
-
result.push(
|
|
1852
|
+
case "Z": {
|
|
1853
|
+
result.push("Z");
|
|
1740
1854
|
curX = subpathStartX;
|
|
1741
1855
|
curY = subpathStartY;
|
|
1742
1856
|
break;
|
|
@@ -1744,11 +1858,11 @@ export function transformPathData(pathData, ctm, options = {}) {
|
|
|
1744
1858
|
|
|
1745
1859
|
default:
|
|
1746
1860
|
// Keep unknown commands as-is
|
|
1747
|
-
result.push(cmd +
|
|
1861
|
+
result.push(cmd + " " + args.join(" "));
|
|
1748
1862
|
}
|
|
1749
1863
|
}
|
|
1750
1864
|
|
|
1751
|
-
return result.join(
|
|
1865
|
+
return result.join(" ");
|
|
1752
1866
|
}
|
|
1753
1867
|
|
|
1754
1868
|
/**
|
|
@@ -1792,9 +1906,13 @@ function parsePathCommands(pathData) {
|
|
|
1792
1906
|
while ((match = commandRegex.exec(pathData)) !== null) {
|
|
1793
1907
|
const cmd = match[1];
|
|
1794
1908
|
const argsStr = match[2].trim();
|
|
1795
|
-
const args =
|
|
1796
|
-
|
|
1797
|
-
|
|
1909
|
+
const args =
|
|
1910
|
+
argsStr.length > 0
|
|
1911
|
+
? argsStr
|
|
1912
|
+
.split(/[\s,]+/)
|
|
1913
|
+
.filter((s) => s.length > 0)
|
|
1914
|
+
.map((s) => parseFloat(s))
|
|
1915
|
+
: [];
|
|
1798
1916
|
commands.push({ cmd, args });
|
|
1799
1917
|
}
|
|
1800
1918
|
|
|
@@ -1833,11 +1951,11 @@ function parsePathCommands(pathData) {
|
|
|
1833
1951
|
* @property {string} improvementFactorGIS - Improvement for GIS/CAD ('1e+93')
|
|
1834
1952
|
*/
|
|
1835
1953
|
export const PRECISION_INFO = {
|
|
1836
|
-
floatErrorGIS: 1.69e-7,
|
|
1837
|
-
floatErrorTypical: 1.14e-13,
|
|
1954
|
+
floatErrorGIS: 1.69e-7, // Error with 1e6+ scale coordinates
|
|
1955
|
+
floatErrorTypical: 1.14e-13, // Error with typical 6-level SVG hierarchy
|
|
1838
1956
|
decimalPrecision: 80,
|
|
1839
|
-
typicalRoundTripError:
|
|
1840
|
-
improvementFactorGIS:
|
|
1957
|
+
typicalRoundTripError: "1e-77",
|
|
1958
|
+
improvementFactorGIS: "1e+93",
|
|
1841
1959
|
};
|
|
1842
1960
|
|
|
1843
1961
|
export default {
|
|
@@ -1870,5 +1988,5 @@ export default {
|
|
|
1870
1988
|
toSVGMatrix,
|
|
1871
1989
|
isIdentity,
|
|
1872
1990
|
transformPathData,
|
|
1873
|
-
PRECISION_INFO
|
|
1991
|
+
PRECISION_INFO,
|
|
1874
1992
|
};
|