@emasoft/svg-matrix 1.0.4 → 1.0.6

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/README.md CHANGED
@@ -1,413 +1,450 @@
1
1
  # @emasoft/svg-matrix
2
2
 
3
- Arbitrary-precision matrix, vector and affine transformation library for JavaScript using decimal.js.
3
+ High-precision matrix, vector, and SVG transformation library for JavaScript. Built on decimal.js for 80-digit precision arithmetic.
4
4
 
5
5
  ## Features
6
6
 
7
- - **Decimal-backed** Matrix and Vector classes for high-precision geometry
8
- - **2D transforms** (3x3 homogeneous matrices): translation, rotation, scale, skew, reflection
9
- - **3D transforms** (4x4 homogeneous matrices): translation, rotation (X/Y/Z axis), scale, reflection
10
- - **SVG transform flattening**: parse transform attributes, build CTMs, flatten nested hierarchies
11
- - **Linear algebra**: LU/QR decomposition, determinant, inverse, solve, matrix exponential
12
- - **10^77 times better precision** than JavaScript floats for round-trip transforms
13
- - Works in **Node.js** and **browsers** (via CDN)
7
+ **Linear Algebra**
8
+ - Matrix and Vector classes with full linear algebra operations
9
+ - LU/QR decomposition, determinant, inverse, solve, matrix exponential
14
10
 
15
- ## Installation
11
+ **Affine Transforms**
12
+ - 2D (3x3 homogeneous): translation, rotation, scale, skew, reflection
13
+ - 3D (4x4 homogeneous): translation, rotation (X/Y/Z/arbitrary axis), scale
14
+
15
+ **SVG Processing**
16
+ - Transform attribute parsing and CTM (Current Transform Matrix) building
17
+ - viewBox, preserveAspectRatio, nested viewports, unit resolution
18
+ - Shape-to-path conversion (circle, ellipse, rect, line, polygon, polyline)
19
+ - Path parsing, normalization (absolute, cubics), transformation
20
+
21
+ **SVG Element Resolution**
22
+ - ClipPath flattening to polygons
23
+ - Mask resolution (luminance and alpha)
24
+ - Pattern tiling expansion
25
+ - Use/symbol inlining with proper transforms
26
+ - Marker positioning and orientation
27
+
28
+ **Advanced**
29
+ - Polygon boolean operations (intersection, union, difference, convex hull)
30
+ - SVG 2.0 mesh gradient parsing and rasterization
31
+ - Text-to-path conversion with font support
32
+ - Browser verification against Chrome's native W3C SVG2 implementation
33
+
34
+ ## Precision
35
+
36
+ | Scenario | Float Error | This Library | Improvement |
37
+ |----------|-------------|--------------|-------------|
38
+ | GIS/CAD coordinates (1e6+ scale) | 1.69e-7 | 0 | 10^93x |
39
+ | 6-level SVG hierarchy | 1.14e-13 | 1e-77 | 10^64x |
40
+ | 1000 round-trip transforms | 5.41e-14 | 0 | 10^86x |
16
41
 
17
- ### npm
42
+ **When precision matters:** GIS, CAD, scientific visualization, deep transform hierarchies, accumulated operations.
43
+
44
+ **When floats suffice:** Simple transforms, small coordinates, visual applications where sub-pixel errors are imperceptible.
45
+
46
+ ```bash
47
+ node test/benchmark-precision.js
48
+ ```
49
+
50
+ ## Installation
18
51
 
19
52
  ```bash
20
53
  npm install @emasoft/svg-matrix
21
54
  ```
22
55
 
23
56
  ```js
24
- import { Decimal, Matrix, Vector, Transforms2D, Transforms3D, SVGFlatten } from '@emasoft/svg-matrix';
57
+ import { Matrix, Vector, Transforms2D, Transforms3D, SVGFlatten } from '@emasoft/svg-matrix';
25
58
  ```
26
59
 
27
- ### CDN (Browser)
28
-
29
- Using esm.sh (recommended - auto-resolves dependencies):
60
+ ### CDN
30
61
 
31
62
  ```html
32
63
  <script type="module">
33
- import { Decimal, Matrix, Vector, Transforms2D, Transforms3D, SVGFlatten } from 'https://esm.sh/@emasoft/svg-matrix';
64
+ import { Matrix, Vector, Transforms2D } from 'https://esm.sh/@emasoft/svg-matrix';
65
+ </script>
66
+ ```
34
67
 
35
- Decimal.set({ precision: 80 });
68
+ ## Quick Start
36
69
 
