@emasoft/svg-matrix 1.3.2 → 1.3.4
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/dist/svg-matrix.global.min.js +2 -2
- package/dist/svg-matrix.min.js +2 -2
- package/dist/svg-toolbox.global.min.js +19 -19
- package/dist/svg-toolbox.min.js +19 -19
- package/dist/svgm.min.js +14 -14
- package/dist/version.json +8 -8
- package/package.json +1 -1
- package/src/index.js +2 -2
- package/src/marker-resolver.js +41 -18
- package/src/off-canvas-detection.js +18 -8
- package/src/svg-matrix-lib.js +2 -2
- package/src/svg-toolbox-lib.js +2 -2
- package/src/svgm-lib.js +2 -2
- package/src/transform-decomposition.js +9 -1
package/dist/version.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.3.
|
|
3
|
-
"buildTime": "2026-01-
|
|
2
|
+
"version": "1.3.4",
|
|
3
|
+
"buildTime": "2026-01-25T12:44:06.936Z",
|
|
4
4
|
"bundles": {
|
|
5
5
|
"esm": [
|
|
6
6
|
{
|
|
@@ -11,14 +11,14 @@
|
|
|
11
11
|
},
|
|
12
12
|
{
|
|
13
13
|
"name": "svg-toolbox.min.js",
|
|
14
|
-
"size":
|
|
15
|
-
"gzipSize":
|
|
14
|
+
"size": 577298,
|
|
15
|
+
"gzipSize": 152361,
|
|
16
16
|
"description": "ESM bundle for bundlers/Node.js (includes decimal.js)"
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
"name": "svgm.min.js",
|
|
20
|
-
"size":
|
|
21
|
-
"gzipSize":
|
|
20
|
+
"size": 602600,
|
|
21
|
+
"gzipSize": 159489,
|
|
22
22
|
"description": "ESM bundle for bundlers/Node.js (includes decimal.js)"
|
|
23
23
|
}
|
|
24
24
|
],
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
"name": "svg-toolbox.global.min.js",
|
|
34
|
-
"size":
|
|
35
|
-
"gzipSize":
|
|
34
|
+
"size": 577427,
|
|
35
|
+
"gzipSize": 151728,
|
|
36
36
|
"description": "IIFE bundle for browsers via <script> (includes decimal.js)"
|
|
37
37
|
},
|
|
38
38
|
{
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* SVG path conversion, and 2D/3D affine transformations using Decimal.js.
|
|
6
6
|
*
|
|
7
7
|
* @module @emasoft/svg-matrix
|
|
8
|
-
* @version 1.3.
|
|
8
|
+
* @version 1.3.4
|
|
9
9
|
* @license MIT
|
|
10
10
|
*
|
|
11
11
|
* @example
|
|
@@ -135,7 +135,7 @@ Decimal.set({ precision: 80 });
|
|
|
135
135
|
* Library version
|
|
136
136
|
* @constant {string}
|
|
137
137
|
*/
|
|
138
|
-
export const VERSION = "1.3.
|
|
138
|
+
export const VERSION = "1.3.4";
|
|
139
139
|
|
|
140
140
|
/**
|
|
141
141
|
* Default precision for path output (decimal places)
|
package/src/marker-resolver.js
CHANGED
|
@@ -378,6 +378,12 @@ export function getMarkerTransform(
|
|
|
378
378
|
}
|
|
379
379
|
|
|
380
380
|
// Step 4: Apply viewBox transformation if present
|
|
381
|
+
// BUG FIX: When viewBox has non-zero origin, the refX/refY are in viewBox coordinates.
|
|
382
|
+
// We must convert them to viewport coordinates before applying the ref translation.
|
|
383
|
+
// Transform chain: T_position * R * T(-ref_viewport) * S * T(-viewBox.origin)
|
|
384
|
+
let hasViewBox = false;
|
|
385
|
+
let viewBoxScaleFactor = 1;
|
|
386
|
+
|
|
381
387
|
if (viewBox) {
|
|
382
388
|
// Calculate scale factors to fit viewBox into marker viewport
|
|
383
389
|
const vbWidth = viewBox.width;
|
|
@@ -397,30 +403,47 @@ export function getMarkerTransform(
|
|
|
397
403
|
// Validate scale factors are finite
|
|
398
404
|
if (isFinite(scaleFactorX) && isFinite(scaleFactorY)) {
|
|
399
405
|
// For now, use uniform scaling (can be enhanced with full preserveAspectRatio parsing)
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
scaleX *= scaleFactor;
|
|
403
|
-
scaleY *= scaleFactor;
|
|
406
|
+
viewBoxScaleFactor = Math.min(scaleFactorX, scaleFactorY);
|
|
407
|
+
hasViewBox = true;
|
|
404
408
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
-viewBox.x,
|
|
408
|
-
-viewBox.y,
|
|
409
|
-
);
|
|
410
|
-
transform = transform.mul(viewBoxTranslate);
|
|
409
|
+
scaleX *= viewBoxScaleFactor;
|
|
410
|
+
scaleY *= viewBoxScaleFactor;
|
|
411
411
|
}
|
|
412
412
|
}
|
|
413
413
|
}
|
|
414
414
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
415
|
+
if (hasViewBox) {
|
|
416
|
+
// When viewBox is present, refX/refY are in viewBox coordinates.
|
|
417
|
+
// Convert to viewport (marker) coordinates by:
|
|
418
|
+
// 1. Subtract viewBox origin to get position relative to viewBox
|
|
419
|
+
// 2. Scale by viewBox scale factor to get position in marker viewport
|
|
420
|
+
const refViewportX = (refX - viewBox.x) * viewBoxScaleFactor;
|
|
421
|
+
const refViewportY = (refY - viewBox.y) * viewBoxScaleFactor;
|
|
422
|
+
|
|
423
|
+
// Apply ref translation in viewport coordinates (BEFORE scaling in the chain)
|
|
424
|
+
const refTranslate = Transforms2D.translation(-refViewportX, -refViewportY);
|
|
425
|
+
transform = transform.mul(refTranslate);
|
|
426
|
+
|
|
427
|
+
// Apply combined scaling
|
|
428
|
+
if (scaleX !== 1 || scaleY !== 1) {
|
|
429
|
+
const scale = Transforms2D.scale(scaleX, scaleY);
|
|
430
|
+
transform = transform.mul(scale);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Apply viewBox origin translation (AFTER scaling, so it scales correctly)
|
|
434
|
+
const viewBoxTranslate = Transforms2D.translation(-viewBox.x, -viewBox.y);
|
|
435
|
+
transform = transform.mul(viewBoxTranslate);
|
|
436
|
+
} else {
|
|
437
|
+
// No viewBox: apply scaling then ref translation in original coordinates
|
|
438
|
+
if (scaleX !== 1 || scaleY !== 1) {
|
|
439
|
+
const scale = Transforms2D.scale(scaleX, scaleY);
|
|
440
|
+
transform = transform.mul(scale);
|
|
441
|
+
}
|
|
420
442
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
443
|
+
// Step 5: Translate by -refX, -refY
|
|
444
|
+
const refTranslate = Transforms2D.translation(-refX, -refY);
|
|
445
|
+
transform = transform.mul(refTranslate);
|
|
446
|
+
}
|
|
424
447
|
|
|
425
448
|
return transform;
|
|
426
449
|
}
|
|
@@ -1564,8 +1564,10 @@ export function clipPathToViewBox(pathCommands, viewBox) {
|
|
|
1564
1564
|
continue;
|
|
1565
1565
|
}
|
|
1566
1566
|
|
|
1567
|
-
|
|
1568
|
-
const
|
|
1567
|
+
// BUG FIX: Handle relative coordinates (lowercase 'm')
|
|
1568
|
+
const isRelative = cmd.type === cmd.type.toLowerCase();
|
|
1569
|
+
const x = isRelative ? currentX.plus(D(cmd.x)) : D(cmd.x);
|
|
1570
|
+
const y = isRelative ? currentY.plus(D(cmd.y)) : D(cmd.y);
|
|
1569
1571
|
|
|
1570
1572
|
// Only add move if point is inside bounds
|
|
1571
1573
|
if (pointInBBox({ x, y }, bounds)) {
|
|
@@ -1587,8 +1589,10 @@ export function clipPathToViewBox(pathCommands, viewBox) {
|
|
|
1587
1589
|
continue;
|
|
1588
1590
|
}
|
|
1589
1591
|
|
|
1590
|
-
|
|
1591
|
-
const
|
|
1592
|
+
// BUG FIX: Handle relative coordinates (lowercase 'l')
|
|
1593
|
+
const isRelative = cmd.type === cmd.type.toLowerCase();
|
|
1594
|
+
const x = isRelative ? currentX.plus(D(cmd.x)) : D(cmd.x);
|
|
1595
|
+
const y = isRelative ? currentY.plus(D(cmd.y)) : D(cmd.y);
|
|
1592
1596
|
|
|
1593
1597
|
// Clip line segment
|
|
1594
1598
|
const clipped = clipLine(
|
|
@@ -1629,7 +1633,9 @@ export function clipPathToViewBox(pathCommands, viewBox) {
|
|
|
1629
1633
|
continue;
|
|
1630
1634
|
}
|
|
1631
1635
|
|
|
1632
|
-
|
|
1636
|
+
// BUG FIX: Handle relative coordinates (lowercase 'h')
|
|
1637
|
+
const isRelative = cmd.type === cmd.type.toLowerCase();
|
|
1638
|
+
const x = isRelative ? currentX.plus(D(cmd.x)) : D(cmd.x);
|
|
1633
1639
|
const clipped = clipLine(
|
|
1634
1640
|
{ x: currentX, y: currentY },
|
|
1635
1641
|
{ x, y: currentY },
|
|
@@ -1665,7 +1671,9 @@ export function clipPathToViewBox(pathCommands, viewBox) {
|
|
|
1665
1671
|
continue;
|
|
1666
1672
|
}
|
|
1667
1673
|
|
|
1668
|
-
|
|
1674
|
+
// BUG FIX: Handle relative coordinates (lowercase 'v')
|
|
1675
|
+
const isRelative = cmd.type === cmd.type.toLowerCase();
|
|
1676
|
+
const y = isRelative ? currentY.plus(D(cmd.y)) : D(cmd.y);
|
|
1669
1677
|
const clipped = clipLine(
|
|
1670
1678
|
{ x: currentX, y: currentY },
|
|
1671
1679
|
{ x: currentX, y },
|
|
@@ -1708,8 +1716,10 @@ export function clipPathToViewBox(pathCommands, viewBox) {
|
|
|
1708
1716
|
// BUG FIX: Sample the curve to better approximate clipping
|
|
1709
1717
|
// A full implementation would clip curves exactly, but sampling provides
|
|
1710
1718
|
// a reasonable approximation (WHY: curves can extend outside bounds even if endpoints are inside)
|
|
1711
|
-
|
|
1712
|
-
const
|
|
1719
|
+
// BUG FIX: Handle relative coordinates (lowercase 'c', 's', 'q', 't', 'a')
|
|
1720
|
+
const isRelative = cmd.type === cmd.type.toLowerCase();
|
|
1721
|
+
const x = isRelative ? currentX.plus(D(cmd.x)) : D(cmd.x);
|
|
1722
|
+
const y = isRelative ? currentY.plus(D(cmd.y)) : D(cmd.y);
|
|
1713
1723
|
|
|
1714
1724
|
// Sample 10 points along the curve path from current to end
|
|
1715
1725
|
const samples = 10;
|
package/src/svg-matrix-lib.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Works in both Node.js and browser environments.
|
|
6
6
|
*
|
|
7
7
|
* @module svg-matrix-lib
|
|
8
|
-
* @version 1.3.
|
|
8
|
+
* @version 1.3.4
|
|
9
9
|
* @license MIT
|
|
10
10
|
*
|
|
11
11
|
* @example Browser usage:
|
|
@@ -32,7 +32,7 @@ Decimal.set({ precision: 80 });
|
|
|
32
32
|
/**
|
|
33
33
|
* Library version
|
|
34
34
|
*/
|
|
35
|
-
export const VERSION = "1.3.
|
|
35
|
+
export const VERSION = "1.3.4";
|
|
36
36
|
|
|
37
37
|
// Export core classes
|
|
38
38
|
export { Decimal, Matrix, Vector };
|
package/src/svg-toolbox-lib.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Provides 69+ operations for cleaning, optimizing, and transforming SVG files.
|
|
6
6
|
*
|
|
7
7
|
* @module svg-toolbox-lib
|
|
8
|
-
* @version 1.3.
|
|
8
|
+
* @version 1.3.4
|
|
9
9
|
* @license MIT
|
|
10
10
|
*
|
|
11
11
|
* @example Browser usage:
|
|
@@ -34,7 +34,7 @@ import * as SVGToolboxModule from "./svg-toolbox.js";
|
|
|
34
34
|
/**
|
|
35
35
|
* Library version
|
|
36
36
|
*/
|
|
37
|
-
export const VERSION = "1.3.
|
|
37
|
+
export const VERSION = "1.3.4";
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
40
|
* Default export for browser global (window.SVGToolbox)
|
package/src/svgm-lib.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* comprehensive SVG manipulation (SVGToolbox). Works in Node.js and browser.
|
|
6
6
|
*
|
|
7
7
|
* @module svgm-lib
|
|
8
|
-
* @version 1.3.
|
|
8
|
+
* @version 1.3.4
|
|
9
9
|
* @license MIT
|
|
10
10
|
*
|
|
11
11
|
* @example Browser usage:
|
|
@@ -49,7 +49,7 @@ Decimal.set({ precision: 80 });
|
|
|
49
49
|
/**
|
|
50
50
|
* Library version
|
|
51
51
|
*/
|
|
52
|
-
export const VERSION = "1.3.
|
|
52
|
+
export const VERSION = "1.3.4";
|
|
53
53
|
|
|
54
54
|
// Export math classes
|
|
55
55
|
export { Decimal, Matrix, Vector };
|
|
@@ -294,7 +294,15 @@ export function decomposeMatrix(matrix) {
|
|
|
294
294
|
}
|
|
295
295
|
|
|
296
296
|
// Calculate rotation angle from first column
|
|
297
|
-
|
|
297
|
+
// BUG FIX: When scaleX is negative (reflection), we must compute rotation from negated
|
|
298
|
+
// column vector to separate the reflection component from the rotation component.
|
|
299
|
+
// Without this fix, X-flip matrix(-1,0,0,1,0,0) gives rotation=π instead of 0.
|
|
300
|
+
let rotation;
|
|
301
|
+
if (scaleX.lessThan(0)) {
|
|
302
|
+
rotation = Decimal.atan2(b.neg(), a.neg());
|
|
303
|
+
} else {
|
|
304
|
+
rotation = Decimal.atan2(b, a);
|
|
305
|
+
}
|
|
298
306
|
|
|
299
307
|
// Calculate skew
|
|
300
308
|
// After removing rotation and scale, we get the skew
|