@emasoft/svg-matrix 1.0.30 → 1.0.31
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/bin/svg-matrix.js +310 -61
- package/bin/svglinter.cjs +102 -3
- package/bin/svgm.js +236 -27
- package/package.json +1 -1
- package/src/animation-optimization.js +137 -17
- package/src/animation-references.js +123 -6
- package/src/arc-length.js +213 -4
- package/src/bezier-analysis.js +217 -21
- package/src/bezier-intersections.js +275 -12
- package/src/browser-verify.js +237 -4
- package/src/clip-path-resolver.js +168 -0
- package/src/convert-path-data.js +479 -28
- package/src/css-specificity.js +73 -10
- package/src/douglas-peucker.js +219 -2
- package/src/flatten-pipeline.js +284 -26
- package/src/geometry-to-path.js +250 -25
- package/src/gjk-collision.js +236 -33
- package/src/index.js +261 -3
- package/src/inkscape-support.js +86 -28
- package/src/logger.js +48 -3
- package/src/marker-resolver.js +278 -74
- package/src/mask-resolver.js +265 -66
- package/src/matrix.js +44 -5
- package/src/mesh-gradient.js +352 -102
- package/src/off-canvas-detection.js +382 -13
- package/src/path-analysis.js +192 -18
- package/src/path-data-plugins.js +309 -5
- package/src/path-optimization.js +129 -5
- package/src/path-simplification.js +188 -32
- package/src/pattern-resolver.js +454 -106
- package/src/polygon-clip.js +324 -1
- package/src/svg-boolean-ops.js +226 -9
- package/src/svg-collections.js +7 -5
- package/src/svg-flatten.js +386 -62
- package/src/svg-parser.js +179 -8
- package/src/svg-rendering-context.js +235 -6
- package/src/svg-toolbox.js +45 -8
- package/src/svg2-polyfills.js +40 -10
- package/src/transform-decomposition.js +258 -32
- package/src/transform-optimization.js +259 -13
- package/src/transforms2d.js +82 -9
- package/src/transforms3d.js +62 -10
- package/src/use-symbol-resolver.js +286 -42
- package/src/vector.js +64 -8
- package/src/verification.js +392 -1
package/src/path-data-plugins.js
CHANGED
|
@@ -41,6 +41,99 @@ import {
|
|
|
41
41
|
formatNumber,
|
|
42
42
|
} from "./convert-path-data.js";
|
|
43
43
|
|
|
44
|
+
// ============================================================================
|
|
45
|
+
// INPUT VALIDATION HELPERS
|
|
46
|
+
// ============================================================================
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Validate path string input - fail fast on invalid input
|
|
50
|
+
* @param {string} d - Path d attribute to validate
|
|
51
|
+
* @throws {TypeError} If d is not a valid string
|
|
52
|
+
*/
|
|
53
|
+
function validatePathString(d) {
|
|
54
|
+
if (typeof d !== "string") {
|
|
55
|
+
throw new TypeError("Path d must be a string");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Validate numeric parameter - fail fast on invalid numbers
|
|
61
|
+
* Reserved for future use - prefixed with underscore to indicate internal/unused status
|
|
62
|
+
* @param {number} value - Numeric value to validate
|
|
63
|
+
* @param {string} name - Parameter name for error message
|
|
64
|
+
* @throws {TypeError} If value is not a finite number
|
|
65
|
+
*/
|
|
66
|
+
function _validateNumber(value, name) {
|
|
67
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
68
|
+
throw new TypeError(`${name} must be a finite number, got ${value}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Validate precision parameter - fail fast on invalid precision
|
|
74
|
+
* @param {number} precision - Precision value to validate
|
|
75
|
+
* @throws {TypeError} If precision is not a valid non-negative finite number
|
|
76
|
+
*/
|
|
77
|
+
function validatePrecision(precision) {
|
|
78
|
+
if (
|
|
79
|
+
typeof precision !== "number" ||
|
|
80
|
+
!Number.isFinite(precision) ||
|
|
81
|
+
precision < 0
|
|
82
|
+
) {
|
|
83
|
+
throw new TypeError(
|
|
84
|
+
`Precision must be a finite non-negative number, got ${precision}`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Validate tolerance parameter - fail fast on invalid tolerance
|
|
91
|
+
* @param {number} tolerance - Tolerance value to validate
|
|
92
|
+
* @throws {TypeError} If tolerance is not a valid non-negative finite number
|
|
93
|
+
*/
|
|
94
|
+
function validateTolerance(tolerance) {
|
|
95
|
+
if (
|
|
96
|
+
typeof tolerance !== "number" ||
|
|
97
|
+
!Number.isFinite(tolerance) ||
|
|
98
|
+
tolerance < 0
|
|
99
|
+
) {
|
|
100
|
+
throw new TypeError(
|
|
101
|
+
`Tolerance must be a finite non-negative number, got ${tolerance}`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Validate command object structure - fail fast on invalid commands
|
|
108
|
+
* @param {object} cmd - Command object to validate
|
|
109
|
+
* @param {number} minArgs - Minimum required args length (optional)
|
|
110
|
+
* @throws {TypeError} If command structure is invalid
|
|
111
|
+
*/
|
|
112
|
+
function validateCommand(cmd, minArgs = 0) {
|
|
113
|
+
if (!cmd || typeof cmd !== "object") {
|
|
114
|
+
throw new TypeError("Command must be an object");
|
|
115
|
+
}
|
|
116
|
+
if (typeof cmd.command !== "string") {
|
|
117
|
+
throw new TypeError("Command must have a string 'command' property");
|
|
118
|
+
}
|
|
119
|
+
if (!Array.isArray(cmd.args)) {
|
|
120
|
+
throw new TypeError("Command must have an array 'args' property");
|
|
121
|
+
}
|
|
122
|
+
if (cmd.args.length < minArgs) {
|
|
123
|
+
throw new TypeError(
|
|
124
|
+
`Command ${cmd.command} requires at least ${minArgs} args, got ${cmd.args.length}`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
// Validate all args are finite numbers
|
|
128
|
+
for (let i = 0; i < cmd.args.length; i++) {
|
|
129
|
+
if (typeof cmd.args[i] !== "number" || !Number.isFinite(cmd.args[i])) {
|
|
130
|
+
throw new TypeError(
|
|
131
|
+
`Command ${cmd.command} arg[${i}] must be a finite number, got ${cmd.args[i]}`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
44
137
|
// ============================================================================
|
|
45
138
|
// PLUGIN: removeLeadingZero
|
|
46
139
|
// Removes leading zeros from decimal numbers (0.5 -> .5)
|
|
@@ -54,16 +147,27 @@ import {
|
|
|
54
147
|
* @returns {string} Optimized path
|
|
55
148
|
*/
|
|
56
149
|
export function removeLeadingZero(d, precision = 3) {
|
|
150
|
+
// Validate input parameters - fail fast if invalid
|
|
151
|
+
validatePathString(d);
|
|
152
|
+
validatePrecision(precision);
|
|
153
|
+
|
|
57
154
|
const commands = parsePath(d);
|
|
58
155
|
if (commands.length === 0) return d;
|
|
59
156
|
|
|
157
|
+
// Validate all commands have proper structure
|
|
158
|
+
commands.forEach((cmd) => validateCommand(cmd));
|
|
159
|
+
|
|
60
160
|
// Format numbers with leading zero removal
|
|
61
161
|
const formatted = commands.map((cmd) => ({
|
|
62
162
|
command: cmd.command,
|
|
63
163
|
args: cmd.args.map((n) => {
|
|
64
164
|
const str = formatNumber(n, precision);
|
|
65
|
-
|
|
66
|
-
|
|
165
|
+
const parsed = parseFloat(str);
|
|
166
|
+
// Ensure the parsed value is valid - fail fast if corrupted
|
|
167
|
+
if (!Number.isFinite(parsed)) {
|
|
168
|
+
throw new Error(`Invalid numeric value encountered: ${str}`);
|
|
169
|
+
}
|
|
170
|
+
return parsed;
|
|
67
171
|
}),
|
|
68
172
|
}));
|
|
69
173
|
|
|
@@ -83,9 +187,16 @@ export function removeLeadingZero(d, precision = 3) {
|
|
|
83
187
|
* @returns {string} Optimized path
|
|
84
188
|
*/
|
|
85
189
|
export function negativeExtraSpace(d, precision = 3) {
|
|
190
|
+
// Validate input parameters - fail fast if invalid
|
|
191
|
+
validatePathString(d);
|
|
192
|
+
validatePrecision(precision);
|
|
193
|
+
|
|
86
194
|
const commands = parsePath(d);
|
|
87
195
|
if (commands.length === 0) return d;
|
|
88
196
|
|
|
197
|
+
// Validate command structures
|
|
198
|
+
commands.forEach((cmd) => validateCommand(cmd));
|
|
199
|
+
|
|
89
200
|
// serializePath already handles this optimization
|
|
90
201
|
return serializePath(commands, precision);
|
|
91
202
|
}
|
|
@@ -102,9 +213,16 @@ export function negativeExtraSpace(d, precision = 3) {
|
|
|
102
213
|
* @returns {string} Path with relative commands
|
|
103
214
|
*/
|
|
104
215
|
export function convertToRelative(d, precision = 3) {
|
|
216
|
+
// Validate input parameters - fail fast if invalid
|
|
217
|
+
validatePathString(d);
|
|
218
|
+
validatePrecision(precision);
|
|
219
|
+
|
|
105
220
|
const commands = parsePath(d);
|
|
106
221
|
if (commands.length === 0) return d;
|
|
107
222
|
|
|
223
|
+
// Validate command structures
|
|
224
|
+
commands.forEach((cmd) => validateCommand(cmd));
|
|
225
|
+
|
|
108
226
|
let cx = 0,
|
|
109
227
|
cy = 0;
|
|
110
228
|
let startX = 0,
|
|
@@ -153,6 +271,8 @@ export function convertToRelative(d, precision = 3) {
|
|
|
153
271
|
cx = startX;
|
|
154
272
|
cy = startY;
|
|
155
273
|
break;
|
|
274
|
+
default:
|
|
275
|
+
break;
|
|
156
276
|
}
|
|
157
277
|
}
|
|
158
278
|
|
|
@@ -171,9 +291,16 @@ export function convertToRelative(d, precision = 3) {
|
|
|
171
291
|
* @returns {string} Path with absolute commands
|
|
172
292
|
*/
|
|
173
293
|
export function convertToAbsolute(d, precision = 3) {
|
|
294
|
+
// Validate input parameters - fail fast if invalid
|
|
295
|
+
validatePathString(d);
|
|
296
|
+
validatePrecision(precision);
|
|
297
|
+
|
|
174
298
|
const commands = parsePath(d);
|
|
175
299
|
if (commands.length === 0) return d;
|
|
176
300
|
|
|
301
|
+
// Validate command structures
|
|
302
|
+
commands.forEach((cmd) => validateCommand(cmd));
|
|
303
|
+
|
|
177
304
|
let cx = 0,
|
|
178
305
|
cy = 0;
|
|
179
306
|
let startX = 0,
|
|
@@ -221,6 +348,8 @@ export function convertToAbsolute(d, precision = 3) {
|
|
|
221
348
|
cx = startX;
|
|
222
349
|
cy = startY;
|
|
223
350
|
break;
|
|
351
|
+
default:
|
|
352
|
+
break;
|
|
224
353
|
}
|
|
225
354
|
}
|
|
226
355
|
|
|
@@ -241,9 +370,17 @@ export function convertToAbsolute(d, precision = 3) {
|
|
|
241
370
|
* @returns {string} Optimized path
|
|
242
371
|
*/
|
|
243
372
|
export function lineShorthands(d, tolerance = 1e-6, precision = 3) {
|
|
373
|
+
// Validate input parameters - fail fast if invalid
|
|
374
|
+
validatePathString(d);
|
|
375
|
+
validateTolerance(tolerance);
|
|
376
|
+
validatePrecision(precision);
|
|
377
|
+
|
|
244
378
|
const commands = parsePath(d);
|
|
245
379
|
if (commands.length === 0) return d;
|
|
246
380
|
|
|
381
|
+
// Validate command structures
|
|
382
|
+
commands.forEach((cmd) => validateCommand(cmd));
|
|
383
|
+
|
|
247
384
|
let cx = 0,
|
|
248
385
|
cy = 0;
|
|
249
386
|
let startX = 0,
|
|
@@ -310,6 +447,8 @@ export function lineShorthands(d, tolerance = 1e-6, precision = 3) {
|
|
|
310
447
|
cx = startX;
|
|
311
448
|
cy = startY;
|
|
312
449
|
break;
|
|
450
|
+
default:
|
|
451
|
+
break;
|
|
313
452
|
}
|
|
314
453
|
}
|
|
315
454
|
|
|
@@ -330,9 +469,17 @@ export function lineShorthands(d, tolerance = 1e-6, precision = 3) {
|
|
|
330
469
|
* @returns {string} Optimized path
|
|
331
470
|
*/
|
|
332
471
|
export function convertToZ(d, tolerance = 1e-6, precision = 3) {
|
|
472
|
+
// Validate input parameters - fail fast if invalid
|
|
473
|
+
validatePathString(d);
|
|
474
|
+
validateTolerance(tolerance);
|
|
475
|
+
validatePrecision(precision);
|
|
476
|
+
|
|
333
477
|
const commands = parsePath(d);
|
|
334
478
|
if (commands.length === 0) return d;
|
|
335
479
|
|
|
480
|
+
// Validate command structures
|
|
481
|
+
commands.forEach((cmd) => validateCommand(cmd));
|
|
482
|
+
|
|
336
483
|
let cx = 0,
|
|
337
484
|
cy = 0;
|
|
338
485
|
let startX = 0,
|
|
@@ -397,6 +544,8 @@ export function convertToZ(d, tolerance = 1e-6, precision = 3) {
|
|
|
397
544
|
cx = startX;
|
|
398
545
|
cy = startY;
|
|
399
546
|
break;
|
|
547
|
+
default:
|
|
548
|
+
break;
|
|
400
549
|
}
|
|
401
550
|
}
|
|
402
551
|
|
|
@@ -407,6 +556,36 @@ export function convertToZ(d, tolerance = 1e-6, precision = 3) {
|
|
|
407
556
|
// BEZIER CURVE UTILITIES - Proper mathematical evaluation and distance
|
|
408
557
|
// ============================================================================
|
|
409
558
|
|
|
559
|
+
/**
|
|
560
|
+
* Validate that all coordinate parameters are finite numbers
|
|
561
|
+
* @param {...number} coords - Coordinate values to validate
|
|
562
|
+
* @throws {TypeError} If any coordinate is not a finite number
|
|
563
|
+
*/
|
|
564
|
+
function validateCoordinates(...coords) {
|
|
565
|
+
for (let i = 0; i < coords.length; i++) {
|
|
566
|
+
if (typeof coords[i] !== "number" || !Number.isFinite(coords[i])) {
|
|
567
|
+
throw new TypeError(
|
|
568
|
+
`Coordinate at position ${i} must be a finite number, got ${coords[i]}`,
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Validate t parameter is in valid range [0, 1]
|
|
576
|
+
* @param {number} t - Parameter value to validate
|
|
577
|
+
* @throws {TypeError} If t is not a valid number
|
|
578
|
+
* @throws {RangeError} If t is outside [0, 1] range
|
|
579
|
+
*/
|
|
580
|
+
function validateT(t) {
|
|
581
|
+
if (typeof t !== "number" || !Number.isFinite(t)) {
|
|
582
|
+
throw new TypeError(`Parameter t must be a finite number, got ${t}`);
|
|
583
|
+
}
|
|
584
|
+
if (t < 0 || t > 1) {
|
|
585
|
+
throw new RangeError(`Parameter t must be in range [0, 1], got ${t}`);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
410
589
|
/**
|
|
411
590
|
* Evaluate a cubic Bezier curve at parameter t.
|
|
412
591
|
* B(t) = (1-t)^3*P0 + 3*(1-t)^2*t*P1 + 3*(1-t)*t^2*P2 + t^3*P3
|
|
@@ -422,6 +601,10 @@ export function convertToZ(d, tolerance = 1e-6, precision = 3) {
|
|
|
422
601
|
* @returns {{x: number, y: number}} Point on curve
|
|
423
602
|
*/
|
|
424
603
|
function cubicBezierPoint(t, p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) {
|
|
604
|
+
// Validate all inputs - fail fast on invalid data
|
|
605
|
+
validateT(t);
|
|
606
|
+
validateCoordinates(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y);
|
|
607
|
+
|
|
425
608
|
const mt = 1 - t;
|
|
426
609
|
const mt2 = mt * mt;
|
|
427
610
|
const mt3 = mt2 * mt;
|
|
@@ -448,6 +631,10 @@ function cubicBezierPoint(t, p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) {
|
|
|
448
631
|
* @returns {{x: number, y: number}} First derivative vector
|
|
449
632
|
*/
|
|
450
633
|
function cubicBezierDeriv1(t, p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) {
|
|
634
|
+
// Validate all inputs - fail fast on invalid data
|
|
635
|
+
validateT(t);
|
|
636
|
+
validateCoordinates(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y);
|
|
637
|
+
|
|
451
638
|
const mt = 1 - t;
|
|
452
639
|
const mt2 = mt * mt;
|
|
453
640
|
const t2 = t * t;
|
|
@@ -472,6 +659,10 @@ function cubicBezierDeriv1(t, p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) {
|
|
|
472
659
|
* @returns {{x: number, y: number}} Second derivative vector
|
|
473
660
|
*/
|
|
474
661
|
function cubicBezierDeriv2(t, p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) {
|
|
662
|
+
// Validate all inputs - fail fast on invalid data
|
|
663
|
+
validateT(t);
|
|
664
|
+
validateCoordinates(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y);
|
|
665
|
+
|
|
475
666
|
const mt = 1 - t;
|
|
476
667
|
return {
|
|
477
668
|
x: 6 * mt * (p2x - 2 * p1x + p0x) + 6 * t * (p3x - 2 * p2x + p1x),
|
|
@@ -508,9 +699,14 @@ function _closestTOnCubicBezier(
|
|
|
508
699
|
p3y,
|
|
509
700
|
tInit = 0.5,
|
|
510
701
|
) {
|
|
702
|
+
// Validate all coordinate inputs - fail fast on invalid data
|
|
703
|
+
validateCoordinates(px, py, p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y);
|
|
704
|
+
// Note: tInit is clamped below so no need to strictly validate it
|
|
705
|
+
|
|
511
706
|
let t = Math.max(0, Math.min(1, tInit));
|
|
512
707
|
|
|
513
708
|
for (let iter = 0; iter < 10; iter++) {
|
|
709
|
+
// Note: t is always clamped to [0,1] so cubicBezierPoint validation won't fail
|
|
514
710
|
const Q = cubicBezierPoint(t, p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y);
|
|
515
711
|
const Q1 = cubicBezierDeriv1(t, p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y);
|
|
516
712
|
const Q2 = cubicBezierDeriv2(t, p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y);
|
|
@@ -523,6 +719,7 @@ function _closestTOnCubicBezier(
|
|
|
523
719
|
// f'(t) = B'(t)·B'(t) + (B(t)-p)·B''(t)
|
|
524
720
|
const fp = Q1.x * Q1.x + Q1.y * Q1.y + diffX * Q2.x + diffY * Q2.y;
|
|
525
721
|
|
|
722
|
+
// Division by zero check - fail fast if derivative is zero
|
|
526
723
|
if (Math.abs(fp) < 1e-12) break;
|
|
527
724
|
|
|
528
725
|
const tNext = Math.max(0, Math.min(1, t - f / fp));
|
|
@@ -547,10 +744,14 @@ function _closestTOnCubicBezier(
|
|
|
547
744
|
* @returns {number} Distance from point to line segment
|
|
548
745
|
*/
|
|
549
746
|
function pointToLineDistance(px, py, x0, y0, x1, y1) {
|
|
747
|
+
// Validate all coordinate inputs - fail fast on invalid data
|
|
748
|
+
validateCoordinates(px, py, x0, y0, x1, y1);
|
|
749
|
+
|
|
550
750
|
const dx = x1 - x0;
|
|
551
751
|
const dy = y1 - y0;
|
|
552
752
|
const lengthSq = dx * dx + dy * dy;
|
|
553
753
|
|
|
754
|
+
// Handle degenerate case where line segment is a point
|
|
554
755
|
if (lengthSq < 1e-10) {
|
|
555
756
|
return Math.sqrt((px - x0) ** 2 + (py - y0) ** 2);
|
|
556
757
|
}
|
|
@@ -579,6 +780,9 @@ function pointToLineDistance(px, py, x0, y0, x1, y1) {
|
|
|
579
780
|
* @returns {number} Maximum distance from any curve point to the line
|
|
580
781
|
*/
|
|
581
782
|
function maxErrorCurveToLine(p0x, p0y, cp1x, cp1y, cp2x, cp2y, p3x, p3y) {
|
|
783
|
+
// Validate all coordinate inputs - fail fast on invalid data
|
|
784
|
+
validateCoordinates(p0x, p0y, cp1x, cp1y, cp2x, cp2y, p3x, p3y);
|
|
785
|
+
|
|
582
786
|
let maxErr = 0;
|
|
583
787
|
|
|
584
788
|
// Sample at regular t intervals and find closest point on line
|
|
@@ -650,9 +854,17 @@ function isCurveStraight(x0, y0, cp1x, cp1y, cp2x, cp2y, x3, y3, tolerance) {
|
|
|
650
854
|
* @returns {string} Optimized path
|
|
651
855
|
*/
|
|
652
856
|
export function straightCurves(d, tolerance = 0.5, precision = 3) {
|
|
857
|
+
// Validate input parameters - fail fast if invalid
|
|
858
|
+
validatePathString(d);
|
|
859
|
+
validateTolerance(tolerance);
|
|
860
|
+
validatePrecision(precision);
|
|
861
|
+
|
|
653
862
|
const commands = parsePath(d);
|
|
654
863
|
if (commands.length === 0) return d;
|
|
655
864
|
|
|
865
|
+
// Validate command structures
|
|
866
|
+
commands.forEach((cmd) => validateCommand(cmd));
|
|
867
|
+
|
|
656
868
|
let cx = 0,
|
|
657
869
|
cy = 0;
|
|
658
870
|
let startX = 0,
|
|
@@ -719,6 +931,8 @@ export function straightCurves(d, tolerance = 0.5, precision = 3) {
|
|
|
719
931
|
cx = startX;
|
|
720
932
|
cy = startY;
|
|
721
933
|
break;
|
|
934
|
+
default:
|
|
935
|
+
break;
|
|
722
936
|
}
|
|
723
937
|
}
|
|
724
938
|
|
|
@@ -739,9 +953,16 @@ export function straightCurves(d, tolerance = 0.5, precision = 3) {
|
|
|
739
953
|
* @returns {string} Optimized path
|
|
740
954
|
*/
|
|
741
955
|
export function collapseRepeated(d, precision = 3) {
|
|
956
|
+
// Validate input parameters - fail fast if invalid
|
|
957
|
+
validatePathString(d);
|
|
958
|
+
validatePrecision(precision);
|
|
959
|
+
|
|
742
960
|
const commands = parsePath(d);
|
|
743
961
|
if (commands.length === 0) return d;
|
|
744
962
|
|
|
963
|
+
// Validate command structures
|
|
964
|
+
commands.forEach((cmd) => validateCommand(cmd));
|
|
965
|
+
|
|
745
966
|
// serializePath already collapses repeated commands
|
|
746
967
|
return serializePath(commands, precision);
|
|
747
968
|
}
|
|
@@ -759,9 +980,21 @@ export function collapseRepeated(d, precision = 3) {
|
|
|
759
980
|
* @returns {string} Path with rounded numbers
|
|
760
981
|
*/
|
|
761
982
|
export function floatPrecision(d, precision = 3) {
|
|
983
|
+
// Validate input parameters - fail fast if invalid
|
|
984
|
+
validatePathString(d);
|
|
985
|
+
validatePrecision(precision);
|
|
986
|
+
|
|
987
|
+
// Ensure precision is reasonable to avoid overflow
|
|
988
|
+
if (precision > 15) {
|
|
989
|
+
throw new RangeError(`Precision ${precision} is too large (max 15)`);
|
|
990
|
+
}
|
|
991
|
+
|
|
762
992
|
const commands = parsePath(d);
|
|
763
993
|
if (commands.length === 0) return d;
|
|
764
994
|
|
|
995
|
+
// Validate command structures
|
|
996
|
+
commands.forEach((cmd) => validateCommand(cmd));
|
|
997
|
+
|
|
765
998
|
const factor = Math.pow(10, precision);
|
|
766
999
|
const rounded = commands.map((cmd) => ({
|
|
767
1000
|
command: cmd.command,
|
|
@@ -785,9 +1018,17 @@ export function floatPrecision(d, precision = 3) {
|
|
|
785
1018
|
* @returns {string} Optimized path
|
|
786
1019
|
*/
|
|
787
1020
|
export function removeUselessCommands(d, tolerance = 1e-6, precision = 3) {
|
|
1021
|
+
// Validate input parameters - fail fast if invalid
|
|
1022
|
+
validatePathString(d);
|
|
1023
|
+
validateTolerance(tolerance);
|
|
1024
|
+
validatePrecision(precision);
|
|
1025
|
+
|
|
788
1026
|
const commands = parsePath(d);
|
|
789
1027
|
if (commands.length === 0) return d;
|
|
790
1028
|
|
|
1029
|
+
// Validate command structures
|
|
1030
|
+
commands.forEach((cmd) => validateCommand(cmd));
|
|
1031
|
+
|
|
791
1032
|
let cx = 0,
|
|
792
1033
|
cy = 0;
|
|
793
1034
|
let startX = 0,
|
|
@@ -832,6 +1073,8 @@ export function removeUselessCommands(d, tolerance = 1e-6, precision = 3) {
|
|
|
832
1073
|
keep = false;
|
|
833
1074
|
}
|
|
834
1075
|
break;
|
|
1076
|
+
default:
|
|
1077
|
+
break;
|
|
835
1078
|
}
|
|
836
1079
|
|
|
837
1080
|
if (keep) {
|
|
@@ -873,6 +1116,8 @@ export function removeUselessCommands(d, tolerance = 1e-6, precision = 3) {
|
|
|
873
1116
|
cx = startX;
|
|
874
1117
|
cy = startY;
|
|
875
1118
|
break;
|
|
1119
|
+
default:
|
|
1120
|
+
break;
|
|
876
1121
|
}
|
|
877
1122
|
}
|
|
878
1123
|
}
|
|
@@ -898,6 +1143,10 @@ export function removeUselessCommands(d, tolerance = 1e-6, precision = 3) {
|
|
|
898
1143
|
* @returns {{x: number, y: number}} Point on curve
|
|
899
1144
|
*/
|
|
900
1145
|
function quadraticBezierPoint(t, p0x, p0y, p1x, p1y, p2x, p2y) {
|
|
1146
|
+
// Validate all inputs - fail fast on invalid data
|
|
1147
|
+
validateT(t);
|
|
1148
|
+
validateCoordinates(p0x, p0y, p1x, p1y, p2x, p2y);
|
|
1149
|
+
|
|
901
1150
|
const mt = 1 - t;
|
|
902
1151
|
const mt2 = mt * mt;
|
|
903
1152
|
const t2 = t * t;
|
|
@@ -934,6 +1183,9 @@ function maxErrorCubicToQuadratic(
|
|
|
934
1183
|
qx,
|
|
935
1184
|
qy,
|
|
936
1185
|
) {
|
|
1186
|
+
// Validate all coordinate inputs - fail fast on invalid data
|
|
1187
|
+
validateCoordinates(p0x, p0y, cp1x, cp1y, cp2x, cp2y, p3x, p3y, qx, qy);
|
|
1188
|
+
|
|
937
1189
|
let maxErr = 0;
|
|
938
1190
|
|
|
939
1191
|
// Dense sampling including midpoints
|
|
@@ -990,6 +1242,10 @@ function cubicToQuadraticControlPoint(
|
|
|
990
1242
|
y3,
|
|
991
1243
|
tolerance,
|
|
992
1244
|
) {
|
|
1245
|
+
// Validate all coordinate inputs - fail fast on invalid data
|
|
1246
|
+
validateCoordinates(x0, y0, cp1x, cp1y, cp2x, cp2y, x3, y3);
|
|
1247
|
+
validateTolerance(tolerance);
|
|
1248
|
+
|
|
993
1249
|
// Calculate the best-fit quadratic control point
|
|
994
1250
|
// For a cubic to be exactly representable as quadratic:
|
|
995
1251
|
// Q = (3*(P1 + P2) - P0 - P3) / 4
|
|
@@ -1026,9 +1282,17 @@ function cubicToQuadraticControlPoint(
|
|
|
1026
1282
|
* @returns {string} Optimized path
|
|
1027
1283
|
*/
|
|
1028
1284
|
export function convertCubicToQuadratic(d, tolerance = 0.5, precision = 3) {
|
|
1285
|
+
// Validate input parameters - fail fast if invalid
|
|
1286
|
+
validatePathString(d);
|
|
1287
|
+
validateTolerance(tolerance);
|
|
1288
|
+
validatePrecision(precision);
|
|
1289
|
+
|
|
1029
1290
|
const commands = parsePath(d);
|
|
1030
1291
|
if (commands.length === 0) return d;
|
|
1031
1292
|
|
|
1293
|
+
// Validate command structures
|
|
1294
|
+
commands.forEach((cmd) => validateCommand(cmd));
|
|
1295
|
+
|
|
1032
1296
|
let cx = 0,
|
|
1033
1297
|
cy = 0;
|
|
1034
1298
|
let startX = 0,
|
|
@@ -1108,6 +1372,8 @@ export function convertCubicToQuadratic(d, tolerance = 0.5, precision = 3) {
|
|
|
1108
1372
|
cx = startX;
|
|
1109
1373
|
cy = startY;
|
|
1110
1374
|
break;
|
|
1375
|
+
default:
|
|
1376
|
+
break;
|
|
1111
1377
|
}
|
|
1112
1378
|
}
|
|
1113
1379
|
|
|
@@ -1128,9 +1394,17 @@ export function convertCubicToQuadratic(d, tolerance = 0.5, precision = 3) {
|
|
|
1128
1394
|
* @returns {string} Optimized path
|
|
1129
1395
|
*/
|
|
1130
1396
|
export function convertQuadraticToSmooth(d, tolerance = 1e-6, precision = 3) {
|
|
1397
|
+
// Validate input parameters - fail fast if invalid
|
|
1398
|
+
validatePathString(d);
|
|
1399
|
+
validateTolerance(tolerance);
|
|
1400
|
+
validatePrecision(precision);
|
|
1401
|
+
|
|
1131
1402
|
const commands = parsePath(d);
|
|
1132
1403
|
if (commands.length === 0) return d;
|
|
1133
1404
|
|
|
1405
|
+
// Validate command structures
|
|
1406
|
+
commands.forEach((cmd) => validateCommand(cmd));
|
|
1407
|
+
|
|
1134
1408
|
let cx = 0,
|
|
1135
1409
|
cy = 0;
|
|
1136
1410
|
let startX = 0,
|
|
@@ -1168,9 +1442,15 @@ export function convertQuadraticToSmooth(d, tolerance = 1e-6, precision = 3) {
|
|
|
1168
1442
|
lastQcpX = abs.args[0];
|
|
1169
1443
|
lastQcpY = abs.args[1];
|
|
1170
1444
|
} else if (abs.command === "T") {
|
|
1171
|
-
// T uses reflected control point
|
|
1172
|
-
lastQcpX
|
|
1173
|
-
|
|
1445
|
+
// T uses reflected control point - only valid if previous was Q or T
|
|
1446
|
+
if (lastQcpX !== null) {
|
|
1447
|
+
lastQcpX = 2 * cx - lastQcpX;
|
|
1448
|
+
lastQcpY = 2 * cy - lastQcpY;
|
|
1449
|
+
} else {
|
|
1450
|
+
// T after non-Q command - control point is current position (no reflection)
|
|
1451
|
+
lastQcpX = cx;
|
|
1452
|
+
lastQcpY = cy;
|
|
1453
|
+
}
|
|
1174
1454
|
} else {
|
|
1175
1455
|
lastQcpX = null;
|
|
1176
1456
|
lastQcpY = null;
|
|
@@ -1212,6 +1492,8 @@ export function convertQuadraticToSmooth(d, tolerance = 1e-6, precision = 3) {
|
|
|
1212
1492
|
cx = startX;
|
|
1213
1493
|
cy = startY;
|
|
1214
1494
|
break;
|
|
1495
|
+
default:
|
|
1496
|
+
break;
|
|
1215
1497
|
}
|
|
1216
1498
|
}
|
|
1217
1499
|
|
|
@@ -1232,9 +1514,17 @@ export function convertQuadraticToSmooth(d, tolerance = 1e-6, precision = 3) {
|
|
|
1232
1514
|
* @returns {string} Optimized path
|
|
1233
1515
|
*/
|
|
1234
1516
|
export function convertCubicToSmooth(d, tolerance = 1e-6, precision = 3) {
|
|
1517
|
+
// Validate input parameters - fail fast if invalid
|
|
1518
|
+
validatePathString(d);
|
|
1519
|
+
validateTolerance(tolerance);
|
|
1520
|
+
validatePrecision(precision);
|
|
1521
|
+
|
|
1235
1522
|
const commands = parsePath(d);
|
|
1236
1523
|
if (commands.length === 0) return d;
|
|
1237
1524
|
|
|
1525
|
+
// Validate command structures
|
|
1526
|
+
commands.forEach((cmd) => validateCommand(cmd));
|
|
1527
|
+
|
|
1238
1528
|
let cx = 0,
|
|
1239
1529
|
cy = 0;
|
|
1240
1530
|
let startX = 0,
|
|
@@ -1284,6 +1574,7 @@ export function convertCubicToSmooth(d, tolerance = 1e-6, precision = 3) {
|
|
|
1284
1574
|
lastCcp2Y = abs.args[3];
|
|
1285
1575
|
} else if (abs.command === "S") {
|
|
1286
1576
|
// S uses its control point as the second cubic control point
|
|
1577
|
+
// This is always valid since S command provides explicit control point
|
|
1287
1578
|
lastCcp2X = abs.args[0];
|
|
1288
1579
|
lastCcp2Y = abs.args[1];
|
|
1289
1580
|
} else {
|
|
@@ -1327,6 +1618,8 @@ export function convertCubicToSmooth(d, tolerance = 1e-6, precision = 3) {
|
|
|
1327
1618
|
cx = startX;
|
|
1328
1619
|
cy = startY;
|
|
1329
1620
|
break;
|
|
1621
|
+
default:
|
|
1622
|
+
break;
|
|
1330
1623
|
}
|
|
1331
1624
|
}
|
|
1332
1625
|
|
|
@@ -1347,12 +1640,23 @@ export function convertCubicToSmooth(d, tolerance = 1e-6, precision = 3) {
|
|
|
1347
1640
|
* @returns {string} Optimized path
|
|
1348
1641
|
*/
|
|
1349
1642
|
export function arcShorthands(d, precision = 3) {
|
|
1643
|
+
// Validate input parameters - fail fast if invalid
|
|
1644
|
+
validatePathString(d);
|
|
1645
|
+
validatePrecision(precision);
|
|
1646
|
+
|
|
1350
1647
|
const commands = parsePath(d);
|
|
1351
1648
|
if (commands.length === 0) return d;
|
|
1352
1649
|
|
|
1650
|
+
// Validate command structures
|
|
1651
|
+
commands.forEach((cmd) => validateCommand(cmd));
|
|
1652
|
+
|
|
1353
1653
|
const result = commands.map((cmd) => {
|
|
1354
1654
|
if (cmd.command === "A" || cmd.command === "a") {
|
|
1355
1655
|
const args = [...cmd.args];
|
|
1656
|
+
// Ensure args array has correct length for arc command
|
|
1657
|
+
if (args.length !== 7) {
|
|
1658
|
+
throw new Error(`Arc command requires 7 arguments, got ${args.length}`);
|
|
1659
|
+
}
|
|
1356
1660
|
// Normalize rotation angle to 0-360
|
|
1357
1661
|
args[2] = ((args[2] % 360) + 360) % 360;
|
|
1358
1662
|
// If rx == ry, rotation doesn't matter, set to 0
|