@emasoft/svg-matrix 1.0.5 → 1.0.7

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,529 +1,535 @@
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
16
14
 
17
- ### npm
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
18
20
 
19
- ```bash
20
- npm install @emasoft/svg-matrix
21
- ```
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
22
27
 
23
- ```js
24
- import { Decimal, Matrix, Vector, Transforms2D, Transforms3D, SVGFlatten } from '@emasoft/svg-matrix';
25
- ```
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
26
33
 
27
- ### CDN (Browser)
34
+ ## Requirements
28
35
 
29
- Using esm.sh (recommended - auto-resolves dependencies):
36
+ - **Node.js 24.0.0** or higher (ES modules, modern JavaScript features)
37
+ - **Playwright** (optional) - for browser verification features
30
38
 
31
- ```html
32
- <script type="module">
33
- import { Decimal, Matrix, Vector, Transforms2D, Transforms3D, SVGFlatten } from 'https://esm.sh/@emasoft/svg-matrix';
39
+ ## Precision
34
40
 
35
- Decimal.set({ precision: 80 });
41
+ | Scenario | Float Error | This Library | Improvement |
42
+ |----------|-------------|--------------|-------------|
43
+ | GIS/CAD coordinates (1e6+ scale) | 1.69e-7 | 0 | 10^93x |
44
+ | 6-level SVG hierarchy | 1.14e-13 | 1e-77 | 10^64x |
45
+ | 1000 round-trip transforms | 5.41e-14 | 0 | 10^86x |
36
46
 
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));
47
+ **When precision matters:** GIS, CAD, scientific visualization, deep transform hierarchies, accumulated operations.
41
48
 
42
- // Apply to a point
43
- const [x, y] = Transforms2D.applyTransform(M, 1, 0);
44
- console.log('Transformed:', x.toString(), y.toString());
45
- </script>
49
+ **When floats suffice:** Simple transforms, small coordinates, visual applications where sub-pixel errors are imperceptible.
50
+
51
+ ```bash
52
+ node test/benchmark-precision.js
46
53
  ```
47
54
 
48
- Using Skypack:
55
+ ## Installation
49
56
 
50
- ```html
51
- <script type="module">
52
- import { Matrix, Vector } from 'https://cdn.skypack.dev/@emasoft/svg-matrix';
53
- </script>
57
+ ```bash
58
+ npm install @emasoft/svg-matrix
54
59
  ```
55
60
 
56
- Using import maps:
61
+ ```js
62
+ import { Matrix, Vector, Transforms2D, Transforms3D, SVGFlatten } from '@emasoft/svg-matrix';
63
+ ```
64
+
65
+ ### CDN
57
66
 
58
67
  ```html
59
- <script type="importmap">
60
- {
61
- "imports": {
62
- "@emasoft/svg-matrix": "https://esm.sh/@emasoft/svg-matrix"
63
- }
64
- }
65
- </script>
66
68
  <script type="module">
67
- import { Matrix, Vector, Transforms2D } from '@emasoft/svg-matrix';
69
+ import { Matrix, Vector, Transforms2D } from 'https://esm.sh/@emasoft/svg-matrix';
68
70
  </script>
69
71
  ```
70
72
 
71
- ## Usage Examples
73
+ ## CLI
72
74
 
73
- ### Vector Operations
75
+ The library includes a command-line interface for batch processing SVG files.
74
76
 
75
- ```js
76
- import { Vector, Decimal } from '@emasoft/svg-matrix';
77
+ ```bash
78
+ # Process single file
79
+ svg-matrix flatten input.svg -o output.svg
77
80
 
78
- Decimal.set({ precision: 80 });
81
+ # Batch process folder
82
+ svg-matrix flatten ./svgs/ -o ./output/
79
83
 
80
- const v = Vector.from([1, 2, 3]);
81
- const w = Vector.from([4, 5, 6]);
84
+ # Process files from list
85
+ svg-matrix flatten --list files.txt -o ./output/
86
+
87
+ # Convert shapes to paths
88
+ svg-matrix convert input.svg -o output.svg
89
+
90
+ # Normalize paths to cubic Beziers
91
+ svg-matrix normalize input.svg -o output.svg
82
92
 
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]
93
+ # Show SVG file info
94
+ svg-matrix info input.svg
88
95
 
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
96
+ # Show help
97
+ svg-matrix help
98
+ ```
99
+
100
+ ### CLI Options
101
+
102
+ | Option | Description |
103
+ |--------|-------------|
104
+ | `-o, --output <path>` | Output file or directory |
105
+ | `-l, --list <file>` | Read input files from text file |
106
+ | `-r, --recursive` | Process directories recursively |
107
+ | `-p, --precision <n>` | Decimal precision (default: 6) |
108
+ | `-f, --force` | Overwrite existing files |
109
+ | `-n, --dry-run` | Show what would be done |
110
+ | `-q, --quiet` | Suppress all output except errors |
111
+ | `-v, --verbose` | Enable verbose/debug output |
112
+ | `--log-file <path>` | Write log to file |
93
113
 
94
- // Projection
95
- const proj = v.projectOnto(w);
96
- console.log('Projection:', proj.toNumberArray());
114
+ ### File List Format
115
+
116
+ Create a text file with one path per line:
117
+
118
+ ```
119
+ # This is a comment
120
+ ./folder1/file1.svg
121
+ ./folder2/file2.svg
122
+ ./entire-folder/
97
123
  ```
98
124
 
99
- ### Matrix Operations
125
+ ## Quick Start
100
126
 
101
127
  ```js
