@emasoft/svg-matrix 1.0.27 → 1.0.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +325 -0
  2. package/bin/svg-matrix.js +994 -378
  3. package/bin/svglinter.cjs +4172 -433
  4. package/bin/svgm.js +744 -184
  5. package/package.json +16 -4
  6. package/src/animation-references.js +71 -52
  7. package/src/arc-length.js +160 -96
  8. package/src/bezier-analysis.js +257 -117
  9. package/src/bezier-intersections.js +411 -148
  10. package/src/browser-verify.js +240 -100
  11. package/src/clip-path-resolver.js +350 -142
  12. package/src/convert-path-data.js +279 -134
  13. package/src/css-specificity.js +78 -70
  14. package/src/flatten-pipeline.js +751 -263
  15. package/src/geometry-to-path.js +511 -182
  16. package/src/index.js +191 -46
  17. package/src/inkscape-support.js +404 -0
  18. package/src/marker-resolver.js +278 -164
  19. package/src/mask-resolver.js +209 -98
  20. package/src/matrix.js +147 -67
  21. package/src/mesh-gradient.js +187 -96
  22. package/src/off-canvas-detection.js +201 -104
  23. package/src/path-analysis.js +187 -107
  24. package/src/path-data-plugins.js +628 -167
  25. package/src/path-simplification.js +0 -1
  26. package/src/pattern-resolver.js +125 -88
  27. package/src/polygon-clip.js +111 -66
  28. package/src/svg-boolean-ops.js +194 -118
  29. package/src/svg-collections.js +48 -19
  30. package/src/svg-flatten.js +282 -164
  31. package/src/svg-parser.js +427 -200
  32. package/src/svg-rendering-context.js +147 -104
  33. package/src/svg-toolbox.js +16411 -3298
  34. package/src/svg2-polyfills.js +114 -245
  35. package/src/transform-decomposition.js +46 -41
  36. package/src/transform-optimization.js +89 -68
  37. package/src/transforms2d.js +49 -16
  38. package/src/transforms3d.js +58 -22
  39. package/src/use-symbol-resolver.js +150 -110
  40. package/src/vector.js +67 -15
  41. package/src/vendor/README.md +110 -0
  42. package/src/vendor/inkscape-hatch-polyfill.js +401 -0
  43. package/src/vendor/inkscape-hatch-polyfill.min.js +8 -0
  44. package/src/vendor/inkscape-mesh-polyfill.js +843 -0
  45. package/src/vendor/inkscape-mesh-polyfill.min.js +8 -0
  46. package/src/verification.js +288 -124
@@ -1,12 +1,13 @@
1
- import Decimal from 'decimal.js';
2
- import { Matrix } from './matrix.js';
1
+ import Decimal from "decimal.js";
2
+ import { Matrix } from "./matrix.js";
3
3
 
4
- const D = x => (x instanceof Decimal ? x : new Decimal(x));
4
+ const D = (x) => (x instanceof Decimal ? x : new Decimal(x));
5
5
 
6
6
  /**
7
7
  * Standard kappa for 90° arcs (4 Bezier curves per circle).
8
8
  * kappa = 4/3 * (sqrt(2) - 1) ≈ 0.5522847498
9
9
  * Maximum radial error: ~0.027%
10
+ * @returns {Decimal} The kappa constant for 90-degree arc approximation
10
11
  */
