@emasoft/svg-matrix 1.0.5 → 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,529 +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
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
+ ## Precision
28
35
 
29
- Using esm.sh (recommended - auto-resolves dependencies):
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 |
30
41
 
31
- ```html
32
- <script type="module">
33
- import { Decimal, Matrix, Vector, Transforms2D, Transforms3D, SVGFlatten } from 'https://esm.sh/@emasoft/svg-matrix';
34
-
35
- Decimal.set({ precision: 80 });
42
+ **When precision matters:** GIS, CAD, scientific visualization, deep transform hierarchies, accumulated operations.
36
43
 
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));
44
+ **When floats suffice:** Simple transforms, small coordinates, visual applications where sub-pixel errors are imperceptible.
41
45
 
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
+ ```bash
47
+ node test/benchmark-precision.js
46
48
  ```
47
49
 
48
- Using Skypack:
50
+ ## Installation
49
51
 
50
- ```html
51
- <script type="module">
52
- import { Matrix, Vector } from 'https://cdn.skypack.dev/@emasoft/svg-matrix';
53
- </script>
52
+ ```bash
53
+ npm install @emasoft/svg-matrix
54
+ ```
55
+
56
+ ```js
57
+ import { Matrix, Vector, Transforms2D, Transforms3D, SVGFlatten } from '@emasoft/svg-matrix';
54
58
  ```
55
59
 
56
- Using import maps:
60
+ ### CDN
57
61
 
58
62
  ```html
59
- <script type="importmap">
60
- {
61
- "imports": {
62
- "@emasoft/svg-matrix": "https://esm.sh/@emasoft/svg-matrix"
63
- }
64
- }
65
- </script>
66
63
  <script type="module">
67
- import { Matrix, Vector, Transforms2D } from '@emasoft/svg-matrix';
64
+ import { Matrix, Vector, Transforms2D } from 'https://esm.sh/@emasoft/svg-matrix';
68
65
  </script>
69
66
  ```
70
67
 
71
- ## Usage Examples
72
-
73
- ### Vector Operations
68
+ ## Quick Start
74
69
 
75
70
  ```js
76
- import { Vector, Decimal } from '@emasoft/svg-matrix';
71
+ import { Decimal, Matrix, Vector, Transforms2D, SVGFlatten } from '@emasoft/svg-matrix';
77
72
 
78
73
  Decimal.set({ precision: 80 });
79
74
 
80
- const v = Vector.from([1, 2, 3]);
81
- const w = Vector.from([4, 5, 6]);
82
-
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]
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));
88
79
 
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
80
+ // Apply to point
81
+ const [x, y] = Transforms2D.applyTransform(M, 1, 0);
93
82
 
94
- // Projection
95
- const proj = v.projectOnto(w);
96
- console.log('Projection:', proj.toNumberArray());
83
+ // Round-trip with inverse
84
+ const [xBack, yBack] = Transforms2D.applyTransform(M.inverse(), x, y);
97
85
  ```
98
86
 
99
- ### Matrix Operations
100
-
101
- ```js
102
- import { Matrix } from '@emasoft/svg-matrix';
87
+ ---
103
88
 
104
- const A = Matrix.from([[1, 2], [3, 4]]);
105
- const B = Matrix.from([[5, 6], [7, 8]]);
89
+ ## API Reference
106
90
 
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
91
+ ### Linear Algebra
112
92
 
113
- // Linear algebra
114
- const inv = A.inverse();
115
- console.log('Inverse:', inv.toNumberArray());
93
+ #### Vector
116
94
 
117
- // Solve Ax = b
118
- const x = A.solve([1, 1]);
119
- console.log('Solution:', x.toNumberArray());
95
+ ```js
96
+ import { Vector } from '@emasoft/svg-matrix';
120
97
 
121
- // Decompositions
122
- const { L, U, P } = A.lu();
123
- const { Q, R } = A.qr();
98
+ const v = Vector.from([1, 2, 3]);
99
+ const w = Vector.from([4, 5, 6]);
124
100
 
125
- // Matrix exponential
126
- const expA = A.exp();
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]
127
113
  ```
128
114
 
129
- ### 2D Transforms
115
+ #### Matrix
130
116
 
131
117
  ```js
