@emasoft/svg-matrix 1.1.0 → 1.2.0
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 +7 -6
- package/bin/svgm.js +109 -40
- package/dist/svg-matrix.min.js +7 -7
- package/dist/svg-toolbox.min.js +148 -228
- package/dist/svgm.min.js +152 -232
- package/dist/version.json +5 -5
- package/package.json +1 -1
- package/scripts/postinstall.js +72 -41
- package/scripts/test-postinstall.js +18 -16
- package/scripts/version-sync.js +78 -60
- package/src/animation-optimization.js +190 -98
- package/src/animation-references.js +11 -3
- package/src/arc-length.js +23 -20
- package/src/bezier-analysis.js +9 -13
- package/src/bezier-intersections.js +18 -4
- package/src/browser-verify.js +35 -8
- package/src/clip-path-resolver.js +285 -114
- package/src/convert-path-data.js +20 -8
- package/src/css-specificity.js +33 -9
- package/src/douglas-peucker.js +272 -141
- package/src/geometry-to-path.js +79 -22
- package/src/gjk-collision.js +287 -126
- package/src/index.js +56 -21
- package/src/inkscape-support.js +122 -101
- package/src/logger.js +43 -27
- package/src/marker-resolver.js +201 -121
- package/src/mask-resolver.js +231 -98
- package/src/matrix.js +9 -5
- package/src/mesh-gradient.js +22 -14
- package/src/off-canvas-detection.js +53 -17
- package/src/path-optimization.js +356 -171
- package/src/path-simplification.js +671 -256
- package/src/pattern-resolver.js +1 -3
- package/src/polygon-clip.js +396 -78
- package/src/svg-boolean-ops.js +90 -23
- package/src/svg-collections.js +1546 -667
- package/src/svg-flatten.js +152 -38
- package/src/svg-matrix-lib.js +2 -2
- package/src/svg-parser.js +5 -1
- package/src/svg-rendering-context.js +3 -1
- package/src/svg-toolbox-lib.js +2 -2
- package/src/svg-toolbox.js +99 -457
- package/src/svg-validation-data.js +513 -345
- package/src/svg2-polyfills.js +156 -93
- package/src/svgm-lib.js +8 -4
- package/src/transform-optimization.js +168 -51
- package/src/transforms2d.js +73 -40
- package/src/transforms3d.js +34 -27
- package/src/use-symbol-resolver.js +175 -76
- package/src/vector.js +80 -44
- package/src/vendor/inkscape-hatch-polyfill.js +143 -108
- package/src/vendor/inkscape-hatch-polyfill.min.js +291 -1
- package/src/vendor/inkscape-mesh-polyfill.js +953 -766
- package/src/vendor/inkscape-mesh-polyfill.min.js +896 -1
- package/src/verification.js +3 -4
package/src/douglas-peucker.js
CHANGED
|
@@ -19,21 +19,42 @@
|
|
|
19
19
|
*/
|
|
20
20
|
export function perpendicularDistance(point, lineStart, lineEnd) {
|
|
21
21
|
// Validate parameters to prevent undefined access and ensure numeric properties
|
|
22
|
-
if (!point || typeof point.x !==
|
|
23
|
-
throw new TypeError(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
if (!point || typeof point.x !== "number" || typeof point.y !== "number") {
|
|
23
|
+
throw new TypeError(
|
|
24
|
+
"perpendicularDistance: point must be an object with numeric x and y properties",
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
if (
|
|
28
|
+
!lineStart ||
|
|
29
|
+
typeof lineStart.x !== "number" ||
|
|
30
|
+
typeof lineStart.y !== "number"
|
|
31
|
+
) {
|
|
32
|
+
throw new TypeError(
|
|
33
|
+
"perpendicularDistance: lineStart must be an object with numeric x and y properties",
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
if (
|
|
37
|
+
!lineEnd ||
|
|
38
|
+
typeof lineEnd.x !== "number" ||
|
|
39
|
+
typeof lineEnd.y !== "number"
|
|
40
|
+
) {
|
|
41
|
+
throw new TypeError(
|
|
42
|
+
"perpendicularDistance: lineEnd must be an object with numeric x and y properties",
|
|
43
|
+
);
|
|
30
44
|
}
|
|
31
45
|
|
|
32
46
|
// Check for NaN/Infinity in coordinates to prevent invalid calculations
|
|
33
|
-
if (
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
47
|
+
if (
|
|
48
|
+
!Number.isFinite(point.x) ||
|
|
49
|
+
!Number.isFinite(point.y) ||
|
|
50
|
+
!Number.isFinite(lineStart.x) ||
|
|
51
|
+
!Number.isFinite(lineStart.y) ||
|
|
52
|
+
!Number.isFinite(lineEnd.x) ||
|
|
53
|
+
!Number.isFinite(lineEnd.y)
|
|
54
|
+
) {
|
|
55
|
+
throw new RangeError(
|
|
56
|
+
"perpendicularDistance: all coordinates must be finite numbers",
|
|
57
|
+
);
|
|
37
58
|
}
|
|
38
59
|
|
|
39
60
|
const dx = lineEnd.x - lineStart.x;
|
|
@@ -50,7 +71,10 @@ export function perpendicularDistance(point, lineStart, lineEnd) {
|
|
|
50
71
|
// Calculate perpendicular distance using cross product formula
|
|
51
72
|
// |((y2-y1)*x0 - (x2-x1)*y0 + x2*y1 - y2*x1)| / sqrt((y2-y1)^2 + (x2-x1)^2)
|
|
52
73
|
const numerator = Math.abs(
|
|
53
|
-
dy * point.x -
|
|
74
|
+
dy * point.x -
|
|
75
|
+
dx * point.y +
|
|
76
|
+
lineEnd.x * lineStart.y -
|
|
77
|
+
lineEnd.y * lineStart.x,
|
|
54
78
|
);
|
|
55
79
|
const denominator = Math.sqrt(lineLengthSq);
|
|
56
80
|
|
|
@@ -66,36 +90,44 @@ export function perpendicularDistance(point, lineStart, lineEnd) {
|
|
|
66
90
|
export function douglasPeucker(points, tolerance) {
|
|
67
91
|
// Validate points parameter to prevent crashes on invalid input
|
|
68
92
|
if (!Array.isArray(points)) {
|
|
69
|
-
throw new TypeError(
|
|
93
|
+
throw new TypeError("douglasPeucker: points must be an array");
|
|
70
94
|
}
|
|
71
95
|
if (points.length === 0) {
|
|
72
|
-
throw new RangeError(
|
|
96
|
+
throw new RangeError("douglasPeucker: points array cannot be empty");
|
|
73
97
|
}
|
|
74
98
|
|
|
75
99
|
// Validate each point has x and y properties with finite numeric values
|
|
76
100
|
for (let i = 0; i < points.length; i++) {
|
|
77
101
|
const p = points[i];
|
|
78
|
-
if (!p || typeof p !==
|
|
79
|
-
throw new TypeError(
|
|
102
|
+
if (!p || typeof p !== "object") {
|
|
103
|
+
throw new TypeError(
|
|
104
|
+
`douglasPeucker: point at index ${i} must be an object`,
|
|
105
|
+
);
|
|
80
106
|
}
|
|
81
|
-
if (typeof p.x !==
|
|
82
|
-
throw new TypeError(
|
|
107
|
+
if (typeof p.x !== "number" || !Number.isFinite(p.x)) {
|
|
108
|
+
throw new TypeError(
|
|
109
|
+
`douglasPeucker: point at index ${i} must have a finite numeric x property`,
|
|
110
|
+
);
|
|
83
111
|
}
|
|
84
|
-
if (typeof p.y !==
|
|
85
|
-
throw new TypeError(
|
|
112
|
+
if (typeof p.y !== "number" || !Number.isFinite(p.y)) {
|
|
113
|
+
throw new TypeError(
|
|
114
|
+
`douglasPeucker: point at index ${i} must have a finite numeric y property`,
|
|
115
|
+
);
|
|
86
116
|
}
|
|
87
117
|
}
|
|
88
118
|
|
|
89
119
|
// Validate tolerance parameter to ensure valid numeric simplification threshold
|
|
90
|
-
if (typeof tolerance !==
|
|
91
|
-
throw new TypeError(
|
|
120
|
+
if (typeof tolerance !== "number" || !Number.isFinite(tolerance)) {
|
|
121
|
+
throw new TypeError("douglasPeucker: tolerance must be a finite number");
|
|
92
122
|
}
|
|
93
123
|
if (tolerance < 0) {
|
|
94
|
-
throw new RangeError(
|
|
124
|
+
throw new RangeError("douglasPeucker: tolerance cannot be negative");
|
|
95
125
|
}
|
|
96
126
|
// Prevent impractically small tolerances that cause numerical precision issues and extreme recursion depth
|
|
97
127
|
if (tolerance > 0 && tolerance < 1e-10) {
|
|
98
|
-
throw new RangeError(
|
|
128
|
+
throw new RangeError(
|
|
129
|
+
"douglasPeucker: tolerance must be either 0 or >= 1e-10 to avoid numerical precision issues",
|
|
130
|
+
);
|
|
99
131
|
}
|
|
100
132
|
|
|
101
133
|
if (points.length <= 2) {
|
|
@@ -192,32 +224,38 @@ function douglasPeuckerIterative(points, tolerance) {
|
|
|
192
224
|
export function visvalingamWhyatt(points, minArea) {
|
|
193
225
|
// Validate points parameter to prevent crashes on invalid input
|
|
194
226
|
if (!Array.isArray(points)) {
|
|
195
|
-
throw new TypeError(
|
|
227
|
+
throw new TypeError("visvalingamWhyatt: points must be an array");
|
|
196
228
|
}
|
|
197
229
|
if (points.length === 0) {
|
|
198
|
-
throw new RangeError(
|
|
230
|
+
throw new RangeError("visvalingamWhyatt: points array cannot be empty");
|
|
199
231
|
}
|
|
200
232
|
|
|
201
233
|
// Validate each point has x and y properties with finite numeric values
|
|
202
234
|
for (let i = 0; i < points.length; i++) {
|
|
203
235
|
const p = points[i];
|
|
204
|
-
if (!p || typeof p !==
|
|
205
|
-
throw new TypeError(
|
|
236
|
+
if (!p || typeof p !== "object") {
|
|
237
|
+
throw new TypeError(
|
|
238
|
+
`visvalingamWhyatt: point at index ${i} must be an object`,
|
|
239
|
+
);
|
|
206
240
|
}
|
|
207
|
-
if (typeof p.x !==
|
|
208
|
-
throw new TypeError(
|
|
241
|
+
if (typeof p.x !== "number" || !Number.isFinite(p.x)) {
|
|
242
|
+
throw new TypeError(
|
|
243
|
+
`visvalingamWhyatt: point at index ${i} must have a finite numeric x property`,
|
|
244
|
+
);
|
|
209
245
|
}
|
|
210
|
-
if (typeof p.y !==
|
|
211
|
-
throw new TypeError(
|
|
246
|
+
if (typeof p.y !== "number" || !Number.isFinite(p.y)) {
|
|
247
|
+
throw new TypeError(
|
|
248
|
+
`visvalingamWhyatt: point at index ${i} must have a finite numeric y property`,
|
|
249
|
+
);
|
|
212
250
|
}
|
|
213
251
|
}
|
|
214
252
|
|
|
215
253
|
// Validate minArea parameter to ensure valid numeric threshold
|
|
216
|
-
if (typeof minArea !==
|
|
217
|
-
throw new TypeError(
|
|
254
|
+
if (typeof minArea !== "number" || !Number.isFinite(minArea)) {
|
|
255
|
+
throw new TypeError("visvalingamWhyatt: minArea must be a finite number");
|
|
218
256
|
}
|
|
219
257
|
if (minArea < 0) {
|
|
220
|
-
throw new RangeError(
|
|
258
|
+
throw new RangeError("visvalingamWhyatt: minArea cannot be negative");
|
|
221
259
|
}
|
|
222
260
|
|
|
223
261
|
if (points.length <= 2) {
|
|
@@ -226,9 +264,10 @@ export function visvalingamWhyatt(points, minArea) {
|
|
|
226
264
|
|
|
227
265
|
// Calculate triangle area for three points
|
|
228
266
|
const triangleArea = (p1, p2, p3) => {
|
|
229
|
-
return
|
|
230
|
-
(p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y)
|
|
231
|
-
|
|
267
|
+
return (
|
|
268
|
+
Math.abs((p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y)) /
|
|
269
|
+
2
|
|
270
|
+
);
|
|
232
271
|
};
|
|
233
272
|
|
|
234
273
|
// Create a copy with area information
|
|
@@ -276,15 +315,24 @@ export function visvalingamWhyatt(points, minArea) {
|
|
|
276
315
|
let prevPrevIndex = prevIndex - 1;
|
|
277
316
|
while (prevPrevIndex >= 0 && !kept[prevPrevIndex]) prevPrevIndex--;
|
|
278
317
|
if (prevPrevIndex >= 0 && nextIndex < pts.length) {
|
|
279
|
-
areas[prevIndex] = triangleArea(
|
|
318
|
+
areas[prevIndex] = triangleArea(
|
|
319
|
+
pts[prevPrevIndex],
|
|
320
|
+
pts[prevIndex],
|
|
321
|
+
pts[nextIndex],
|
|
322
|
+
);
|
|
280
323
|
}
|
|
281
324
|
}
|
|
282
325
|
|
|
283
326
|
if (nextIndex < pts.length - 1) {
|
|
284
327
|
let nextNextIndex = nextIndex + 1;
|
|
285
|
-
while (nextNextIndex < pts.length && !kept[nextNextIndex])
|
|
328
|
+
while (nextNextIndex < pts.length && !kept[nextNextIndex])
|
|
329
|
+
nextNextIndex++;
|
|
286
330
|
if (nextNextIndex < pts.length && prevIndex >= 0) {
|
|
287
|
-
areas[nextIndex] = triangleArea(
|
|
331
|
+
areas[nextIndex] = triangleArea(
|
|
332
|
+
pts[prevIndex],
|
|
333
|
+
pts[nextIndex],
|
|
334
|
+
pts[nextNextIndex],
|
|
335
|
+
);
|
|
288
336
|
}
|
|
289
337
|
}
|
|
290
338
|
}
|
|
@@ -300,37 +348,45 @@ export function visvalingamWhyatt(points, minArea) {
|
|
|
300
348
|
* @param {'douglas-peucker' | 'visvalingam'} algorithm - Algorithm to use
|
|
301
349
|
* @returns {Array<{x: number, y: number}>} Simplified points
|
|
302
350
|
*/
|
|
303
|
-
export function simplifyPolyline(
|
|
351
|
+
export function simplifyPolyline(
|
|
352
|
+
points,
|
|
353
|
+
tolerance,
|
|
354
|
+
algorithm = "douglas-peucker",
|
|
355
|
+
) {
|
|
304
356
|
// Validate points parameter to prevent crashes on invalid input
|
|
305
357
|
if (!Array.isArray(points)) {
|
|
306
|
-
throw new TypeError(
|
|
358
|
+
throw new TypeError("simplifyPolyline: points must be an array");
|
|
307
359
|
}
|
|
308
360
|
if (points.length === 0) {
|
|
309
|
-
throw new RangeError(
|
|
361
|
+
throw new RangeError("simplifyPolyline: points array cannot be empty");
|
|
310
362
|
}
|
|
311
363
|
|
|
312
364
|
// Validate tolerance parameter to ensure valid numeric threshold
|
|
313
|
-
if (typeof tolerance !==
|
|
314
|
-
throw new TypeError(
|
|
365
|
+
if (typeof tolerance !== "number" || !Number.isFinite(tolerance)) {
|
|
366
|
+
throw new TypeError("simplifyPolyline: tolerance must be a finite number");
|
|
315
367
|
}
|
|
316
368
|
if (tolerance < 0) {
|
|
317
|
-
throw new RangeError(
|
|
369
|
+
throw new RangeError("simplifyPolyline: tolerance cannot be negative");
|
|
318
370
|
}
|
|
319
371
|
// Prevent impractically small tolerances that cause numerical precision issues
|
|
320
372
|
if (tolerance > 0 && tolerance < 1e-10) {
|
|
321
|
-
throw new RangeError(
|
|
373
|
+
throw new RangeError(
|
|
374
|
+
"simplifyPolyline: tolerance must be either 0 or >= 1e-10 to avoid numerical precision issues",
|
|
375
|
+
);
|
|
322
376
|
}
|
|
323
377
|
|
|
324
378
|
// Validate algorithm parameter to ensure only valid algorithms are used
|
|
325
|
-
if (typeof algorithm !==
|
|
326
|
-
throw new TypeError(
|
|
379
|
+
if (typeof algorithm !== "string") {
|
|
380
|
+
throw new TypeError("simplifyPolyline: algorithm must be a string");
|
|
327
381
|
}
|
|
328
|
-
const validAlgorithms = [
|
|
382
|
+
const validAlgorithms = ["douglas-peucker", "visvalingam"];
|
|
329
383
|
if (!validAlgorithms.includes(algorithm)) {
|
|
330
|
-
throw new RangeError(
|
|
384
|
+
throw new RangeError(
|
|
385
|
+
`simplifyPolyline: algorithm must be one of: ${validAlgorithms.join(", ")}`,
|
|
386
|
+
);
|
|
331
387
|
}
|
|
332
388
|
|
|
333
|
-
if (algorithm ===
|
|
389
|
+
if (algorithm === "visvalingam") {
|
|
334
390
|
// For Visvalingam, tolerance is the minimum triangle area
|
|
335
391
|
return visvalingamWhyatt(points, tolerance * tolerance);
|
|
336
392
|
}
|
|
@@ -345,23 +401,31 @@ export function simplifyPolyline(points, tolerance, algorithm = 'douglas-peucker
|
|
|
345
401
|
export function extractPolylinePoints(commands) {
|
|
346
402
|
// Validate commands parameter to prevent crashes on invalid input
|
|
347
403
|
if (!Array.isArray(commands)) {
|
|
348
|
-
throw new TypeError(
|
|
404
|
+
throw new TypeError("extractPolylinePoints: commands must be an array");
|
|
349
405
|
}
|
|
350
406
|
|
|
351
407
|
const points = [];
|
|
352
|
-
let cx = 0,
|
|
353
|
-
|
|
408
|
+
let cx = 0,
|
|
409
|
+
cy = 0;
|
|
410
|
+
let startX = 0,
|
|
411
|
+
startY = 0;
|
|
354
412
|
|
|
355
413
|
for (const cmd of commands) {
|
|
356
414
|
// Validate each command object to prevent undefined access
|
|
357
|
-
if (!cmd || typeof cmd !==
|
|
358
|
-
throw new TypeError(
|
|
415
|
+
if (!cmd || typeof cmd !== "object") {
|
|
416
|
+
throw new TypeError(
|
|
417
|
+
"extractPolylinePoints: each command must be an object",
|
|
418
|
+
);
|
|
359
419
|
}
|
|
360
|
-
if (typeof cmd.command !==
|
|
361
|
-
throw new TypeError(
|
|
420
|
+
if (typeof cmd.command !== "string") {
|
|
421
|
+
throw new TypeError(
|
|
422
|
+
'extractPolylinePoints: each command must have a string "command" property',
|
|
423
|
+
);
|
|
362
424
|
}
|
|
363
425
|
if (!Array.isArray(cmd.args)) {
|
|
364
|
-
throw new TypeError(
|
|
426
|
+
throw new TypeError(
|
|
427
|
+
'extractPolylinePoints: each command must have an "args" array',
|
|
428
|
+
);
|
|
365
429
|
}
|
|
366
430
|
|
|
367
431
|
const { command, args } = cmd;
|
|
@@ -369,108 +433,149 @@ export function extractPolylinePoints(commands) {
|
|
|
369
433
|
// Helper to validate args length to prevent out-of-bounds access
|
|
370
434
|
const requireArgs = (count) => {
|
|
371
435
|
if (args.length < count) {
|
|
372
|
-
throw new RangeError(
|
|
436
|
+
throw new RangeError(
|
|
437
|
+
`extractPolylinePoints: command "${command}" requires at least ${count} arguments, got ${args.length}`,
|
|
438
|
+
);
|
|
373
439
|
}
|
|
374
440
|
};
|
|
375
441
|
|
|
376
442
|
// Helper to validate arg is a finite number to prevent NaN/Infinity in calculations
|
|
377
443
|
const requireFiniteNumber = (index) => {
|
|
378
|
-
if (typeof args[index] !==
|
|
379
|
-
throw new TypeError(
|
|
444
|
+
if (typeof args[index] !== "number" || !Number.isFinite(args[index])) {
|
|
445
|
+
throw new TypeError(
|
|
446
|
+
`extractPolylinePoints: command "${command}" argument at index ${index} must be a finite number, got ${args[index]}`,
|
|
447
|
+
);
|
|
380
448
|
}
|
|
381
449
|
};
|
|
382
450
|
|
|
383
451
|
switch (command) {
|
|
384
|
-
case
|
|
452
|
+
case "M":
|
|
385
453
|
requireArgs(2);
|
|
386
|
-
requireFiniteNumber(0);
|
|
387
|
-
|
|
388
|
-
|
|
454
|
+
requireFiniteNumber(0);
|
|
455
|
+
requireFiniteNumber(1);
|
|
456
|
+
cx = args[0];
|
|
457
|
+
cy = args[1];
|
|
458
|
+
startX = cx;
|
|
459
|
+
startY = cy;
|
|
389
460
|
points.push({ x: cx, y: cy });
|
|
390
461
|
break;
|
|
391
|
-
case
|
|
462
|
+
case "m":
|
|
392
463
|
requireArgs(2);
|
|
393
|
-
requireFiniteNumber(0);
|
|
394
|
-
|
|
395
|
-
|
|
464
|
+
requireFiniteNumber(0);
|
|
465
|
+
requireFiniteNumber(1);
|
|
466
|
+
cx += args[0];
|
|
467
|
+
cy += args[1];
|
|
468
|
+
startX = cx;
|
|
469
|
+
startY = cy;
|
|
396
470
|
points.push({ x: cx, y: cy });
|
|
397
471
|
break;
|
|
398
|
-
case
|
|
472
|
+
case "L":
|
|
399
473
|
requireArgs(2);
|
|
400
|
-
requireFiniteNumber(0);
|
|
401
|
-
|
|
474
|
+
requireFiniteNumber(0);
|
|
475
|
+
requireFiniteNumber(1);
|
|
476
|
+
cx = args[0];
|
|
477
|
+
cy = args[1];
|
|
402
478
|
points.push({ x: cx, y: cy });
|
|
403
479
|
break;
|
|
404
|
-
case
|
|
480
|
+
case "l":
|
|
405
481
|
requireArgs(2);
|
|
406
|
-
requireFiniteNumber(0);
|
|
407
|
-
|
|
482
|
+
requireFiniteNumber(0);
|
|
483
|
+
requireFiniteNumber(1);
|
|
484
|
+
cx += args[0];
|
|
485
|
+
cy += args[1];
|
|
408
486
|
points.push({ x: cx, y: cy });
|
|
409
487
|
break;
|
|
410
|
-
case
|
|
488
|
+
case "H":
|
|
411
489
|
requireArgs(1);
|
|
412
490
|
requireFiniteNumber(0);
|
|
413
491
|
cx = args[0];
|
|
414
492
|
points.push({ x: cx, y: cy });
|
|
415
493
|
break;
|
|
416
|
-
case
|
|
494
|
+
case "h":
|
|
417
495
|
requireArgs(1);
|
|
418
496
|
requireFiniteNumber(0);
|
|
419
497
|
cx += args[0];
|
|
420
498
|
points.push({ x: cx, y: cy });
|
|
421
499
|
break;
|
|
422
|
-
case
|
|
500
|
+
case "V":
|
|
423
501
|
requireArgs(1);
|
|
424
502
|
requireFiniteNumber(0);
|
|
425
503
|
cy = args[0];
|
|
426
504
|
points.push({ x: cx, y: cy });
|
|
427
505
|
break;
|
|
428
|
-
case
|
|
506
|
+
case "v":
|
|
429
507
|
requireArgs(1);
|
|
430
508
|
requireFiniteNumber(0);
|
|
431
509
|
cy += args[0];
|
|
432
510
|
points.push({ x: cx, y: cy });
|
|
433
511
|
break;
|
|
434
|
-
case
|
|
435
|
-
case
|
|
512
|
+
case "Z":
|
|
513
|
+
case "z":
|
|
436
514
|
if (cx !== startX || cy !== startY) {
|
|
437
515
|
points.push({ x: startX, y: startY });
|
|
438
516
|
}
|
|
439
|
-
cx = startX;
|
|
517
|
+
cx = startX;
|
|
518
|
+
cy = startY;
|
|
440
519
|
break;
|
|
441
520
|
// For curves (C, S, Q, T, A), we just track the endpoint
|
|
442
|
-
case
|
|
521
|
+
case "C":
|
|
443
522
|
requireArgs(6);
|
|
444
|
-
requireFiniteNumber(4);
|
|
445
|
-
|
|
446
|
-
|
|
523
|
+
requireFiniteNumber(4);
|
|
524
|
+
requireFiniteNumber(5);
|
|
525
|
+
cx = args[4];
|
|
526
|
+
cy = args[5];
|
|
527
|
+
break;
|
|
528
|
+
case "c":
|
|
447
529
|
requireArgs(6);
|
|
448
|
-
requireFiniteNumber(4);
|
|
449
|
-
|
|
450
|
-
|
|
530
|
+
requireFiniteNumber(4);
|
|
531
|
+
requireFiniteNumber(5);
|
|
532
|
+
cx += args[4];
|
|
533
|
+
cy += args[5];
|
|
534
|
+
break;
|
|
535
|
+
case "S":
|
|
536
|
+
case "Q":
|
|
451
537
|
requireArgs(4);
|
|
452
|
-
requireFiniteNumber(2);
|
|
453
|
-
|
|
454
|
-
|
|
538
|
+
requireFiniteNumber(2);
|
|
539
|
+
requireFiniteNumber(3);
|
|
540
|
+
cx = args[2];
|
|
541
|
+
cy = args[3];
|
|
542
|
+
break;
|
|
543
|
+
case "s":
|
|
544
|
+
case "q":
|
|
455
545
|
requireArgs(4);
|
|
456
|
-
requireFiniteNumber(2);
|
|
457
|
-
|
|
458
|
-
|
|
546
|
+
requireFiniteNumber(2);
|
|
547
|
+
requireFiniteNumber(3);
|
|
548
|
+
cx += args[2];
|
|
549
|
+
cy += args[3];
|
|
550
|
+
break;
|
|
551
|
+
case "T":
|
|
459
552
|
requireArgs(2);
|
|
460
|
-
requireFiniteNumber(0);
|
|
461
|
-
|
|
462
|
-
|
|
553
|
+
requireFiniteNumber(0);
|
|
554
|
+
requireFiniteNumber(1);
|
|
555
|
+
cx = args[0];
|
|
556
|
+
cy = args[1];
|
|
557
|
+
break;
|
|
558
|
+
case "t":
|
|
463
559
|
requireArgs(2);
|
|
464
|
-
requireFiniteNumber(0);
|
|
465
|
-
|
|
466
|
-
|
|
560
|
+
requireFiniteNumber(0);
|
|
561
|
+
requireFiniteNumber(1);
|
|
562
|
+
cx += args[0];
|
|
563
|
+
cy += args[1];
|
|
564
|
+
break;
|
|
565
|
+
case "A":
|
|
467
566
|
requireArgs(7);
|
|
468
|
-
requireFiniteNumber(5);
|
|
469
|
-
|
|
470
|
-
|
|
567
|
+
requireFiniteNumber(5);
|
|
568
|
+
requireFiniteNumber(6);
|
|
569
|
+
cx = args[5];
|
|
570
|
+
cy = args[6];
|
|
571
|
+
break;
|
|
572
|
+
case "a":
|
|
471
573
|
requireArgs(7);
|
|
472
|
-
requireFiniteNumber(5);
|
|
473
|
-
|
|
574
|
+
requireFiniteNumber(5);
|
|
575
|
+
requireFiniteNumber(6);
|
|
576
|
+
cx += args[5];
|
|
577
|
+
cy += args[6];
|
|
578
|
+
break;
|
|
474
579
|
default:
|
|
475
580
|
break;
|
|
476
581
|
}
|
|
@@ -488,42 +593,48 @@ export function extractPolylinePoints(commands) {
|
|
|
488
593
|
export function rebuildPathFromPoints(points, closed = false) {
|
|
489
594
|
// Validate points parameter to prevent crashes on invalid input
|
|
490
595
|
if (!Array.isArray(points)) {
|
|
491
|
-
throw new TypeError(
|
|
596
|
+
throw new TypeError("rebuildPathFromPoints: points must be an array");
|
|
492
597
|
}
|
|
493
598
|
|
|
494
599
|
if (points.length === 0) return [];
|
|
495
600
|
|
|
496
601
|
// Validate closed parameter to ensure boolean type
|
|
497
|
-
if (typeof closed !==
|
|
498
|
-
throw new TypeError(
|
|
602
|
+
if (typeof closed !== "boolean") {
|
|
603
|
+
throw new TypeError("rebuildPathFromPoints: closed must be a boolean");
|
|
499
604
|
}
|
|
500
605
|
|
|
501
606
|
// Validate each point has x and y properties with finite numeric values
|
|
502
607
|
for (let i = 0; i < points.length; i++) {
|
|
503
608
|
const p = points[i];
|
|
504
|
-
if (!p || typeof p !==
|
|
505
|
-
throw new TypeError(
|
|
609
|
+
if (!p || typeof p !== "object") {
|
|
610
|
+
throw new TypeError(
|
|
611
|
+
`rebuildPathFromPoints: point at index ${i} must be an object`,
|
|
612
|
+
);
|
|
506
613
|
}
|
|
507
|
-
if (typeof p.x !==
|
|
508
|
-
throw new TypeError(
|
|
614
|
+
if (typeof p.x !== "number" || !Number.isFinite(p.x)) {
|
|
615
|
+
throw new TypeError(
|
|
616
|
+
`rebuildPathFromPoints: point at index ${i} must have a finite numeric x property`,
|
|
617
|
+
);
|
|
509
618
|
}
|
|
510
|
-
if (typeof p.y !==
|
|
511
|
-
throw new TypeError(
|
|
619
|
+
if (typeof p.y !== "number" || !Number.isFinite(p.y)) {
|
|
620
|
+
throw new TypeError(
|
|
621
|
+
`rebuildPathFromPoints: point at index ${i} must have a finite numeric y property`,
|
|
622
|
+
);
|
|
512
623
|
}
|
|
513
624
|
}
|
|
514
625
|
|
|
515
626
|
const commands = [];
|
|
516
627
|
|
|
517
628
|
// First point is M
|
|
518
|
-
commands.push({ command:
|
|
629
|
+
commands.push({ command: "M", args: [points[0].x, points[0].y] });
|
|
519
630
|
|
|
520
631
|
// Remaining points are L
|
|
521
632
|
for (let i = 1; i < points.length; i++) {
|
|
522
|
-
commands.push({ command:
|
|
633
|
+
commands.push({ command: "L", args: [points[i].x, points[i].y] });
|
|
523
634
|
}
|
|
524
635
|
|
|
525
636
|
if (closed) {
|
|
526
|
-
commands.push({ command:
|
|
637
|
+
commands.push({ command: "Z", args: [] });
|
|
527
638
|
}
|
|
528
639
|
|
|
529
640
|
return commands;
|
|
@@ -537,13 +648,24 @@ export function rebuildPathFromPoints(points, closed = false) {
|
|
|
537
648
|
export function isPurePolyline(commands) {
|
|
538
649
|
// Validate commands parameter to prevent crashes on invalid input
|
|
539
650
|
if (!Array.isArray(commands)) {
|
|
540
|
-
throw new TypeError(
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
const polylineCommands = new Set([
|
|
544
|
-
|
|
651
|
+
throw new TypeError("isPurePolyline: commands must be an array");
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
const polylineCommands = new Set([
|
|
655
|
+
"M",
|
|
656
|
+
"m",
|
|
657
|
+
"L",
|
|
658
|
+
"l",
|
|
659
|
+
"H",
|
|
660
|
+
"h",
|
|
661
|
+
"V",
|
|
662
|
+
"v",
|
|
663
|
+
"Z",
|
|
664
|
+
"z",
|
|
665
|
+
]);
|
|
666
|
+
return commands.every((cmd) => {
|
|
545
667
|
// Validate each command has the required structure to prevent undefined access
|
|
546
|
-
if (!cmd || typeof cmd !==
|
|
668
|
+
if (!cmd || typeof cmd !== "object" || typeof cmd.command !== "string") {
|
|
547
669
|
return false;
|
|
548
670
|
}
|
|
549
671
|
return polylineCommands.has(cmd.command);
|
|
@@ -557,22 +679,28 @@ export function isPurePolyline(commands) {
|
|
|
557
679
|
* @param {string} algorithm - Algorithm to use
|
|
558
680
|
* @returns {{commands: Array<{command: string, args: number[]}>, simplified: boolean, originalPoints: number, simplifiedPoints: number}}
|
|
559
681
|
*/
|
|
560
|
-
export function simplifyPath(
|
|
682
|
+
export function simplifyPath(
|
|
683
|
+
commands,
|
|
684
|
+
tolerance,
|
|
685
|
+
algorithm = "douglas-peucker",
|
|
686
|
+
) {
|
|
561
687
|
// Validate commands parameter to prevent crashes on invalid input
|
|
562
688
|
if (!Array.isArray(commands)) {
|
|
563
|
-
throw new TypeError(
|
|
689
|
+
throw new TypeError("simplifyPath: commands must be an array");
|
|
564
690
|
}
|
|
565
691
|
|
|
566
692
|
// Validate tolerance parameter to ensure valid numeric threshold
|
|
567
|
-
if (typeof tolerance !==
|
|
568
|
-
throw new TypeError(
|
|
693
|
+
if (typeof tolerance !== "number" || !Number.isFinite(tolerance)) {
|
|
694
|
+
throw new TypeError("simplifyPath: tolerance must be a finite number");
|
|
569
695
|
}
|
|
570
696
|
if (tolerance < 0) {
|
|
571
|
-
throw new RangeError(
|
|
697
|
+
throw new RangeError("simplifyPath: tolerance cannot be negative");
|
|
572
698
|
}
|
|
573
699
|
// Prevent impractically small tolerances that cause numerical precision issues
|
|
574
700
|
if (tolerance > 0 && tolerance < 1e-10) {
|
|
575
|
-
throw new RangeError(
|
|
701
|
+
throw new RangeError(
|
|
702
|
+
"simplifyPath: tolerance must be either 0 or >= 1e-10 to avoid numerical precision issues",
|
|
703
|
+
);
|
|
576
704
|
}
|
|
577
705
|
|
|
578
706
|
if (!isPurePolyline(commands) || commands.length < 3) {
|
|
@@ -580,7 +708,7 @@ export function simplifyPath(commands, tolerance, algorithm = 'douglas-peucker')
|
|
|
580
708
|
commands,
|
|
581
709
|
simplified: false,
|
|
582
710
|
originalPoints: 0,
|
|
583
|
-
simplifiedPoints: 0
|
|
711
|
+
simplifiedPoints: 0,
|
|
584
712
|
};
|
|
585
713
|
}
|
|
586
714
|
|
|
@@ -592,19 +720,22 @@ export function simplifyPath(commands, tolerance, algorithm = 'douglas-peucker')
|
|
|
592
720
|
commands,
|
|
593
721
|
simplified: false,
|
|
594
722
|
originalPoints: originalCount,
|
|
595
|
-
simplifiedPoints: originalCount
|
|
723
|
+
simplifiedPoints: originalCount,
|
|
596
724
|
};
|
|
597
725
|
}
|
|
598
726
|
|
|
599
727
|
// Check if path is closed
|
|
600
|
-
const isClosed = commands[commands.length - 1].command.toLowerCase() ===
|
|
728
|
+
const isClosed = commands[commands.length - 1].command.toLowerCase() === "z";
|
|
601
729
|
|
|
602
730
|
// For closed paths, remove duplicate endpoint to avoid degenerate zero-length line segment
|
|
603
731
|
if (isClosed && points.length > 1) {
|
|
604
732
|
const first = points[0];
|
|
605
733
|
const last = points[points.length - 1];
|
|
606
734
|
// Check if last point is duplicate of first (within floating point tolerance)
|
|
607
|
-
if (
|
|
735
|
+
if (
|
|
736
|
+
Math.abs(last.x - first.x) < 1e-10 &&
|
|
737
|
+
Math.abs(last.y - first.y) < 1e-10
|
|
738
|
+
) {
|
|
608
739
|
points = points.slice(0, -1);
|
|
609
740
|
}
|
|
610
741
|
}
|
|
@@ -617,7 +748,7 @@ export function simplifyPath(commands, tolerance, algorithm = 'douglas-peucker')
|
|
|
617
748
|
commands,
|
|
618
749
|
simplified: false,
|
|
619
750
|
originalPoints: originalCount,
|
|
620
|
-
simplifiedPoints: originalCount
|
|
751
|
+
simplifiedPoints: originalCount,
|
|
621
752
|
};
|
|
622
753
|
}
|
|
623
754
|
|
|
@@ -627,7 +758,7 @@ export function simplifyPath(commands, tolerance, algorithm = 'douglas-peucker')
|
|
|
627
758
|
commands: newCommands,
|
|
628
759
|
simplified: true,
|
|
629
760
|
originalPoints: originalCount,
|
|
630
|
-
simplifiedPoints: simplifiedCount
|
|
761
|
+
simplifiedPoints: simplifiedCount,
|
|
631
762
|
};
|
|
632
763
|
}
|
|
633
764
|
|
|
@@ -639,5 +770,5 @@ export default {
|
|
|
639
770
|
extractPolylinePoints,
|
|
640
771
|
rebuildPathFromPoints,
|
|
641
772
|
isPurePolyline,
|
|
642
|
-
simplifyPath
|
|
773
|
+
simplifyPath,
|
|
643
774
|
};
|