11
12
  export function getKappa() {
12
13
  const two = new Decimal(2);
@@ -90,16 +91,20 @@ export function circleToPathDataHP(cx, cy, r, arcs = 8, precision = 6) {
90
91
  */
91
92
  export function ellipseToPathDataHP(cx, cy, rx, ry, arcs = 8, precision = 6) {
92
93
  // Enforce multiple of 4 for symmetry
93
- if (arcs % 4 !== 0) {
94
- arcs = Math.ceil(arcs / 4) * 4;
94
+ let numArcs = arcs;
95
+ if (numArcs % 4 !== 0) {
96
+ numArcs = Math.ceil(numArcs / 4) * 4;
95
97
  }
96
- const cxD = D(cx), cyD = D(cy), rxD = D(rx), ryD = D(ry);
97
- const f = v => formatNumber(v, precision);
98
+ const cxD = D(cx),
99
+ cyD = D(cy),
100
+ rxD = D(rx),
101
+ ryD = D(ry);
102
+ const f = (v) => formatNumber(v, precision);
98
103
 
99
104
  // Angle per arc in radians
100
105
  const PI = Decimal.acos(-1);
101
106
  const TWO_PI = PI.mul(2);
102
- const arcAngle = TWO_PI.div(arcs);
107
+ const arcAngle = TWO_PI.div(numArcs);
103
108
 
104
109
  // Control point distance for this arc angle
105
110
  const kappa = getKappaForArc(arcAngle);
@@ -107,7 +112,7 @@ export function ellipseToPathDataHP(cx, cy, rx, ry, arcs = 8, precision = 6) {
107
112
  // Generate path
108
113
  const commands = [];
109
114
 
110
- for (let i = 0; i < arcs; i++) {
115
+ for (let i = 0; i < numArcs; i++) {
111
116
  const startAngle = arcAngle.mul(i);
112
117
  const endAngle = arcAngle.mul(i + 1);
113
118
 
@@ -126,10 +131,10 @@ export function ellipseToPathDataHP(cx, cy, rx, ry, arcs = 8, precision = 6) {
126
131
  // Tangent at angle θ: (-sin(θ), cos(θ))
127
132
  // Control point 1: start + kappa * tangent_at_start * radius
128
133
  // Control point 2: end - kappa * tangent_at_end * radius
129
- const tx0 = sinStart.neg(); // tangent x at start
130
- const ty0 = cosStart; // tangent y at start
131
- const tx3 = sinEnd.neg(); // tangent x at end
132
- const ty3 = cosEnd; // tangent y at end
134
+ const tx0 = sinStart.neg(); // tangent x at start
135
+ const ty0 = cosStart; // tangent y at start
136
+ const tx3 = sinEnd.neg(); // tangent x at end
137
+ const ty3 = cosEnd; // tangent y at end
133
138
 
134
139
  const x1 = x0.plus(kappa.mul(rxD).mul(tx0));
135
140
  const y1 = y0.plus(kappa.mul(ryD).mul(ty0));
@@ -142,77 +147,212 @@ export function ellipseToPathDataHP(cx, cy, rx, ry, arcs = 8, precision = 6) {
142
147
  commands.push(`C${f(x1)} ${f(y1)} ${f(x2)} ${f(y2)} ${f(x3)} ${f(y3)}`);
143
148
  }
144
149
 
145
- commands.push('Z');
146
- return commands.join(' ');
150
+ commands.push("Z");
151
+ return commands.join(" ");
147
152
  }
148
153
 
154
+ /**
155
+ * Format a number with specified precision, removing trailing zeros.
156
+ * @param {number|Decimal} value - Value to format
157
+ * @param {number} precision - Number of decimal places
158
+ * @returns {string} Formatted number string
159
+ */
149
160
  function formatNumber(value, precision = 6) {
150
161
  // Format with precision then remove trailing zeros for smaller output
151
162
  let str = value.toFixed(precision);
152
163
  // Remove trailing zeros after decimal point
153
- if (str.includes('.')) {
154
- str = str.replace(/\.?0+$/, '');
164
+ if (str.includes(".")) {
165
+ str = str.replace(/\.?0+$/, "");
155
166
  }
156
167
  return str;
157
168
  }
158
169
 
170
+ /**
171
+ * Convert a circle to SVG path data using 4 Bezier arcs (standard kappa).
172
+ * @param {number|Decimal} cx - Center X coordinate
173
+ * @param {number|Decimal} cy - Center Y coordinate
174
+ * @param {number|Decimal} r - Radius
175
+ * @param {number} precision - Decimal precision for output coordinates
176
+ * @returns {string} SVG path data string
177
+ */
159
178
  export function circleToPathData(cx, cy, r, precision = 6) {
160
- const cxD = D(cx), cyD = D(cy), rD = D(r);
179
+ const cxD = D(cx),
180
+ cyD = D(cy),
181
+ rD = D(r);
161
182
  const k = getKappa().mul(rD);
162
- const x0 = cxD.plus(rD), y0 = cyD;
163
- const c1x1 = x0, c1y1 = y0.minus(k), c1x2 = cxD.plus(k), c1y2 = cyD.minus(rD), x1 = cxD, y1 = cyD.minus(rD);
164
- const c2x1 = cxD.minus(k), c2y1 = y1, c2x2 = cxD.minus(rD), c2y2 = cyD.minus(k), x2 = cxD.minus(rD), y2 = cyD;
165
- const c3x1 = x2, c3y1 = cyD.plus(k), c3x2 = cxD.minus(k), c3y2 = cyD.plus(rD), x3 = cxD, y3 = cyD.plus(rD);
166
- const c4x1 = cxD.plus(k), c4y1 = y3, c4x2 = x0, c4y2 = cyD.plus(k);
167
- const f = v => formatNumber(v, precision);
183
+ const x0 = cxD.plus(rD),
184
+ y0 = cyD;
185
+ const c1x1 = x0,
186
+ c1y1 = y0.minus(k),
187
+ c1x2 = cxD.plus(k),
188
+ c1y2 = cyD.minus(rD),
189
+ x1 = cxD,
190
+ y1 = cyD.minus(rD);
191
+ const c2x1 = cxD.minus(k),
192
+ c2y1 = y1,
193
+ c2x2 = cxD.minus(rD),
194
+ c2y2 = cyD.minus(k),
195
+ x2 = cxD.minus(rD),
196
+ y2 = cyD;
197
+ const c3x1 = x2,
198
+ c3y1 = cyD.plus(k),
199
+ c3x2 = cxD.minus(k),
200
+ c3y2 = cyD.plus(rD),
201
+ x3 = cxD,
202
+ y3 = cyD.plus(rD);
203
+ const c4x1 = cxD.plus(k),
204
+ c4y1 = y3,
205
+ c4x2 = x0,
206
+ c4y2 = cyD.plus(k);
207
+ const f = (v) => formatNumber(v, precision);
168
208
  return `M${f(x0)} ${f(y0)}C${f(c1x1)} ${f(c1y1)} ${f(c1x2)} ${f(c1y2)} ${f(x1)} ${f(y1)}C${f(c2x1)} ${f(c2y1)} ${f(c2x2)} ${f(c2y2)} ${f(x2)} ${f(y2)}C${f(c3x1)} ${f(c3y1)} ${f(c3x2)} ${f(c3y2)} ${f(x3)} ${f(y3)}C${f(c4x1)} ${f(c4y1)} ${f(c4x2)} ${f(c4y2)} ${f(x0)} ${f(y0)}Z`;
169
209
  }
170
210
 
211
+ /**
212
+ * Convert an ellipse to SVG path data using 4 Bezier arcs (standard kappa).
213
+ * @param {number|Decimal} cx - Center X coordinate
214
+ * @param {number|Decimal} cy - Center Y coordinate
215
+ * @param {number|Decimal} rx - X radius
216
+ * @param {number|Decimal} ry - Y radius
217
+ * @param {number} precision - Decimal precision for output coordinates
218
+ * @returns {string} SVG path data string
219
+ */
171
220
  export function ellipseToPathData(cx, cy, rx, ry, precision = 6) {
172
- const cxD = D(cx), cyD = D(cy), rxD = D(rx), ryD = D(ry);
173
- const kappa = getKappa(), kx = kappa.mul(rxD), ky = kappa.mul(ryD);
174
- const x0 = cxD.plus(rxD), y0 = cyD;
175
- const c1x1 = x0, c1y1 = y0.minus(ky), c1x2 = cxD.plus(kx), c1y2 = cyD.minus(ryD), x1 = cxD, y1 = cyD.minus(ryD);
176
- const c2x1 = cxD.minus(kx), c2y1 = y1, c2x2 = cxD.minus(rxD), c2y2 = cyD.minus(ky), x2 = cxD.minus(rxD), y2 = cyD;
177
- const c3x1 = x2, c3y1 = cyD.plus(ky), c3x2 = cxD.minus(kx), c3y2 = cyD.plus(ryD), x3 = cxD, y3 = cyD.plus(ryD);
178
- const c4x1 = cxD.plus(kx), c4y1 = y3, c4x2 = x0, c4y2 = cyD.plus(ky);
179
- const f = v => formatNumber(v, precision);
221
+ const cxD = D(cx),
222
+ cyD = D(cy),
223
+ rxD = D(rx),
224
+ ryD = D(ry);
225
+ const kappa = getKappa(),
226
+ kx = kappa.mul(rxD),
227
+ ky = kappa.mul(ryD);
228
+ const x0 = cxD.plus(rxD),
229
+ y0 = cyD;
230
+ const c1x1 = x0,
231
+ c1y1 = y0.minus(ky),
232
+ c1x2 = cxD.plus(kx),
233
+ c1y2 = cyD.minus(ryD),
234
+ x1 = cxD,
235
+ y1 = cyD.minus(ryD);
236
+ const c2x1 = cxD.minus(kx),
237
+ c2y1 = y1,
238
+ c2x2 = cxD.minus(rxD),
239
+ c2y2 = cyD.minus(ky),
240
+ x2 = cxD.minus(rxD),
241
+ y2 = cyD;
242
+ const c3x1 = x2,
243
+ c3y1 = cyD.plus(ky),
244
+ c3x2 = cxD.minus(kx),
245
+ c3y2 = cyD.plus(ryD),
246
+ x3 = cxD,
247
+ y3 = cyD.plus(ryD);
248
+ const c4x1 = cxD.plus(kx),
249
+ c4y1 = y3,
250
+ c4x2 = x0,
251
+ c4y2 = cyD.plus(ky);
252
+ const f = (v) => formatNumber(v, precision);
180
253
  return `M${f(x0)} ${f(y0)}C${f(c1x1)} ${f(c1y1)} ${f(c1x2)} ${f(c1y2)} ${f(x1)} ${f(y1)}C${f(c2x1)} ${f(c2y1)} ${f(c2x2)} ${f(c2y2)} ${f(x2)} ${f(y2)}C${f(c3x1)} ${f(c3y1)} ${f(c3x2)} ${f(c3y2)} ${f(x3)} ${f(y3)}C${f(c4x1)} ${f(c4y1)} ${f(c4x2)} ${f(c4y2)} ${f(x0)} ${f(y0)}Z`;
181
254
  }
182
255
 
183
- export function rectToPathData(x, y, width, height, rx = 0, ry = null, useArcs = false, precision = 6) {
184
- const xD = D(x), yD = D(y), wD = D(width), hD = D(height);
185
- let rxD = D(rx || 0), ryD = ry !== null ? D(ry) : rxD;
186
- const halfW = wD.div(2), halfH = hD.div(2);
256
+ /**
257
+ * Convert a rectangle to SVG path data, with optional rounded corners.
258
+ * @param {number|Decimal} x - Top-left X coordinate
259
+ * @param {number|Decimal} y - Top-left Y coordinate
260
+ * @param {number|Decimal} width - Rectangle width
261
+ * @param {number|Decimal} height - Rectangle height
262
+ * @param {number|Decimal} rx - X-axis corner radius (default 0)
263
+ * @param {number|Decimal|null} ry - Y-axis corner radius (defaults to rx if null)
264
+ * @param {boolean} useArcs - Use arc commands instead of Bezier curves for corners
265
+ * @param {number} precision - Decimal precision for output coordinates
266
+ * @returns {string} SVG path data string
267
+ */
268
+ export function rectToPathData(
269
+ x,
270
+ y,
271
+ width,
272
+ height,
273
+ rx = 0,
274
+ ry = null,
275
+ useArcs = false,
276
+ precision = 6,
277
+ ) {
278
+ const xD = D(x),
279
+ yD = D(y),
280
+ wD = D(width),
281
+ hD = D(height);
282
+ let rxD = D(rx || 0),
283
+ ryD = ry !== null ? D(ry) : rxD;
284
+ const halfW = wD.div(2),
285
+ halfH = hD.div(2);
187
286
  if (rxD.gt(halfW)) rxD = halfW;
188
287
  if (ryD.gt(halfH)) ryD = halfH;
189
- const f = v => formatNumber(v, precision);
288
+ const f = (v) => formatNumber(v, precision);
190
289
  if (rxD.isZero() || ryD.isZero()) {
191
- const x1 = xD.plus(wD), y1 = yD.plus(hD);
290
+ const x1 = xD.plus(wD),
291
+ y1 = yD.plus(hD);
192
292
  // Use H (horizontal) and V (vertical) commands for smaller output
193
293
  return `M${f(xD)} ${f(yD)}H${f(x1)}V${f(y1)}H${f(xD)}Z`;
194
294
  }
195
- const left = xD, right = xD.plus(wD), top = yD, bottom = yD.plus(hD);
196
- const leftInner = left.plus(rxD), rightInner = right.minus(rxD);
197
- const topInner = top.plus(ryD), bottomInner = bottom.minus(ryD);
295
+ const left = xD,
296
+ right = xD.plus(wD),
297
+ top = yD,
298
+ bottom = yD.plus(hD);
299
+ const leftInner = left.plus(rxD),
300
+ rightInner = right.minus(rxD);
301
+ const topInner = top.plus(ryD),
302
+ bottomInner = bottom.minus(ryD);
198
303
  if (useArcs) {
199
304
  return `M${f(leftInner)} ${f(top)}L${f(rightInner)} ${f(top)}A${f(rxD)} ${f(ryD)} 0 0 1 ${f(right)} ${f(topInner)}L${f(right)} ${f(bottomInner)}A${f(rxD)} ${f(ryD)} 0 0 1 ${f(rightInner)} ${f(bottom)}L${f(leftInner)} ${f(bottom)}A${f(rxD)} ${f(ryD)} 0 0 1 ${f(left)} ${f(bottomInner)}L${f(left)} ${f(topInner)}A${f(rxD)} ${f(ryD)} 0 0 1 ${f(leftInner)} ${f(top)}Z`;
200
305
  }
201
- const kappa = getKappa(), kx = kappa.mul(rxD), ky = kappa.mul(ryD);
306
+ const kappa = getKappa(),
307
+ kx = kappa.mul(rxD),
308
+ ky = kappa.mul(ryD);
202
309
  // Each corner has two Bezier control points:
203
310
  // First control point: offset from start point along the edge tangent
204
311
  // Second control point: offset from end point along the edge tangent
205
312
  return `M${f(leftInner)} ${f(top)}L${f(rightInner)} ${f(top)}C${f(rightInner.plus(kx))} ${f(top)} ${f(right)} ${f(topInner.minus(ky))} ${f(right)} ${f(topInner)}L${f(right)} ${f(bottomInner)}C${f(right)} ${f(bottomInner.plus(ky))} ${f(rightInner.plus(kx))} ${f(bottom)} ${f(rightInner)} ${f(bottom)}L${f(leftInner)} ${f(bottom)}C${f(leftInner.minus(kx))} ${f(bottom)} ${f(left)} ${f(bottomInner.plus(ky))} ${f(left)} ${f(bottomInner)}L${f(left)} ${f(topInner)}C${f(left)} ${f(topInner.minus(ky))} ${f(leftInner.minus(kx))} ${f(top)} ${f(leftInner)} ${f(top)}Z`;
206
313
  }
207
314
 
315
+ /**
316
+ * Convert a line segment to SVG path data.
317
+ * @param {number|Decimal} x1 - Start point X coordinate
318
+ * @param {number|Decimal} y1 - Start point Y coordinate
319
+ * @param {number|Decimal} x2 - End point X coordinate
320
+ * @param {number|Decimal} y2 - End point Y coordinate
321
+ * @param {number} precision - Decimal precision for output coordinates
322
+ * @returns {string} SVG path data string
323
+ */
208
324
  export function lineToPathData(x1, y1, x2, y2, precision = 6) {
209
- const f = v => formatNumber(D(v), precision);
325
+ const f = (v) => formatNumber(D(v), precision);
210
326
  return `M${f(x1)} ${f(y1)}L${f(x2)} ${f(y2)}`;
211
327
  }
212
328
 
329
+ /**
330
+ * Parse SVG points attribute string into array of Decimal coordinate pairs.
331
+ * @param {string|Array|Object} points - Points string, array, or SVGAnimatedPoints object
332
+ * @returns {Array<Array<Decimal>>} Array of [x, y] Decimal pairs
333
+ */
213
334
  function parsePoints(points) {
335
+ // Handle null/undefined
336
+ if (points == null) return [];
337
+ // Handle arrays
214
338
  if (Array.isArray(points)) return points.map(([x, y]) => [D(x), D(y)]);
215
- const nums = points.split(/[\s,]+/).filter(s => s.length > 0).map(s => D(s));
339
+ // Handle SVGAnimatedPoints or objects with baseVal
340
+ let pointsValue = points;
341
+ if (typeof pointsValue === "object" && pointsValue.baseVal !== undefined) {
342
+ pointsValue = pointsValue.baseVal;
343
+ }
344
+ // Convert to string if not already a string
345
+ if (typeof pointsValue !== "string") {
346
+ if (typeof pointsValue.toString === "function") {
347
+ pointsValue = pointsValue.toString();
348
+ } else {
349
+ return [];
350
+ }
351
+ }
352
+ const nums = pointsValue
353
+ .split(/[\s,]+/)
354
+ .filter((s) => s.length > 0)
355
+ .map((s) => D(s));
216
356
  const pairs = [];
217
357
  for (let i = 0; i < nums.length; i += 2) {
218
358
  if (i + 1 < nums.length) pairs.push([nums[i], nums[i + 1]]);
@@ -220,10 +360,16 @@ function parsePoints(points) {
220
360
  return pairs;
221
361
  }
222
362
 
363
+ /**
364
+ * Convert polyline points to SVG path data.
365
+ * @param {string|Array} points - Points string or array of coordinate pairs
366
+ * @param {number} precision - Decimal precision for output coordinates
367
+ * @returns {string} SVG path data string
368
+ */
223
369
  export function polylineToPathData(points, precision = 6) {
224
370
  const pairs = parsePoints(points);
225
- if (pairs.length === 0) return '';
226
- const f = v => formatNumber(v, precision);
371
+ if (pairs.length === 0) return "";
372
+ const f = (v) => formatNumber(v, precision);
227
373
  const [x0, y0] = pairs[0];
228
374
  let path = `M${f(x0)} ${f(y0)}`;
229
375
  for (let i = 1; i < pairs.length; i++) {
@@ -233,18 +379,46 @@ export function polylineToPathData(points, precision = 6) {
233
379
  return path;
234
380
  }
235
381
 
382
+ /**
383
+ * Convert polygon points to SVG path data (closed path).
384
+ * @param {string|Array} points - Points string or array of coordinate pairs
385
+ * @param {number} precision - Decimal precision for output coordinates
386
+ * @returns {string} SVG path data string with Z (closepath) command
387
+ */
236
388
  export function polygonToPathData(points, precision = 6) {
237
389
  const path = polylineToPathData(points, precision);
238
- return path ? path + ' Z' : '';
390
+ return path ? path + " Z" : "";
239
391
  }
240
392
 
241
393
  // Parameter count for each SVG path command
242
394
  const COMMAND_PARAMS = {
243
- M: 2, m: 2, L: 2, l: 2, H: 1, h: 1, V: 1, v: 1,
244
- C: 6, c: 6, S: 4, s: 4, Q: 4, q: 4, T: 2, t: 2,
245
- A: 7, a: 7, Z: 0, z: 0
395
+ M: 2,
396
+ m: 2,
397
+ L: 2,
398
+ l: 2,
399
+ H: 1,
400
+ h: 1,
401
+ V: 1,
402
+ v: 1,
403
+ C: 6,
404
+ c: 6,
405
+ S: 4,
406
+ s: 4,
407
+ Q: 4,
408
+ q: 4,
409
+ T: 2,
410
+ t: 2,
411
+ A: 7,
412
+ a: 7,
413
+ Z: 0,
414
+ z: 0,
246
415
  };
247
416
 
417
+ /**
418
+ * Parse SVG path data string into command objects.
419
+ * @param {string} pathData - SVG path data string
420
+ * @returns {Array<Object>} Array of {command, args} objects
421
+ */
248
422
  export function parsePathData(pathData) {
249
423
  const commands = [];
250
424
  const commandRegex = /([MmLlHhVvCcSsQqTtAaZz])\s*([^MmLlHhVvCcSsQqTtAaZz]*)/g;
@@ -252,7 +426,14 @@ export function parsePathData(pathData) {
252
426
  while ((match = commandRegex.exec(pathData)) !== null) {
253
427
  const command = match[1];
254
428
  const argsStr = match[2].trim();
255
- const allArgs = argsStr.length > 0 ? argsStr.split(/[\s,]+/).filter(s => s.length > 0).map(s => D(s)) : [];
429
+
430
+ // FIX: Use regex to extract numbers, handles implicit negative separators (e.g., "0.8-2.9" -> ["0.8", "-2.9"])
431
+ // Per W3C SVG spec, negative signs can act as delimiters without spaces
432
+ const numRegex = /-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/g;
433
+ const allArgs =
434
+ argsStr.length > 0
435
+ ? Array.from(argsStr.matchAll(numRegex), (m) => D(m[0]))
436
+ : [];
256
437
 
257
438
  const paramCount = COMMAND_PARAMS[command];
258
439
 
@@ -267,8 +448,8 @@ export function parsePathData(pathData) {
267
448
  if (args.length === paramCount) {
268
449
  // For M/m, first group is moveto, subsequent groups become implicit lineto (L/l)
269
450
  let effectiveCmd = command;
270
- if (i > 0 && (command === 'M' || command === 'm')) {
271
- effectiveCmd = command === 'M' ? 'L' : 'l';
451
+ if (i > 0 && (command === "M" || command === "m")) {
452
+ effectiveCmd = command === "M" ? "L" : "l";
272
453
  }
273
454
  commands.push({ command: effectiveCmd, args });
274
455
  }
@@ -279,62 +460,84 @@ export function parsePathData(pathData) {
279
460
  return commands;
280
461
  }
281
462
 
463
+ /**
464
+ * Convert path command array back to SVG path data string.
465
+ * @param {Array<Object>} commands - Array of {command, args} objects
466
+ * @param {number} precision - Decimal precision for output coordinates
467
+ * @returns {string} SVG path data string
468
+ */
282
469
  export function pathArrayToString(commands, precision = 6) {
283
- return commands.map(({ command, args }) => {
284
- const argsStr = args.map(a => formatNumber(a, precision)).join(' ');
285
- return argsStr.length > 0 ? `${command} ${argsStr}` : command;
286
- }).join(' ');
470
+ return commands
471
+ .map(({ command, args }) => {
472
+ const argsStr = args.map((a) => formatNumber(a, precision)).join(" ");
473
+ return argsStr.length > 0 ? `${command} ${argsStr}` : command;
474
+ })
475
+ .join(" ");
287
476
  }
288
477
 
478
+ /**
479
+ * Convert all path commands to absolute coordinates.
480
+ * @param {string} pathData - SVG path data string
481
+ * @returns {string} Path data with all absolute commands
482
+ */
289
483
  export function pathToAbsolute(pathData) {
290
484
  const commands = parsePathData(pathData);
291
485
  const result = [];
292
- let currentX = new Decimal(0), currentY = new Decimal(0);
293
- let subpathStartX = new Decimal(0), subpathStartY = new Decimal(0);
294
- let lastControlX = new Decimal(0), lastControlY = new Decimal(0);
295
- let lastCommand = '';
486
+ let currentX = new Decimal(0),
487
+ currentY = new Decimal(0);
488
+ let subpathStartX = new Decimal(0),
489
+ subpathStartY = new Decimal(0);
490
+ let lastControlX = new Decimal(0),
491
+ lastControlY = new Decimal(0);
492
+ let lastCommand = "";
296
493
 
297
494
  for (const { command, args } of commands) {
298
495
  const isRelative = command === command.toLowerCase();
299
496
  const upperCmd = command.toUpperCase();
300
- if (upperCmd === 'M') {
497
+ if (upperCmd === "M") {
301
498
  const x = isRelative ? currentX.plus(args[0]) : args[0];
302
499
  const y = isRelative ? currentY.plus(args[1]) : args[1];
303
- currentX = x; currentY = y; subpathStartX = x; subpathStartY = y;
304
- result.push({ command: 'M', args: [x, y] });
305
- lastCommand = 'M';
306
- } else if (upperCmd === 'L') {
500
+ currentX = x;
501
+ currentY = y;
502
+ subpathStartX = x;
503
+ subpathStartY = y;
504
+ result.push({ command: "M", args: [x, y] });
505
+ lastCommand = "M";
506
+ } else if (upperCmd === "L") {
307
507
  const x = isRelative ? currentX.plus(args[0]) : args[0];
308
508
  const y = isRelative ? currentY.plus(args[1]) : args[1];
309
- currentX = x; currentY = y;
310
- result.push({ command: 'L', args: [x, y] });
311
- lastCommand = 'L';
312
- } else if (upperCmd === 'H') {
509
+ currentX = x;
510
+ currentY = y;
511
+ result.push({ command: "L", args: [x, y] });
512
+ lastCommand = "L";
513
+ } else if (upperCmd === "H") {
313
514
  const x = isRelative ? currentX.plus(args[0]) : args[0];
314
515
  currentX = x;
315
- result.push({ command: 'L', args: [x, currentY] });
316
- lastCommand = 'H';
317
- } else if (upperCmd === 'V') {
516
+ result.push({ command: "L", args: [x, currentY] });
517
+ lastCommand = "H";
518
+ } else if (upperCmd === "V") {
318
519
  const y = isRelative ? currentY.plus(args[0]) : args[0];
319
520
  currentY = y;
320
- result.push({ command: 'L', args: [currentX, y] });
321
- lastCommand = 'V';
322
- } else if (upperCmd === 'C') {
521
+ result.push({ command: "L", args: [currentX, y] });
522
+ lastCommand = "V";
523
+ } else if (upperCmd === "C") {
323
524
  const x1 = isRelative ? currentX.plus(args[0]) : args[0];
324
525
  const y1 = isRelative ? currentY.plus(args[1]) : args[1];
325
526
  const x2 = isRelative ? currentX.plus(args[2]) : args[2];
326
527
  const y2 = isRelative ? currentY.plus(args[3]) : args[3];
327
528
  const x = isRelative ? currentX.plus(args[4]) : args[4];
328
529
  const y = isRelative ? currentY.plus(args[5]) : args[5];
329
- lastControlX = x2; lastControlY = y2;
330
- currentX = x; currentY = y;
331
- result.push({ command: 'C', args: [x1, y1, x2, y2, x, y] });
332
- lastCommand = 'C';
333
- } else if (upperCmd === 'S') {
530
+ lastControlX = x2;
531
+ lastControlY = y2;
532
+ currentX = x;
533
+ currentY = y;
534
+ result.push({ command: "C", args: [x1, y1, x2, y2, x, y] });
535
+ lastCommand = "C";
536
+ } else if (upperCmd === "S") {
334
537
  // Smooth cubic Bezier: 4 args (x2, y2, x, y)
335
538
  // First control point is reflection of previous second control point
336
539
  let x1, y1;
337
- if (lastCommand === 'C' || lastCommand === 'S') {
540
+ if (lastCommand === "C" || lastCommand === "S") {
338
541
  x1 = currentX.mul(2).minus(lastControlX);
339
542
  y1 = currentY.mul(2).minus(lastControlY);
340
543
  } else {
@@ -345,25 +548,29 @@ export function pathToAbsolute(pathData) {
345
548
  const y2 = isRelative ? currentY.plus(args[1]) : args[1];
346
549
  const x = isRelative ? currentX.plus(args[2]) : args[2];
347
550
  const y = isRelative ? currentY.plus(args[3]) : args[3];
348
- lastControlX = x2; lastControlY = y2;
349
- currentX = x; currentY = y;
350
- result.push({ command: 'C', args: [x1, y1, x2, y2, x, y] });
351
- lastCommand = 'S';
352
- } else if (upperCmd === 'Q') {
551
+ lastControlX = x2;
552
+ lastControlY = y2;
553
+ currentX = x;
554
+ currentY = y;
555
+ result.push({ command: "C", args: [x1, y1, x2, y2, x, y] });
556
+ lastCommand = "S";
557
+ } else if (upperCmd === "Q") {
353
558
  // Quadratic Bezier: 4 args (x1, y1, x, y)
354
559
  const x1 = isRelative ? currentX.plus(args[0]) : args[0];
355
560
  const y1 = isRelative ? currentY.plus(args[1]) : args[1];
356
561
  const x = isRelative ? currentX.plus(args[2]) : args[2];
357
562
  const y = isRelative ? currentY.plus(args[3]) : args[3];
358
- lastControlX = x1; lastControlY = y1;
359
- currentX = x; currentY = y;
360
- result.push({ command: 'Q', args: [x1, y1, x, y] });
361
- lastCommand = 'Q';
362
- } else if (upperCmd === 'T') {
563
+ lastControlX = x1;
564
+ lastControlY = y1;
565
+ currentX = x;
566
+ currentY = y;
567
+ result.push({ command: "Q", args: [x1, y1, x, y] });
568
+ lastCommand = "Q";
569
+ } else if (upperCmd === "T") {
363
570
  // Smooth quadratic Bezier: 2 args (x, y)
364
571
  // Control point is reflection of previous control point
365
572
  let x1, y1;
366
- if (lastCommand === 'Q' || lastCommand === 'T') {
573
+ if (lastCommand === "Q" || lastCommand === "T") {
367
574
  x1 = currentX.mul(2).minus(lastControlX);
368
575
  y1 = currentY.mul(2).minus(lastControlY);
369
576
  } else {
@@ -372,20 +579,27 @@ export function pathToAbsolute(pathData) {
372
579
  }
373
580
  const x = isRelative ? currentX.plus(args[0]) : args[0];
374
581
  const y = isRelative ? currentY.plus(args[1]) : args[1];
375
- lastControlX = x1; lastControlY = y1;
376
- currentX = x; currentY = y;
377
- result.push({ command: 'Q', args: [x1, y1, x, y] });
378
- lastCommand = 'T';
379
- } else if (upperCmd === 'A') {
582
+ lastControlX = x1;
583
+ lastControlY = y1;
584
+ currentX = x;
585
+ currentY = y;
586
+ result.push({ command: "Q", args: [x1, y1, x, y] });
587
+ lastCommand = "T";
588
+ } else if (upperCmd === "A") {
380
589
  const x = isRelative ? currentX.plus(args[5]) : args[5];
381
590
  const y = isRelative ? currentY.plus(args[6]) : args[6];
382
- currentX = x; currentY = y;
383
- result.push({ command: 'A', args: [args[0], args[1], args[2], args[3], args[4], x, y] });
384
- lastCommand = 'A';
385
- } else if (upperCmd === 'Z') {
386
- currentX = subpathStartX; currentY = subpathStartY;
387
- result.push({ command: 'Z', args: [] });
388
- lastCommand = 'Z';
591
+ currentX = x;
592
+ currentY = y;
593
+ result.push({
594
+ command: "A",
595
+ args: [args[0], args[1], args[2], args[3], args[4], x, y],
596
+ });
597
+ lastCommand = "A";
598
+ } else if (upperCmd === "Z") {
599
+ currentX = subpathStartX;
600
+ currentY = subpathStartY;
601
+ result.push({ command: "Z", args: [] });
602
+ lastCommand = "Z";
389
603
  } else {
390
604
  result.push({ command, args });
391
605
  lastCommand = command;
@@ -394,9 +608,33 @@ export function pathToAbsolute(pathData) {
394
608
  return pathArrayToString(result);
395
609
  }
396
610
 
397
- export function transformArcParams(rx, ry, xAxisRotation, largeArc, sweep, endX, endY, matrix) {
398
- const rxD = D(rx), ryD = D(ry), rotD = D(xAxisRotation);
399
- const endXD = D(endX), endYD = D(endY);
611
+ /**
612
+ * Transform arc command parameters by an affine transformation matrix.
613
+ * @param {number|Decimal} rx - X radius
614
+ * @param {number|Decimal} ry - Y radius
615
+ * @param {number|Decimal} xAxisRotation - Rotation angle in degrees
616
+ * @param {number} largeArc - Large arc flag (0 or 1)
617
+ * @param {number} sweep - Sweep flag (0 or 1)
618
+ * @param {number|Decimal} endX - Arc end point X
619
+ * @param {number|Decimal} endY - Arc end point Y
620
+ * @param {Matrix} matrix - 3x3 transformation matrix
621
+ * @returns {Array} Transformed arc parameters [rx, ry, rotation, largeArc, sweep, endX, endY]
622
+ */
623
+ export function transformArcParams(
624
+ rx,
625
+ ry,
626
+ xAxisRotation,
627
+ largeArc,
628
+ sweep,
629
+ endX,
630
+ endY,
631
+ matrix,
632
+ ) {
633
+ const rxD = D(rx),
634
+ ryD = D(ry),
635
+ rotD = D(xAxisRotation);
636
+ const endXD = D(endX),
637
+ endYD = D(endY);
400
638
 
401
639
  // Transform the endpoint
402
640
  const endPoint = Matrix.from([[endXD], [endYD], [new Decimal(1)]]);
@@ -405,8 +643,10 @@ export function transformArcParams(rx, ry, xAxisRotation, largeArc, sweep, endX,
405
643
  const newEndY = transformedEnd.data[1][0].div(transformedEnd.data[2][0]);
406
644
 
407
645
  // Extract the 2x2 linear part of the affine transformation
408
- const a = matrix.data[0][0], b = matrix.data[0][1];
409
- const c = matrix.data[1][0], d = matrix.data[1][1];
646
+ const a = matrix.data[0][0],
647
+ b = matrix.data[0][1];
648
+ const c = matrix.data[1][0],
649
+ d = matrix.data[1][1];
410
650
 
411
651
  // Calculate determinant to check for reflection (flips sweep direction)
412
652
  const det = a.mul(d).minus(b.mul(c));
@@ -416,7 +656,8 @@ export function transformArcParams(rx, ry, xAxisRotation, largeArc, sweep, endX,
416
656
  // For an ellipse with rotation, we need to compute the transformed ellipse parameters
417
657
  // Convert rotation to radians
418
658
  const rotRad = rotD.mul(new Decimal(Math.PI)).div(180);
419
- const cosRot = Decimal.cos(rotRad), sinRot = Decimal.sin(rotRad);
659
+ const cosRot = Decimal.cos(rotRad),
660
+ sinRot = Decimal.sin(rotRad);
420
661
 
421
662
  // Unit ellipse basis vectors (before xAxisRotation)
422
663
  // The ellipse can be parameterized as: [rx*cos(t)*cos(rot) - ry*sin(t)*sin(rot), rx*cos(t)*sin(rot) + ry*sin(t)*cos(rot)]
@@ -448,30 +689,53 @@ export function transformArcParams(rx, ry, xAxisRotation, largeArc, sweep, endX,
448
689
  return [newRx, newRy, newRot, largeArc, newSweep, newEndX, newEndY];
449
690
  }
450
691
 
692
+ /**
693
+ * Transform path data by an affine transformation matrix.
694
+ * @param {string} pathData - SVG path data string
695
+ * @param {Matrix} matrix - 3x3 transformation matrix
696
+ * @param {number} precision - Decimal precision for output coordinates
697
+ * @returns {string} Transformed SVG path data
698
+ */
451
699
  export function transformPathData(pathData, matrix, precision = 6) {
452
700
  const absPath = pathToAbsolute(pathData);
453
701
  const commands = parsePathData(absPath);
454
702
  const result = [];
455
703
  for (const { command, args } of commands) {
456
- if (command === 'M' || command === 'L') {
704
+ if (command === "M" || command === "L") {
457
705
  const pt = Matrix.from([[args[0]], [args[1]], [new Decimal(1)]]);
458
706
  const transformed = matrix.mul(pt);
459
707
  const x = transformed.data[0][0].div(transformed.data[2][0]);
460
708
  const y = transformed.data[1][0].div(transformed.data[2][0]);
461
709
  result.push({ command, args: [x, y] });
462
- } else if (command === 'C') {
710
+ } else if (command === "C") {
463
711
  const transformedArgs = [];
464
712
  for (let i = 0; i < 6; i += 2) {
465
713
  const pt = Matrix.from([[args[i]], [args[i + 1]], [new Decimal(1)]]);
466
714
  const transformed = matrix.mul(pt);
467
- transformedArgs.push(transformed.data[0][0].div(transformed.data[2][0]));
468
- transformedArgs.push(transformed.data[1][0].div(transformed.data[2][0]));
715
+ transformedArgs.push(
716
+ transformed.data[0][0].div(transformed.data[2][0]),
717
+ );
718
+ transformedArgs.push(
719
+ transformed.data[1][0].div(transformed.data[2][0]),
720
+ );
469
721
  }
470
722
  result.push({ command, args: transformedArgs });
471
- } else if (command === 'A') {
723
+ } else if (command === "A") {
472
724
  const [newRx, newRy, newRot, newLarge, newSweep, newEndX, newEndY] =
473
- transformArcParams(args[0], args[1], args[2], args[3], args[4], args[5], args[6], matrix);
474
- result.push({ command, args: [newRx, newRy, newRot, newLarge, newSweep, newEndX, newEndY] });
725
+ transformArcParams(
726
+ args[0],
727
+ args[1],
728
+ args[2],
729
+ args[3],
730
+ args[4],
731
+ args[5],
732
+ args[6],
733
+ matrix,
734
+ );
735
+ result.push({
736
+ command,
737
+ args: [newRx, newRy, newRot, newLarge, newSweep, newEndX, newEndY],
738
+ });
475
739
  } else {
476
740
  result.push({ command, args });
477
741
  }
@@ -479,6 +743,16 @@ export function transformPathData(pathData, matrix, precision = 6) {
479
743
  return pathArrayToString(result, precision);
480
744
  }
481
745
 
746
+ /**
747
+ * Convert quadratic Bezier to cubic Bezier control points.
748
+ * @param {Decimal} x0 - Start point X
749
+ * @param {Decimal} y0 - Start point Y
750
+ * @param {Decimal} x1 - Control point X
751
+ * @param {Decimal} y1 - Control point Y
752
+ * @param {Decimal} x2 - End point X
753
+ * @param {Decimal} y2 - End point Y
754
+ * @returns {Array<Decimal>} Cubic Bezier control points [cp1x, cp1y, cp2x, cp2y, x2, y2]
755
+ */
482
756
  function quadraticToCubic(x0, y0, x1, y1, x2, y2) {
483
757
  const twoThirds = new Decimal(2).div(3);
484
758
  const cp1x = x0.plus(twoThirds.mul(x1.minus(x0)));
@@ -488,66 +762,86 @@ function quadraticToCubic(x0, y0, x1, y1, x2, y2) {
488
762
  return [cp1x, cp1y, cp2x, cp2y, x2, y2];
489
763
  }
490
764
 
765
+ /**
766
+ * Convert all path commands to cubic Bezier curves.
767
+ * @param {string} pathData - SVG path data string
768
+ * @returns {string} Path data with only M, C, and Z commands
769
+ */
491
770
  export function pathToCubics(pathData) {
492
771
  const absPath = pathToAbsolute(pathData);
493
772
  const commands = parsePathData(absPath);
494
773
  const result = [];
495
- let currentX = new Decimal(0), currentY = new Decimal(0);
496
- let lastControlX = new Decimal(0), lastControlY = new Decimal(0);
497
- let lastCommand = '';
774
+ let currentX = new Decimal(0),
775
+ currentY = new Decimal(0);
776
+ let lastControlX = new Decimal(0),
777
+ lastControlY = new Decimal(0);
778
+ let lastCommand = "";
498
779
  for (const { command, args } of commands) {
499
- if (command === 'M') {
500
- currentX = args[0]; currentY = args[1];
501
- result.push({ command: 'M', args: [currentX, currentY] });
502
- lastCommand = 'M';
503
- } else if (command === 'L') {
504
- const x = args[0], y = args[1];
505
- result.push({ command: 'C', args: [currentX, currentY, x, y, x, y] });
506
- currentX = x; currentY = y;
507
- lastCommand = 'L';
508
- } else if (command === 'C') {
780
+ if (command === "M") {
781
+ currentX = args[0];
782
+ currentY = args[1];
783
+ result.push({ command: "M", args: [currentX, currentY] });
784
+ lastCommand = "M";
785
+ } else if (command === "L") {
786
+ const x = args[0],
787
+ y = args[1];
788
+ result.push({ command: "C", args: [currentX, currentY, x, y, x, y] });
789
+ currentX = x;
790
+ currentY = y;
791
+ lastCommand = "L";
792
+ } else if (command === "C") {
509
793
  const [x1, y1, x2, y2, x, y] = args;
510
- result.push({ command: 'C', args: [x1, y1, x2, y2, x, y] });
511
- lastControlX = x2; lastControlY = y2;
512
- currentX = x; currentY = y;
513
- lastCommand = 'C';
514
- } else if (command === 'S') {
794
+ result.push({ command: "C", args: [x1, y1, x2, y2, x, y] });
795
+ lastControlX = x2;
796
+ lastControlY = y2;
797
+ currentX = x;
798
+ currentY = y;
799
+ lastCommand = "C";
800
+ } else if (command === "S") {
515
801
  let x1, y1;
516
- if (lastCommand === 'C' || lastCommand === 'S') {
802
+ if (lastCommand === "C" || lastCommand === "S") {
517
803
  x1 = currentX.mul(2).minus(lastControlX);
518
804
  y1 = currentY.mul(2).minus(lastControlY);
519
805
  } else {
520
- x1 = currentX; y1 = currentY;
806
+ x1 = currentX;
807
+ y1 = currentY;
521
808
  }
522
809
  const [x2, y2, x, y] = args;
523
- result.push({ command: 'C', args: [x1, y1, x2, y2, x, y] });
524
- lastControlX = x2; lastControlY = y2;
525
- currentX = x; currentY = y;
526
- lastCommand = 'S';
527
- } else if (command === 'Q') {
810
+ result.push({ command: "C", args: [x1, y1, x2, y2, x, y] });
811
+ lastControlX = x2;
812
+ lastControlY = y2;
813
+ currentX = x;
814
+ currentY = y;
815
+ lastCommand = "S";
816
+ } else if (command === "Q") {
528
817
  const [x1, y1, x, y] = args;
529
818
  const cubic = quadraticToCubic(currentX, currentY, x1, y1, x, y);
530
- result.push({ command: 'C', args: cubic });
531
- lastControlX = x1; lastControlY = y1;
532
- currentX = x; currentY = y;
533
- lastCommand = 'Q';
534
- } else if (command === 'T') {
819
+ result.push({ command: "C", args: cubic });
820
+ lastControlX = x1;
821
+ lastControlY = y1;
822
+ currentX = x;
823
+ currentY = y;
824
+ lastCommand = "Q";
825
+ } else if (command === "T") {
535
826
  let x1, y1;
536
- if (lastCommand === 'Q' || lastCommand === 'T') {
827
+ if (lastCommand === "Q" || lastCommand === "T") {
537
828
  x1 = currentX.mul(2).minus(lastControlX);
538
829
  y1 = currentY.mul(2).minus(lastControlY);
539
830
  } else {
540
- x1 = currentX; y1 = currentY;
831
+ x1 = currentX;
832
+ y1 = currentY;
541
833
  }
542
834
  const [x, y] = args;
543
835
  const cubic = quadraticToCubic(currentX, currentY, x1, y1, x, y);
544
- result.push({ command: 'C', args: cubic });
545
- lastControlX = x1; lastControlY = y1;
546
- currentX = x; currentY = y;
547
- lastCommand = 'T';
548
- } else if (command === 'Z') {
549
- result.push({ command: 'Z', args: [] });
550
- lastCommand = 'Z';
836
+ result.push({ command: "C", args: cubic });
837
+ lastControlX = x1;
838
+ lastControlY = y1;
839
+ currentX = x;
840
+ currentY = y;
841
+ lastCommand = "T";
842
+ } else if (command === "Z") {
843
+ result.push({ command: "Z", args: [] });
844
+ lastCommand = "Z";
551
845
  } else {
552
846
  result.push({ command, args });
553
847
  lastCommand = command;
@@ -556,6 +850,65 @@ export function pathToCubics(pathData) {
556
850
  return pathArrayToString(result);
557
851
  }
558
852
 
853
+ /**
854
+ * Convert SVG shape element to path element with equivalent path data.
855
+ * @param {Object} element - SVG shape element (circle, ellipse, rect, line, polyline, polygon)
856
+ * @param {number} precision - Decimal precision for output coordinates
857
+ * @returns {string|null} SVG path data string, or null if element type not supported
858
+ */
859
+ export function convertElementToPath(element, precision = 6) {
860
+ const getAttr = (name, defaultValue = 0) => {
861
+ const rawValue = element.getAttribute
862
+ ? element.getAttribute(name)
863
+ : element[name];
864
+ const value =
865
+ rawValue !== undefined && rawValue !== null ? rawValue : defaultValue;
866
+ // Strip CSS units before returning (handles px, em, %, etc.)
867
+ return stripUnits(value);
868
+ };
869
+ const tagName = (element.tagName || element.type || "").toLowerCase();
870
+ if (tagName === "circle") {
871
+ return circleToPathData(
872
+ getAttr("cx", 0),
873
+ getAttr("cy", 0),
874
+ getAttr("r", 0),
875
+ precision,
876
+ );
877
+ } else if (tagName === "ellipse") {
878
+ return ellipseToPathData(
879
+ getAttr("cx", 0),
880
+ getAttr("cy", 0),
881
+ getAttr("rx", 0),
882
+ getAttr("ry", 0),
883
+ precision,
884
+ );
885
+ } else if (tagName === "rect") {
886
+ return rectToPathData(
887
+ getAttr("x", 0),
888
+ getAttr("y", 0),
889
+ getAttr("width", 0),
890
+ getAttr("height", 0),
891
+ getAttr("rx", 0),
892
+ getAttr("ry", null),
893
+ false,
894
+ precision,
895
+ );
896
+ } else if (tagName === "line") {
897
+ return lineToPathData(
898
+ getAttr("x1", 0),
899
+ getAttr("y1", 0),
900
+ getAttr("x2", 0),
901
+ getAttr("y2", 0),
902
+ precision,
903
+ );
904
+ } else if (tagName === "polyline") {
905
+ return polylineToPathData(getAttr("points", ""), precision);
906
+ } else if (tagName === "polygon") {
907
+ return polygonToPathData(getAttr("points", ""), precision);
908
+ }
909
+ return null;
910
+ }
911
+
559
912
  /**
560
913
  * Strip CSS units from a value string (e.g., "100px" -> 100, "50%" -> 50, "2em" -> 2)
561
914
  * Returns the numeric value or 0 if parsing fails.
@@ -563,32 +916,8 @@ export function pathToCubics(pathData) {
563
916
  * @returns {number} Numeric value without units
564
917
  */
565
918
  function stripUnits(val) {
566
- if (typeof val === 'string') {
919
+ if (typeof val === "string") {
567
920
  return parseFloat(val) || 0;
568
921
  }
569
922
  return val;
570
923
  }
571
-
572
- export function convertElementToPath(element, precision = 6) {
573
- const getAttr = (name, defaultValue = 0) => {
574
- const rawValue = element.getAttribute ? element.getAttribute(name) : element[name];
575
- const value = rawValue !== undefined && rawValue !== null ? rawValue : defaultValue;
576
- // Strip CSS units before returning (handles px, em, %, etc.)
577
- return stripUnits(value);
578
- };
579
- const tagName = (element.tagName || element.type || '').toLowerCase();
580
- if (tagName === 'circle') {
581
- return circleToPathData(getAttr('cx', 0), getAttr('cy', 0), getAttr('r', 0), precision);
582
- } else if (tagName === 'ellipse') {
583
- return ellipseToPathData(getAttr('cx', 0), getAttr('cy', 0), getAttr('rx', 0), getAttr('ry', 0), precision);
584
- } else if (tagName === 'rect') {
585
- return rectToPathData(getAttr('x', 0), getAttr('y', 0), getAttr('width', 0), getAttr('height', 0), getAttr('rx', 0), getAttr('ry', null), false, precision);
586
- } else if (tagName === 'line') {
587
- return lineToPathData(getAttr('x1', 0), getAttr('y1', 0), getAttr('x2', 0), getAttr('y2', 0), precision);
588
- } else if (tagName === 'polyline') {
589
- return polylineToPathData(getAttr('points', ''), precision);
590
- } else if (tagName === 'polygon') {
591
- return polygonToPathData(getAttr('points', ''), precision);
592
- }
593
- return null;
594
- }