132
- import { Transforms2D } from '@emasoft/svg-matrix';
118
+ import { Matrix } from '@emasoft/svg-matrix';
133
119
 
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
120
+ const A = Matrix.from([[1, 2], [3, 4]]);
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
+ ```
139
137
 
140
- // Rotation around a point
141
- const Rp = Transforms2D.rotateAroundPoint(Math.PI / 2, 5, 5);
138
+ ### Transforms
142
139
 
143
- // Skew and stretch
144
- const Sk = Transforms2D.skew(0.5, 0);
145
- const St = Transforms2D.stretchAlongAxis(1, 0, 2); // stretch 2x along X
140
+ #### 2D (3x3 matrices)
146
141
 
147
- // Reflections
148
- const Rx = Transforms2D.reflectX(); // flip Y
149
- const Ry = Transforms2D.reflectY(); // flip X
150
- const Ro = Transforms2D.reflectOrigin(); // flip both
142
+ ```js
143
+ import { Transforms2D } from '@emasoft/svg-matrix';
151
144
 
152
- // Compose transforms (right-to-left: S first, then R, then T)
153
- const M = T.mul(R).mul(S);
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()
154
154
 
155
155
  // 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);
156
+ const [x, y] = Transforms2D.applyTransform(matrix, px, py);
161
157
  ```
162
158
 
163
- ### 3D Transforms
159
+ #### 3D (4x4 matrices)
164
160
 
165
161
  ```js
166
162
  import { Transforms3D } from '@emasoft/svg-matrix';
167
163
 
168
- // Basic transforms
169
- const T = Transforms3D.translation(1, 2, 3);
170
- const S = Transforms3D.scale(2, 2, 2);
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
+ ```
171
177
 
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);
178
+ ### SVG Processing
176
179
 
177
- // Rotation around arbitrary axis
178
- const Raxis = Transforms3D.rotateAroundAxis(1, 1, 0, Math.PI / 3);
180
+ #### SVGFlatten - Transform Parsing & CTM
179
181
 
180
- // Rotation around a point
181
- const Rpt = Transforms3D.rotateAroundPoint(0, 0, 1, Math.PI / 2, 5, 5, 0);
182
+ ```js
183
+ import { SVGFlatten } from '@emasoft/svg-matrix';
182
184
 
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
185
+ // Parse transform attributes
186
+ const m = SVGFlatten.parseTransformAttribute('translate(50,50) rotate(45) scale(2)');
188
187
 
189
- // Compose and apply
190
- const M = T.mul(Rx).mul(S);
191
- const [x, y, z] = Transforms3D.applyTransform(M, 1, 0, 0);
192
- ```
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
+ ]);
193
195
 
194
- ## API Reference
196
+ // Apply to point
197
+ const { x, y } = SVGFlatten.applyToPoint(ctm, 10, 10);
195
198
 
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
- | **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:
199
+ // Transform path data
200
+ const transformed = SVGFlatten.transformPathData('M 100 100 L 200 200', ctm);
310
201
 
311
- ```
312
- Original coordinates: (10, 10)
313
- After round-trip (float): (9.9857, 9.9857) // Error: 0.0143
314
- ```
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);
315
206
 
316
- With `@emasoft/svg-matrix` using 80-digit Decimal precision:
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
+ ]);
317
214
 
318
- ```
319
- Original coordinates: (10, 10)
320
- After round-trip: (10.00000000000000000000000000000000000000,
321
- 9.999999999999999999999999999999999999999999999999999999999999999999999999999999998)
322
- Round-trip error: X=0, Y=2e-79
323
- ```
215
+ // Unit resolution (px, %, em, pt, in, cm, mm, pc)
216
+ SVGFlatten.resolveLength('50%', 800); // → 400
217
+ SVGFlatten.resolveLength('1in', 800); // → 96
324
218
 
325
- **Improvement: 10^77 times better precision than JavaScript floats.**
219
+ // objectBoundingBox transform
220
+ const bboxTransform = SVGFlatten.objectBoundingBoxTransform(100, 50, 200, 100);
221
+ ```
326
222
 