37
- // Create and compose 2D transforms
38
- const M = Transforms2D.translation(2, 3)
39
- .mul(Transforms2D.rotate(Math.PI / 4))
40
- .mul(Transforms2D.scale(1.5));
70
+ ```js
71
+ import { Decimal, Matrix, Vector, Transforms2D, SVGFlatten } from '@emasoft/svg-matrix';
41
72
 
42
- // Apply to a point
43
- const [x, y] = Transforms2D.applyTransform(M, 1, 0);
44
- console.log('Transformed:', x.toString(), y.toString());
45
- </script>
46
- ```
73
+ Decimal.set({ precision: 80 });
47
74
 
48
- Using Skypack:
75
+ // Compose transforms (right-to-left: scale first, then rotate, then translate)
76
+ const M = Transforms2D.translation(10, 20)
77
+ .mul(Transforms2D.rotate(Math.PI / 4))
78
+ .mul(Transforms2D.scale(2));
49
79
 
50
- ```html
51
- <script type="module">
52
- import { Matrix, Vector } from 'https://cdn.skypack.dev/@emasoft/svg-matrix';
53
- </script>
80
+ // Apply to point
81
+ const [x, y] = Transforms2D.applyTransform(M, 1, 0);
82
+
83
+ // Round-trip with inverse
84
+ const [xBack, yBack] = Transforms2D.applyTransform(M.inverse(), x, y);
54
85
  ```
55
86
 
56
- Using import maps:
87
+ ---
57
88
 
58
- ```html
59
- <script type="importmap">
60
- {
61
- "imports": {
62
- "@emasoft/svg-matrix": "https://esm.sh/@emasoft/svg-matrix"
63
- }
64
- }
65
- </script>
66
- <script type="module">
67
- import { Matrix, Vector, Transforms2D } from '@emasoft/svg-matrix';
68
- </script>
69
- ```
89
+ ## API Reference
70
90
 
71
- ## Usage Examples
91
+ ### Linear Algebra
72
92
 
73
- ### Vector Operations
93
+ #### Vector
74
94
 
75
95
  ```js
76
- import { Vector, Decimal } from '@emasoft/svg-matrix';
77
-
78
- Decimal.set({ precision: 80 });
96
+ import { Vector } from '@emasoft/svg-matrix';
79
97
 
80
98
  const v = Vector.from([1, 2, 3]);
81
99
  const w = Vector.from([4, 5, 6]);
82
100
 
83
- // Basic operations
84
- console.log('Add:', v.add(w).toNumberArray()); // [5, 7, 9]
85
- console.log('Scale:', v.scale(2).toNumberArray()); // [2, 4, 6]
86
- console.log('Dot:', v.dot(w).toString()); // 32
87
- console.log('Cross:', v.cross(w).toNumberArray()); // [-3, 6, -3]
88
-
89
- // Geometry
90
- console.log('Norm:', v.norm().toString()); // 3.7416...
91
- console.log('Normalized:', v.normalize().toNumberArray());
92
- console.log('Angle:', v.angleBetween(w).toString()); // radians
93
-
94
- // Projection
95
- const proj = v.projectOnto(w);
96
- console.log('Projection:', proj.toNumberArray());
101
+ v.add(w) // Element-wise addition
102
+ v.sub(w) // Element-wise subtraction
103
+ v.scale(2) // Scalar multiplication
104
+ v.dot(w) // Dot product → Decimal
105
+ v.cross(w) // Cross product (3D)
106
+ v.norm() // Euclidean length
107
+ v.normalize() // Unit vector
108
+ v.angleBetween(w) // Angle in radians
109
+ v.projectOnto(w) // Vector projection
110
+ v.orthogonal() // Perpendicular vector
111
+ v.distance(w) // Euclidean distance
112
+ v.toNumberArray() // [1, 2, 3]
97
113
  ```
98
114
 
99
- ### Matrix Operations
115
+ #### Matrix
100
116
 
101
117
  ```js
102
118
  import { Matrix } from '@emasoft/svg-matrix';
103
119
 
104
120
  const A = Matrix.from([[1, 2], [3, 4]]);
105
- const B = Matrix.from([[5, 6], [7, 8]]);
121
+ const I = Matrix.identity(3);
122
+ const Z = Matrix.zeros(2, 3);
123
+
124
+ A.add(B) // Element-wise addition
125
+ A.sub(B) // Element-wise subtraction
126
+ A.mul(B) // Matrix multiplication
127
+ A.transpose() // Transpose
128
+ A.trace() // Sum of diagonal
129
+ A.determinant() // Determinant
130
+ A.inverse() // Matrix inverse
131
+ A.solve([1, 1]) // Solve Ax = b
132
+ A.lu() // { L, U, P } decomposition
133
+ A.qr() // { Q, R } decomposition
134
+ A.exp() // Matrix exponential
135
+ A.applyToVector(v) // Matrix-vector product
136
+ ```
106
137
 