102
- import { Matrix } from '@emasoft/svg-matrix';
128
+ import { Decimal, Matrix, Vector, Transforms2D, SVGFlatten } from '@emasoft/svg-matrix';
103
129
 
104
- const A = Matrix.from([[1, 2], [3, 4]]);
105
- const B = Matrix.from([[5, 6], [7, 8]]);
130
+ Decimal.set({ precision: 80 });
131
+
132
+ // Compose transforms (right-to-left: scale first, then rotate, then translate)
133
+ const M = Transforms2D.translation(10, 20)
134
+ .mul(Transforms2D.rotate(Math.PI / 4))
135
+ .mul(Transforms2D.scale(2));
136
+
137
+ // Apply to point
138
+ const [x, y] = Transforms2D.applyTransform(M, 1, 0);
106
139
 
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
140
+ // Round-trip with inverse
141
+ const [xBack, yBack] = Transforms2D.applyTransform(M.inverse(), x, y);
142
+ ```
143
+
144
+ ---
145
+
146
+ ## API Reference
112
147
 
113
- // Linear algebra
114
- const inv = A.inverse();
115
- console.log('Inverse:', inv.toNumberArray());
148
+ ### Linear Algebra
116
149
 
117
- // Solve Ax = b
118
- const x = A.solve([1, 1]);
119
- console.log('Solution:', x.toNumberArray());
150
+ #### Vector
120
151
 
121
- // Decompositions
122
- const { L, U, P } = A.lu();
123
- const { Q, R } = A.qr();
152
+ ```js
153
+ import { Vector } from '@emasoft/svg-matrix';
154
+
155
+ const v = Vector.from([1, 2, 3]);
156
+ const w = Vector.from([4, 5, 6]);
124
157
 
125
- // Matrix exponential
126
- const expA = A.exp();
158
+ v.add(w) // Element-wise addition
159
+ v.sub(w) // Element-wise subtraction
160
+ v.scale(2) // Scalar multiplication
161
+ v.dot(w) // Dot product → Decimal
162
+ v.cross(w) // Cross product (3D)
163
+ v.norm() // Euclidean length
164
+ v.normalize() // Unit vector
165
+ v.angleBetween(w) // Angle in radians
166
+ v.projectOnto(w) // Vector projection
167
+ v.orthogonal() // Perpendicular vector
168
+ v.distance(w) // Euclidean distance
169
+ v.toNumberArray() // [1, 2, 3]
127
170
  ```
128
171
 
129
- ### 2D Transforms
172
+ #### Matrix
130
173
 
131
174
  ```js
132
- import { Transforms2D } from '@emasoft/svg-matrix';
175
+ import { Matrix } from '@emasoft/svg-matrix';
133
176
 
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
177
+ const A = Matrix.from([[1, 2], [3, 4]]);
178
+ const I = Matrix.identity(3);
179
+ const Z = Matrix.zeros(2, 3);
180
+
181
+ A.add(B) // Element-wise addition
182
+ A.sub(B) // Element-wise subtraction
183
+ A.mul(B) // Matrix multiplication
184
+ A.transpose() // Transpose
185
+ A.trace() // Sum of diagonal
186
+ A.determinant() // Determinant
187
+ A.inverse() // Matrix inverse
188
+ A.solve([1, 1]) // Solve Ax = b
189
+ A.lu() // { L, U, P } decomposition
190
+ A.qr() // { Q, R } decomposition
191
+ A.exp() // Matrix exponential
192
+ A.applyToVector(v) // Matrix-vector product
193
+ ```
139
194
 
140
- // Rotation around a point
141
- const Rp = Transforms2D.rotateAroundPoint(Math.PI / 2, 5, 5);
195
+ ### Transforms
142
196
 
143
- // Skew and stretch
144
- const Sk = Transforms2D.skew(0.5, 0);
145
- const St = Transforms2D.stretchAlongAxis(1, 0, 2); // stretch 2x along X
197
+ #### 2D (3x3 matrices)
146
198
 
147
- // Reflections
148
- const Rx = Transforms2D.reflectX(); // flip Y
149
- const Ry = Transforms2D.reflectY(); // flip X
150
- const Ro = Transforms2D.reflectOrigin(); // flip both
199
+ ```js
200
+ import { Transforms2D } from '@emasoft/svg-matrix';
151
201
 