327
- ### Parsing SVG Transforms
223
+ #### GeometryToPath - Shape Conversion
328
224
 
329
225
  ```js
330
- import { SVGFlatten } from '@emasoft/svg-matrix';
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()
246
+ ```
331
247
 
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)');
248
+ #### BrowserVerify - Chrome Verification
338
249
 
339
- // Parse chained transforms
340
- const combined = SVGFlatten.parseTransformAttribute('translate(50,50) rotate(45) scale(2)');
250
+ ```js
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 });
341
267
  ```
342
268
 
343
- ### Building CTM from Nested Elements
269
+ ### Polygon Operations
344
270
 
345
- ```js
346
- import { Decimal, SVGFlatten } from '@emasoft/svg-matrix';
271
+ #### PolygonClip
347
272
 
348
- Decimal.set({ precision: 80 });
273
+ ```js
274
+ import { PolygonClip } from '@emasoft/svg-matrix';
349
275
 
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
276
+ const square = [
277
+ PolygonClip.point(0, 0),
278
+ PolygonClip.point(2, 0),
279
+ PolygonClip.point(2, 2),
280
+ PolygonClip.point(0, 2)
365
281
  ];
366
282
 
367
- // Build combined CTM
368
- const ctm = SVGFlatten.buildCTM(transformStack);
283
+ // Boolean operations
284
+ PolygonClip.polygonIntersection(poly1, poly2)
285
+ PolygonClip.polygonUnion(poly1, poly2)
286
+ PolygonClip.polygonDifference(poly1, poly2)
369
287
 
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);
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
373
293
 
374
- // Transform back to local coordinates
375
- const inverseCTM = ctm.inverse();
376
- const recovered = SVGFlatten.applyToPoint(inverseCTM, viewport.x, viewport.y);
294
+ // Convex hull
295
+ PolygonClip.convexHull(points)
377
296
 
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
297
+ // Bounding box
298
+ PolygonClip.boundingBox(polygon) // {minX, minY, maxX, maxY}
299
+ PolygonClip.bboxIntersects(bbox1, bbox2)
383
300
  ```
384
301
 
385
- ### Transforming Path Data
302
+ #### ClipPathResolver
386
303
 
387
304
  ```js
388
- import { SVGFlatten } from '@emasoft/svg-matrix';
305
+ import { ClipPathResolver } from '@emasoft/svg-matrix';
389
306
 
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);
307
+ // Parse and resolve clipPath
308
+ const clipData = ClipPathResolver.parseClipPathElement(element);
309
+ const clipPolygon = ClipPathResolver.resolveClipPath(clipData, targetBBox);
393
310
 
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
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)
315
+
316
+ // Apply clipPath to element
317
+ ClipPathResolver.applyClipPath(elementData, clipPathData, targetBBox)
396
318
  ```
397
319
 
398
- ### Flattening All Transforms
320
+ ### SVG Element Resolution
399
321
 
400
- To flatten an SVG (remove all transform attributes and apply them directly to coordinates):
322
+ #### UseSymbolResolver
401
323
 
402
324
  ```js
403
- import { SVGFlatten } from '@emasoft/svg-matrix';
325
+ import { UseSymbolResolver } from '@emasoft/svg-matrix';
404
326
 
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)
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)
410
332
  ```
411
333
 
412
- ### viewBox and preserveAspectRatio
413
-
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:
334
+ #### MarkerResolver
415
335
 
416
336
  ```js
417
- import { Decimal, SVGFlatten } from '@emasoft/svg-matrix';
337
+ import { MarkerResolver } from '@emasoft/svg-matrix';
418
338
 
419
- Decimal.set({ precision: 80 });
420
-
421
- // Parse viewBox and preserveAspectRatio
422
- const viewBox = SVGFlatten.parseViewBox('0 0 100 100');
423
- const par = SVGFlatten.parsePreserveAspectRatio('xMidYMid meet');
424
-
425
- // Compute the viewBox-to-viewport transform
426
- const vbTransform = SVGFlatten.computeViewBoxTransform(viewBox, 800, 600, par);
427
-
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));
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)
431
344
  ```
432
345
 
433
- ### Full CTM with viewBox and Nested Elements
434
-
435
- Use `buildFullCTM` for complete SVG hierarchies including viewBox transforms:
346
+ #### PatternResolver
436
347
 
437
348
  ```js
