@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/css-specificity.js
CHANGED
|
@@ -46,7 +46,6 @@ export function parseSelector(selectorString) {
|
|
|
46
46
|
|
|
47
47
|
const selector = selectorString.trim();
|
|
48
48
|
const components = [];
|
|
49
|
-
const _i = 0;
|
|
50
49
|
|
|
51
50
|
// Split by combinators first (>, +, ~, space) to handle complex selectors
|
|
52
51
|
const parts = splitByCombinators(selector);
|
|
@@ -69,6 +68,10 @@ export function parseSelector(selectorString) {
|
|
|
69
68
|
* @returns {Array<string>} Parts split by combinators
|
|
70
69
|
*/
|
|
71
70
|
function splitByCombinators(selector) {
|
|
71
|
+
if (typeof selector !== "string") {
|
|
72
|
+
throw new Error("Selector must be a string");
|
|
73
|
+
}
|
|
74
|
+
|
|
72
75
|
// Match combinators outside of brackets and parentheses
|
|
73
76
|
const parts = [];
|
|
74
77
|
let current = "";
|
|
@@ -84,12 +87,18 @@ function splitByCombinators(selector) {
|
|
|
84
87
|
depth++;
|
|
85
88
|
} else if (char === "]") {
|
|
86
89
|
depth--;
|
|
90
|
+
if (depth < 0) {
|
|
91
|
+
throw new Error(`Unbalanced brackets at position ${i}`);
|
|
92
|
+
}
|
|
87
93
|
if (depth === 0) inBracket = false;
|
|
88
94
|
} else if (char === "(") {
|
|
89
95
|
inParen = true;
|
|
90
96
|
depth++;
|
|
91
97
|
} else if (char === ")") {
|
|
92
98
|
depth--;
|
|
99
|
+
if (depth < 0) {
|
|
100
|
+
throw new Error(`Unbalanced parentheses at position ${i}`);
|
|
101
|
+
}
|
|
93
102
|
if (depth === 0) inParen = false;
|
|
94
103
|
}
|
|
95
104
|
|
|
@@ -111,6 +120,10 @@ function splitByCombinators(selector) {
|
|
|
111
120
|
current += char;
|
|
112
121
|
}
|
|
113
122
|
|
|
123
|
+
if (depth !== 0) {
|
|
124
|
+
throw new Error("Unclosed brackets or parentheses in selector");
|
|
125
|
+
}
|
|
126
|
+
|
|
114
127
|
if (current.trim()) {
|
|
115
128
|
parts.push(current.trim());
|
|
116
129
|
}
|
|
@@ -125,6 +138,10 @@ function splitByCombinators(selector) {
|
|
|
125
138
|
* @returns {Array<Object>} Array of component objects
|
|
126
139
|
*/
|
|
127
140
|
function parseSimpleSelector(selector) {
|
|
141
|
+
if (typeof selector !== "string") {
|
|
142
|
+
throw new Error("Selector must be a string");
|
|
143
|
+
}
|
|
144
|
+
|
|
128
145
|
const components = [];
|
|
129
146
|
let i = 0;
|
|
130
147
|
|
|
@@ -156,7 +173,7 @@ function parseSimpleSelector(selector) {
|
|
|
156
173
|
}
|
|
157
174
|
// Pseudo-element (::) or pseudo-class (:)
|
|
158
175
|
else if (char === ":") {
|
|
159
|
-
if (selector[i + 1] === ":") {
|
|
176
|
+
if (i + 1 < selector.length && selector[i + 1] === ":") {
|
|
160
177
|
// Pseudo-element
|
|
161
178
|
const match = selector.slice(i).match(/^::([\w-]+)/);
|
|
162
179
|
if (!match) throw new Error(`Invalid pseudo-element at position ${i}`);
|
|
@@ -174,7 +191,7 @@ function parseSimpleSelector(selector) {
|
|
|
174
191
|
i += match[0].length;
|
|
175
192
|
|
|
176
193
|
// Check for function notation like :not()
|
|
177
|
-
if (selector[i] === "(") {
|
|
194
|
+
if (i < selector.length && selector[i] === "(") {
|
|
178
195
|
const endIdx = findMatchingParen(selector, i);
|
|
179
196
|
if (endIdx === -1)
|
|
180
197
|
throw new Error(`Unclosed pseudo-class function at position ${i}`);
|
|
@@ -219,6 +236,13 @@ function parseSimpleSelector(selector) {
|
|
|
219
236
|
* @returns {number} Index of closing bracket or -1 if not found
|
|
220
237
|
*/
|
|
221
238
|
function findMatchingBracket(str, startIdx) {
|
|
239
|
+
if (typeof str !== "string") {
|
|
240
|
+
throw new Error("str must be a string");
|
|
241
|
+
}
|
|
242
|
+
if (typeof startIdx !== "number" || startIdx < 0 || startIdx >= str.length) {
|
|
243
|
+
throw new Error("startIdx must be a valid index within the string");
|
|
244
|
+
}
|
|
245
|
+
|
|
222
246
|
let depth = 0;
|
|
223
247
|
for (let i = startIdx; i < str.length; i++) {
|
|
224
248
|
if (str[i] === "[") depth++;
|
|
@@ -238,6 +262,13 @@ function findMatchingBracket(str, startIdx) {
|
|
|
238
262
|
* @returns {number} Index of closing parenthesis or -1 if not found
|
|
239
263
|
*/
|
|
240
264
|
function findMatchingParen(str, startIdx) {
|
|
265
|
+
if (typeof str !== "string") {
|
|
266
|
+
throw new Error("str must be a string");
|
|
267
|
+
}
|
|
268
|
+
if (typeof startIdx !== "number" || startIdx < 0 || startIdx >= str.length) {
|
|
269
|
+
throw new Error("startIdx must be a valid index within the string");
|
|
270
|
+
}
|
|
271
|
+
|
|
241
272
|
let depth = 0;
|
|
242
273
|
for (let i = startIdx; i < str.length; i++) {
|
|
243
274
|
if (str[i] === "(") depth++;
|
|
@@ -272,11 +303,18 @@ export function calculateSpecificity(selector) {
|
|
|
272
303
|
const components =
|
|
273
304
|
typeof selector === "string" ? parseSelector(selector) : selector;
|
|
274
305
|
|
|
306
|
+
if (!Array.isArray(components)) {
|
|
307
|
+
throw new Error("Selector must be a string or an array of components");
|
|
308
|
+
}
|
|
309
|
+
|
|
275
310
|
let a = 0; // IDs
|
|
276
311
|
let b = 0; // Classes, attributes, pseudo-classes
|
|
277
312
|
let c = 0; // Types, pseudo-elements
|
|
278
313
|
|
|
279
314
|
for (const component of components) {
|
|
315
|
+
if (!component || typeof component.type !== "string") {
|
|
316
|
+
throw new Error("Invalid component: must have a 'type' property");
|
|
317
|
+
}
|
|
280
318
|
switch (component.type) {
|
|
281
319
|
case SELECTOR_TYPES.ID:
|
|
282
320
|
a++;
|
|
@@ -289,12 +327,15 @@ export function calculateSpecificity(selector) {
|
|
|
289
327
|
|
|
290
328
|
case SELECTOR_TYPES.PSEUDO_CLASS:
|
|
291
329
|
// Handle :not() - it doesn't count itself, but its argument does
|
|
292
|
-
if (component.value.startsWith("not(")) {
|
|
330
|
+
if (component.value.startsWith("not(") && component.value.endsWith(")")) {
|
|
293
331
|
const notContent = component.value.slice(4, -1); // Extract content inside :not()
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
332
|
+
if (notContent.trim()) {
|
|
333
|
+
// Only process non-empty :not() content
|
|
334
|
+
const notSpec = calculateSpecificity(notContent);
|
|
335
|
+
a += notSpec[0];
|
|
336
|
+
b += notSpec[1];
|
|
337
|
+
c += notSpec[2];
|
|
338
|
+
}
|
|
298
339
|
} else {
|
|
299
340
|
b++;
|
|
300
341
|
}
|
|
@@ -310,7 +351,8 @@ export function calculateSpecificity(selector) {
|
|
|
310
351
|
break;
|
|
311
352
|
|
|
312
353
|
default:
|
|
313
|
-
//
|
|
354
|
+
// No default case needed - all known SELECTOR_TYPES handled above
|
|
355
|
+
// Unknown types are silently ignored per CSS spec
|
|
314
356
|
break;
|
|
315
357
|
}
|
|
316
358
|
}
|
|
@@ -337,6 +379,16 @@ export function compareSpecificity(spec1, spec2) {
|
|
|
337
379
|
throw new Error("spec2 must be an array of 3 numbers");
|
|
338
380
|
}
|
|
339
381
|
|
|
382
|
+
// Validate all elements are valid numbers
|
|
383
|
+
for (let i = 0; i < 3; i++) {
|
|
384
|
+
if (typeof spec1[i] !== "number" || !Number.isFinite(spec1[i]) || spec1[i] < 0) {
|
|
385
|
+
throw new Error(`spec1[${i}] must be a non-negative finite number`);
|
|
386
|
+
}
|
|
387
|
+
if (typeof spec2[i] !== "number" || !Number.isFinite(spec2[i]) || spec2[i] < 0) {
|
|
388
|
+
throw new Error(`spec2[${i}] must be a non-negative finite number`);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
340
392
|
// Compare lexicographically: a first, then b, then c
|
|
341
393
|
for (let i = 0; i < 3; i++) {
|
|
342
394
|
if (spec1[i] < spec2[i]) return -1;
|
|
@@ -400,7 +452,13 @@ export function stringifySelector(components) {
|
|
|
400
452
|
}
|
|
401
453
|
|
|
402
454
|
return components
|
|
403
|
-
.map((component) => {
|
|
455
|
+
.map((component, index) => {
|
|
456
|
+
if (!component || typeof component.type !== "string") {
|
|
457
|
+
throw new Error(`Component at index ${index} must have a 'type' property`);
|
|
458
|
+
}
|
|
459
|
+
if (component.value === undefined) {
|
|
460
|
+
throw new Error(`Component at index ${index} must have a 'value' property`);
|
|
461
|
+
}
|
|
404
462
|
switch (component.type) {
|
|
405
463
|
case SELECTOR_TYPES.ID:
|
|
406
464
|
return `#${component.value}`;
|
|
@@ -417,6 +475,7 @@ export function stringifySelector(components) {
|
|
|
417
475
|
case SELECTOR_TYPES.UNIVERSAL:
|
|
418
476
|
return "*";
|
|
419
477
|
default:
|
|
478
|
+
// Unknown types stringify to empty string for graceful degradation
|
|
420
479
|
return "";
|
|
421
480
|
}
|
|
422
481
|
})
|
|
@@ -431,6 +490,10 @@ export function stringifySelector(components) {
|
|
|
431
490
|
* @returns {boolean} True if round-trip matches
|
|
432
491
|
*/
|
|
433
492
|
export function verifySelector(selector) {
|
|
493
|
+
if (typeof selector !== "string") {
|
|
494
|
+
throw new Error("Selector must be a string");
|
|
495
|
+
}
|
|
496
|
+
|
|
434
497
|
const components = parseSelector(selector);
|
|
435
498
|
const reconstructed = stringifySelector(components);
|
|
436
499
|
|
package/src/douglas-peucker.js
CHANGED
|
@@ -18,6 +18,24 @@
|
|
|
18
18
|
* @returns {number} Perpendicular distance
|
|
19
19
|
*/
|
|
20
20
|
export function perpendicularDistance(point, lineStart, lineEnd) {
|
|
21
|
+
// Validate parameters to prevent undefined access and ensure numeric properties
|
|
22
|
+
if (!point || typeof point.x !== 'number' || typeof point.y !== 'number') {
|
|
23
|
+
throw new TypeError('perpendicularDistance: point must be an object with numeric x and y properties');
|
|
24
|
+
}
|
|
25
|
+
if (!lineStart || typeof lineStart.x !== 'number' || typeof lineStart.y !== 'number') {
|
|
26
|
+
throw new TypeError('perpendicularDistance: lineStart must be an object with numeric x and y properties');
|
|
27
|
+
}
|
|
28
|
+
if (!lineEnd || typeof lineEnd.x !== 'number' || typeof lineEnd.y !== 'number') {
|
|
29
|
+
throw new TypeError('perpendicularDistance: lineEnd must be an object with numeric x and y properties');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Check for NaN/Infinity in coordinates to prevent invalid calculations
|
|
33
|
+
if (!Number.isFinite(point.x) || !Number.isFinite(point.y) ||
|
|
34
|
+
!Number.isFinite(lineStart.x) || !Number.isFinite(lineStart.y) ||
|
|
35
|
+
!Number.isFinite(lineEnd.x) || !Number.isFinite(lineEnd.y)) {
|
|
36
|
+
throw new RangeError('perpendicularDistance: all coordinates must be finite numbers');
|
|
37
|
+
}
|
|
38
|
+
|
|
21
39
|
const dx = lineEnd.x - lineStart.x;
|
|
22
40
|
const dy = lineEnd.y - lineStart.y;
|
|
23
41
|
|
|
@@ -46,6 +64,36 @@ export function perpendicularDistance(point, lineStart, lineEnd) {
|
|
|
46
64
|
* @returns {Array<{x: number, y: number}>} Simplified points
|
|
47
65
|
*/
|
|
48
66
|
export function douglasPeucker(points, tolerance) {
|
|
67
|
+
// Validate points parameter to prevent crashes on invalid input
|
|
68
|
+
if (!Array.isArray(points)) {
|
|
69
|
+
throw new TypeError('douglasPeucker: points must be an array');
|
|
70
|
+
}
|
|
71
|
+
if (points.length === 0) {
|
|
72
|
+
throw new RangeError('douglasPeucker: points array cannot be empty');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Validate each point has x and y properties with finite numeric values
|
|
76
|
+
for (let i = 0; i < points.length; i++) {
|
|
77
|
+
const p = points[i];
|
|
78
|
+
if (!p || typeof p !== 'object') {
|
|
79
|
+
throw new TypeError(`douglasPeucker: point at index ${i} must be an object`);
|
|
80
|
+
}
|
|
81
|
+
if (typeof p.x !== 'number' || !Number.isFinite(p.x)) {
|
|
82
|
+
throw new TypeError(`douglasPeucker: point at index ${i} must have a finite numeric x property`);
|
|
83
|
+
}
|
|
84
|
+
if (typeof p.y !== 'number' || !Number.isFinite(p.y)) {
|
|
85
|
+
throw new TypeError(`douglasPeucker: point at index ${i} must have a finite numeric y property`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Validate tolerance parameter to ensure valid numeric simplification threshold
|
|
90
|
+
if (typeof tolerance !== 'number' || !Number.isFinite(tolerance)) {
|
|
91
|
+
throw new TypeError('douglasPeucker: tolerance must be a finite number');
|
|
92
|
+
}
|
|
93
|
+
if (tolerance < 0) {
|
|
94
|
+
throw new RangeError('douglasPeucker: tolerance cannot be negative');
|
|
95
|
+
}
|
|
96
|
+
|
|
49
97
|
if (points.length <= 2) {
|
|
50
98
|
return points;
|
|
51
99
|
}
|
|
@@ -90,6 +138,36 @@ export function douglasPeucker(points, tolerance) {
|
|
|
90
138
|
* @returns {Array<{x: number, y: number}>} Simplified points
|
|
91
139
|
*/
|
|
92
140
|
export function visvalingamWhyatt(points, minArea) {
|
|
141
|
+
// Validate points parameter to prevent crashes on invalid input
|
|
142
|
+
if (!Array.isArray(points)) {
|
|
143
|
+
throw new TypeError('visvalingamWhyatt: points must be an array');
|
|
144
|
+
}
|
|
145
|
+
if (points.length === 0) {
|
|
146
|
+
throw new RangeError('visvalingamWhyatt: points array cannot be empty');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Validate each point has x and y properties with finite numeric values
|
|
150
|
+
for (let i = 0; i < points.length; i++) {
|
|
151
|
+
const p = points[i];
|
|
152
|
+
if (!p || typeof p !== 'object') {
|
|
153
|
+
throw new TypeError(`visvalingamWhyatt: point at index ${i} must be an object`);
|
|
154
|
+
}
|
|
155
|
+
if (typeof p.x !== 'number' || !Number.isFinite(p.x)) {
|
|
156
|
+
throw new TypeError(`visvalingamWhyatt: point at index ${i} must have a finite numeric x property`);
|
|
157
|
+
}
|
|
158
|
+
if (typeof p.y !== 'number' || !Number.isFinite(p.y)) {
|
|
159
|
+
throw new TypeError(`visvalingamWhyatt: point at index ${i} must have a finite numeric y property`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Validate minArea parameter to ensure valid numeric threshold
|
|
164
|
+
if (typeof minArea !== 'number' || !Number.isFinite(minArea)) {
|
|
165
|
+
throw new TypeError('visvalingamWhyatt: minArea must be a finite number');
|
|
166
|
+
}
|
|
167
|
+
if (minArea < 0) {
|
|
168
|
+
throw new RangeError('visvalingamWhyatt: minArea cannot be negative');
|
|
169
|
+
}
|
|
170
|
+
|
|
93
171
|
if (points.length <= 2) {
|
|
94
172
|
return points;
|
|
95
173
|
}
|
|
@@ -171,6 +249,31 @@ export function visvalingamWhyatt(points, minArea) {
|
|
|
171
249
|
* @returns {Array<{x: number, y: number}>} Simplified points
|
|
172
250
|
*/
|
|
173
251
|
export function simplifyPolyline(points, tolerance, algorithm = 'douglas-peucker') {
|
|
252
|
+
// Validate points parameter to prevent crashes on invalid input
|
|
253
|
+
if (!Array.isArray(points)) {
|
|
254
|
+
throw new TypeError('simplifyPolyline: points must be an array');
|
|
255
|
+
}
|
|
256
|
+
if (points.length === 0) {
|
|
257
|
+
throw new RangeError('simplifyPolyline: points array cannot be empty');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Validate tolerance parameter to ensure valid numeric threshold
|
|
261
|
+
if (typeof tolerance !== 'number' || !Number.isFinite(tolerance)) {
|
|
262
|
+
throw new TypeError('simplifyPolyline: tolerance must be a finite number');
|
|
263
|
+
}
|
|
264
|
+
if (tolerance < 0) {
|
|
265
|
+
throw new RangeError('simplifyPolyline: tolerance cannot be negative');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Validate algorithm parameter to ensure only valid algorithms are used
|
|
269
|
+
if (typeof algorithm !== 'string') {
|
|
270
|
+
throw new TypeError('simplifyPolyline: algorithm must be a string');
|
|
271
|
+
}
|
|
272
|
+
const validAlgorithms = ['douglas-peucker', 'visvalingam'];
|
|
273
|
+
if (!validAlgorithms.includes(algorithm)) {
|
|
274
|
+
throw new RangeError(`simplifyPolyline: algorithm must be one of: ${validAlgorithms.join(', ')}`);
|
|
275
|
+
}
|
|
276
|
+
|
|
174
277
|
if (algorithm === 'visvalingam') {
|
|
175
278
|
// For Visvalingam, tolerance is the minimum triangle area
|
|
176
279
|
return visvalingamWhyatt(points, tolerance * tolerance);
|
|
@@ -184,43 +287,91 @@ export function simplifyPolyline(points, tolerance, algorithm = 'douglas-peucker
|
|
|
184
287
|
* @returns {Array<{x: number, y: number}>} Extracted points
|
|
185
288
|
*/
|
|
186
289
|
export function extractPolylinePoints(commands) {
|
|
290
|
+
// Validate commands parameter to prevent crashes on invalid input
|
|
291
|
+
if (!Array.isArray(commands)) {
|
|
292
|
+
throw new TypeError('extractPolylinePoints: commands must be an array');
|
|
293
|
+
}
|
|
294
|
+
|
|
187
295
|
const points = [];
|
|
188
296
|
let cx = 0, cy = 0;
|
|
189
297
|
let startX = 0, startY = 0;
|
|
190
298
|
|
|
191
|
-
for (const
|
|
299
|
+
for (const cmd of commands) {
|
|
300
|
+
// Validate each command object to prevent undefined access
|
|
301
|
+
if (!cmd || typeof cmd !== 'object') {
|
|
302
|
+
throw new TypeError('extractPolylinePoints: each command must be an object');
|
|
303
|
+
}
|
|
304
|
+
if (typeof cmd.command !== 'string') {
|
|
305
|
+
throw new TypeError('extractPolylinePoints: each command must have a string "command" property');
|
|
306
|
+
}
|
|
307
|
+
if (!Array.isArray(cmd.args)) {
|
|
308
|
+
throw new TypeError('extractPolylinePoints: each command must have an "args" array');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const { command, args } = cmd;
|
|
312
|
+
|
|
313
|
+
// Helper to validate args length to prevent out-of-bounds access
|
|
314
|
+
const requireArgs = (count) => {
|
|
315
|
+
if (args.length < count) {
|
|
316
|
+
throw new RangeError(`extractPolylinePoints: command "${command}" requires at least ${count} arguments, got ${args.length}`);
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
// Helper to validate arg is a finite number to prevent NaN/Infinity in calculations
|
|
321
|
+
const requireFiniteNumber = (index) => {
|
|
322
|
+
if (typeof args[index] !== 'number' || !Number.isFinite(args[index])) {
|
|
323
|
+
throw new TypeError(`extractPolylinePoints: command "${command}" argument at index ${index} must be a finite number, got ${args[index]}`);
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
|
|
192
327
|
switch (command) {
|
|
193
328
|
case 'M':
|
|
329
|
+
requireArgs(2);
|
|
330
|
+
requireFiniteNumber(0); requireFiniteNumber(1);
|
|
194
331
|
cx = args[0]; cy = args[1];
|
|
195
332
|
startX = cx; startY = cy;
|
|
196
333
|
points.push({ x: cx, y: cy });
|
|
197
334
|
break;
|
|
198
335
|
case 'm':
|
|
336
|
+
requireArgs(2);
|
|
337
|
+
requireFiniteNumber(0); requireFiniteNumber(1);
|
|
199
338
|
cx += args[0]; cy += args[1];
|
|
200
339
|
startX = cx; startY = cy;
|
|
201
340
|
points.push({ x: cx, y: cy });
|
|
202
341
|
break;
|
|
203
342
|
case 'L':
|
|
343
|
+
requireArgs(2);
|
|
344
|
+
requireFiniteNumber(0); requireFiniteNumber(1);
|
|
204
345
|
cx = args[0]; cy = args[1];
|
|
205
346
|
points.push({ x: cx, y: cy });
|
|
206
347
|
break;
|
|
207
348
|
case 'l':
|
|
349
|
+
requireArgs(2);
|
|
350
|
+
requireFiniteNumber(0); requireFiniteNumber(1);
|
|
208
351
|
cx += args[0]; cy += args[1];
|
|
209
352
|
points.push({ x: cx, y: cy });
|
|
210
353
|
break;
|
|
211
354
|
case 'H':
|
|
355
|
+
requireArgs(1);
|
|
356
|
+
requireFiniteNumber(0);
|
|
212
357
|
cx = args[0];
|
|
213
358
|
points.push({ x: cx, y: cy });
|
|
214
359
|
break;
|
|
215
360
|
case 'h':
|
|
361
|
+
requireArgs(1);
|
|
362
|
+
requireFiniteNumber(0);
|
|
216
363
|
cx += args[0];
|
|
217
364
|
points.push({ x: cx, y: cy });
|
|
218
365
|
break;
|
|
219
366
|
case 'V':
|
|
367
|
+
requireArgs(1);
|
|
368
|
+
requireFiniteNumber(0);
|
|
220
369
|
cy = args[0];
|
|
221
370
|
points.push({ x: cx, y: cy });
|
|
222
371
|
break;
|
|
223
372
|
case 'v':
|
|
373
|
+
requireArgs(1);
|
|
374
|
+
requireFiniteNumber(0);
|
|
224
375
|
cy += args[0];
|
|
225
376
|
points.push({ x: cx, y: cy });
|
|
226
377
|
break;
|
|
@@ -233,21 +384,39 @@ export function extractPolylinePoints(commands) {
|
|
|
233
384
|
break;
|
|
234
385
|
// For curves (C, S, Q, T, A), we just track the endpoint
|
|
235
386
|
case 'C':
|
|
387
|
+
requireArgs(6);
|
|
388
|
+
requireFiniteNumber(4); requireFiniteNumber(5);
|
|
236
389
|
cx = args[4]; cy = args[5]; break;
|
|
237
390
|
case 'c':
|
|
391
|
+
requireArgs(6);
|
|
392
|
+
requireFiniteNumber(4); requireFiniteNumber(5);
|
|
238
393
|
cx += args[4]; cy += args[5]; break;
|
|
239
394
|
case 'S': case 'Q':
|
|
395
|
+
requireArgs(4);
|
|
396
|
+
requireFiniteNumber(2); requireFiniteNumber(3);
|
|
240
397
|
cx = args[2]; cy = args[3]; break;
|
|
241
398
|
case 's': case 'q':
|
|
399
|
+
requireArgs(4);
|
|
400
|
+
requireFiniteNumber(2); requireFiniteNumber(3);
|
|
242
401
|
cx += args[2]; cy += args[3]; break;
|
|
243
402
|
case 'T':
|
|
403
|
+
requireArgs(2);
|
|
404
|
+
requireFiniteNumber(0); requireFiniteNumber(1);
|
|
244
405
|
cx = args[0]; cy = args[1]; break;
|
|
245
406
|
case 't':
|
|
407
|
+
requireArgs(2);
|
|
408
|
+
requireFiniteNumber(0); requireFiniteNumber(1);
|
|
246
409
|
cx += args[0]; cy += args[1]; break;
|
|
247
410
|
case 'A':
|
|
411
|
+
requireArgs(7);
|
|
412
|
+
requireFiniteNumber(5); requireFiniteNumber(6);
|
|
248
413
|
cx = args[5]; cy = args[6]; break;
|
|
249
414
|
case 'a':
|
|
415
|
+
requireArgs(7);
|
|
416
|
+
requireFiniteNumber(5); requireFiniteNumber(6);
|
|
250
417
|
cx += args[5]; cy += args[6]; break;
|
|
418
|
+
default:
|
|
419
|
+
break;
|
|
251
420
|
}
|
|
252
421
|
}
|
|
253
422
|
|
|
@@ -261,8 +430,32 @@ export function extractPolylinePoints(commands) {
|
|
|
261
430
|
* @returns {Array<{command: string, args: number[]}>} Path commands
|
|
262
431
|
*/
|
|
263
432
|
export function rebuildPathFromPoints(points, closed = false) {
|
|
433
|
+
// Validate points parameter to prevent crashes on invalid input
|
|
434
|
+
if (!Array.isArray(points)) {
|
|
435
|
+
throw new TypeError('rebuildPathFromPoints: points must be an array');
|
|
436
|
+
}
|
|
437
|
+
|
|
264
438
|
if (points.length === 0) return [];
|
|
265
439
|
|
|
440
|
+
// Validate closed parameter to ensure boolean type
|
|
441
|
+
if (typeof closed !== 'boolean') {
|
|
442
|
+
throw new TypeError('rebuildPathFromPoints: closed must be a boolean');
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Validate each point has x and y properties with finite numeric values
|
|
446
|
+
for (let i = 0; i < points.length; i++) {
|
|
447
|
+
const p = points[i];
|
|
448
|
+
if (!p || typeof p !== 'object') {
|
|
449
|
+
throw new TypeError(`rebuildPathFromPoints: point at index ${i} must be an object`);
|
|
450
|
+
}
|
|
451
|
+
if (typeof p.x !== 'number' || !Number.isFinite(p.x)) {
|
|
452
|
+
throw new TypeError(`rebuildPathFromPoints: point at index ${i} must have a finite numeric x property`);
|
|
453
|
+
}
|
|
454
|
+
if (typeof p.y !== 'number' || !Number.isFinite(p.y)) {
|
|
455
|
+
throw new TypeError(`rebuildPathFromPoints: point at index ${i} must have a finite numeric y property`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
266
459
|
const commands = [];
|
|
267
460
|
|
|
268
461
|
// First point is M
|
|
@@ -286,8 +479,19 @@ export function rebuildPathFromPoints(points, closed = false) {
|
|
|
286
479
|
* @returns {boolean} True if pure polyline
|
|
287
480
|
*/
|
|
288
481
|
export function isPurePolyline(commands) {
|
|
482
|
+
// Validate commands parameter to prevent crashes on invalid input
|
|
483
|
+
if (!Array.isArray(commands)) {
|
|
484
|
+
throw new TypeError('isPurePolyline: commands must be an array');
|
|
485
|
+
}
|
|
486
|
+
|
|
289
487
|
const polylineCommands = new Set(['M', 'm', 'L', 'l', 'H', 'h', 'V', 'v', 'Z', 'z']);
|
|
290
|
-
return commands.every(cmd =>
|
|
488
|
+
return commands.every(cmd => {
|
|
489
|
+
// Validate each command has the required structure to prevent undefined access
|
|
490
|
+
if (!cmd || typeof cmd !== 'object' || typeof cmd.command !== 'string') {
|
|
491
|
+
return false;
|
|
492
|
+
}
|
|
493
|
+
return polylineCommands.has(cmd.command);
|
|
494
|
+
});
|
|
291
495
|
}
|
|
292
496
|
|
|
293
497
|
/**
|
|
@@ -298,6 +502,19 @@ export function isPurePolyline(commands) {
|
|
|
298
502
|
* @returns {{commands: Array<{command: string, args: number[]}>, simplified: boolean, originalPoints: number, simplifiedPoints: number}}
|
|
299
503
|
*/
|
|
300
504
|
export function simplifyPath(commands, tolerance, algorithm = 'douglas-peucker') {
|
|
505
|
+
// Validate commands parameter to prevent crashes on invalid input
|
|
506
|
+
if (!Array.isArray(commands)) {
|
|
507
|
+
throw new TypeError('simplifyPath: commands must be an array');
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Validate tolerance parameter to ensure valid numeric threshold
|
|
511
|
+
if (typeof tolerance !== 'number' || !Number.isFinite(tolerance)) {
|
|
512
|
+
throw new TypeError('simplifyPath: tolerance must be a finite number');
|
|
513
|
+
}
|
|
514
|
+
if (tolerance < 0) {
|
|
515
|
+
throw new RangeError('simplifyPath: tolerance cannot be negative');
|
|
516
|
+
}
|
|
517
|
+
|
|
301
518
|
if (!isPurePolyline(commands) || commands.length < 3) {
|
|
302
519
|
return {
|
|
303
520
|
commands,
|