152
- // Compose transforms (right-to-left: S first, then R, then T)
153
- const M = T.mul(R).mul(S);
202
+ Transforms2D.translation(tx, ty)
203
+ Transforms2D.scale(sx, sy) // sy defaults to sx
204
+ Transforms2D.rotate(theta) // radians
205
+ Transforms2D.rotateAroundPoint(theta, px, py)
206
+ Transforms2D.skew(ax, ay)
207
+ Transforms2D.stretchAlongAxis(ux, uy, k)
208
+ Transforms2D.reflectX() // flip across X axis
209
+ Transforms2D.reflectY() // flip across Y axis
210
+ Transforms2D.reflectOrigin()
154
211
 
155
212
  // Apply to point
156
- const [x, y] = Transforms2D.applyTransform(M, 1, 0);
157
-
158
- // Inverse transform
159
- const Minv = M.inverse();
160
- const [xBack, yBack] = Transforms2D.applyTransform(Minv, x, y);
213
+ const [x, y] = Transforms2D.applyTransform(matrix, px, py);
161
214
  ```
162
215
 
163
- ### 3D Transforms
216
+ #### 3D (4x4 matrices)
164
217
 
165
218
  ```js
166
219
  import { Transforms3D } from '@emasoft/svg-matrix';
167
220
 
168
- // Basic transforms
169
- const T = Transforms3D.translation(1, 2, 3);
170
- const S = Transforms3D.scale(2, 2, 2);
221
+ Transforms3D.translation(tx, ty, tz)
222
+ Transforms3D.scale(sx, sy, sz)
223
+ Transforms3D.rotateX(theta)
224
+ Transforms3D.rotateY(theta)
225
+ Transforms3D.rotateZ(theta)
226
+ Transforms3D.rotateAroundAxis(ux, uy, uz, theta)
227
+ Transforms3D.rotateAroundPoint(ux, uy, uz, theta, px, py, pz)
228
+ Transforms3D.reflectXY() // flip Z
229
+ Transforms3D.reflectXZ() // flip Y
230
+ Transforms3D.reflectYZ() // flip X
231
+
232
+ const [x, y, z] = Transforms3D.applyTransform(matrix, px, py, pz);
233
+ ```
171
234
 
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);
235
+ ### SVG Processing
176
236
 
177
- // Rotation around arbitrary axis
178
- const Raxis = Transforms3D.rotateAroundAxis(1, 1, 0, Math.PI / 3);
237
+ #### SVGFlatten - Transform Parsing & CTM
179
238
 
180
- // Rotation around a point
181
- const Rpt = Transforms3D.rotateAroundPoint(0, 0, 1, Math.PI / 2, 5, 5, 0);
239
+ ```js
240
+ import { SVGFlatten } from '@emasoft/svg-matrix';
182
241
 
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
242
+ // Parse transform attributes
243
+ const m = SVGFlatten.parseTransformAttribute('translate(50,50) rotate(45) scale(2)');
188
244
 