438
- import { Decimal, SVGFlatten } from '@emasoft/svg-matrix';
439
-
440
- Decimal.set({ precision: 80 });
441
-
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
- ];
349
+ import { PatternResolver } from '@emasoft/svg-matrix';
449
350
 
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);
453
-
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
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)
458
356
  ```
459
357
 
460
- ### Nested SVG Viewports
461
-
462
- Handle deeply nested `<svg>` elements, each with its own viewBox:
358
+ #### MaskResolver
463
359
 
464
360
  ```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
- ];
361
+ import { MaskResolver } from '@emasoft/svg-matrix';
471
362
 
472
- const ctm = SVGFlatten.buildFullCTM(nestedHierarchy);
473
- // Point (10, 10) in innermost viewBox -> (240, 240) in outer viewport
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
474
367
  ```
475
368
 
476
- ### Unit and Percentage Resolution
369
+ ### Advanced Features
477
370
 
478
- Resolve SVG length values with units or percentages:
371
+ #### MeshGradient (SVG 2.0)
479
372
 
480
373
  ```js
481
- import { Decimal, SVGFlatten } from '@emasoft/svg-matrix';
374
+ import { MeshGradient } from '@emasoft/svg-matrix';
482
375
 
483
- const viewportWidth = new Decimal(800);
376
+ // Parse mesh gradient
377
+ const meshDef = MeshGradient.parseMeshGradientElement(element);
378
+ const meshData = MeshGradient.parseMeshGradient(meshDef);
484
379
 
485
- // Percentages resolve relative to reference size
486
- SVGFlatten.resolveLength('50%', viewportWidth); // -> 400
487
- SVGFlatten.resolveLength('25%', viewportWidth); // -> 200
380
+ // Coons patch evaluation
381
+ const patch = new MeshGradient.CoonsPatch(topEdge, rightEdge, bottomEdge, leftEdge, cornerColors);
382
+ const { point, color } = patch.evaluate(u, v);
488
383
 
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)
384
+ // Rasterize to ImageData
385
+ const imageData = MeshGradient.rasterizeMeshGradient(meshData, width, height);
386
+
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
+ ```
494
394
 
495
- // Font-relative units (assumes 16px base)
496
- SVGFlatten.resolveLength('2em', viewportWidth); // -> 32
395
+ #### TextToPath
497
396
 
498
- // Normalized diagonal for non-directional percentages
499
- const diag = SVGFlatten.normalizedDiagonal(800, 600); // -> 707.1
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);
500
419
  ```
501
420
 
502
- ### Object Bounding Box Transform
421
+ ### Convenience Functions
503
422
 
504
- For gradients/patterns with `objectBoundingBox` units:
423
+ Direct exports for common operations:
505
424
 
506
425
  ```js
507
- // Element bounding box: x=100, y=50, width=200, height=100
508
- const bboxTransform = SVGFlatten.objectBoundingBoxTransform(100, 50, 200, 100);
426
+ import {
427
+ // Transforms
428
+ translate2D, rotate2D, scale2D, transform2D,
429
+ translate3D, scale3D, transform3D,
509
430
 
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
- ```
431
+ // Shape conversion
432
+ circleToPath, ellipseToPath, rectToPath, lineToPath,
433
+ polygonToPath, polylineToPath,
514
434
 
515
- ### CDN Usage
435
+ // Path manipulation
436
+ parsePath, pathToString, pathToAbsolute, pathToCubics, transformPath,
437
+ elementToPath,
516
438
 
517
- ```html
518
- <script type="module">
519
- import { Decimal, SVGFlatten } from 'https://esm.sh/@emasoft/svg-matrix';
439
+ // Matrix/Vector creation
440
+ identity, zeros, vec, mat,
520
441
 
521
- Decimal.set({ precision: 80 });
442
+ // Precision control
443
+ setPrecision, getPrecision,
522
444
 
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>
445
+ // Constants
446
+ getKappa
447
+ } from '@emasoft/svg-matrix';
527
448
  ```
528
449
 
529
450
  ## License