107
- // Basic operations
108
- console.log('Multiply:', A.mul(B).toNumberArray());
109
- console.log('Transpose:', A.transpose().toNumberArray());
110
- console.log('Determinant:', A.determinant().toString()); // -2
111
- console.log('Trace:', A.trace().toString()); // 5
138
+ ### Transforms
112
139
 
113
- // Linear algebra
114
- const inv = A.inverse();
115
- console.log('Inverse:', inv.toNumberArray());
140
+ #### 2D (3x3 matrices)
116
141
 
117
- // Solve Ax = b
118
- const x = A.solve([1, 1]);
119
- console.log('Solution:', x.toNumberArray());
142
+ ```js
143
+ import { Transforms2D } from '@emasoft/svg-matrix';
120
144
 
121
- // Decompositions
122
- const { L, U, P } = A.lu();
123
- const { Q, R } = A.qr();
145
+ Transforms2D.translation(tx, ty)
146
+ Transforms2D.scale(sx, sy) // sy defaults to sx
147
+ Transforms2D.rotate(theta) // radians
148
+ Transforms2D.rotateAroundPoint(theta, px, py)
149
+ Transforms2D.skew(ax, ay)
150
+ Transforms2D.stretchAlongAxis(ux, uy, k)
151
+ Transforms2D.reflectX() // flip across X axis
152
+ Transforms2D.reflectY() // flip across Y axis
153
+ Transforms2D.reflectOrigin()
124
154
 
125
- // Matrix exponential
126
- const expA = A.exp();
155
+ // Apply to point
156
+ const [x, y] = Transforms2D.applyTransform(matrix, px, py);
127
157
  ```
128
158
 
129
- ### 2D Transforms
159
+ #### 3D (4x4 matrices)
130
160
 
131
161
  ```js
132
- import { Transforms2D } from '@emasoft/svg-matrix';
162
+ import { Transforms3D } from '@emasoft/svg-matrix';
133
163
 
134
- // Basic transforms
135
- const T = Transforms2D.translation(10, 20);
136
- const R = Transforms2D.rotate(Math.PI / 4); // 45 degrees
137
- const S = Transforms2D.scale(2, 3); // non-uniform
138
- const Su = Transforms2D.scale(2); // uniform
164
+ Transforms3D.translation(tx, ty, tz)
165
+ Transforms3D.scale(sx, sy, sz)
166
+ Transforms3D.rotateX(theta)
167
+ Transforms3D.rotateY(theta)
168
+ Transforms3D.rotateZ(theta)
169
+ Transforms3D.rotateAroundAxis(ux, uy, uz, theta)
170
+ Transforms3D.rotateAroundPoint(ux, uy, uz, theta, px, py, pz)
171
+ Transforms3D.reflectXY() // flip Z
172
+ Transforms3D.reflectXZ() // flip Y
173
+ Transforms3D.reflectYZ() // flip X
174
+
175
+ const [x, y, z] = Transforms3D.applyTransform(matrix, px, py, pz);
176
+ ```
139
177
 
140
- // Rotation around a point
141
- const Rp = Transforms2D.rotateAroundPoint(Math.PI / 2, 5, 5);
178
+ ### SVG Processing
142
179
 
143
- // Skew and stretch
144
- const Sk = Transforms2D.skew(0.5, 0);
145
- const St = Transforms2D.stretchAlongAxis(1, 0, 2); // stretch 2x along X
180
+ #### SVGFlatten - Transform Parsing & CTM
146
181
 