189
- // Compose and apply
190
- const M = T.mul(Rx).mul(S);
191
- const [x, y, z] = Transforms3D.applyTransform(M, 1, 0, 0);
192
- ```
245
+ // Build CTM from transform stack
246
+ const ctm = SVGFlatten.buildCTM([
247
+ 'scale(1.5)',
248
+ 'translate(-13.6, -10.2)',
249
+ 'rotate(15)',
250
+ 'matrix(0.716, 0, 0, 1.397, 0, 0)'
251
+ ]);
193
252
 
194
- ## API Reference
253
+ // Apply to point
254
+ const { x, y } = SVGFlatten.applyToPoint(ctm, 10, 10);
195
255
 
196
- ### Vector
256
+ // Transform path data
257
+ const transformed = SVGFlatten.transformPathData('M 100 100 L 200 200', ctm);
197
258
 
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
- | **viewBox & Viewport** | |
282
- | `parseViewBox(str)` | Parse viewBox attribute "minX minY width height" |
283
- | `parsePreserveAspectRatio(str)` | Parse preserveAspectRatio (align, meet/slice) |
284
- | `computeViewBoxTransform(vb, vpW, vpH, par)` | Compute viewBox to viewport matrix |
285
- | `SVGViewport` class | Represents viewport with viewBox + preserveAspectRatio |
286
- | `buildFullCTM(hierarchy)` | Build CTM from SVG/group/element hierarchy |
287
- | **Units & Percentages** | |
288
- | `resolveLength(value, ref, dpi?)` | Resolve px, %, em, pt, in, cm, mm, pc units |
289
- | `resolvePercentages(x, y, vpW, vpH)` | Resolve x/y percentages to viewport |
290
- | `normalizedDiagonal(w, h)` | Compute sqrt(w^2+h^2)/sqrt(2) for percentages |
291
- | **Object Bounding Box** | |
292
- | `objectBoundingBoxTransform(x, y, w, h)` | Transform for objectBoundingBox units |
293
- | **Transform Parsing** | |
294
- | `parseTransformFunction(func, args)` | Parse a single SVG transform function |
295
- | `parseTransformAttribute(str)` | Parse a full SVG transform attribute string |
296
- | `buildCTM(transformStack)` | Build CTM from array of transform strings |
297
- | `applyToPoint(ctm, x, y)` | Apply CTM to a 2D point |
298
- | `toSVGMatrix(ctm, precision?)` | Convert CTM back to SVG matrix() notation |
299
- | `isIdentity(m, tolerance?)` | Check if matrix is effectively identity |
300
- | `transformPathData(pathD, ctm)` | Transform path data coordinates |
301
- | `PRECISION_INFO` | Object with precision comparison data |
302
-
303
- ## SVG Transform Flattening
304
-
305
- The `SVGFlatten` module provides tools for parsing SVG transform attributes, building CTMs (Current Transform Matrices), and flattening nested transforms with arbitrary precision.
306
-
307
- ### Why Use SVGFlatten?
308
-
309
- 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:
259
+ // viewBox handling
260
+ const viewBox = SVGFlatten.parseViewBox('0 0 100 100');
261
+ const par = SVGFlatten.parsePreserveAspectRatio('xMidYMid meet');
262
+ const vbTransform = SVGFlatten.computeViewBoxTransform(viewBox, 800, 600, par);
310
263
 
311
- ```
312
- Original coordinates: (10, 10)
313
- After round-trip (float): (9.9857, 9.9857) // Error: 0.0143
314
- ```
264
+ // Full CTM with viewBox + nested transforms
265
+ const fullCtm = SVGFlatten.buildFullCTM([
266
+ { type: 'svg', width: 800, height: 600, viewBox: '0 0 400 300' },
267
+ { type: 'g', transform: 'translate(50, 50)' },
268
+ { type: 'g', transform: 'rotate(45)' },
269
+ { type: 'element', transform: 'scale(2)' }
270
+ ]);
315
271
 
316
- With `@emasoft/svg-matrix` using 80-digit Decimal precision:
272
+ // Unit resolution (px, %, em, pt, in, cm, mm, pc)
273
+ SVGFlatten.resolveLength('50%', 800); // → 400
274
+ SVGFlatten.resolveLength('1in', 800); // → 96
317
275
 
276
+ // objectBoundingBox transform
277
+ const bboxTransform = SVGFlatten.objectBoundingBoxTransform(100, 50, 200, 100);
318
278
  ```
319
- Original coordinates: (10, 10)
320
- After round-trip: (10.00000000000000000000000000000000000000,
321
- 9.999999999999999999999999999999999999999999999999999999999999999999999999999999998)
322
- Round-trip error: X=0, Y=2e-79
323
- ```
324
-
325
- **Improvement: 10^77 times better precision than JavaScript floats.**
326
279
 
327
- ### Parsing SVG Transforms
280
+ #### GeometryToPath - Shape Conversion
328
281
 
329
282
  ```js
