@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/pattern-resolver.js
CHANGED
|
@@ -71,6 +71,15 @@ const D = (x) => (x instanceof Decimal ? x : new Decimal(x));
|
|
|
71
71
|
* // }
|
|
72
72
|
*/
|
|
73
73
|
export function parsePatternElement(patternElement) {
|
|
74
|
+
// Validate patternElement is a DOM element with getAttribute method
|
|
75
|
+
if (!patternElement)
|
|
76
|
+
throw new Error("parsePatternElement: patternElement is required");
|
|
77
|
+
if (typeof patternElement.getAttribute !== "function") {
|
|
78
|
+
throw new Error(
|
|
79
|
+
"parsePatternElement: patternElement must be a DOM element with getAttribute method",
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
74
83
|
const data = {
|
|
75
84
|
id: patternElement.getAttribute("id") || "",
|
|
76
85
|
patternUnits:
|
|
@@ -92,11 +101,18 @@ export function parsePatternElement(patternElement) {
|
|
|
92
101
|
children: [],
|
|
93
102
|
};
|
|
94
103
|
|
|
104
|
+
// Helper to parse numeric values with NaN validation
|
|
105
|
+
const parseValidFloat = (val, defaultVal) => {
|
|
106
|
+
if (val === null || val === undefined) return defaultVal;
|
|
107
|
+
const parsed = parseFloat(val);
|
|
108
|
+
return isNaN(parsed) ? defaultVal : parsed;
|
|
109
|
+
};
|
|
110
|
+
|
|
95
111
|
// Parse numeric values
|
|
96
|
-
data.x =
|
|
97
|
-
data.y =
|
|
98
|
-
data.width =
|
|
99
|
-
data.height =
|
|
112
|
+
data.x = parseValidFloat(data.x, 0);
|
|
113
|
+
data.y = parseValidFloat(data.y, 0);
|
|
114
|
+
data.width = parseValidFloat(data.width, 0);
|
|
115
|
+
data.height = parseValidFloat(data.height, 0);
|
|
100
116
|
|
|
101
117
|
// Parse viewBox if present
|
|
102
118
|
if (data.viewBox) {
|
|
@@ -104,7 +120,8 @@ export function parsePatternElement(patternElement) {
|
|
|
104
120
|
.trim()
|
|
105
121
|
.split(/[\s,]+/)
|
|
106
122
|
.map(Number);
|
|
107
|
-
|
|
123
|
+
// Validate all parts are valid numbers
|
|
124
|
+
if (parts.length === 4 && parts.every((p) => !isNaN(p) && isFinite(p))) {
|
|
108
125
|
data.viewBoxParsed = {
|
|
109
126
|
x: parts[0],
|
|
110
127
|
y: parts[1],
|
|
@@ -114,73 +131,94 @@ export function parsePatternElement(patternElement) {
|
|
|
114
131
|
}
|
|
115
132
|
}
|
|
116
133
|
|
|
117
|
-
// Parse child elements
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
134
|
+
// Parse child elements - validate children exists and is iterable
|
|
135
|
+
if (
|
|
136
|
+
patternElement.children &&
|
|
137
|
+
typeof patternElement.children[Symbol.iterator] === "function"
|
|
138
|
+
) {
|
|
139
|
+
for (const child of patternElement.children) {
|
|
140
|
+
// Validate child is an Element before accessing properties
|
|
141
|
+
if (!child || typeof child.getAttribute !== "function") continue;
|
|
142
|
+
|
|
143
|
+
const tagName = child.tagName ? child.tagName.toLowerCase() : "";
|
|
144
|
+
if (!tagName) continue;
|
|
145
|
+
|
|
146
|
+
const childData = {
|
|
147
|
+
type: tagName,
|
|
148
|
+
fill: child.getAttribute("fill") || "black",
|
|
149
|
+
stroke: child.getAttribute("stroke") || "none",
|
|
150
|
+
strokeWidth: parseValidFloat(child.getAttribute("stroke-width"), 1),
|
|
151
|
+
opacity: parseValidFloat(child.getAttribute("opacity"), 1),
|
|
152
|
+
transform: child.getAttribute("transform") || null,
|
|
153
|
+
};
|
|
128
154
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
childData.children
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
155
|
+
// Parse shape-specific attributes
|
|
156
|
+
switch (tagName) {
|
|
157
|
+
case "rect":
|
|
158
|
+
childData.x = parseValidFloat(child.getAttribute("x"), 0);
|
|
159
|
+
childData.y = parseValidFloat(child.getAttribute("y"), 0);
|
|
160
|
+
childData.width = parseValidFloat(child.getAttribute("width"), 0);
|
|
161
|
+
childData.height = parseValidFloat(child.getAttribute("height"), 0);
|
|
162
|
+
childData.rx = parseValidFloat(child.getAttribute("rx"), 0);
|
|
163
|
+
childData.ry = parseValidFloat(child.getAttribute("ry"), 0);
|
|
164
|
+
break;
|
|
165
|
+
case "circle":
|
|
166
|
+
childData.cx = parseValidFloat(child.getAttribute("cx"), 0);
|
|
167
|
+
childData.cy = parseValidFloat(child.getAttribute("cy"), 0);
|
|
168
|
+
childData.r = parseValidFloat(child.getAttribute("r"), 0);
|
|
169
|
+
break;
|
|
170
|
+
case "ellipse":
|
|
171
|
+
childData.cx = parseValidFloat(child.getAttribute("cx"), 0);
|
|
172
|
+
childData.cy = parseValidFloat(child.getAttribute("cy"), 0);
|
|
173
|
+
childData.rx = parseValidFloat(child.getAttribute("rx"), 0);
|
|
174
|
+
childData.ry = parseValidFloat(child.getAttribute("ry"), 0);
|
|
175
|
+
break;
|
|
176
|
+
case "path":
|
|
177
|
+
childData.d = child.getAttribute("d") || "";
|
|
178
|
+
break;
|
|
179
|
+
case "polygon":
|
|
180
|
+
childData.points = child.getAttribute("points") || "";
|
|
181
|
+
break;
|
|
182
|
+
case "polyline":
|
|
183
|
+
childData.points = child.getAttribute("points") || "";
|
|
184
|
+
break;
|
|
185
|
+
case "line":
|
|
186
|
+
childData.x1 = parseValidFloat(child.getAttribute("x1"), 0);
|
|
187
|
+
childData.y1 = parseValidFloat(child.getAttribute("y1"), 0);
|
|
188
|
+
childData.x2 = parseValidFloat(child.getAttribute("x2"), 0);
|
|
189
|
+
childData.y2 = parseValidFloat(child.getAttribute("y2"), 0);
|
|
190
|
+
break;
|
|
191
|
+
case "use":
|
|
192
|
+
childData.href =
|
|
193
|
+
child.getAttribute("href") ||
|
|
194
|
+
child.getAttribute("xlink:href") ||
|
|
195
|
+
"";
|
|
196
|
+
childData.x = parseValidFloat(child.getAttribute("x"), 0);
|
|
197
|
+
childData.y = parseValidFloat(child.getAttribute("y"), 0);
|
|
198
|
+
break;
|
|
199
|
+
case "g":
|
|
200
|
+
// Groups can contain nested shapes - validate children exists
|
|
201
|
+
childData.children = [];
|
|
202
|
+
if (
|
|
203
|
+
child.children &&
|
|
204
|
+
typeof child.children[Symbol.iterator] === "function"
|
|
205
|
+
) {
|
|
206
|
+
for (const gc of child.children) {
|
|
207
|
+
if (gc && gc.tagName && typeof gc.getAttribute === "function") {
|
|
208
|
+
childData.children.push({
|
|
209
|
+
type: gc.tagName.toLowerCase(),
|
|
210
|
+
fill: gc.getAttribute("fill") || "inherit",
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
break;
|
|
216
|
+
default:
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
182
219
|
|
|
183
|
-
|
|
220
|
+
data.children.push(childData);
|
|
221
|
+
}
|
|
184
222
|
}
|
|
185
223
|
|
|
186
224
|
return data;
|
|
@@ -233,6 +271,36 @@ export function parsePatternElement(patternElement) {
|
|
|
233
271
|
* // Tile uses absolute coordinates, bbox is ignored
|
|
234
272
|
*/
|
|
235
273
|
export function getPatternTile(patternData, targetBBox) {
|
|
274
|
+
// Validate parameters
|
|
275
|
+
if (!patternData) {
|
|
276
|
+
throw new Error("getPatternTile: patternData is required");
|
|
277
|
+
}
|
|
278
|
+
if (!targetBBox) {
|
|
279
|
+
throw new Error("getPatternTile: targetBBox is required");
|
|
280
|
+
}
|
|
281
|
+
// Validate targetBBox has required numeric properties
|
|
282
|
+
if (
|
|
283
|
+
typeof targetBBox.x !== "number" ||
|
|
284
|
+
typeof targetBBox.y !== "number" ||
|
|
285
|
+
typeof targetBBox.width !== "number" ||
|
|
286
|
+
typeof targetBBox.height !== "number"
|
|
287
|
+
) {
|
|
288
|
+
throw new Error(
|
|
289
|
+
"getPatternTile: targetBBox must have numeric x, y, width, and height properties",
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
// Validate patternData has required numeric properties
|
|
293
|
+
if (
|
|
294
|
+
typeof patternData.x !== "number" ||
|
|
295
|
+
typeof patternData.y !== "number" ||
|
|
296
|
+
typeof patternData.width !== "number" ||
|
|
297
|
+
typeof patternData.height !== "number"
|
|
298
|
+
) {
|
|
299
|
+
throw new Error(
|
|
300
|
+
"getPatternTile: patternData must have numeric x, y, width, and height properties",
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
236
304
|
if (patternData.patternUnits === "objectBoundingBox") {
|
|
237
305
|
// Dimensions are fractions of target bbox
|
|
238
306
|
return {
|
|
@@ -308,14 +376,61 @@ export function getPatternTile(patternData, targetBBox) {
|
|
|
308
376
|
* // M translates to bbox origin and scales by bbox dimensions
|
|
309
377
|
*/
|
|
310
378
|
export function getPatternContentTransform(patternData, tile, targetBBox) {
|
|
379
|
+
// Validate parameters
|
|
380
|
+
if (!patternData) {
|
|
381
|
+
throw new Error("getPatternContentTransform: patternData is required");
|
|
382
|
+
}
|
|
383
|
+
if (!tile) {
|
|
384
|
+
throw new Error("getPatternContentTransform: tile is required");
|
|
385
|
+
}
|
|
386
|
+
if (!targetBBox) {
|
|
387
|
+
throw new Error("getPatternContentTransform: targetBBox is required");
|
|
388
|
+
}
|
|
389
|
+
// Validate tile has required properties
|
|
390
|
+
if (!tile.width || !tile.height) {
|
|
391
|
+
throw new Error(
|
|
392
|
+
"getPatternContentTransform: tile must have width and height properties",
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
// Validate targetBBox has required numeric properties
|
|
396
|
+
if (
|
|
397
|
+
typeof targetBBox.x !== "number" ||
|
|
398
|
+
typeof targetBBox.y !== "number" ||
|
|
399
|
+
typeof targetBBox.width !== "number" ||
|
|
400
|
+
typeof targetBBox.height !== "number"
|
|
401
|
+
) {
|
|
402
|
+
throw new Error(
|
|
403
|
+
"getPatternContentTransform: targetBBox must have numeric x, y, width, and height properties",
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
|
|
311
407
|
let M = Matrix.identity(3);
|
|
312
408
|
|
|
313
409
|
// Apply viewBox transform if present
|
|
314
410
|
if (patternData.viewBoxParsed) {
|
|
315
411
|
const vb = patternData.viewBoxParsed;
|
|
412
|
+
// Validate viewBox has required numeric properties
|
|
413
|
+
if (
|
|
414
|
+
typeof vb.x !== "number" ||
|
|
415
|
+
typeof vb.y !== "number" ||
|
|
416
|
+
typeof vb.width !== "number" ||
|
|
417
|
+
typeof vb.height !== "number"
|
|
418
|
+
) {
|
|
419
|
+
throw new Error(
|
|
420
|
+
"getPatternContentTransform: viewBoxParsed must have numeric x, y, width, and height properties",
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
316
424
|
const tileWidth = Number(tile.width);
|
|
317
425
|
const tileHeight = Number(tile.height);
|
|
318
426
|
|
|
427
|
+
// Check for division by zero in viewBox dimensions
|
|
428
|
+
if (vb.width === 0 || vb.height === 0) {
|
|
429
|
+
throw new Error(
|
|
430
|
+
"getPatternContentTransform: viewBox width and height must be non-zero",
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
|
|
319
434
|
// Scale from viewBox to tile
|
|
320
435
|
const scaleX = tileWidth / vb.width;
|
|
321
436
|
const scaleY = tileHeight / vb.height;
|
|
@@ -323,6 +438,13 @@ export function getPatternContentTransform(patternData, tile, targetBBox) {
|
|
|
323
438
|
// For 'xMidYMid meet', use uniform scale
|
|
324
439
|
const scale = Math.min(scaleX, scaleY);
|
|
325
440
|
|
|
441
|
+
// Validate scale is finite
|
|
442
|
+
if (!isFinite(scale)) {
|
|
443
|
+
throw new Error(
|
|
444
|
+
"getPatternContentTransform: computed scale is not finite",
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
|
|
326
448
|
// Center the content
|
|
327
449
|
const offsetX = (tileWidth - vb.width * scale) / 2;
|
|
328
450
|
const offsetY = (tileHeight - vb.height * scale) / 2;
|
|
@@ -367,6 +489,20 @@ export function getPatternContentTransform(patternData, tile, targetBBox) {
|
|
|
367
489
|
* // Returns rectangle vertices translated to (100, 50)
|
|
368
490
|
*/
|
|
369
491
|
export function patternChildToPolygon(child, transform = null, samples = 20) {
|
|
492
|
+
// Validate child parameter
|
|
493
|
+
if (!child) {
|
|
494
|
+
throw new Error("patternChildToPolygon: child is required");
|
|
495
|
+
}
|
|
496
|
+
if (!child.type) {
|
|
497
|
+
throw new Error("patternChildToPolygon: child must have a type property");
|
|
498
|
+
}
|
|
499
|
+
// Validate samples is a positive number
|
|
500
|
+
if (typeof samples !== "number" || samples <= 0 || !isFinite(samples)) {
|
|
501
|
+
throw new Error(
|
|
502
|
+
"patternChildToPolygon: samples must be a positive finite number",
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
|
|
370
506
|
// Create element-like object for ClipPathResolver
|
|
371
507
|
const element = {
|
|
372
508
|
type: child.type,
|
|
@@ -377,9 +513,20 @@ export function patternChildToPolygon(child, transform = null, samples = 20) {
|
|
|
377
513
|
// Get polygon using ClipPathResolver
|
|
378
514
|
let polygon = ClipPathResolver.shapeToPolygon(element, null, samples);
|
|
379
515
|
|
|
516
|
+
// Validate polygon is an array
|
|
517
|
+
if (!Array.isArray(polygon)) {
|
|
518
|
+
return [];
|
|
519
|
+
}
|
|
520
|
+
|
|
380
521
|
// Apply additional transform if provided
|
|
381
522
|
if (transform && polygon.length > 0) {
|
|
382
523
|
polygon = polygon.map((p) => {
|
|
524
|
+
// Validate point has x and y properties
|
|
525
|
+
if (!p || typeof p.x !== "number" || typeof p.y !== "number") {
|
|
526
|
+
throw new Error(
|
|
527
|
+
"patternChildToPolygon: invalid polygon point - must have numeric x and y properties",
|
|
528
|
+
);
|
|
529
|
+
}
|
|
383
530
|
const [x, y] = Transforms2D.applyTransform(transform, p.x, p.y);
|
|
384
531
|
return { x, y };
|
|
385
532
|
});
|
|
@@ -423,6 +570,36 @@ export function patternChildToPolygon(child, transform = null, samples = 20) {
|
|
|
423
570
|
* // { x: 0, y: 50 }, { x: 50, y: 50 }, { x: 100, y: 50 }, { x: 150, y: 50 }]
|
|
424
571
|
*/
|
|
425
572
|
export function getTilePositions(tile, coverBBox) {
|
|
573
|
+
// Validate parameters
|
|
574
|
+
if (!tile) {
|
|
575
|
+
throw new Error("getTilePositions: tile is required");
|
|
576
|
+
}
|
|
577
|
+
if (!coverBBox) {
|
|
578
|
+
throw new Error("getTilePositions: coverBBox is required");
|
|
579
|
+
}
|
|
580
|
+
// Validate tile has required properties
|
|
581
|
+
if (
|
|
582
|
+
!("x" in tile) ||
|
|
583
|
+
!("y" in tile) ||
|
|
584
|
+
!("width" in tile) ||
|
|
585
|
+
!("height" in tile)
|
|
586
|
+
) {
|
|
587
|
+
throw new Error(
|
|
588
|
+
"getTilePositions: tile must have x, y, width, and height properties",
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
// Validate coverBBox has required numeric properties
|
|
592
|
+
if (
|
|
593
|
+
typeof coverBBox.x !== "number" ||
|
|
594
|
+
typeof coverBBox.y !== "number" ||
|
|
595
|
+
typeof coverBBox.width !== "number" ||
|
|
596
|
+
typeof coverBBox.height !== "number"
|
|
597
|
+
) {
|
|
598
|
+
throw new Error(
|
|
599
|
+
"getTilePositions: coverBBox must have numeric x, y, width, and height properties",
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
|
|
426
603
|
const positions = [];
|
|
427
604
|
|
|
428
605
|
const tileX = Number(tile.x);
|
|
@@ -430,6 +607,17 @@ export function getTilePositions(tile, coverBBox) {
|
|
|
430
607
|
const tileW = Number(tile.width);
|
|
431
608
|
const tileH = Number(tile.height);
|
|
432
609
|
|
|
610
|
+
// Validate tile dimensions are finite
|
|
611
|
+
if (
|
|
612
|
+
!isFinite(tileX) ||
|
|
613
|
+
!isFinite(tileY) ||
|
|
614
|
+
!isFinite(tileW) ||
|
|
615
|
+
!isFinite(tileH)
|
|
616
|
+
) {
|
|
617
|
+
throw new Error("getTilePositions: tile dimensions must be finite numbers");
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Return empty array if tile dimensions are not positive
|
|
433
621
|
if (tileW <= 0 || tileH <= 0) return positions;
|
|
434
622
|
|
|
435
623
|
// Calculate start and end indices
|
|
@@ -438,6 +626,16 @@ export function getTilePositions(tile, coverBBox) {
|
|
|
438
626
|
const startJ = Math.floor((coverBBox.y - tileY) / tileH);
|
|
439
627
|
const endJ = Math.ceil((coverBBox.y + coverBBox.height - tileY) / tileH);
|
|
440
628
|
|
|
629
|
+
// Validate indices are finite
|
|
630
|
+
if (
|
|
631
|
+
!isFinite(startI) ||
|
|
632
|
+
!isFinite(endI) ||
|
|
633
|
+
!isFinite(startJ) ||
|
|
634
|
+
!isFinite(endJ)
|
|
635
|
+
) {
|
|
636
|
+
throw new Error("getTilePositions: computed tile indices are not finite");
|
|
637
|
+
}
|
|
638
|
+
|
|
441
639
|
for (let i = startI; i < endI; i++) {
|
|
442
640
|
for (let j = startJ; j < endJ; j++) {
|
|
443
641
|
positions.push({
|
|
@@ -488,6 +686,18 @@ export function getTilePositions(tile, coverBBox) {
|
|
|
488
686
|
* // Each polygon can be rendered with its associated fill and stroke
|
|
489
687
|
*/
|
|
490
688
|
export function resolvePattern(patternData, targetBBox, options = {}) {
|
|
689
|
+
// Validate parameters
|
|
690
|
+
if (!patternData) {
|
|
691
|
+
throw new Error("resolvePattern: patternData is required");
|
|
692
|
+
}
|
|
693
|
+
if (!targetBBox) {
|
|
694
|
+
throw new Error("resolvePattern: targetBBox is required");
|
|
695
|
+
}
|
|
696
|
+
// Validate patternData has children array
|
|
697
|
+
if (!Array.isArray(patternData.children)) {
|
|
698
|
+
throw new Error("resolvePattern: patternData.children must be an array");
|
|
699
|
+
}
|
|
700
|
+
|
|
491
701
|
const { samples = 20, maxTiles = 1000 } = options;
|
|
492
702
|
const result = [];
|
|
493
703
|
|
|
@@ -578,6 +788,20 @@ export function applyPattern(
|
|
|
578
788
|
targetBBox,
|
|
579
789
|
options = {},
|
|
580
790
|
) {
|
|
791
|
+
// Validate parameters
|
|
792
|
+
if (!targetPolygon) {
|
|
793
|
+
throw new Error("applyPattern: targetPolygon is required");
|
|
794
|
+
}
|
|
795
|
+
if (!Array.isArray(targetPolygon)) {
|
|
796
|
+
throw new Error("applyPattern: targetPolygon must be an array");
|
|
797
|
+
}
|
|
798
|
+
if (!patternData) {
|
|
799
|
+
throw new Error("applyPattern: patternData is required");
|
|
800
|
+
}
|
|
801
|
+
if (!targetBBox) {
|
|
802
|
+
throw new Error("applyPattern: targetBBox is required");
|
|
803
|
+
}
|
|
804
|
+
|
|
581
805
|
const patternPolygons = resolvePattern(patternData, targetBBox, options);
|
|
582
806
|
const result = [];
|
|
583
807
|
|
|
@@ -589,13 +813,16 @@ export function applyPattern(
|
|
|
589
813
|
polygon,
|
|
590
814
|
);
|
|
591
815
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
816
|
+
// Validate intersection is an array
|
|
817
|
+
if (Array.isArray(intersection)) {
|
|
818
|
+
for (const clippedPoly of intersection) {
|
|
819
|
+
if (clippedPoly.length >= 3) {
|
|
820
|
+
result.push({
|
|
821
|
+
polygon: clippedPoly,
|
|
822
|
+
fill,
|
|
823
|
+
opacity,
|
|
824
|
+
});
|
|
825
|
+
}
|
|
599
826
|
}
|
|
600
827
|
}
|
|
601
828
|
}
|
|
@@ -629,6 +856,14 @@ export function applyPattern(
|
|
|
629
856
|
* // Returns single polygon that is the union of all dots in the pattern
|
|
630
857
|
*/
|
|
631
858
|
export function patternToClipPath(patternData, targetBBox, options = {}) {
|
|
859
|
+
// Validate parameters
|
|
860
|
+
if (!patternData) {
|
|
861
|
+
throw new Error("patternToClipPath: patternData is required");
|
|
862
|
+
}
|
|
863
|
+
if (!targetBBox) {
|
|
864
|
+
throw new Error("patternToClipPath: targetBBox is required");
|
|
865
|
+
}
|
|
866
|
+
|
|
632
867
|
const patternPolygons = resolvePattern(patternData, targetBBox, options);
|
|
633
868
|
|
|
634
869
|
// Union all polygons
|
|
@@ -640,7 +875,13 @@ export function patternToClipPath(patternData, targetBBox, options = {}) {
|
|
|
640
875
|
result = polygon;
|
|
641
876
|
} else {
|
|
642
877
|
const unionResult = PolygonClip.polygonUnion(result, polygon);
|
|
643
|
-
|
|
878
|
+
// Validate unionResult is an array
|
|
879
|
+
if (
|
|
880
|
+
Array.isArray(unionResult) &&
|
|
881
|
+
unionResult.length > 0 &&
|
|
882
|
+
Array.isArray(unionResult[0]) &&
|
|
883
|
+
unionResult[0].length >= 3
|
|
884
|
+
) {
|
|
644
885
|
result = unionResult[0];
|
|
645
886
|
}
|
|
646
887
|
}
|
|
@@ -675,13 +916,38 @@ export function patternToClipPath(patternData, targetBBox, options = {}) {
|
|
|
675
916
|
* // Can be used in: <path d={pathData} />
|
|
676
917
|
*/
|
|
677
918
|
export function patternToPathData(patternData, targetBBox, options = {}) {
|
|
919
|
+
// Validate parameters
|
|
920
|
+
if (!patternData) {
|
|
921
|
+
throw new Error("patternToPathData: patternData is required");
|
|
922
|
+
}
|
|
923
|
+
if (!targetBBox) {
|
|
924
|
+
throw new Error("patternToPathData: targetBBox is required");
|
|
925
|
+
}
|
|
926
|
+
|
|
678
927
|
const polygon = patternToClipPath(patternData, targetBBox, options);
|
|
679
928
|
|
|
929
|
+
// Validate polygon is an array
|
|
930
|
+
if (!Array.isArray(polygon)) {
|
|
931
|
+
return "";
|
|
932
|
+
}
|
|
933
|
+
|
|
680
934
|
if (polygon.length < 3) return "";
|
|
681
935
|
|
|
682
936
|
let d = "";
|
|
683
937
|
for (let i = 0; i < polygon.length; i++) {
|
|
684
938
|
const p = polygon[i];
|
|
939
|
+
// Validate point has x and y properties
|
|
940
|
+
if (!p || typeof p.x !== "number" || typeof p.y !== "number") {
|
|
941
|
+
throw new Error(
|
|
942
|
+
"patternToPathData: invalid polygon point - must have numeric x and y properties",
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
// Validate x and y are finite
|
|
946
|
+
if (!isFinite(p.x) || !isFinite(p.y)) {
|
|
947
|
+
throw new Error(
|
|
948
|
+
"patternToPathData: polygon point coordinates must be finite numbers",
|
|
949
|
+
);
|
|
950
|
+
}
|
|
685
951
|
const x = Number(p.x).toFixed(6);
|
|
686
952
|
const y = Number(p.y).toFixed(6);
|
|
687
953
|
d += i === 0 ? `M ${x} ${y}` : ` L ${x} ${y}`;
|
|
@@ -714,18 +980,46 @@ export function patternToPathData(patternData, targetBBox, options = {}) {
|
|
|
714
980
|
* // Result: { columns: 4, rows: 4, total: 16 }
|
|
715
981
|
*/
|
|
716
982
|
export function getPatternTileCount(patternData, targetBBox) {
|
|
983
|
+
// Validate parameters
|
|
984
|
+
if (!patternData) {
|
|
985
|
+
throw new Error("getPatternTileCount: patternData is required");
|
|
986
|
+
}
|
|
987
|
+
if (!targetBBox) {
|
|
988
|
+
throw new Error("getPatternTileCount: targetBBox is required");
|
|
989
|
+
}
|
|
990
|
+
// Validate targetBBox has required numeric properties
|
|
991
|
+
if (
|
|
992
|
+
typeof targetBBox.width !== "number" ||
|
|
993
|
+
typeof targetBBox.height !== "number"
|
|
994
|
+
) {
|
|
995
|
+
throw new Error(
|
|
996
|
+
"getPatternTileCount: targetBBox must have numeric width and height properties",
|
|
997
|
+
);
|
|
998
|
+
}
|
|
999
|
+
|
|
717
1000
|
const tile = getPatternTile(patternData, targetBBox);
|
|
718
1001
|
|
|
719
1002
|
const tileW = Number(tile.width);
|
|
720
1003
|
const tileH = Number(tile.height);
|
|
721
1004
|
|
|
1005
|
+
// Check for zero or negative dimensions
|
|
722
1006
|
if (tileW <= 0 || tileH <= 0) {
|
|
723
1007
|
return { columns: 0, rows: 0, total: 0 };
|
|
724
1008
|
}
|
|
725
1009
|
|
|
1010
|
+
// Check for zero targetBBox dimensions
|
|
1011
|
+
if (targetBBox.width === 0 || targetBBox.height === 0) {
|
|
1012
|
+
return { columns: 0, rows: 0, total: 0 };
|
|
1013
|
+
}
|
|
1014
|
+
|
|
726
1015
|
const columns = Math.ceil(targetBBox.width / tileW);
|
|
727
1016
|
const rows = Math.ceil(targetBBox.height / tileH);
|
|
728
1017
|
|
|
1018
|
+
// Validate computed values are finite
|
|
1019
|
+
if (!isFinite(columns) || !isFinite(rows)) {
|
|
1020
|
+
throw new Error("getPatternTileCount: computed tile counts are not finite");
|
|
1021
|
+
}
|
|
1022
|
+
|
|
729
1023
|
return {
|
|
730
1024
|
columns,
|
|
731
1025
|
rows,
|
|
@@ -764,54 +1058,108 @@ export function getPatternTileCount(patternData, targetBBox) {
|
|
|
764
1058
|
* // (from x:10 to x:90, y:20 to y:60)
|
|
765
1059
|
*/
|
|
766
1060
|
export function getPatternContentBBox(patternData) {
|
|
1061
|
+
// Validate parameter
|
|
1062
|
+
if (!patternData) {
|
|
1063
|
+
throw new Error("getPatternContentBBox: patternData is required");
|
|
1064
|
+
}
|
|
1065
|
+
// Validate patternData has children array
|
|
1066
|
+
if (!Array.isArray(patternData.children)) {
|
|
1067
|
+
throw new Error(
|
|
1068
|
+
"getPatternContentBBox: patternData.children must be an array",
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
767
1072
|
let minX = Infinity;
|
|
768
1073
|
let minY = Infinity;
|
|
769
1074
|
let maxX = -Infinity;
|
|
770
1075
|
let maxY = -Infinity;
|
|
771
1076
|
|
|
772
1077
|
for (const child of patternData.children) {
|
|
1078
|
+
if (!child || !child.type) continue;
|
|
1079
|
+
|
|
773
1080
|
let childBBox = null;
|
|
774
1081
|
|
|
775
1082
|
switch (child.type) {
|
|
776
1083
|
case "rect":
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
1084
|
+
// Validate rect has required numeric properties
|
|
1085
|
+
if (
|
|
1086
|
+
typeof child.x === "number" &&
|
|
1087
|
+
typeof child.y === "number" &&
|
|
1088
|
+
typeof child.width === "number" &&
|
|
1089
|
+
typeof child.height === "number"
|
|
1090
|
+
) {
|
|
1091
|
+
childBBox = {
|
|
1092
|
+
x: child.x,
|
|
1093
|
+
y: child.y,
|
|
1094
|
+
width: child.width,
|
|
1095
|
+
height: child.height,
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
783
1098
|
break;
|
|
784
1099
|
case "circle":
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
1100
|
+
// Validate circle has required numeric properties
|
|
1101
|
+
if (
|
|
1102
|
+
typeof child.cx === "number" &&
|
|
1103
|
+
typeof child.cy === "number" &&
|
|
1104
|
+
typeof child.r === "number"
|
|
1105
|
+
) {
|
|
1106
|
+
childBBox = {
|
|
1107
|
+
x: child.cx - child.r,
|
|
1108
|
+
y: child.cy - child.r,
|
|
1109
|
+
width: child.r * 2,
|
|
1110
|
+
height: child.r * 2,
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
791
1113
|
break;
|
|
792
1114
|
case "ellipse":
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
1115
|
+
// Validate ellipse has required numeric properties
|
|
1116
|
+
if (
|
|
1117
|
+
typeof child.cx === "number" &&
|
|
1118
|
+
typeof child.cy === "number" &&
|
|
1119
|
+
typeof child.rx === "number" &&
|
|
1120
|
+
typeof child.ry === "number"
|
|
1121
|
+
) {
|
|
1122
|
+
childBBox = {
|
|
1123
|
+
x: child.cx - child.rx,
|
|
1124
|
+
y: child.cy - child.ry,
|
|
1125
|
+
width: child.rx * 2,
|
|
1126
|
+
height: child.ry * 2,
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
799
1129
|
break;
|
|
800
1130
|
case "line":
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
1131
|
+
// Validate line has required numeric properties
|
|
1132
|
+
if (
|
|
1133
|
+
typeof child.x1 === "number" &&
|
|
1134
|
+
typeof child.y1 === "number" &&
|
|
1135
|
+
typeof child.x2 === "number" &&
|
|
1136
|
+
typeof child.y2 === "number"
|
|
1137
|
+
) {
|
|
1138
|
+
childBBox = {
|
|
1139
|
+
x: Math.min(child.x1, child.x2),
|
|
1140
|
+
y: Math.min(child.y1, child.y2),
|
|
1141
|
+
width: Math.abs(child.x2 - child.x1),
|
|
1142
|
+
height: Math.abs(child.y2 - child.y1),
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
break;
|
|
1146
|
+
default:
|
|
807
1147
|
break;
|
|
808
1148
|
}
|
|
809
1149
|
|
|
810
1150
|
if (childBBox) {
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
1151
|
+
// Validate bbox values are finite
|
|
1152
|
+
if (
|
|
1153
|
+
isFinite(childBBox.x) &&
|
|
1154
|
+
isFinite(childBBox.y) &&
|
|
1155
|
+
isFinite(childBBox.width) &&
|
|
1156
|
+
isFinite(childBBox.height)
|
|
1157
|
+
) {
|
|
1158
|
+
minX = Math.min(minX, childBBox.x);
|
|
1159
|
+
minY = Math.min(minY, childBBox.y);
|
|
1160
|
+
maxX = Math.max(maxX, childBBox.x + childBBox.width);
|
|
1161
|
+
maxY = Math.max(maxY, childBBox.y + childBBox.height);
|
|
1162
|
+
}
|
|
815
1163
|
}
|
|
816
1164
|
}
|
|
817
1165
|
|