147
- // Reflections
148
- const Rx = Transforms2D.reflectX(); // flip Y
149
- const Ry = Transforms2D.reflectY(); // flip X
150
- const Ro = Transforms2D.reflectOrigin(); // flip both
182
+ ```js
183
+ import { SVGFlatten } from '@emasoft/svg-matrix';
184
+
185
+ // Parse transform attributes
186
+ const m = SVGFlatten.parseTransformAttribute('translate(50,50) rotate(45) scale(2)');
151
187
 
152
- // Compose transforms (right-to-left: S first, then R, then T)
153
- const M = T.mul(R).mul(S);
188
+ // Build CTM from transform stack
189
+ const ctm = SVGFlatten.buildCTM([
190
+ 'scale(1.5)',
191
+ 'translate(-13.6, -10.2)',
192
+ 'rotate(15)',
193
+ 'matrix(0.716, 0, 0, 1.397, 0, 0)'
194
+ ]);
154
195
 
155
196
  // Apply to point
156
- const [x, y] = Transforms2D.applyTransform(M, 1, 0);
197
+ const { x, y } = SVGFlatten.applyToPoint(ctm, 10, 10);
198
+
199
+ // Transform path data
200
+ const transformed = SVGFlatten.transformPathData('M 100 100 L 200 200', ctm);
201
+
202
+ // viewBox handling
203
+ const viewBox = SVGFlatten.parseViewBox('0 0 100 100');
204
+ const par = SVGFlatten.parsePreserveAspectRatio('xMidYMid meet');
205
+ const vbTransform = SVGFlatten.computeViewBoxTransform(viewBox, 800, 600, par);
206
+
207
+ // Full CTM with viewBox + nested transforms
208
+ const fullCtm = SVGFlatten.buildFullCTM([
209
+ { type: 'svg', width: 800, height: 600, viewBox: '0 0 400 300' },
210
+ { type: 'g', transform: 'translate(50, 50)' },
211
+ { type: 'g', transform: 'rotate(45)' },
212
+ { type: 'element', transform: 'scale(2)' }
213
+ ]);
214
+
215
+ // Unit resolution (px, %, em, pt, in, cm, mm, pc)
216
+ SVGFlatten.resolveLength('50%', 800); // → 400
217
+ SVGFlatten.resolveLength('1in', 800); // → 96
218
+
219
+ // objectBoundingBox transform
220
+ const bboxTransform = SVGFlatten.objectBoundingBoxTransform(100, 50, 200, 100);
221
+ ```
222
+
223
+ #### GeometryToPath - Shape Conversion
157
224
 
158
- // Inverse transform
159
- const Minv = M.inverse();
160
- const [xBack, yBack] = Transforms2D.applyTransform(Minv, x, y);
225
+ ```js
226
+ import { GeometryToPath } from '@emasoft/svg-matrix';
227
+
228
+ // Shape to path
229
+ GeometryToPath.circleToPathData(cx, cy, r, precision)
230
+ GeometryToPath.ellipseToPathData(cx, cy, rx, ry, precision)
231
+ GeometryToPath.rectToPathData(x, y, w, h, rx, ry, useArcs, precision)
232
+ GeometryToPath.lineToPathData(x1, y1, x2, y2, precision)
233
+ GeometryToPath.polylineToPathData(points, precision)
234
+ GeometryToPath.polygonToPathData(points, precision)
235
+ GeometryToPath.convertElementToPath(element, precision)
236
+
237
+ // Path manipulation
238
+ GeometryToPath.parsePathData(pathData) // → [{command, args}]
239
+ GeometryToPath.pathArrayToString(commands) // → path string
240
+ GeometryToPath.pathToAbsolute(pathData) // relative → absolute
241
+ GeometryToPath.pathToCubics(pathData) // all → cubic Beziers
242
+ GeometryToPath.transformPathData(pathData, matrix, precision)
243
+
244
+ // Bezier kappa constant: 4*(sqrt(2)-1)/3
245
+ GeometryToPath.getKappa()
161
246
  ```
162
247
 
163
- ### 3D Transforms
248
+ #### BrowserVerify - Chrome Verification
164
249
 
165
250
  ```js
166
- import { Transforms3D } from '@emasoft/svg-matrix';
251
+ import { BrowserVerify } from '@emasoft/svg-matrix';
252
+
253
+ // One-off verification
254
+ await BrowserVerify.verifyViewBox(800, 600, '0 0 400 300', 'xMidYMid meet');
255
+ await BrowserVerify.verifyTransform('rotate(45) translate(100, 50) scale(2)');
256
+
257
+ // Session-based verification
258
+ const verifier = new BrowserVerify.BrowserVerifier();
259
+ await verifier.init({ headless: true });
260
+ await verifier.verifyViewBoxTransform(800, 600, '0 0 100 100');
261
+ await verifier.verifyMatrix(ctm, { width: 100, height: 100, transform: '...' });
262
+ await verifier.verifyPointTransform(ctm, 10, 20, config);
263
+ await verifier.close();
264
+
265
+ // Standard test suite (28 tests including W3C issue #215 cases)
266
+ await BrowserVerify.runStandardTests({ verbose: true });
267
+ ```
167
268
 
168
- // Basic transforms
169
- const T = Transforms3D.translation(1, 2, 3);
170
- const S = Transforms3D.scale(2, 2, 2);
269
+ ### Polygon Operations
171
270
 
172
- // Axis rotations (radians, right-hand rule)
173
- const Rx = Transforms3D.rotateX(Math.PI / 2);
174
- const Ry = Transforms3D.rotateY(Math.PI / 4);
175
- const Rz = Transforms3D.rotateZ(Math.PI / 6);
271
+ #### PolygonClip
176
272
 