330
- import { SVGFlatten } from '@emasoft/svg-matrix';
283
+ import { GeometryToPath } from '@emasoft/svg-matrix';
284
+
285
+ // Shape to path
286
+ GeometryToPath.circleToPathData(cx, cy, r, precision)
287
+ GeometryToPath.ellipseToPathData(cx, cy, rx, ry, precision)
288
+ GeometryToPath.rectToPathData(x, y, w, h, rx, ry, useArcs, precision)
289
+ GeometryToPath.lineToPathData(x1, y1, x2, y2, precision)
290
+ GeometryToPath.polylineToPathData(points, precision)
291
+ GeometryToPath.polygonToPathData(points, precision)
292
+ GeometryToPath.convertElementToPath(element, precision)
293
+
294
+ // Path manipulation
295
+ GeometryToPath.parsePathData(pathData) // → [{command, args}]
296
+ GeometryToPath.pathArrayToString(commands) // → path string
297
+ GeometryToPath.pathToAbsolute(pathData) // relative → absolute
298
+ GeometryToPath.pathToCubics(pathData) // all → cubic Beziers
299
+ GeometryToPath.transformPathData(pathData, matrix, precision)
300
+
301
+ // Bezier kappa constant: 4*(sqrt(2)-1)/3
302
+ GeometryToPath.getKappa()
303
+ ```
331
304
 
332
- // Parse individual transforms
333
- const m1 = SVGFlatten.parseTransformAttribute('translate(10, 20)');
334
- const m2 = SVGFlatten.parseTransformAttribute('rotate(45)');
335
- const m3 = SVGFlatten.parseTransformAttribute('scale(2, 0.5)');
336
- const m4 = SVGFlatten.parseTransformAttribute('skewX(15)');
337
- const m5 = SVGFlatten.parseTransformAttribute('matrix(0.866, 0.5, -0.5, 0.866, 0, 0)');
305
+ #### BrowserVerify - Chrome Verification
338
306
 
339
- // Parse chained transforms
340
- const combined = SVGFlatten.parseTransformAttribute('translate(50,50) rotate(45) scale(2)');
307
+ ```js
308
+ import { BrowserVerify } from '@emasoft/svg-matrix';
309
+
310
+ // One-off verification
311
+ await BrowserVerify.verifyViewBox(800, 600, '0 0 400 300', 'xMidYMid meet');
312
+ await BrowserVerify.verifyTransform('rotate(45) translate(100, 50) scale(2)');
313
+
314
+ // Session-based verification
315
+ const verifier = new BrowserVerify.BrowserVerifier();
316
+ await verifier.init({ headless: true });
317
+ await verifier.verifyViewBoxTransform(800, 600, '0 0 100 100');
318
+ await verifier.verifyMatrix(ctm, { width: 100, height: 100, transform: '...' });
319
+ await verifier.verifyPointTransform(ctm, 10, 20, config);
320
+ await verifier.close();
321
+
322
+ // Standard test suite (28 tests including W3C issue #215 cases)
323
+ await BrowserVerify.runStandardTests({ verbose: true });
341
324
  ```
342
325
 
343
- ### Building CTM from Nested Elements
326
+ ### Polygon Operations
344
327
 
345
- ```js
346
- import { Decimal, SVGFlatten } from '@emasoft/svg-matrix';
328
+ #### PolygonClip
347
329
 
348
- Decimal.set({ precision: 80 });
330
+ ```js
331
+ import { PolygonClip } from '@emasoft/svg-matrix';
349
332
 
350
- // Simulate a 6-level SVG hierarchy:
351
- // <svg viewBox="..."> <!-- viewBox scaling -->
352
- // <g transform="translate(-13.6,-10.2)"> <!-- g1 -->
353
- // <g transform="translate(-1144.8,517.6)"> <!-- g2 -->
354
- // <g transform="rotate(15)"> <!-- g3 -->
355
- // <g transform="scale(1.2, 0.8)"> <!-- g4 -->
356
- // <path transform="matrix(...)"/> <!-- element -->
357
-
358
- const transformStack = [
359
- 'scale(1.5)', // viewBox scaling
360
- 'translate(-13.613145,-10.209854)', // g1
361
- 'translate(-1144.8563,517.64642)', // g2
362
- 'rotate(15)', // g3
363
- 'scale(1.2, 0.8)', // g4
364
- 'matrix(0.71577068,0,0,1.3970955,0,0)' // element
333
+ const square = [
334
+ PolygonClip.point(0, 0),
335
+ PolygonClip.point(2, 0),
336
+ PolygonClip.point(2, 2),
337
+ PolygonClip.point(0, 2)
365
338
  ];
366
339
 
367
- // Build combined CTM
368
- const ctm = SVGFlatten.buildCTM(transformStack);
340
+ // Boolean operations
341
+ PolygonClip.polygonIntersection(poly1, poly2)
342
+ PolygonClip.polygonUnion(poly1, poly2)
343
+ PolygonClip.polygonDifference(poly1, poly2)
369
344
 
370
- // Transform a point from local to viewport coordinates
371
- const local = { x: new Decimal('10'), y: new Decimal('10') };
372
- const viewport = SVGFlatten.applyToPoint(ctm, local.x, local.y);
345
+ // Properties
346
+ PolygonClip.polygonArea(polygon)
347
+ PolygonClip.isCounterClockwise(polygon)
348
+ PolygonClip.isConvex(polygon)
349
+ PolygonClip.pointInPolygon(point, polygon) // 1=inside, 0=boundary, -1=outside
373
350
 
374
- // Transform back to local coordinates
375
- const inverseCTM = ctm.inverse();
376
- const recovered = SVGFlatten.applyToPoint(inverseCTM, viewport.x, viewport.y);
351
+ // Convex hull
352
+ PolygonClip.convexHull(points)
377
353
 
378
- // Verify precision
379
- const errorX = recovered.x.minus(local.x).abs();
380
- const errorY = recovered.y.minus(local.y).abs();
381
- console.log('Round-trip error X:', errorX.toString()); // 0
382
- console.log('Round-trip error Y:', errorY.toString()); // ~2e-79
354
+ // Bounding box
355
+ PolygonClip.boundingBox(polygon) // {minX, minY, maxX, maxY}
356
+ PolygonClip.bboxIntersects(bbox1, bbox2)
383
357
  ```
