@emasoft/svg-matrix 1.0.18 → 1.0.20
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 +256 -759
- package/bin/svg-matrix.js +171 -2
- package/bin/svglinter.cjs +1162 -0
- package/package.json +8 -2
- package/scripts/postinstall.js +6 -9
- package/src/animation-optimization.js +394 -0
- package/src/animation-references.js +440 -0
- package/src/arc-length.js +940 -0
- package/src/bezier-analysis.js +1626 -0
- package/src/bezier-intersections.js +1369 -0
- package/src/clip-path-resolver.js +110 -2
- package/src/convert-path-data.js +583 -0
- package/src/css-specificity.js +443 -0
- package/src/douglas-peucker.js +356 -0
- package/src/flatten-pipeline.js +109 -4
- package/src/geometry-to-path.js +126 -16
- package/src/gjk-collision.js +840 -0
- package/src/index.js +175 -2
- package/src/off-canvas-detection.js +1222 -0
- package/src/path-analysis.js +1241 -0
- package/src/path-data-plugins.js +928 -0
- package/src/path-optimization.js +825 -0
- package/src/path-simplification.js +1140 -0
- package/src/polygon-clip.js +376 -99
- package/src/svg-boolean-ops.js +898 -0
- package/src/svg-collections.js +910 -0
- package/src/svg-parser.js +175 -16
- package/src/svg-rendering-context.js +627 -0
- package/src/svg-toolbox.js +7495 -0
- package/src/svg-validation-data.js +944 -0
- package/src/transform-decomposition.js +810 -0
- package/src/transform-optimization.js +936 -0
- package/src/use-symbol-resolver.js +75 -7
package/README.md
CHANGED
|
@@ -1,896 +1,393 @@
|
|
|
1
1
|
# @emasoft/svg-matrix
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='80' viewBox='0 0 600 80'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0%25' y1='0%25' x2='100%25' y2='0%25'%3E%3Cstop offset='0%25' stop-color='%23e0e0e0'/%3E%3Cstop offset='50%25' stop-color='%23404040'/%3E%3Cstop offset='100%25' stop-color='%23e0e0e0'/%3E%3C/linearGradient%3E%3C/defs%3E%3Cg stroke='%23888' stroke-width='0.5' fill='none'%3E%3Ccircle cx='40' cy='40' r='25'/%3E%3Ccircle cx='40' cy='40' r='18'/%3E%3Cline x1='40' y1='15' x2='40' y2='65'/%3E%3Cline x1='15' y1='40' x2='65' y2='40'/%3E%3Cline x1='22' y1='22' x2='58' y2='58'/%3E%3Cline x1='58' y1='22' x2='22' y2='58'/%3E%3Crect x='100' y='20' width='40' height='40' transform='rotate(15 120 40)'/%3E%3Crect x='100' y='20' width='40' height='40' transform='rotate(30 120 40)'/%3E%3Crect x='100' y='20' width='40' height='40' transform='rotate(45 120 40)'/%3E%3Cpath d='M180 60 Q220 10 260 60' /%3E%3Ccircle cx='180' cy='60' r='3'/%3E%3Ccircle cx='220' cy='10' r='2'/%3E%3Ccircle cx='260' cy='60' r='3'/%3E%3Cline x1='180' y1='60' x2='220' y2='10' stroke-dasharray='4,2'/%3E%3Cline x1='220' y1='10' x2='260' y2='60' stroke-dasharray='4,2'/%3E%3Cpolygon points='320,15 360,40 320,65 340,40' /%3E%3Cline x1='320' y1='15' x2='360' y2='40'/%3E%3Cline x1='360' y1='40' x2='320' y2='65'/%3E%3Cpath d='M400 20 L440 20 L440 60 L400 60 Z M400 20 L440 60 M440 20 L400 60'/%3E%3Cg transform='translate(480 40)'%3E%3Ccircle r='25'/%3E%3Cpath d='M-25 0 A25 25 0 0 1 0 -25'/%3E%3Cpath d='M0 -25 A25 25 0 0 1 25 0'/%3E%3Cline x1='0' y1='-25' x2='0' y2='0'/%3E%3Cline x1='0' y1='0' x2='25' y2='0'/%3E%3Ctext x='-8' y='4' font-size='8' fill='%23666' font-family='monospace'%3E90%C2%B0%3C/text%3E%3C/g%3E%3Cline x1='540' y1='40' x2='600' y2='40'/%3E%3C/g%3E%3C/svg%3E" alt="Geometric precision illustration"/>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>Arbitrary-precision mathematics for vectors, matrices, and SVG transformations</strong><br/>
|
|
9
|
+
<em>80 significant digits. Mathematically verified. Zero floating-point errors.</em>
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
<a href="#part-1-core-math-library">Core Math</a> •
|
|
14
|
+
<a href="#part-2-svg-toolbox">SVG Toolbox</a> •
|
|
15
|
+
<a href="#installation">Install</a> •
|
|
16
|
+
<a href="API.md">API Reference</a>
|
|
17
|
+
</p>
|
|
4
18
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
**Linear Algebra**
|
|
8
|
-
- Matrix and Vector classes with full linear algebra operations
|
|
9
|
-
- LU/QR decomposition, determinant, inverse, solve, matrix exponential
|
|
10
|
-
|
|
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
|
-
## Requirements
|
|
35
|
-
|
|
36
|
-
- **Node.js 24.0.0** or higher (ES modules, modern JavaScript features)
|
|
37
|
-
- **Playwright** (optional) - for browser verification features
|
|
38
|
-
|
|
39
|
-
## Precision
|
|
40
|
-
|
|
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 |
|
|
46
|
-
|
|
47
|
-
**When precision matters:** GIS, CAD, scientific visualization, deep transform hierarchies, accumulated operations.
|
|
48
|
-
|
|
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
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
## Installation
|
|
56
|
-
|
|
57
|
-
### Node.js (Local Installation)
|
|
58
|
-
|
|
59
|
-
**What does "local installation" mean?** It means downloading the library to your computer so you can use it in your Node.js projects. This is what most developers do.
|
|
60
|
-
|
|
61
|
-
#### Step 1: Install the package
|
|
62
|
-
|
|
63
|
-
Open your terminal and run:
|
|
64
|
-
|
|
65
|
-
```bash
|
|
66
|
-
npm install @emasoft/svg-matrix
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
This downloads the library into a folder called `node_modules` in your project.
|
|
70
|
-
|
|
71
|
-
#### Upgrading to the Latest Version
|
|
72
|
-
|
|
73
|
-
To update to the latest version:
|
|
19
|
+
---
|
|
74
20
|
|
|
75
|
-
|
|
76
|
-
npm update @emasoft/svg-matrix
|
|
77
|
-
```
|
|
21
|
+
## What Is This?
|
|
78
22
|
|
|
79
|
-
|
|
23
|
+
This package contains **two libraries** that work together:
|
|
80
24
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
25
|
+
| Library | Purpose | Precision |
|
|
26
|
+
|---------|---------|-----------|
|
|
27
|
+
| **Core Math** | Vectors, matrices, 2D/3D transforms | 80 digits (configurable to 10^9) |
|
|
28
|
+
| **SVG Toolbox** | Parse, transform, validate, optimize SVG files | 80 digits + visual verification |
|
|
84
29
|
|
|
85
|
-
|
|
30
|
+
**Think of it like this:**
|
|
86
31
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
```
|
|
32
|
+
> **Core Math** is a calculator that never makes rounding errors.
|
|
33
|
+
> **SVG Toolbox** uses that calculator to work with SVG graphics perfectly.
|
|
90
34
|
|
|
91
|
-
|
|
35
|
+
---
|
|
92
36
|
|
|
93
|
-
|
|
37
|
+
<!-- Geometric divider: Golden ratio spiral construction -->
|
|
38
|
+
<p align="center">
|
|
39
|
+
<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='20' viewBox='0 0 400 20'%3E%3Cg stroke='%23ccc' stroke-width='0.5' fill='none'%3E%3Cline x1='0' y1='10' x2='120' y2='10'/%3E%3Crect x='125' y='5' width='10' height='10'/%3E%3Crect x='137' y='5' width='6.18' height='6.18'/%3E%3Crect x='145' y='5' width='3.82' height='3.82'/%3E%3Crect x='150' y='5' width='2.36' height='2.36'/%3E%3Cpath d='M160 10 Q170 3 180 10 Q190 17 200 10 Q210 3 220 10 Q230 17 240 10'/%3E%3Crect x='250' y='7' width='2.36' height='2.36'/%3E%3Crect x='254' y='5' width='3.82' height='3.82'/%3E%3Crect x='259' y='5' width='6.18' height='6.18'/%3E%3Crect x='267' y='5' width='10' height='10'/%3E%3Cline x1='280' y1='10' x2='400' y2='10'/%3E%3C/g%3E%3C/svg%3E" alt=""/>
|
|
40
|
+
</p>
|
|
94
41
|
|
|
95
|
-
|
|
42
|
+
# Part 1: Core Math Library
|
|
96
43
|
|
|
97
|
-
|
|
44
|
+
**For:** Scientists, engineers, game developers, anyone who needs exact calculations.
|
|
98
45
|
|
|
99
|
-
|
|
46
|
+
## What Can It Do?
|
|
100
47
|
|
|
101
|
-
|
|
48
|
+
Imagine you want to rotate a spaceship in a game, or calculate where two laser beams cross. Normal JavaScript math has tiny errors that add up. This library has **zero errors** because it uses 80-digit precision.
|
|
102
49
|
|
|
103
50
|
```js
|
|
104
|
-
//
|
|
105
|
-
|
|
106
|
-
// Import only the modules you need
|
|
107
|
-
import { Matrix, Vector, Transforms2D } from '@emasoft/svg-matrix';
|
|
108
|
-
|
|
109
|
-
// Now you can use them directly
|
|
110
|
-
const v = Vector.from([1, 2, 3]);
|
|
111
|
-
const rotation = Transforms2D.rotate(Math.PI / 4); // 45 degrees
|
|
112
|
-
|
|
113
|
-
console.log('Vector:', v.toNumberArray());
|
|
114
|
-
console.log('Rotation matrix created!');
|
|
51
|
+
// Normal JavaScript: 0.1 + 0.2 = 0.30000000000000004 (wrong!)
|
|
52
|
+
// svg-matrix: 0.1 + 0.2 = 0.3 (exactly right)
|
|
115
53
|
```
|
|
116
54
|
|
|
117
|
-
|
|
55
|
+
### Vectors (Arrows in Space)
|
|
118
56
|
|
|
119
|
-
|
|
57
|
+
A vector is like an arrow pointing somewhere. You can add arrows, measure them, find angles between them.
|
|
120
58
|
|
|
121
59
|
```js
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
// Import EVERYTHING as one big object called "SVGMatrix"
|
|
125
|
-
import * as SVGMatrix from '@emasoft/svg-matrix';
|
|
126
|
-
|
|
127
|
-
// Now use SVGMatrix.ModuleName to access each tool
|
|
128
|
-
const v = SVGMatrix.Vector.from([1, 2, 3]);
|
|
129
|
-
const m = SVGMatrix.Matrix.identity(3);
|
|
130
|
-
const rotation = SVGMatrix.Transforms2D.rotate(Math.PI / 4);
|
|
131
|
-
const pathData = SVGMatrix.GeometryToPath.circleToPathData(100, 100, 50);
|
|
132
|
-
|
|
133
|
-
// See everything that's available:
|
|
134
|
-
console.log('Available modules:', Object.keys(SVGMatrix));
|
|
135
|
-
```
|
|
60
|
+
import { Vector } from '@emasoft/svg-matrix';
|
|
136
61
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
- `SVGMatrix.Vector` - For vector math
|
|
140
|
-
- `SVGMatrix.Transforms2D` - For 2D rotations, translations, scaling
|
|
141
|
-
- `SVGMatrix.Transforms3D` - For 3D transformations
|
|
142
|
-
- `SVGMatrix.SVGFlatten` - For flattening SVG transforms
|
|
143
|
-
- `SVGMatrix.GeometryToPath` - For converting shapes to paths
|
|
144
|
-
- `SVGMatrix.PolygonClip` - For polygon boolean operations
|
|
145
|
-
- ...and many more!
|
|
62
|
+
// Create an arrow pointing right 3 units and up 4 units
|
|
63
|
+
const arrow = Vector.from([3, 4]);
|
|
146
64
|
|
|
147
|
-
|
|
65
|
+
// How long is the arrow? (It's 5 - like a 3-4-5 triangle!)
|
|
66
|
+
console.log(arrow.norm().toString()); // "5"
|
|
148
67
|
|
|
149
|
-
|
|
150
|
-
|
|
68
|
+
// Make it exactly 1 unit long (normalize)
|
|
69
|
+
const unit = arrow.normalize();
|
|
70
|
+
console.log(unit.toNumberArray()); // [0.6, 0.8]
|
|
151
71
|
```
|
|
152
72
|
|
|
153
|
-
|
|
73
|
+
### Matrices (Grids of Numbers)
|
|
154
74
|
|
|
155
|
-
|
|
75
|
+
A matrix is a grid of numbers. You can multiply them, flip them, use them to solve puzzles.
|
|
156
76
|
|
|
157
77
|
```js
|
|
158
|
-
|
|
159
|
-
// A complete example showing the main features
|
|
160
|
-
|
|
161
|
-
import {
|
|
162
|
-
Matrix,
|
|
163
|
-
Vector,
|
|
164
|
-
Transforms2D,
|
|
165
|
-
GeometryToPath,
|
|
166
|
-
SVGFlatten
|
|
167
|
-
} from '@emasoft/svg-matrix';
|
|
168
|
-
|
|
169
|
-
console.log('=== Vector Example ===');
|
|
170
|
-
const v = Vector.from([1, 2, 3]);
|
|
171
|
-
const w = Vector.from([4, 5, 6]);
|
|
172
|
-
console.log('Vector v:', v.toNumberArray());
|
|
173
|
-
console.log('Vector w:', w.toNumberArray());
|
|
174
|
-
console.log('Dot product (v · w):', v.dot(w).toString());
|
|
175
|
-
|
|
176
|
-
console.log('\n=== Matrix Example ===');
|
|
177
|
-
const A = Matrix.from([[1, 2], [3, 4]]);
|
|
178
|
-
console.log('Matrix A:', A.toNumberArray());
|
|
179
|
-
console.log('Determinant:', A.determinant().toString());
|
|
180
|
-
|
|
181
|
-
console.log('\n=== Transform Example ===');
|
|
182
|
-
// Create a transform: move 100 right, rotate 45°, scale 2x
|
|
183
|
-
const transform = Transforms2D.translation(100, 0)
|
|
184
|
-
.mul(Transforms2D.rotate(Math.PI / 4))
|
|
185
|
-
.mul(Transforms2D.scale(2));
|
|
186
|
-
|
|
187
|
-
// Apply to a point
|
|
188
|
-
const [x, y] = Transforms2D.applyTransform(transform, 10, 0);
|
|
189
|
-
console.log(`Point (10, 0) after transform: (${x.toFixed(2)}, ${y.toFixed(2)})`);
|
|
190
|
-
|
|
191
|
-
console.log('\n=== SVG Path Example ===');
|
|
192
|
-
// Convert a circle to a path
|
|
193
|
-
const circlePath = GeometryToPath.circleToPathData(0, 0, 50);
|
|
194
|
-
console.log('Circle as path:', circlePath.substring(0, 50) + '...');
|
|
195
|
-
|
|
196
|
-
console.log('\nDone! Everything works!');
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
Run it:
|
|
200
|
-
```bash
|
|
201
|
-
node my-first-example.js
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
#### Troubleshooting
|
|
78
|
+
import { Matrix } from '@emasoft/svg-matrix';
|
|
205
79
|
|
|
206
|
-
|
|
80
|
+
// Create a 2x2 grid
|
|
81
|
+
const grid = Matrix.from([
|
|
82
|
+
[4, 7],
|
|
83
|
+
[2, 6]
|
|
84
|
+
]);
|
|
207
85
|
|
|
208
|
-
|
|
86
|
+
// Find the determinant (a special number about the grid)
|
|
87
|
+
console.log(grid.determinant().toString()); // "10"
|
|
209
88
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
"type": "module"
|
|
214
|
-
}
|
|
89
|
+
// Solve a puzzle: find x and y where 4x + 7y = 1 and 2x + 6y = 0
|
|
90
|
+
const answer = grid.solve([1, 0]);
|
|
91
|
+
console.log(answer.toNumberArray()); // [0.6, -0.2]
|
|
215
92
|
```
|
|
216
93
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
**Using older Node.js syntax (require)?**
|
|
94
|
+
### Transforms (Moving & Spinning Things)
|
|
220
95
|
|
|
221
|
-
|
|
96
|
+
Transforms move, rotate, scale, or skew shapes. This is how video games move characters around!
|
|
222
97
|
|
|
223
98
|
```js
|
|
224
|
-
|
|
225
|
-
// const { Matrix } = require('@emasoft/svg-matrix'); // ❌ Error!
|
|
226
|
-
|
|
227
|
-
// NEW WAY (use dynamic import)
|
|
228
|
-
async function main() {
|
|
229
|
-
const { Matrix, Vector } = await import('@emasoft/svg-matrix');
|
|
230
|
-
|
|
231
|
-
const v = Vector.from([1, 2, 3]);
|
|
232
|
-
console.log(v.norm().toString());
|
|
233
|
-
}
|
|
234
|
-
main();
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
### Browser Usage (CDN)
|
|
238
|
-
|
|
239
|
-
You can use this library directly in a web browser without installing anything. Just add a `<script>` tag to your HTML file.
|
|
240
|
-
|
|
241
|
-
**What is a CDN?** A CDN (Content Delivery Network) hosts the library files so you can load them directly in your browser. No `npm install` needed!
|
|
242
|
-
|
|
243
|
-
#### Option 1: esm.sh (Recommended)
|
|
244
|
-
|
|
245
|
-
Best for modern browsers. Automatically handles dependencies.
|
|
246
|
-
|
|
247
|
-
```html
|
|
248
|
-
<!DOCTYPE html>
|
|
249
|
-
<html>
|
|
250
|
-
<head>
|
|
251
|
-
<title>SVG Matrix Example</title>
|
|
252
|
-
</head>
|
|
253
|
-
<body>
|
|
254
|
-
<script type="module">
|
|
255
|
-
// Import the modules you need
|
|
256
|
-
import { Matrix, Vector, Transforms2D } from 'https://esm.sh/@emasoft/svg-matrix';
|
|
257
|
-
|
|
258
|
-
// Now you can use them!
|
|
259
|
-
const rotation = Transforms2D.rotate(Math.PI / 4); // 45 degrees
|
|
260
|
-
const [x, y] = Transforms2D.applyTransform(rotation, 10, 0);
|
|
261
|
-
|
|
262
|
-
console.log(`Point (10, 0) rotated 45 degrees = (${x}, ${y})`);
|
|
263
|
-
</script>
|
|
264
|
-
</body>
|
|
265
|
-
</html>
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
#### Option 2: unpkg
|
|
269
|
-
|
|
270
|
-
Another reliable CDN option.
|
|
271
|
-
|
|
272
|
-
```html
|
|
273
|
-
<script type="module">
|
|
274
|
-
import { SVGFlatten, GeometryToPath } from 'https://unpkg.com/@emasoft/svg-matrix/src/index.js';
|
|
275
|
-
|
|
276
|
-
// Convert a circle to a path
|
|
277
|
-
const pathData = GeometryToPath.circleToPathData(100, 100, 50);
|
|
278
|
-
console.log(pathData);
|
|
279
|
-
</script>
|
|
280
|
-
```
|
|
99
|
+
import { Transforms2D } from '@emasoft/svg-matrix';
|
|
281
100
|
|
|
282
|
-
|
|
101
|
+
// Move something 100 pixels right
|
|
102
|
+
const move = Transforms2D.translation(100, 0);
|
|
283
103
|
|
|
284
|
-
|
|
104
|
+
// Spin something 45 degrees
|
|
105
|
+
const spin = Transforms2D.rotate(Math.PI / 4);
|
|
285
106
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
import { Matrix, Vector } from 'https://cdn.jsdelivr.net/npm/@emasoft/svg-matrix/src/index.js';
|
|
107
|
+
// Make something twice as big
|
|
108
|
+
const grow = Transforms2D.scale(2);
|
|
289
109
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
</script>
|
|
110
|
+
// Apply spin to a point at (10, 0)
|
|
111
|
+
const [x, y] = Transforms2D.applyTransform(spin, 10, 0);
|
|
112
|
+
console.log(x.toFixed(4), y.toFixed(4)); // "7.0711 7.0711"
|
|
294
113
|
```
|
|
295
114
|
|
|
296
|
-
|
|
115
|
+
---
|
|
297
116
|
|
|
298
|
-
|
|
117
|
+
<!-- Geometric divider: Intersecting circles construction -->
|
|
118
|
+
<p align="center">
|
|
119
|
+
<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='20' viewBox='0 0 400 20'%3E%3Cg stroke='%23ccc' stroke-width='0.5' fill='none'%3E%3Cline x1='0' y1='10' x2='140' y2='10'/%3E%3Ccircle cx='170' cy='10' r='8'/%3E%3Ccircle cx='182' cy='10' r='8'/%3E%3Ccircle cx='200' cy='10' r='3'/%3E%3Ccircle cx='218' cy='10' r='8'/%3E%3Ccircle cx='230' cy='10' r='8'/%3E%3Cline x1='260' y1='10' x2='400' y2='10'/%3E%3C/g%3E%3C/svg%3E" alt=""/>
|
|
120
|
+
</p>
|
|
299
121
|
|
|
300
|
-
|
|
301
|
-
<script type="module">
|
|
302
|
-
// Import EVERYTHING as one object called "SVGMatrix"
|
|
303
|
-
// The "* as" means "everything as"
|
|
304
|
-
import * as SVGMatrix from 'https://esm.sh/@emasoft/svg-matrix';
|
|
305
|
-
|
|
306
|
-
// Now use SVGMatrix.ModuleName to access each tool
|
|
307
|
-
const v = SVGMatrix.Vector.from([1, 2, 3]);
|
|
308
|
-
const m = SVGMatrix.Matrix.identity(3);
|
|
309
|
-
const rotation = SVGMatrix.Transforms2D.rotate(Math.PI / 4);
|
|
310
|
-
const [x, y] = SVGMatrix.Transforms2D.applyTransform(rotation, 10, 0);
|
|
311
|
-
|
|
312
|
-
// See everything that's available:
|
|
313
|
-
console.log('All modules:', Object.keys(SVGMatrix));
|
|
314
|
-
// Output: ['Matrix', 'Vector', 'Transforms2D', 'Transforms3D', 'SVGFlatten', ...]
|
|
315
|
-
</script>
|
|
316
|
-
```
|
|
122
|
+
## Core Math API Quick Reference
|
|
317
123
|
|
|
318
|
-
|
|
124
|
+
### Vector
|
|
319
125
|
|
|
320
|
-
|
|
|
126
|
+
| Method | What It Does |
|
|
127
|
+
|--------|--------------|
|
|
128
|
+
| `Vector.from([x, y, z])` | Create a vector |
|
|
129
|
+
| `.add(v)` | Add two vectors |
|
|
130
|
+
| `.sub(v)` | Subtract vectors |
|
|
131
|
+
| `.scale(n)` | Multiply by a number |
|
|
132
|
+
| `.dot(v)` | Dot product (single number result) |
|
|
133
|
+
| `.cross(v)` | Cross product (3D only) |
|
|
134
|
+
| `.norm()` | Length of the vector |
|
|
135
|
+
| `.normalize()` | Make length = 1 |
|
|
136
|
+
| `.angleBetween(v)` | Angle between two vectors |
|
|
137
|
+
| `.toNumberArray()` | Convert to regular JavaScript array |
|
|
138
|
+
|
|
139
|
+
### Matrix
|
|
140
|
+
|
|
141
|
+
| Method | What It Does |
|
|
142
|
+
|--------|--------------|
|
|
143
|
+
| `Matrix.from([[...], [...]])` | Create from 2D array |
|
|
144
|
+
| `Matrix.identity(n)` | Identity matrix (1s on diagonal) |
|
|
145
|
+
| `Matrix.zeros(r, c)` | Matrix of zeros |
|
|
146
|
+
| `.mul(M)` | Multiply matrices |
|
|
147
|
+
| `.transpose()` | Flip rows and columns |
|
|
148
|
+
| `.determinant()` | Calculate determinant |
|
|
149
|
+
| `.inverse()` | Calculate inverse |
|
|
150
|
+
| `.solve(b)` | Solve system of equations |
|
|
151
|
+
| `.lu()` | LU decomposition |
|
|
152
|
+
| `.qr()` | QR decomposition |
|
|
153
|
+
|
|
154
|
+
### Transforms2D / Transforms3D
|
|
155
|
+
|
|
156
|
+
| Method | What It Does |
|
|
321
157
|
|--------|--------------|
|
|
322
|
-
| `
|
|
323
|
-
| `
|
|
324
|
-
| `
|
|
325
|
-
| `
|
|
326
|
-
| `
|
|
327
|
-
| `
|
|
328
|
-
| `
|
|
329
|
-
| `SVGMatrix.ClipPathResolver` | Resolve SVG clipPath elements |
|
|
330
|
-
| `SVGMatrix.BrowserVerify` | Verify against browser's SVG engine |
|
|
331
|
-
| `SVGMatrix.Logger` | Control library logging |
|
|
158
|
+
| `translation(x, y)` | Move transform |
|
|
159
|
+
| `scale(sx, sy)` | Size transform |
|
|
160
|
+
| `rotate(angle)` | Spin transform (radians) |
|
|
161
|
+
| `rotateAroundPoint(angle, px, py)` | Spin around a specific point |
|
|
162
|
+
| `skew(ax, ay)` | Slant transform |
|
|
163
|
+
| `reflectX()` / `reflectY()` | Mirror transform |
|
|
164
|
+
| `applyTransform(M, x, y)` | Apply transform to a point |
|
|
332
165
|
|
|
333
|
-
|
|
166
|
+
---
|
|
334
167
|
|
|
335
|
-
|
|
168
|
+
<!-- Geometric divider: Bezier curve construction -->
|
|
169
|
+
<p align="center">
|
|
170
|
+
<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='30' viewBox='0 0 400 30'%3E%3Cg stroke='%23ccc' stroke-width='0.5' fill='none'%3E%3Cline x1='0' y1='15' x2='100' y2='15'/%3E%3Cpath d='M110 25 C130 5, 150 5, 170 15 S210 25, 230 15 S270 5, 290 15' stroke='%23999'/%3E%3Ccircle cx='110' cy='25' r='2' fill='%23999'/%3E%3Ccircle cx='170' cy='15' r='2' fill='%23999'/%3E%3Ccircle cx='230' cy='15' r='2' fill='%23999'/%3E%3Ccircle cx='290' cy='15' r='2' fill='%23999'/%3E%3Cline x1='110' y1='25' x2='130' y2='5' stroke-dasharray='2,2'/%3E%3Cline x1='150' y1='5' x2='170' y2='15' stroke-dasharray='2,2'/%3E%3Cline x1='300' y1='15' x2='400' y2='15'/%3E%3C/g%3E%3C/svg%3E" alt=""/>
|
|
171
|
+
</p>
|
|
336
172
|
|
|
337
|
-
|
|
338
|
-
<!-- esm.sh with version -->
|
|
339
|
-
<script type="module">
|
|
340
|
-
import { Transforms2D } from 'https://esm.sh/@emasoft/svg-matrix@1.0.7';
|
|
341
|
-
</script>
|
|
173
|
+
# Part 2: SVG Toolbox
|
|
342
174
|
|
|
343
|
-
|
|
344
|
-
<script type="module">
|
|
345
|
-
import { Matrix } from 'https://unpkg.com/@emasoft/svg-matrix@1.0.7/src/index.js';
|
|
346
|
-
</script>
|
|
175
|
+
**For:** Web developers, designers, anyone working with SVG graphics.
|
|
347
176
|
|
|
348
|
-
|
|
349
|
-
<script type="module">
|
|
350
|
-
import { Vector } from 'https://cdn.jsdelivr.net/npm/@emasoft/svg-matrix@1.0.7/src/index.js';
|
|
351
|
-
</script>
|
|
352
|
-
```
|
|
177
|
+
## What Can It Do?
|
|
353
178
|
|
|
354
|
-
|
|
179
|
+
SVG files are pictures made of shapes, paths, and effects. This toolbox can:
|
|
355
180
|
|
|
356
|
-
|
|
181
|
+
- **Flatten** - Bake all transforms into coordinates (no more `transform="rotate(45)"`)
|
|
182
|
+
- **Convert** - Turn circles, rectangles into path commands
|
|
183
|
+
- **Validate** - Find and fix problems in SVG files
|
|
184
|
+
- **Optimize** - Remove unused elements, simplify paths
|
|
357
185
|
|
|
358
|
-
|
|
359
|
-
<!DOCTYPE html>
|
|
360
|
-
<html>
|
|
361
|
-
<head>
|
|
362
|
-
<title>SVG Matrix - Complete Example</title>
|
|
363
|
-
<style>
|
|
364
|
-
body { font-family: sans-serif; padding: 20px; }
|
|
365
|
-
svg { border: 1px solid #ccc; }
|
|
366
|
-
pre { background: #f5f5f5; padding: 10px; }
|
|
367
|
-
</style>
|
|
368
|
-
</head>
|
|
369
|
-
<body>
|
|
370
|
-
<h1>SVG Matrix Demo</h1>
|
|
371
|
-
|
|
372
|
-
<h2>Original Circle</h2>
|
|
373
|
-
<svg width="200" height="200">
|
|
374
|
-
<circle id="original" cx="100" cy="100" r="40" fill="blue" opacity="0.5"/>
|
|
375
|
-
</svg>
|
|
376
|
-
|
|
377
|
-
<h2>Transformed (rotate 45 + scale 1.5)</h2>
|
|
378
|
-
<svg width="200" height="200">
|
|
379
|
-
<path id="transformed" fill="red" opacity="0.5"/>
|
|
380
|
-
</svg>
|
|
381
|
-
|
|
382
|
-
<h2>Generated Path Data</h2>
|
|
383
|
-
<pre id="output"></pre>
|
|
384
|
-
|
|
385
|
-
<script type="module">
|
|
386
|
-
import {
|
|
387
|
-
Transforms2D,
|
|
388
|
-
GeometryToPath,
|
|
389
|
-
SVGFlatten
|
|
390
|
-
} from 'https://esm.sh/@emasoft/svg-matrix';
|
|
391
|
-
|
|
392
|
-
// Step 1: Convert circle to path
|
|
393
|
-
const circlePath = GeometryToPath.circleToPathData(100, 100, 40);
|
|
394
|
-
|
|
395
|
-
// Step 2: Create a combined transform (rotate 45 degrees, then scale 1.5x)
|
|
396
|
-
const transform = Transforms2D.scale(1.5)
|
|
397
|
-
.mul(Transforms2D.rotate(Math.PI / 4));
|
|
398
|
-
|
|
399
|
-
// Step 3: Apply transform to the path
|
|
400
|
-
const transformedPath = SVGFlatten.transformPathData(circlePath, transform);
|
|
401
|
-
|
|
402
|
-
// Step 4: Display the result
|
|
403
|
-
document.getElementById('transformed').setAttribute('d', transformedPath);
|
|
404
|
-
document.getElementById('output').textContent = transformedPath;
|
|
405
|
-
</script>
|
|
406
|
-
</body>
|
|
407
|
-
</html>
|
|
408
|
-
```
|
|
186
|
+
### Why Use This Instead of SVGO?
|
|
409
187
|
|
|
410
|
-
|
|
188
|
+
| | SVGO | svg-matrix |
|
|
189
|
+
|--|------|-----------|
|
|
190
|
+
| **Math precision** | 15 digits (can accumulate errors) | 80 digits (no errors) |
|
|
191
|
+
| **Verification** | None (hope it works) | Mathematical proof each step is correct |
|
|
192
|
+
| **Attribute handling** | May lose clip-path, mask, filter | Guarantees ALL attributes preserved |
|
|
193
|
+
| **Use case** | Quick file size reduction | Precision-critical applications |
|
|
411
194
|
|
|
412
|
-
**
|
|
413
|
-
Make sure you have `type="module"` in your script tag:
|
|
414
|
-
```html
|
|
415
|
-
<script type="module"> <!-- This is required! -->
|
|
416
|
-
```
|
|
195
|
+
**Use svg-matrix when:** CAD, GIS, scientific visualization, or when visual correctness matters more than file size.
|
|
417
196
|
|
|
418
|
-
**
|
|
419
|
-
Some browsers block CDN imports when opening files with `file://`. Solutions:
|
|
420
|
-
1. Use a local server: `npx serve .` or `python -m http.server`
|
|
421
|
-
2. Or use a code playground like CodePen, JSFiddle, or StackBlitz
|
|
197
|
+
**Use SVGO when:** Quick optimization where small rounding errors are acceptable.
|
|
422
198
|
|
|
423
|
-
|
|
424
|
-
This library requires ES modules (modern browsers). For IE11 or very old browsers, you'll need a bundler like Webpack or Rollup.
|
|
199
|
+
---
|
|
425
200
|
|
|
426
|
-
##
|
|
201
|
+
## Command Line Tools
|
|
427
202
|
|
|
428
|
-
|
|
203
|
+
### `svg-matrix` - Process SVG files
|
|
429
204
|
|
|
430
205
|
```bash
|
|
431
|
-
#
|
|
432
|
-
svg-matrix --help
|
|
433
|
-
|
|
434
|
-
# Show command-specific help
|
|
435
|
-
svg-matrix flatten --help
|
|
436
|
-
|
|
437
|
-
# Show version
|
|
438
|
-
svg-matrix --version
|
|
439
|
-
|
|
440
|
-
# Process single file
|
|
206
|
+
# Flatten all transforms into coordinates
|
|
441
207
|
svg-matrix flatten input.svg -o output.svg
|
|
442
208
|
|
|
443
|
-
#
|
|
444
|
-
svg-matrix flatten ./svgs/ -o ./output/
|
|
445
|
-
|
|
446
|
-
# Process files from list
|
|
447
|
-
svg-matrix flatten --list files.txt -o ./output/
|
|
448
|
-
|
|
449
|
-
# Convert shapes to paths
|
|
209
|
+
# Convert shapes (circle, rect, etc.) to paths
|
|
450
210
|
svg-matrix convert input.svg -o output.svg
|
|
451
211
|
|
|
452
|
-
# Normalize paths to cubic Beziers
|
|
212
|
+
# Normalize all paths to cubic Beziers
|
|
453
213
|
svg-matrix normalize input.svg -o output.svg
|
|
454
214
|
|
|
455
|
-
# Show
|
|
215
|
+
# Show file information
|
|
456
216
|
svg-matrix info input.svg
|
|
457
217
|
```
|
|
458
218
|
|
|
459
|
-
|
|
219
|
+
**Options:**
|
|
460
220
|
|
|
461
|
-
| Option |
|
|
462
|
-
|
|
463
|
-
| `-o
|
|
464
|
-
| `-
|
|
465
|
-
| `-
|
|
466
|
-
| `-p
|
|
467
|
-
| `-
|
|
468
|
-
| `-
|
|
469
|
-
|
|
|
470
|
-
|
|
|
471
|
-
| `--
|
|
221
|
+
| Option | What It Does |
|
|
222
|
+
|--------|--------------|
|
|
223
|
+
| `-o file.svg` | Output file |
|
|
224
|
+
| `-r` | Process folders recursively |
|
|
225
|
+
| `-f` | Overwrite existing files |
|
|
226
|
+
| `-p N` | Decimal precision (default: 6, max: 50) |
|
|
227
|
+
| `-q` | Quiet mode |
|
|
228
|
+
| `-v` | Verbose mode |
|
|
229
|
+
| `--transform-only` | Only flatten transforms (faster) |
|
|
230
|
+
| `--no-clip-paths` | Skip clip-path processing |
|
|
231
|
+
| `--no-masks` | Skip mask processing |
|
|
472
232
|
|
|
473
|
-
|
|
233
|
+
Run `svg-matrix --help` for all options.
|
|
474
234
|
|
|
475
|
-
|
|
235
|
+
### `svglinter` - Find problems in SVG files
|
|
476
236
|
|
|
237
|
+
```bash
|
|
238
|
+
svglinter myfile.svg # Check one file
|
|
239
|
+
svglinter icons/ # Check all SVGs in folder
|
|
240
|
+
svglinter --fix icons/ # Auto-fix problems
|
|
241
|
+
svglinter --errors-only icons/ # Only show errors
|
|
477
242
|
```
|
|
478
|
-
# This is a comment
|
|
479
|
-
./folder1/file1.svg
|
|
480
|
-
./folder2/file2.svg
|
|
481
|
-
./entire-folder/
|
|
482
|
-
```
|
|
483
|
-
|
|
484
|
-
## Quick Start
|
|
485
243
|
|
|
486
|
-
|
|
487
|
-
import { Decimal, Matrix, Vector, Transforms2D, SVGFlatten } from '@emasoft/svg-matrix';
|
|
488
|
-
|
|
489
|
-
Decimal.set({ precision: 80 });
|
|
490
|
-
|
|
491
|
-
// Compose transforms (right-to-left: scale first, then rotate, then translate)
|
|
492
|
-
const M = Transforms2D.translation(10, 20)
|
|
493
|
-
.mul(Transforms2D.rotate(Math.PI / 4))
|
|
494
|
-
.mul(Transforms2D.scale(2));
|
|
244
|
+
Finds: broken references, invalid colors, typos in element names, missing attributes.
|
|
495
245
|
|
|
496
|
-
|
|
497
|
-
const [x, y] = Transforms2D.applyTransform(M, 1, 0);
|
|
498
|
-
|
|
499
|
-
// Round-trip with inverse
|
|
500
|
-
const [xBack, yBack] = Transforms2D.applyTransform(M.inverse(), x, y);
|
|
501
|
-
```
|
|
246
|
+
See [full svglinter documentation](docs/SVGLINTER.md).
|
|
502
247
|
|
|
503
248
|
---
|
|
504
249
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
250
|
+
<!-- Geometric divider: Triangle construction -->
|
|
251
|
+
<p align="center">
|
|
252
|
+
<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='20' viewBox='0 0 400 20'%3E%3Cg stroke='%23ccc' stroke-width='0.5' fill='none'%3E%3Cline x1='0' y1='10' x2='150' y2='10'/%3E%3Cpolygon points='175,5 200,15 175,15'/%3E%3Cline x1='175' y1='5' x2='187.5' y2='10' stroke-dasharray='2,2'/%3E%3Cpolygon points='210,15 235,5 235,15'/%3E%3Cline x1='235' y1='5' x2='222.5' y2='10' stroke-dasharray='2,2'/%3E%3Cline x1='250' y1='10' x2='400' y2='10'/%3E%3C/g%3E%3C/svg%3E" alt=""/>
|
|
253
|
+
</p>
|
|
508
254
|
|
|
509
|
-
|
|
255
|
+
## SVG Toolbox API Quick Reference
|
|
510
256
|
|
|
511
|
-
|
|
512
|
-
import { Vector } from '@emasoft/svg-matrix';
|
|
513
|
-
|
|
514
|
-
const v = Vector.from([1, 2, 3]);
|
|
515
|
-
const w = Vector.from([4, 5, 6]);
|
|
516
|
-
|
|
517
|
-
v.add(w) // Element-wise addition
|
|
518
|
-
v.sub(w) // Element-wise subtraction
|
|
519
|
-
v.scale(2) // Scalar multiplication
|
|
520
|
-
v.dot(w) // Dot product → Decimal
|
|
521
|
-
v.cross(w) // Cross product (3D)
|
|
522
|
-
v.norm() // Euclidean length
|
|
523
|
-
v.normalize() // Unit vector
|
|
524
|
-
v.angleBetween(w) // Angle in radians
|
|
525
|
-
v.projectOnto(w) // Vector projection
|
|
526
|
-
v.orthogonal() // Perpendicular vector
|
|
527
|
-
v.distance(w) // Euclidean distance
|
|
528
|
-
v.toNumberArray() // [1, 2, 3]
|
|
529
|
-
```
|
|
257
|
+
### GeometryToPath
|
|
530
258
|
|
|
531
|
-
|
|
259
|
+
Convert shapes to path data:
|
|
532
260
|
|
|
533
261
|
```js
|
|
534
|
-
import {
|
|
535
|
-
|
|
536
|
-
const A = Matrix.from([[1, 2], [3, 4]]);
|
|
537
|
-
const I = Matrix.identity(3);
|
|
538
|
-
const Z = Matrix.zeros(2, 3);
|
|
539
|
-
|
|
540
|
-
A.add(B) // Element-wise addition
|
|
541
|
-
A.sub(B) // Element-wise subtraction
|
|
542
|
-
A.mul(B) // Matrix multiplication
|
|
543
|
-
A.transpose() // Transpose
|
|
544
|
-
A.trace() // Sum of diagonal
|
|
545
|
-
A.determinant() // Determinant
|
|
546
|
-
A.inverse() // Matrix inverse
|
|
547
|
-
A.solve([1, 1]) // Solve Ax = b
|
|
548
|
-
A.lu() // { L, U, P } decomposition
|
|
549
|
-
A.qr() // { Q, R } decomposition
|
|
550
|
-
A.exp() // Matrix exponential
|
|
551
|
-
A.applyToVector(v) // Matrix-vector product
|
|
552
|
-
```
|
|
553
|
-
|
|
554
|
-
### Transforms
|
|
555
|
-
|
|
556
|
-
#### 2D (3x3 matrices)
|
|
557
|
-
|
|
558
|
-
```js
|
|
559
|
-
import { Transforms2D } from '@emasoft/svg-matrix';
|
|
560
|
-
|
|
561
|
-
Transforms2D.translation(tx, ty)
|
|
562
|
-
Transforms2D.scale(sx, sy) // sy defaults to sx
|
|
563
|
-
Transforms2D.rotate(theta) // radians
|
|
564
|
-
Transforms2D.rotateAroundPoint(theta, px, py)
|
|
565
|
-
Transforms2D.skew(ax, ay)
|
|
566
|
-
Transforms2D.stretchAlongAxis(ux, uy, k)
|
|
567
|
-
Transforms2D.reflectX() // flip across X axis
|
|
568
|
-
Transforms2D.reflectY() // flip across Y axis
|
|
569
|
-
Transforms2D.reflectOrigin()
|
|
570
|
-
|
|
571
|
-
// Apply to point
|
|
572
|
-
const [x, y] = Transforms2D.applyTransform(matrix, px, py);
|
|
573
|
-
```
|
|
574
|
-
|
|
575
|
-
#### 3D (4x4 matrices)
|
|
262
|
+
import { GeometryToPath } from '@emasoft/svg-matrix';
|
|
576
263
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
Transforms3D.translation(tx, ty, tz)
|
|
581
|
-
Transforms3D.scale(sx, sy, sz)
|
|
582
|
-
Transforms3D.rotateX(theta)
|
|
583
|
-
Transforms3D.rotateY(theta)
|
|
584
|
-
Transforms3D.rotateZ(theta)
|
|
585
|
-
Transforms3D.rotateAroundAxis(ux, uy, uz, theta)
|
|
586
|
-
Transforms3D.rotateAroundPoint(ux, uy, uz, theta, px, py, pz)
|
|
587
|
-
Transforms3D.reflectXY() // flip Z
|
|
588
|
-
Transforms3D.reflectXZ() // flip Y
|
|
589
|
-
Transforms3D.reflectYZ() // flip X
|
|
590
|
-
|
|
591
|
-
const [x, y, z] = Transforms3D.applyTransform(matrix, px, py, pz);
|
|
264
|
+
const circle = GeometryToPath.circleToPathData(50, 50, 25);
|
|
265
|
+
const rect = GeometryToPath.rectToPathData(0, 0, 100, 50, 5, 5);
|
|
266
|
+
const ellipse = GeometryToPath.ellipseToPathData(50, 50, 30, 20);
|
|
592
267
|
```
|
|
593
268
|
|
|
594
|
-
###
|
|
269
|
+
### SVGFlatten
|
|
595
270
|
|
|
596
|
-
|
|
271
|
+
Parse and transform SVG data:
|
|
597
272
|
|
|
598
273
|
```js
|
|
599
274
|
import { SVGFlatten } from '@emasoft/svg-matrix';
|
|
600
275
|
|
|
601
|
-
// Parse transform
|
|
602
|
-
const
|
|
603
|
-
|
|
604
|
-
// Build CTM from transform stack
|
|
605
|
-
const ctm = SVGFlatten.buildCTM([
|
|
606
|
-
'scale(1.5)',
|
|
607
|
-
'translate(-13.6, -10.2)',
|
|
608
|
-
'rotate(15)',
|
|
609
|
-
'matrix(0.716, 0, 0, 1.397, 0, 0)'
|
|
610
|
-
]);
|
|
611
|
-
|
|
612
|
-
// Apply to point
|
|
613
|
-
const { x, y } = SVGFlatten.applyToPoint(ctm, 10, 10);
|
|
276
|
+
// Parse transform string
|
|
277
|
+
const matrix = SVGFlatten.parseTransformAttribute('rotate(45) scale(2)');
|
|
614
278
|
|
|
615
279
|
// Transform path data
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
// viewBox handling
|
|
619
|
-
const viewBox = SVGFlatten.parseViewBox('0 0 100 100');
|
|
620
|
-
const par = SVGFlatten.parsePreserveAspectRatio('xMidYMid meet');
|
|
621
|
-
const vbTransform = SVGFlatten.computeViewBoxTransform(viewBox, 800, 600, par);
|
|
622
|
-
|
|
623
|
-
// Full CTM with viewBox + nested transforms
|
|
624
|
-
const fullCtm = SVGFlatten.buildFullCTM([
|
|
625
|
-
{ type: 'svg', width: 800, height: 600, viewBox: '0 0 400 300' },
|
|
626
|
-
{ type: 'g', transform: 'translate(50, 50)' },
|
|
627
|
-
{ type: 'g', transform: 'rotate(45)' },
|
|
628
|
-
{ type: 'element', transform: 'scale(2)' }
|
|
629
|
-
]);
|
|
630
|
-
|
|
631
|
-
// Unit resolution (px, %, em, pt, in, cm, mm, pc)
|
|
632
|
-
SVGFlatten.resolveLength('50%', 800); // → 400
|
|
633
|
-
SVGFlatten.resolveLength('1in', 800); // → 96
|
|
634
|
-
|
|
635
|
-
// objectBoundingBox transform
|
|
636
|
-
const bboxTransform = SVGFlatten.objectBoundingBoxTransform(100, 50, 200, 100);
|
|
637
|
-
```
|
|
638
|
-
|
|
639
|
-
#### GeometryToPath - Shape Conversion
|
|
640
|
-
|
|
641
|
-
```js
|
|
642
|
-
import { GeometryToPath } from '@emasoft/svg-matrix';
|
|
643
|
-
|
|
644
|
-
// Shape to path
|
|
645
|
-
GeometryToPath.circleToPathData(cx, cy, r, precision)
|
|
646
|
-
GeometryToPath.ellipseToPathData(cx, cy, rx, ry, precision)
|
|
647
|
-
GeometryToPath.rectToPathData(x, y, w, h, rx, ry, useArcs, precision)
|
|
648
|
-
GeometryToPath.lineToPathData(x1, y1, x2, y2, precision)
|
|
649
|
-
GeometryToPath.polylineToPathData(points, precision)
|
|
650
|
-
GeometryToPath.polygonToPathData(points, precision)
|
|
651
|
-
GeometryToPath.convertElementToPath(element, precision)
|
|
652
|
-
|
|
653
|
-
// Path manipulation
|
|
654
|
-
GeometryToPath.parsePathData(pathData) // → [{command, args}]
|
|
655
|
-
GeometryToPath.pathArrayToString(commands) // → path string
|
|
656
|
-
GeometryToPath.pathToAbsolute(pathData) // relative → absolute
|
|
657
|
-
GeometryToPath.pathToCubics(pathData) // all → cubic Beziers
|
|
658
|
-
GeometryToPath.transformPathData(pathData, matrix, precision)
|
|
659
|
-
|
|
660
|
-
// Bezier kappa constant: 4*(sqrt(2)-1)/3
|
|
661
|
-
GeometryToPath.getKappa()
|
|
662
|
-
```
|
|
663
|
-
|
|
664
|
-
#### BrowserVerify - Chrome Verification
|
|
280
|
+
const newPath = SVGFlatten.transformPathData('M 0 0 L 100 100', matrix);
|
|
665
281
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
// One-off verification
|
|
670
|
-
await BrowserVerify.verifyViewBox(800, 600, '0 0 400 300', 'xMidYMid meet');
|
|
671
|
-
await BrowserVerify.verifyTransform('rotate(45) translate(100, 50) scale(2)');
|
|
672
|
-
|
|
673
|
-
// Session-based verification
|
|
674
|
-
const verifier = new BrowserVerify.BrowserVerifier();
|
|
675
|
-
await verifier.init({ headless: true });
|
|
676
|
-
await verifier.verifyViewBoxTransform(800, 600, '0 0 100 100');
|
|
677
|
-
await verifier.verifyMatrix(ctm, { width: 100, height: 100, transform: '...' });
|
|
678
|
-
await verifier.verifyPointTransform(ctm, 10, 20, config);
|
|
679
|
-
await verifier.close();
|
|
680
|
-
|
|
681
|
-
// Standard test suite (28 tests including W3C issue #215 cases)
|
|
682
|
-
await BrowserVerify.runStandardTests({ verbose: true });
|
|
282
|
+
// Resolve CSS units
|
|
283
|
+
SVGFlatten.resolveLength('50%', 800); // 400
|
|
284
|
+
SVGFlatten.resolveLength('1in', 96); // 96
|
|
683
285
|
```
|
|
684
286
|
|
|
685
|
-
###
|
|
686
|
-
|
|
687
|
-
#### PolygonClip
|
|
688
|
-
|
|
689
|
-
```js
|
|
690
|
-
import { PolygonClip } from '@emasoft/svg-matrix';
|
|
691
|
-
|
|
692
|
-
const square = [
|
|
693
|
-
PolygonClip.point(0, 0),
|
|
694
|
-
PolygonClip.point(2, 0),
|
|
695
|
-
PolygonClip.point(2, 2),
|
|
696
|
-
PolygonClip.point(0, 2)
|
|
697
|
-
];
|
|
698
|
-
|
|
699
|
-
// Boolean operations
|
|
700
|
-
PolygonClip.polygonIntersection(poly1, poly2)
|
|
701
|
-
PolygonClip.polygonUnion(poly1, poly2)
|
|
702
|
-
PolygonClip.polygonDifference(poly1, poly2)
|
|
703
|
-
|
|
704
|
-
// Properties
|
|
705
|
-
PolygonClip.polygonArea(polygon)
|
|
706
|
-
PolygonClip.isCounterClockwise(polygon)
|
|
707
|
-
PolygonClip.isConvex(polygon)
|
|
708
|
-
PolygonClip.pointInPolygon(point, polygon) // 1=inside, 0=boundary, -1=outside
|
|
709
|
-
|
|
710
|
-
// Convex hull
|
|
711
|
-
PolygonClip.convexHull(points)
|
|
712
|
-
|
|
713
|
-
// Bounding box
|
|
714
|
-
PolygonClip.boundingBox(polygon) // {minX, minY, maxX, maxY}
|
|
715
|
-
PolygonClip.bboxIntersects(bbox1, bbox2)
|
|
716
|
-
```
|
|
287
|
+
### Validation
|
|
717
288
|
|
|
718
|
-
|
|
289
|
+
Find and fix problems:
|
|
719
290
|
|
|
720
291
|
```js
|
|
721
|
-
import {
|
|
722
|
-
|
|
723
|
-
// Parse and resolve clipPath
|
|
724
|
-
const clipData = ClipPathResolver.parseClipPathElement(element);
|
|
725
|
-
const clipPolygon = ClipPathResolver.resolveClipPath(clipData, targetBBox);
|
|
726
|
-
|
|
727
|
-
// Shape to polygon
|
|
728
|
-
ClipPathResolver.shapeToPolygon({ type: 'circle', cx: 100, cy: 100, r: 50 }, { samples: 32 })
|
|
729
|
-
ClipPathResolver.pathToPolygon(pathData, { samples: 20 })
|
|
730
|
-
ClipPathResolver.polygonToPathData(polygon)
|
|
731
|
-
|
|
732
|
-
// Apply clipPath to element
|
|
733
|
-
ClipPathResolver.applyClipPath(elementData, clipPathData, targetBBox)
|
|
734
|
-
```
|
|
735
|
-
|
|
736
|
-
### SVG Element Resolution
|
|
292
|
+
import { validateSvg, fixInvalidSvg } from '@emasoft/svg-matrix';
|
|
737
293
|
|
|
738
|
-
|
|
294
|
+
const result = await validateSvg('icon.svg');
|
|
295
|
+
console.log(result.valid); // true/false
|
|
296
|
+
console.log(result.issues); // Array of problems
|
|
739
297
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
const useData = UseSymbolResolver.parseUseElement(useElement);
|
|
744
|
-
const symbolData = UseSymbolResolver.parseSymbolElement(symbolElement);
|
|
745
|
-
const resolved = UseSymbolResolver.resolveUse(useData, svgDocument);
|
|
746
|
-
const flattened = UseSymbolResolver.flattenResolvedUse(resolved);
|
|
747
|
-
UseSymbolResolver.resolveAllUses(svgDocument)
|
|
298
|
+
const fixed = await fixInvalidSvg('broken.svg');
|
|
299
|
+
console.log(fixed.svg); // Fixed SVG string
|
|
748
300
|
```
|
|
749
301
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
```js
|
|
753
|
-
import { MarkerResolver } from '@emasoft/svg-matrix';
|
|
754
|
-
|
|
755
|
-
const markerData = MarkerResolver.parseMarkerElement(markerElement);
|
|
756
|
-
const vertices = MarkerResolver.getPathVertices(pathData);
|
|
757
|
-
const transform = MarkerResolver.getMarkerTransform(markerData, vertex, angle, strokeWidth);
|
|
758
|
-
const instances = MarkerResolver.resolveMarkers(pathD, { 'marker-start': m1, 'marker-end': m2, strokeWidth: 2 });
|
|
759
|
-
MarkerResolver.markersToPathData(instances)
|
|
760
|
-
```
|
|
302
|
+
### Exclusive Features (Not in SVGO)
|
|
761
303
|
|
|
762
|
-
|
|
304
|
+
| Function | Description |
|
|
305
|
+
|----------|-------------|
|
|
306
|
+
| `flattenClipPaths()` | Flatten clip-paths to geometry |
|
|
307
|
+
| `flattenMasks()` | Flatten masks to geometry |
|
|
308
|
+
| `flattenGradients()` | Bake gradients into fills |
|
|
309
|
+
| `flattenPatterns()` | Expand pattern tiles |
|
|
310
|
+
| `flattenUseElements()` | Inline use/symbol references |
|
|
311
|
+
| `textToPath()` | Convert text to path outlines |
|
|
312
|
+
| `detectCollisions()` | GJK collision detection |
|
|
313
|
+
| `validateSVG()` | W3C schema validation |
|
|
314
|
+
| `decomposeTransform()` | Matrix decomposition |
|
|
763
315
|
|
|
764
|
-
|
|
765
|
-
import { PatternResolver } from '@emasoft/svg-matrix';
|
|
316
|
+
### Attribute Preservation
|
|
766
317
|
|
|
767
|
-
|
|
768
|
-
const tiles = PatternResolver.resolvePattern(patternData, targetBBox);
|
|
769
|
-
PatternResolver.applyPattern(elementData, patternData, targetBBox)
|
|
770
|
-
PatternResolver.patternToClipPath(patternData, targetBBox)
|
|
771
|
-
PatternResolver.patternToPathData(patternData, targetBBox)
|
|
772
|
-
```
|
|
318
|
+
When converting shapes or flattening transforms, ALL attributes are preserved:
|
|
773
319
|
|
|
774
|
-
|
|
320
|
+
| Category | Attributes |
|
|
321
|
+
|----------|------------|
|
|
322
|
+
| **Critical** | `clip-path`, `mask`, `filter`, `opacity` |
|
|
323
|
+
| **Markers** | `marker-start`, `marker-mid`, `marker-end` |
|
|
324
|
+
| **Paint** | `fill`, `stroke`, `fill-opacity`, `stroke-opacity` |
|
|
325
|
+
| **Stroke** | `stroke-width`, `stroke-dasharray`, `stroke-linecap` |
|
|
326
|
+
| **URL refs** | `url(#gradient)`, `url(#pattern)`, `url(#clip)` |
|
|
775
327
|
|
|
776
|
-
|
|
777
|
-
import { MaskResolver } from '@emasoft/svg-matrix';
|
|
328
|
+
> **Why this matters:** Many SVG tools silently drop `clip-path` and `mask` attributes, causing visual corruption. svg-matrix preserves everything.
|
|
778
329
|
|
|
779
|
-
|
|
780
|
-
const maskPolygon = MaskResolver.resolveMask(maskData, targetBBox);
|
|
781
|
-
MaskResolver.applyMask(elementPolygon, maskData, targetBBox)
|
|
782
|
-
MaskResolver.colorToLuminance({ r, g, b }) // sRGB luminance
|
|
783
|
-
```
|
|
330
|
+
---
|
|
784
331
|
|
|
785
|
-
|
|
332
|
+
<!-- Geometric divider: Angle construction -->
|
|
333
|
+
<p align="center">
|
|
334
|
+
<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='20' viewBox='0 0 400 20'%3E%3Cg stroke='%23ccc' stroke-width='0.5' fill='none'%3E%3Cline x1='0' y1='10' x2='160' y2='10'/%3E%3Cpath d='M180 15 L200 5 L220 15'/%3E%3Cpath d='M190 12 A7 7 0 0 1 195 8' stroke='%23999'/%3E%3Cline x1='240' y1='10' x2='400' y2='10'/%3E%3C/g%3E%3C/svg%3E" alt=""/>
|
|
335
|
+
</p>
|
|
786
336
|
|
|
787
|
-
|
|
337
|
+
## Precision Comparison
|
|
788
338
|
|
|
789
|
-
|
|
790
|
-
import { MeshGradient } from '@emasoft/svg-matrix';
|
|
339
|
+
svg-matrix vs standard JavaScript (float64):
|
|
791
340
|
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
341
|
+
| Operation | JS Error | svg-matrix Error | Improvement |
|
|
342
|
+
|-----------|----------|------------------|-------------|
|
|
343
|
+
| Point evaluation | `1.4e-14` | `0` (exact) | 14+ digits |
|
|
344
|
+
| Bezier tangent | `1.1e-16` | `< 1e-78` | 62+ digits |
|
|
345
|
+
| Arc length | `2.8e-13` | `< 1e-50` | 37+ digits |
|
|
346
|
+
| Bounding box | `1.1e-13` | `0` (exact) | 13+ digits |
|
|
347
|
+
| Self-intersection | Boolean only | `1.4e-58` | 58+ digits |
|
|
795
348
|
|
|
796
|
-
|
|
797
|
-
const patch = new MeshGradient.CoonsPatch(topEdge, rightEdge, bottomEdge, leftEdge, cornerColors);
|
|
798
|
-
const { point, color } = patch.evaluate(u, v);
|
|
349
|
+
---
|
|
799
350
|
|
|
800
|
-
|
|
801
|
-
const imageData = MeshGradient.rasterizeMeshGradient(meshData, width, height);
|
|
351
|
+
## Installation
|
|
802
352
|
|
|
803
|
-
|
|
804
|
-
const polygons = MeshGradient.meshGradientToPolygons(meshData, { subdivisions: 16 });
|
|
353
|
+
**Requires Node.js 24+** (released 2025)
|
|
805
354
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
const svgPaths = MeshGradient.clippedMeshToSVG(clipped);
|
|
355
|
+
```bash
|
|
356
|
+
npm install @emasoft/svg-matrix
|
|
809
357
|
```
|
|
810
358
|
|
|
811
|
-
|
|
359
|
+
### In JavaScript/TypeScript
|
|
812
360
|
|
|
813
361
|
```js
|
|
814
|
-
import {
|
|
815
|
-
import opentype from 'opentype.js';
|
|
816
|
-
|
|
817
|
-
const font = await opentype.load('font.ttf');
|
|
818
|
-
|
|
819
|
-
// Convert text to path
|
|
820
|
-
const pathData = TextToPath.textToPath("Hello", {
|
|
821
|
-
x: 100, y: 100,
|
|
822
|
-
fontSize: 24,
|
|
823
|
-
font: font,
|
|
824
|
-
textAnchor: TextToPath.TextAnchor.MIDDLE,
|
|
825
|
-
dominantBaseline: TextToPath.DominantBaseline.MIDDLE
|
|
826
|
-
});
|
|
827
|
-
|
|
828
|
-
// Parse text element
|
|
829
|
-
const textData = TextToPath.parseTextElement(textElement);
|
|
830
|
-
const result = TextToPath.textElementToPath(textData, { font });
|
|
831
|
-
|
|
832
|
-
// Measure text
|
|
833
|
-
const metrics = TextToPath.measureText("Hello", { fontSize: "20px" }, font);
|
|
834
|
-
const bbox = TextToPath.getTextBBox(textData);
|
|
362
|
+
import { Matrix, Vector, Transforms2D } from '@emasoft/svg-matrix';
|
|
835
363
|
```
|
|
836
364
|
|
|
837
|
-
###
|
|
838
|
-
|
|
839
|
-
Direct exports for common operations:
|
|
840
|
-
|
|
841
|
-
```js
|
|
842
|
-
import {
|
|
843
|
-
// Transforms
|
|
844
|
-
translate2D, rotate2D, scale2D, transform2D,
|
|
845
|
-
translate3D, scale3D, transform3D,
|
|
846
|
-
|
|
847
|
-
// Shape conversion
|
|
848
|
-
circleToPath, ellipseToPath, rectToPath, lineToPath,
|
|
849
|
-
polygonToPath, polylineToPath,
|
|
850
|
-
|
|
851
|
-
// Path manipulation
|
|
852
|
-
parsePath, pathToString, pathToAbsolute, pathToCubics, transformPath,
|
|
853
|
-
elementToPath,
|
|
854
|
-
|
|
855
|
-
// Matrix/Vector creation
|
|
856
|
-
identity, zeros, vec, mat,
|
|
857
|
-
|
|
858
|
-
// Precision control
|
|
859
|
-
setPrecision, getPrecision,
|
|
365
|
+
### In HTML (no install)
|
|
860
366
|
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
} from '
|
|
367
|
+
```html
|
|
368
|
+
<script type="module">
|
|
369
|
+
import { Matrix, Transforms2D } from 'https://esm.sh/@emasoft/svg-matrix';
|
|
370
|
+
</script>
|
|
864
371
|
```
|
|
865
372
|
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
Control library logging output:
|
|
869
|
-
|
|
870
|
-
```js
|
|
871
|
-
import { Logger, LogLevel, setLogLevel, enableFileLogging } from '@emasoft/svg-matrix';
|
|
373
|
+
> **Note:** Node.js 24+ is required for modern ECMAScript features. If you need older Node support, please [open an issue](https://github.com/Emasoft/SVG-MATRIX/issues).
|
|
872
374
|
|
|
873
|
-
|
|
874
|
-
setLogLevel(LogLevel.SILENT);
|
|
875
|
-
|
|
876
|
-
// Enable only errors
|
|
877
|
-
setLogLevel(LogLevel.ERROR);
|
|
878
|
-
|
|
879
|
-
// Enable warnings and errors (default)
|
|
880
|
-
setLogLevel(LogLevel.WARN);
|
|
375
|
+
---
|
|
881
376
|
|
|
882
|
-
|
|
883
|
-
setLogLevel(LogLevel.DEBUG);
|
|
377
|
+
## More Documentation
|
|
884
378
|
|
|
885
|
-
|
|
886
|
-
|
|
379
|
+
- [Full API Reference](API.md)
|
|
380
|
+
- [svglinter Documentation](docs/SVGLINTER.md)
|
|
381
|
+
- [Bezier Analysis Examples](test/bezier-analysis-example.js)
|
|
382
|
+
- [Path Analysis Examples](test/path-analysis-example.js)
|
|
887
383
|
|
|
888
|
-
|
|
889
|
-
Logger.level = LogLevel.INFO;
|
|
890
|
-
Logger.warn('Custom warning');
|
|
891
|
-
Logger.debug('Debug info');
|
|
892
|
-
```
|
|
384
|
+
---
|
|
893
385
|
|
|
894
|
-
|
|
386
|
+
<p align="center">
|
|
387
|
+
<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='30' viewBox='0 0 200 30'%3E%3Cg stroke='%23ddd' stroke-width='0.5' fill='none'%3E%3Cline x1='0' y1='15' x2='60' y2='15'/%3E%3Crect x='70' y='10' width='10' height='10' transform='rotate(45 75 15)'/%3E%3Ccircle cx='100' cy='15' r='5'/%3E%3Crect x='120' y='10' width='10' height='10' transform='rotate(45 125 15)'/%3E%3Cline x1='140' y1='15' x2='200' y2='15'/%3E%3C/g%3E%3C/svg%3E" alt=""/>
|
|
388
|
+
</p>
|
|
895
389
|
|
|
896
|
-
|
|
390
|
+
<p align="center">
|
|
391
|
+
<strong>MIT License</strong><br/>
|
|
392
|
+
<em>Built with mathematical precision</em>
|
|
393
|
+
</p>
|