177
- // Rotation around arbitrary axis
178
- const Raxis = Transforms3D.rotateAroundAxis(1, 1, 0, Math.PI / 3);
273
+ ```js
274
+ import { PolygonClip } from '@emasoft/svg-matrix';
179
275
 
180
- // Rotation around a point
181
- const Rpt = Transforms3D.rotateAroundPoint(0, 0, 1, Math.PI / 2, 5, 5, 0);
276
+ const square = [
277
+ PolygonClip.point(0, 0),
278
+ PolygonClip.point(2, 0),
279
+ PolygonClip.point(2, 2),
280
+ PolygonClip.point(0, 2)
281
+ ];
182
282
 
183
- // Reflections
184
- const RfXY = Transforms3D.reflectXY(); // flip Z
185
- const RfXZ = Transforms3D.reflectXZ(); // flip Y
186
- const RfYZ = Transforms3D.reflectYZ(); // flip X
187
- const RfO = Transforms3D.reflectOrigin(); // flip all
283
+ // Boolean operations
284
+ PolygonClip.polygonIntersection(poly1, poly2)
285
+ PolygonClip.polygonUnion(poly1, poly2)
286
+ PolygonClip.polygonDifference(poly1, poly2)
188
287
 
189
- // Compose and apply
190
- const M = T.mul(Rx).mul(S);
191
- const [x, y, z] = Transforms3D.applyTransform(M, 1, 0, 0);
288
+ // Properties
289
+ PolygonClip.polygonArea(polygon)
290
+ PolygonClip.isCounterClockwise(polygon)
291
+ PolygonClip.isConvex(polygon)
292
+ PolygonClip.pointInPolygon(point, polygon) // 1=inside, 0=boundary, -1=outside
293
+
294
+ // Convex hull
295
+ PolygonClip.convexHull(points)
296
+
297
+ // Bounding box
298
+ PolygonClip.boundingBox(polygon) // {minX, minY, maxX, maxY}
299
+ PolygonClip.bboxIntersects(bbox1, bbox2)
192
300
  ```
193
301
 
194
- ## API Reference
302
+ #### ClipPathResolver
195
303
 