384
358
 
385
- ### Transforming Path Data
359
+ #### ClipPathResolver
386
360
 
387
361
  ```js
388
- import { SVGFlatten } from '@emasoft/svg-matrix';
362
+ import { ClipPathResolver } from '@emasoft/svg-matrix';
389
363
 
390
- const pathD = 'M 100 100 L 200 100 L 200 200 L 100 200 Z';
391
- const ctm = SVGFlatten.parseTransformAttribute('translate(50, 50) scale(2)');
392
- const transformed = SVGFlatten.transformPathData(pathD, ctm);
364
+ // Parse and resolve clipPath
365
+ const clipData = ClipPathResolver.parseClipPathElement(element);
366
+ const clipPolygon = ClipPathResolver.resolveClipPath(clipData, targetBBox);
393
367
 
394
- console.log(transformed);
395
- // M 250.000000 250.000000 L 450.000000 250.000000 L 450.000000 450.000000 L 250.000000 450.000000 Z
368
+ // Shape to polygon
369
+ ClipPathResolver.shapeToPolygon({ type: 'circle', cx: 100, cy: 100, r: 50 }, { samples: 32 })
370
+ ClipPathResolver.pathToPolygon(pathData, { samples: 20 })
371
+ ClipPathResolver.polygonToPathData(polygon)
372
+
373
+ // Apply clipPath to element
374
+ ClipPathResolver.applyClipPath(elementData, clipPathData, targetBBox)
396
375
  ```
397
376
 
398
- ### Flattening All Transforms
377
+ ### SVG Element Resolution
399
378
 
400
- To flatten an SVG (remove all transform attributes and apply them directly to coordinates):
379
+ #### UseSymbolResolver
401
380
 
402
381
  ```js
403
- import { SVGFlatten } from '@emasoft/svg-matrix';
382
+ import { UseSymbolResolver } from '@emasoft/svg-matrix';
404
383
 
405
- // 1. For each element, collect transforms from root to element
406
- // 2. Build CTM: const ctm = SVGFlatten.buildCTM(transformStack);
407
- // 3. Transform path data: const newD = SVGFlatten.transformPathData(d, ctm);
408
- // 4. Remove transform attribute, update path d attribute
409
- // 5. Convert CTM back to SVG: SVGFlatten.toSVGMatrix(ctm, 6)
384
+ const useData = UseSymbolResolver.parseUseElement(useElement);
385
+ const symbolData = UseSymbolResolver.parseSymbolElement(symbolElement);
386
+ const resolved = UseSymbolResolver.resolveUse(useData, svgDocument);
387
+ const flattened = UseSymbolResolver.flattenResolvedUse(resolved);
388
+ UseSymbolResolver.resolveAllUses(svgDocument)
410
389
  ```
411
390
 
412
- ### viewBox and preserveAspectRatio
391
+ #### MarkerResolver
413
392
 
414
- Per the [SVG 2 specification](https://www.w3.org/TR/SVG2/coords.html), the viewBox establishes a new coordinate system that maps to the viewport:
393
+ ```js
394
+ import { MarkerResolver } from '@emasoft/svg-matrix';
395
+
396
+ const markerData = MarkerResolver.parseMarkerElement(markerElement);
397
+ const vertices = MarkerResolver.getPathVertices(pathData);
398
+ const transform = MarkerResolver.getMarkerTransform(markerData, vertex, angle, strokeWidth);
399
+ const instances = MarkerResolver.resolveMarkers(pathD, { 'marker-start': m1, 'marker-end': m2, strokeWidth: 2 });
400
+ MarkerResolver.markersToPathData(instances)
401
+ ```
402
+
403
+ #### PatternResolver
415
404
 
416
405
  ```js
417
- import { Decimal, SVGFlatten } from '@emasoft/svg-matrix';
406
+ import { PatternResolver } from '@emasoft/svg-matrix';
418
407
 
419
- Decimal.set({ precision: 80 });
408
+ const patternData = PatternResolver.parsePatternElement(patternElement);
409
+ const tiles = PatternResolver.resolvePattern(patternData, targetBBox);
410
+ PatternResolver.applyPattern(elementData, patternData, targetBBox)
411
+ PatternResolver.patternToClipPath(patternData, targetBBox)
412
+ PatternResolver.patternToPathData(patternData, targetBBox)
413
+ ```
420
414
 
421
- // Parse viewBox and preserveAspectRatio
422
- const viewBox = SVGFlatten.parseViewBox('0 0 100 100');
423
- const par = SVGFlatten.parsePreserveAspectRatio('xMidYMid meet');
415
+ #### MaskResolver
424
416
 
425
- // Compute the viewBox-to-viewport transform
426
- const vbTransform = SVGFlatten.computeViewBoxTransform(viewBox, 800, 600, par);
417
+ ```js
418
+ import { MaskResolver } from '@emasoft/svg-matrix';
427
419
 
428
- // Point (50, 50) in viewBox coords maps to viewport
429
- const point = SVGFlatten.applyToPoint(vbTransform, 50, 50);
430
- console.log('Viewport coords:', point.x.toFixed(2), point.y.toFixed(2));
420
+ const maskData = MaskResolver.parseMaskElement(maskElement);
421
+ const maskPolygon = MaskResolver.resolveMask(maskData, targetBBox);
422
+ MaskResolver.applyMask(elementPolygon, maskData, targetBBox)
423
+ MaskResolver.colorToLuminance({ r, g, b }) // sRGB luminance
431
424
  ```
432
425
 
433
- ### Full CTM with viewBox and Nested Elements
426
+ ### Advanced Features
434
427
 
435
- Use `buildFullCTM` for complete SVG hierarchies including viewBox transforms:
428
+ #### MeshGradient (SVG 2.0)
436
429
 
437
430
  ```js
438
- import { Decimal, SVGFlatten } from '@emasoft/svg-matrix';
431
+ import { MeshGradient } from '@emasoft/svg-matrix';
439
432
 
440
- Decimal.set({ precision: 80 });
433
+ // Parse mesh gradient
434
+ const meshDef = MeshGradient.parseMeshGradientElement(element);
435
+ const meshData = MeshGradient.parseMeshGradient(meshDef);
441
436
 
442
- // Real SVG structure with viewBox and nested transforms
443
- const hierarchy = [
444
- { type: 'svg', width: 800, height: 600, viewBox: '0 0 400 300', preserveAspectRatio: 'xMidYMid meet' },
445
- { type: 'g', transform: 'translate(50, 50)' },
446
- { type: 'g', transform: 'rotate(45)' },
447
- { type: 'element', transform: 'scale(2)' }
448
- ];
437
+ // Coons patch evaluation
438
+ const patch = new MeshGradient.CoonsPatch(topEdge, rightEdge, bottomEdge, leftEdge, cornerColors);
439
+ const { point, color } = patch.evaluate(u, v);
449
440
 
450
- const ctm = SVGFlatten.buildFullCTM(hierarchy);
451
- const local = { x: new Decimal('10'), y: new Decimal('10') };
452
- const viewport = SVGFlatten.applyToPoint(ctm, local.x, local.y);
441
+ // Rasterize to ImageData
442
+ const imageData = MeshGradient.rasterizeMeshGradient(meshData, width, height);
453
443
 
454
- // Round-trip with perfect precision
455
- const inverse = ctm.inverse();
456
- const recovered = SVGFlatten.applyToPoint(inverse, viewport.x, viewport.y);
457
- // Error: X=2e-78, Y=2e-78
458
- ```
444
+ // Convert to polygons for vector export
445
+ const polygons = MeshGradient.meshGradientToPolygons(meshData, { subdivisions: 16 });
459
446
 
460
- ### Nested SVG Viewports
447
+ // Clip and export
448
+ const clipped = MeshGradient.clipMeshGradient(meshData, clipPolygon, { subdivisions: 32 });
449
+ const svgPaths = MeshGradient.clippedMeshToSVG(clipped);
450
+ ```
461
451
 
462
- Handle deeply nested `<svg>` elements, each with its own viewBox:
452
+ #### TextToPath
463
453
 
464
454
  ```js
465
- const nestedHierarchy = [
466
- { type: 'svg', width: 1000, height: 800, viewBox: '0 0 500 400' },
467
- { type: 'g', transform: 'translate(100, 100)' },
468
- { type: 'svg', width: 200, height: 150, viewBox: '0 0 100 75' },
469
- { type: 'element' }
470
- ];
471
-
472
- const ctm = SVGFlatten.buildFullCTM(nestedHierarchy);
473
- // Point (10, 10) in innermost viewBox -> (240, 240) in outer viewport
455
+ import { TextToPath } from '@emasoft/svg-matrix';
456
+ import opentype from 'opentype.js';
457
+
458
+ const font = await opentype.load('font.ttf');
459
+
460
+ // Convert text to path
461
+ const pathData = TextToPath.textToPath("Hello", {
462
+ x: 100, y: 100,
463
+ fontSize: 24,
464
+ font: font,
465
+ textAnchor: TextToPath.TextAnchor.MIDDLE,
466
+ dominantBaseline: TextToPath.DominantBaseline.MIDDLE
467
+ });
468
+
469
+ // Parse text element
470
+ const textData = TextToPath.parseTextElement(textElement);
471
+ const result = TextToPath.textElementToPath(textData, { font });
472
+
473
+ // Measure text
474
+ const metrics = TextToPath.measureText("Hello", { fontSize: "20px" }, font);
475
+ const bbox = TextToPath.getTextBBox(textData);
474
476
  ```
475
477
 
476
- ### Unit and Percentage Resolution
478
+ ### Convenience Functions
477
479
 
478
- Resolve SVG length values with units or percentages:
480
+ Direct exports for common operations:
479
481
 
480
482
  ```js
481
- import { Decimal, SVGFlatten } from '@emasoft/svg-matrix';
483
+ import {
484
+ // Transforms
485
+ translate2D, rotate2D, scale2D, transform2D,
486
+ translate3D, scale3D, transform3D,
482
487
 
483
- const viewportWidth = new Decimal(800);
488
+ // Shape conversion
489
+ circleToPath, ellipseToPath, rectToPath, lineToPath,
490
+ polygonToPath, polylineToPath,
484
491
 
485
- // Percentages resolve relative to reference size
486
- SVGFlatten.resolveLength('50%', viewportWidth); // -> 400
487
- SVGFlatten.resolveLength('25%', viewportWidth); // -> 200
492
+ // Path manipulation
493
+ parsePath, pathToString, pathToAbsolute, pathToCubics, transformPath,
494
+ elementToPath,
488
495
 
489
- // Absolute units (at 96 DPI)
490
- SVGFlatten.resolveLength('1in', viewportWidth); // -> 96
491
- SVGFlatten.resolveLength('2.54cm', viewportWidth);// -> 96 (1 inch)
492
- SVGFlatten.resolveLength('72pt', viewportWidth); // -> 96 (1 inch)
493
- SVGFlatten.resolveLength('6pc', viewportWidth); // -> 96 (1 inch)
496
+ // Matrix/Vector creation
497
+ identity, zeros, vec, mat,
494
498
 
495
- // Font-relative units (assumes 16px base)
496
- SVGFlatten.resolveLength('2em', viewportWidth); // -> 32
499
+ // Precision control
500
+ setPrecision, getPrecision,
497
501
 
498
- // Normalized diagonal for non-directional percentages
499
- const diag = SVGFlatten.normalizedDiagonal(800, 600); // -> 707.1
502
+ // Constants
503
+ getKappa
504
+ } from '@emasoft/svg-matrix';
500
505
  ```
501
506
 
502
- ### Object Bounding Box Transform
507
+ ### Logging
503
508
 
504
- For gradients/patterns with `objectBoundingBox` units:
509
+ Control library logging output:
505
510
 
506
511
  ```js
507
- // Element bounding box: x=100, y=50, width=200, height=100
508
- const bboxTransform = SVGFlatten.objectBoundingBoxTransform(100, 50, 200, 100);
512
+ import { Logger, LogLevel, setLogLevel, enableFileLogging } from '@emasoft/svg-matrix';
509
513
 
510
- // (0, 0) in bbox -> (100, 50) in user space
511
- // (1, 1) in bbox -> (300, 150) in user space
512
- // (0.5, 0.5) in bbox -> (200, 100) in user space (center)
513
- ```
514
+ // Suppress all logging
515
+ setLogLevel(LogLevel.SILENT);
514
516
 
515
- ### CDN Usage
517
+ // Enable only errors
518
+ setLogLevel(LogLevel.ERROR);
516
519
 
517
- ```html
518
- <script type="module">
519
- import { Decimal, SVGFlatten } from 'https://esm.sh/@emasoft/svg-matrix';
520
+ // Enable warnings and errors (default)
521
+ setLogLevel(LogLevel.WARN);
520
522
 
521
- Decimal.set({ precision: 80 });
523
+ // Enable all logging including debug
524
+ setLogLevel(LogLevel.DEBUG);
522
525
 
523
- const ctm = SVGFlatten.parseTransformAttribute('rotate(45, 100, 100)');
524
- const point = SVGFlatten.applyToPoint(ctm, 50, 50);
525
- console.log('Transformed:', point.x.toFixed(6), point.y.toFixed(6));
526
- </script>
526
+ // Write logs to file
527
+ enableFileLogging('/path/to/log.txt');
528
+
529
+ // Direct Logger access
530
+ Logger.level = LogLevel.INFO;
531
+ Logger.warn('Custom warning');
532
+ Logger.debug('Debug info');
527
533
  ```
528
534
 
529
535
  ## License