196
- ### Vector
197
-
198
- | Method | Description |
199
- |--------|-------------|
200
- | `Vector.from(arr)` | Create vector from array |
201
- | `add(v)` | Element-wise addition |
202
- | `sub(v)` | Element-wise subtraction |
203
- | `scale(s)` | Scalar multiplication |
204
- | `negate()` | Negate all components |
205
- | `dot(v)` | Dot product |
206
- | `cross(v)` | Cross product (3D only) |
207
- | `outer(v)` | Outer product (returns 2D array) |
208
- | `norm()` | Euclidean length |
209
- | `normalize()` | Unit vector |
210
- | `angleBetween(v)` | Angle in radians |
211
- | `projectOnto(v)` | Vector projection |
212
- | `orthogonal()` | Perpendicular vector |
213
- | `distance(v)` | Euclidean distance |
214
- | `equals(v, tol?)` | Equality check with optional tolerance |
215
- | `toArray()` | Get Decimal array |
216
- | `toNumberArray()` | Get number array |
217
- | `toStringArray()` | Get string array |
218
-
219
- ### Matrix
220
-
221
- | Method | Description |
222
- |--------|-------------|
223
- | `Matrix.from(arr)` | Create from 2D array |
224
- | `Matrix.zeros(r, c)` | Zero matrix |
225
- | `Matrix.identity(n)` | Identity matrix |
226
- | `add(M)` | Element-wise addition |
227
- | `sub(M)` | Element-wise subtraction |
228
- | `mul(M)` | Matrix multiplication |
229
- | `div(s)` | Scalar division |
230
- | `negate()` | Negate all elements |
231
- | `transpose()` | Transpose |
232
- | `trace()` | Sum of diagonal |
233
- | `determinant()` | Determinant |
234
- | `inverse()` | Matrix inverse |
235
- | `solve(b)` | Solve Ax = b |
236
- | `lu()` | LU decomposition |
237
- | `qr()` | QR decomposition |
238
- | `exp()` | Matrix exponential |
239
- | `applyToVector(v)` | Matrix-vector product |
240
- | `equals(M, tol?)` | Equality check |
241
- | `isSquare()` | Check if square |
242
- | `toNumberArray()` | Get number 2D array |
243
- | `toArrayOfStrings()` | Get string 2D array |
244
-
245
- ### Transforms2D
246
-
247
- | Function | Description |
248
- |----------|-------------|
249
- | `translation(tx, ty)` | Translation matrix |
250
- | `scale(sx, sy?)` | Scale matrix (uniform if sy omitted) |
251
- | `rotate(theta)` | Rotation matrix (radians) |
252
- | `rotateAroundPoint(theta, px, py)` | Rotation around point |
253
- | `skew(ax, ay)` | Skew/shear matrix |
254
- | `stretchAlongAxis(ux, uy, k)` | Stretch along direction |
255
- | `reflectX()` | Reflect across X axis |
256
- | `reflectY()` | Reflect across Y axis |
257
- | `reflectOrigin()` | Reflect through origin |
258
- | `applyTransform(M, x, y)` | Apply matrix to point |
259
-
260
- ### Transforms3D
261
-
262
- | Function | Description |
263
- |----------|-------------|
264
- | `translation(tx, ty, tz)` | Translation matrix |
265
- | `scale(sx, sy?, sz?)` | Scale matrix |
266
- | `rotateX(theta)` | Rotation around X axis |
267
- | `rotateY(theta)` | Rotation around Y axis |
268
- | `rotateZ(theta)` | Rotation around Z axis |
269
- | `rotateAroundAxis(ux, uy, uz, theta)` | Rotation around arbitrary axis |
270
- | `rotateAroundPoint(ux, uy, uz, theta, px, py, pz)` | Rotation around point |
271
- | `reflectXY()` | Reflect across XY plane |
272
- | `reflectXZ()` | Reflect across XZ plane |
273
- | `reflectYZ()` | Reflect across YZ plane |
274
- | `reflectOrigin()` | Reflect through origin |
275
- | `applyTransform(M, x, y, z)` | Apply matrix to point |
276
-
277
- ### SVGFlatten
278
-
279
- | Function | Description |
280
- |----------|-------------|
281
- | `parseTransformFunction(func, args)` | Parse a single SVG transform function |
282
- | `parseTransformAttribute(str)` | Parse a full SVG transform attribute string |
283
- | `buildCTM(transformStack)` | Build CTM from array of transform strings |
284
- | `applyToPoint(ctm, x, y)` | Apply CTM to a 2D point |
285
- | `toSVGMatrix(ctm, precision?)` | Convert CTM back to SVG matrix() notation |
286
- | `isIdentity(m, tolerance?)` | Check if matrix is effectively identity |
287
- | `transformPathData(pathD, ctm)` | Transform path data coordinates |
288
- | `PRECISION_INFO` | Object with precision comparison data |
289
-
290
- ## SVG Transform Flattening
291
-
292
- The `SVGFlatten` module provides tools for parsing SVG transform attributes, building CTMs (Current Transform Matrices), and flattening nested transforms with arbitrary precision.
293
-
294
- ### Why Use SVGFlatten?
295
-
296
- SVG elements can have deeply nested transforms through parent groups. When coordinates are transformed from local space to viewport and back using JavaScript's native 64-bit floats, precision is lost:
304
+ ```js
305
+ import { ClipPathResolver } from '@emasoft/svg-matrix';
297
306
 
298
- ```
299
- Original coordinates: (10, 10)
300
- After round-trip (float): (9.9857, 9.9857) // Error: 0.0143
301
- ```
307
+ // Parse and resolve clipPath
308
+ const clipData = ClipPathResolver.parseClipPathElement(element);
309
+ const clipPolygon = ClipPathResolver.resolveClipPath(clipData, targetBBox);
302
310
 
303
- With `@emasoft/svg-matrix` using 80-digit Decimal precision:
311
+ // Shape to polygon
312
+ ClipPathResolver.shapeToPolygon({ type: 'circle', cx: 100, cy: 100, r: 50 }, { samples: 32 })
313
+ ClipPathResolver.pathToPolygon(pathData, { samples: 20 })
314
+ ClipPathResolver.polygonToPathData(polygon)
304
315
 
305
- ```
306
- Original coordinates: (10, 10)
307
- After round-trip: (10.00000000000000000000000000000000000000,
308
- 9.999999999999999999999999999999999999999999999999999999999999999999999999999999998)
309
- Round-trip error: X=0, Y=2e-79
316
+ // Apply clipPath to element
317
+ ClipPathResolver.applyClipPath(elementData, clipPathData, targetBBox)
310
318
  ```
311
319
 
312
- **Improvement: 10^77 times better precision than JavaScript floats.**
320
+ ### SVG Element Resolution
313
321
 
314
- ### Parsing SVG Transforms
322
+ #### UseSymbolResolver
315
323
 
316
324
  ```js
317
- import { SVGFlatten } from '@emasoft/svg-matrix';
325
+ import { UseSymbolResolver } from '@emasoft/svg-matrix';
318
326
 
319
- // Parse individual transforms
320
- const m1 = SVGFlatten.parseTransformAttribute('translate(10, 20)');
321
- const m2 = SVGFlatten.parseTransformAttribute('rotate(45)');
322
- const m3 = SVGFlatten.parseTransformAttribute('scale(2, 0.5)');
323
- const m4 = SVGFlatten.parseTransformAttribute('skewX(15)');
324
- const m5 = SVGFlatten.parseTransformAttribute('matrix(0.866, 0.5, -0.5, 0.866, 0, 0)');
325
-
326
- // Parse chained transforms
327
- const combined = SVGFlatten.parseTransformAttribute('translate(50,50) rotate(45) scale(2)');
327
+ const useData = UseSymbolResolver.parseUseElement(useElement);
328
+ const symbolData = UseSymbolResolver.parseSymbolElement(symbolElement);
329
+ const resolved = UseSymbolResolver.resolveUse(useData, svgDocument);
330
+ const flattened = UseSymbolResolver.flattenResolvedUse(resolved);
331
+ UseSymbolResolver.resolveAllUses(svgDocument)
328
332
  ```
329
333
 
330
- ### Building CTM from Nested Elements
334
+ #### MarkerResolver
331
335
 
332
336
  ```js
333
- import { Decimal, SVGFlatten } from '@emasoft/svg-matrix';
337
+ import { MarkerResolver } from '@emasoft/svg-matrix';
334
338
 
335
- Decimal.set({ precision: 80 });
339
+ const markerData = MarkerResolver.parseMarkerElement(markerElement);
340
+ const vertices = MarkerResolver.getPathVertices(pathData);
341
+ const transform = MarkerResolver.getMarkerTransform(markerData, vertex, angle, strokeWidth);
342
+ const instances = MarkerResolver.resolveMarkers(pathD, { 'marker-start': m1, 'marker-end': m2, strokeWidth: 2 });
343
+ MarkerResolver.markersToPathData(instances)
344
+ ```
336
345
 
337
- // Simulate a 6-level SVG hierarchy:
338
- // <svg viewBox="..."> <!-- viewBox scaling -->
339
- // <g transform="translate(-13.6,-10.2)"> <!-- g1 -->
340
- // <g transform="translate(-1144.8,517.6)"> <!-- g2 -->
341
- // <g transform="rotate(15)"> <!-- g3 -->
342
- // <g transform="scale(1.2, 0.8)"> <!-- g4 -->
343
- // <path transform="matrix(...)"/> <!-- element -->
344
-
345
- const transformStack = [
346
- 'scale(1.5)', // viewBox scaling
347
- 'translate(-13.613145,-10.209854)', // g1
348
- 'translate(-1144.8563,517.64642)', // g2
349
- 'rotate(15)', // g3
350
- 'scale(1.2, 0.8)', // g4
351
- 'matrix(0.71577068,0,0,1.3970955,0,0)' // element
352
- ];
346
+ #### PatternResolver
353
347
 
354
- // Build combined CTM
355
- const ctm = SVGFlatten.buildCTM(transformStack);
348
+ ```js
349
+ import { PatternResolver } from '@emasoft/svg-matrix';
350
+
351
+ const patternData = PatternResolver.parsePatternElement(patternElement);
352
+ const tiles = PatternResolver.resolvePattern(patternData, targetBBox);
353
+ PatternResolver.applyPattern(elementData, patternData, targetBBox)
354
+ PatternResolver.patternToClipPath(patternData, targetBBox)
355
+ PatternResolver.patternToPathData(patternData, targetBBox)
356
+ ```
356
357
 
357
- // Transform a point from local to viewport coordinates
358
- const local = { x: new Decimal('10'), y: new Decimal('10') };
359
- const viewport = SVGFlatten.applyToPoint(ctm, local.x, local.y);
358
+ #### MaskResolver
360
359
 
361
- // Transform back to local coordinates
362
- const inverseCTM = ctm.inverse();
363
- const recovered = SVGFlatten.applyToPoint(inverseCTM, viewport.x, viewport.y);
360
+ ```js
361
+ import { MaskResolver } from '@emasoft/svg-matrix';
364
362
 
365
- // Verify precision
366
- const errorX = recovered.x.minus(local.x).abs();
367
- const errorY = recovered.y.minus(local.y).abs();
368
- console.log('Round-trip error X:', errorX.toString()); // 0
369
- console.log('Round-trip error Y:', errorY.toString()); // ~2e-79
363
+ const maskData = MaskResolver.parseMaskElement(maskElement);
364
+ const maskPolygon = MaskResolver.resolveMask(maskData, targetBBox);
365
+ MaskResolver.applyMask(elementPolygon, maskData, targetBBox)
366
+ MaskResolver.colorToLuminance({ r, g, b }) // sRGB luminance
370
367
  ```
371
368
 
372
- ### Transforming Path Data
369
+ ### Advanced Features
370
+
371
+ #### MeshGradient (SVG 2.0)
373
372
 
374
373
  ```js
375
- import { SVGFlatten } from '@emasoft/svg-matrix';
374
+ import { MeshGradient } from '@emasoft/svg-matrix';
375
+
376
+ // Parse mesh gradient
377
+ const meshDef = MeshGradient.parseMeshGradientElement(element);
378
+ const meshData = MeshGradient.parseMeshGradient(meshDef);
379
+
380
+ // Coons patch evaluation
381
+ const patch = new MeshGradient.CoonsPatch(topEdge, rightEdge, bottomEdge, leftEdge, cornerColors);
382
+ const { point, color } = patch.evaluate(u, v);
376
383
 
377
- const pathD = 'M 100 100 L 200 100 L 200 200 L 100 200 Z';
378
- const ctm = SVGFlatten.parseTransformAttribute('translate(50, 50) scale(2)');
379
- const transformed = SVGFlatten.transformPathData(pathD, ctm);
384
+ // Rasterize to ImageData
385
+ const imageData = MeshGradient.rasterizeMeshGradient(meshData, width, height);
380
386
 
381
- console.log(transformed);
382
- // M 250.000000 250.000000 L 450.000000 250.000000 L 450.000000 450.000000 L 250.000000 450.000000 Z
387
+ // Convert to polygons for vector export
388
+ const polygons = MeshGradient.meshGradientToPolygons(meshData, { subdivisions: 16 });
389
+
390
+ // Clip and export
391
+ const clipped = MeshGradient.clipMeshGradient(meshData, clipPolygon, { subdivisions: 32 });
392
+ const svgPaths = MeshGradient.clippedMeshToSVG(clipped);
393
+ ```
394
+
395
+ #### TextToPath
396
+
397
+ ```js
398
+ import { TextToPath } from '@emasoft/svg-matrix';
399
+ import opentype from 'opentype.js';
400
+
401
+ const font = await opentype.load('font.ttf');
402
+
403
+ // Convert text to path
404
+ const pathData = TextToPath.textToPath("Hello", {
405
+ x: 100, y: 100,
406
+ fontSize: 24,
407
+ font: font,
408
+ textAnchor: TextToPath.TextAnchor.MIDDLE,
409
+ dominantBaseline: TextToPath.DominantBaseline.MIDDLE
410
+ });
411
+
412
+ // Parse text element
413
+ const textData = TextToPath.parseTextElement(textElement);
414
+ const result = TextToPath.textElementToPath(textData, { font });
415
+
416
+ // Measure text
417
+ const metrics = TextToPath.measureText("Hello", { fontSize: "20px" }, font);
418
+ const bbox = TextToPath.getTextBBox(textData);
383
419
  ```
384
420
 
385
- ### Flattening All Transforms
421
+ ### Convenience Functions
386
422
 
387
- To flatten an SVG (remove all transform attributes and apply them directly to coordinates):
423
+ Direct exports for common operations:
388
424
 
389
425
  ```js
390
- import { SVGFlatten } from '@emasoft/svg-matrix';
426
+ import {
427
+ // Transforms
428
+ translate2D, rotate2D, scale2D, transform2D,
429
+ translate3D, scale3D, transform3D,
391
430
 
392
- // 1. For each element, collect transforms from root to element
393
- // 2. Build CTM: const ctm = SVGFlatten.buildCTM(transformStack);
394
- // 3. Transform path data: const newD = SVGFlatten.transformPathData(d, ctm);
395
- // 4. Remove transform attribute, update path d attribute
396
- // 5. Convert CTM back to SVG: SVGFlatten.toSVGMatrix(ctm, 6)
397
- ```
431
+ // Shape conversion
432
+ circleToPath, ellipseToPath, rectToPath, lineToPath,
433
+ polygonToPath, polylineToPath,
398
434
 
399
- ### CDN Usage
435
+ // Path manipulation
436
+ parsePath, pathToString, pathToAbsolute, pathToCubics, transformPath,
437
+ elementToPath,
400
438
 
401
- ```html
402
- <script type="module">
403
- import { Decimal, SVGFlatten } from 'https://esm.sh/@emasoft/svg-matrix';
439
+ // Matrix/Vector creation
440
+ identity, zeros, vec, mat,
404
441
 
405
- Decimal.set({ precision: 80 });
442
+ // Precision control
443
+ setPrecision, getPrecision,
406
444
 
407
- const ctm = SVGFlatten.parseTransformAttribute('rotate(45, 100, 100)');
408
- const point = SVGFlatten.applyToPoint(ctm, 50, 50);
409
- console.log('Transformed:', point.x.toFixed(6), point.y.toFixed(6));
410
- </script>
445
+ // Constants
446
+ getKappa
447
+ } from '@emasoft/svg-matrix';
411
448
  ```
412
449
 
413
450
  ## License