@emasoft/svg-matrix 1.0.28 → 1.0.30
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 +325 -0
- package/bin/svg-matrix.js +985 -378
- package/bin/svglinter.cjs +4172 -433
- package/bin/svgm.js +723 -180
- package/package.json +16 -4
- package/src/animation-references.js +71 -52
- package/src/arc-length.js +160 -96
- package/src/bezier-analysis.js +257 -117
- package/src/bezier-intersections.js +411 -148
- package/src/browser-verify.js +240 -100
- package/src/clip-path-resolver.js +350 -142
- package/src/convert-path-data.js +279 -134
- package/src/css-specificity.js +78 -70
- package/src/flatten-pipeline.js +751 -263
- package/src/geometry-to-path.js +511 -182
- package/src/index.js +191 -46
- package/src/inkscape-support.js +18 -7
- package/src/marker-resolver.js +278 -164
- package/src/mask-resolver.js +209 -98
- package/src/matrix.js +147 -67
- package/src/mesh-gradient.js +187 -96
- package/src/off-canvas-detection.js +201 -104
- package/src/path-analysis.js +187 -107
- package/src/path-data-plugins.js +628 -167
- package/src/path-simplification.js +0 -1
- package/src/pattern-resolver.js +125 -88
- package/src/polygon-clip.js +111 -66
- package/src/svg-boolean-ops.js +194 -118
- package/src/svg-collections.js +22 -18
- package/src/svg-flatten.js +282 -164
- package/src/svg-parser.js +427 -200
- package/src/svg-rendering-context.js +147 -104
- package/src/svg-toolbox.js +16381 -3370
- package/src/svg2-polyfills.js +93 -224
- package/src/transform-decomposition.js +46 -41
- package/src/transform-optimization.js +89 -68
- package/src/transforms2d.js +49 -16
- package/src/transforms3d.js +58 -22
- package/src/use-symbol-resolver.js +150 -110
- package/src/vector.js +67 -15
- package/src/vendor/README.md +110 -0
- package/src/vendor/inkscape-hatch-polyfill.js +401 -0
- package/src/vendor/inkscape-hatch-polyfill.min.js +8 -0
- package/src/vendor/inkscape-mesh-polyfill.js +843 -0
- package/src/vendor/inkscape-mesh-polyfill.min.js +8 -0
- package/src/verification.js +288 -124
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Vendor Dependencies
|
|
2
|
+
|
|
3
|
+
This directory contains third-party code used by SVG-MATRIX for SVG 2.0 browser compatibility.
|
|
4
|
+
|
|
5
|
+
## Files
|
|
6
|
+
|
|
7
|
+
| File | Size | License | Author |
|
|
8
|
+
|------|------|---------|--------|
|
|
9
|
+
| inkscape-mesh-polyfill.js | ~35KB | GPLv3 | Tavmjong Bah |
|
|
10
|
+
| inkscape-mesh-polyfill.min.js | ~16KB | GPLv3 | Tavmjong Bah |
|
|
11
|
+
| inkscape-hatch-polyfill.js | ~10KB | CC0 | Valentin Ionita |
|
|
12
|
+
| inkscape-hatch-polyfill.min.js | ~5KB | CC0 | Valentin Ionita |
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## inkscape-mesh-polyfill.js / inkscape-mesh-polyfill.min.js
|
|
17
|
+
|
|
18
|
+
**Source:** https://gitlab.com/Tavmjong/mesh.js/
|
|
19
|
+
**Author:** Tavmjong Bah
|
|
20
|
+
**License:** GNU General Public License version 3 or later (GPLv3+)
|
|
21
|
+
**Copyright:** 2017 Tavmjong Bah
|
|
22
|
+
|
|
23
|
+
### Description
|
|
24
|
+
|
|
25
|
+
JavaScript polyfill for SVG 2.0 mesh gradients. Renders mesh gradients to HTML5 Canvas and converts them to images for browser compatibility.
|
|
26
|
+
|
|
27
|
+
### Features
|
|
28
|
+
|
|
29
|
+
- Multi-patch grid support (multiple meshrows and meshpatches)
|
|
30
|
+
- Bezier curve edge parsing (l, L, c, C path commands)
|
|
31
|
+
- Adaptive tessellation via de Casteljau subdivision
|
|
32
|
+
- gradientTransform support (translate, rotate, scale, skew, matrix)
|
|
33
|
+
- Proper shape clipping via clipPath
|
|
34
|
+
- objectBoundingBox and userSpaceOnUse gradientUnits
|
|
35
|
+
|
|
36
|
+
### License Notice
|
|
37
|
+
|
|
38
|
+
This file is distributed under the GNU General Public License version 3 or later.
|
|
39
|
+
See https://www.gnu.org/licenses/gpl-3.0.html for the full license text.
|
|
40
|
+
|
|
41
|
+
**IMPORTANT:** When using SVG-MATRIX with the `--svg2-polyfills` option on SVGs
|
|
42
|
+
containing mesh gradients, the generated SVG files will contain this GPLv3-licensed
|
|
43
|
+
polyfill code embedded in a `<script>` element. Distribution of such files must
|
|
44
|
+
comply with GPLv3 terms.
|
|
45
|
+
|
|
46
|
+
### Usage in SVG-MATRIX
|
|
47
|
+
|
|
48
|
+
The polyfill is automatically embedded when:
|
|
49
|
+
1. The SVG contains `<meshgradient>` elements
|
|
50
|
+
2. The `--svg2-polyfills` CLI option is used
|
|
51
|
+
|
|
52
|
+
The polyfill runs in the browser at page load and:
|
|
53
|
+
1. Detects elements using mesh gradient fills
|
|
54
|
+
2. Rasterizes each mesh gradient to a Canvas
|
|
55
|
+
3. Converts the Canvas to a PNG data URL
|
|
56
|
+
4. Creates an `<image>` element with proper clipping
|
|
57
|
+
5. Replaces the original fill with the rasterized image
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## inkscape-hatch-polyfill.js / inkscape-hatch-polyfill.min.js
|
|
62
|
+
|
|
63
|
+
**Source:** https://gitlab.com/inkscape/inkscape/-/tree/master/src/extension/internal/polyfill
|
|
64
|
+
**Author:** Valentin Ionita (2019)
|
|
65
|
+
**License:** CC0 / Public Domain
|
|
66
|
+
**Copyright:** None (dedicated to public domain)
|
|
67
|
+
|
|
68
|
+
### Description
|
|
69
|
+
|
|
70
|
+
JavaScript polyfill for SVG 2.0 hatch patterns. Converts SVG 2 `<hatch>` elements
|
|
71
|
+
to SVG 1.1 `<pattern>` elements for browser compatibility.
|
|
72
|
+
|
|
73
|
+
### Features
|
|
74
|
+
|
|
75
|
+
- Full SVG path command support (M, L, C, S, Q, T, A, H, V, Z)
|
|
76
|
+
- Both relative and absolute path coordinates
|
|
77
|
+
- objectBoundingBox and userSpaceOnUse coordinate systems
|
|
78
|
+
- Hatch rotation and transform support
|
|
79
|
+
- Multiple hatchpath elements with offset values
|
|
80
|
+
- Proper pitch and spacing calculations
|
|
81
|
+
- Pattern tiling and repetition
|
|
82
|
+
|
|
83
|
+
### License Notice
|
|
84
|
+
|
|
85
|
+
This file is dedicated to the public domain under CC0 (Creative Commons Zero).
|
|
86
|
+
See https://creativecommons.org/publicdomain/zero/1.0/ for details.
|
|
87
|
+
|
|
88
|
+
There are no restrictions on distribution or use of this polyfill.
|
|
89
|
+
|
|
90
|
+
### Usage in SVG-MATRIX
|
|
91
|
+
|
|
92
|
+
The polyfill is automatically embedded when:
|
|
93
|
+
1. The SVG contains `<hatch>` elements
|
|
94
|
+
2. The `--svg2-polyfills` CLI option is used
|
|
95
|
+
|
|
96
|
+
The polyfill runs in the browser at page load and:
|
|
97
|
+
1. Detects elements using hatch pattern fills
|
|
98
|
+
2. Calculates bounding boxes and pitch values
|
|
99
|
+
3. Generates equivalent `<pattern>` elements
|
|
100
|
+
4. Updates fill references to use the generated patterns
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Minified vs Full Versions
|
|
105
|
+
|
|
106
|
+
By default, SVG-MATRIX uses the minified polyfills for smaller file sizes:
|
|
107
|
+
- Mesh: ~16KB minified vs ~35KB full
|
|
108
|
+
- Hatch: ~5KB minified vs ~10KB full
|
|
109
|
+
|
|
110
|
+
Use `--no-minify-polyfills` to embed the full, readable versions (useful for debugging).
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
// SPDX-License-Identifier: CC0
|
|
2
|
+
/** @file
|
|
3
|
+
* Use patterns to render a hatch paint server via this polyfill
|
|
4
|
+
*//*
|
|
5
|
+
* Authors:
|
|
6
|
+
* - Valentin Ionita (2019)
|
|
7
|
+
* License: CC0 / Public Domain
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
(function () {
|
|
11
|
+
// Name spaces -----------------------------------
|
|
12
|
+
const svgNS = 'http://www.w3.org/2000/svg';
|
|
13
|
+
const xlinkNS = 'http://www.w3.org/1999/xlink';
|
|
14
|
+
const unitObjectBoundingBox = 'objectBoundingBox';
|
|
15
|
+
const unitUserSpace = 'userSpaceOnUse';
|
|
16
|
+
|
|
17
|
+
// Set multiple attributes to an element
|
|
18
|
+
const setAttributes = (el, attrs) => {
|
|
19
|
+
for (let key in attrs) {
|
|
20
|
+
el.setAttribute(key, attrs[key]);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Copy attributes from the hatch with 'id' to the current element
|
|
25
|
+
const setReference = (el, id) => {
|
|
26
|
+
const attr = [
|
|
27
|
+
'x', 'y', 'pitch', 'rotate',
|
|
28
|
+
'hatchUnits', 'hatchContentUnits', 'transform'
|
|
29
|
+
];
|
|
30
|
+
const template = document.getElementById(id.slice(1));
|
|
31
|
+
|
|
32
|
+
if (template && template.nodeName === 'hatch') {
|
|
33
|
+
attr.forEach(a => {
|
|
34
|
+
let t = template.getAttribute(a);
|
|
35
|
+
if (el.getAttribute(a) === null && t !== null) {
|
|
36
|
+
el.setAttribute(a, t);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
if (el.children.length === 0) {
|
|
41
|
+
Array.from(template.children).forEach(c => {
|
|
42
|
+
el.appendChild(c.cloneNode(true));
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Order pain-order of hatchpaths relative to their pitch
|
|
49
|
+
const orderHatchPaths = (paths) => {
|
|
50
|
+
const nodeArray = [];
|
|
51
|
+
paths.forEach(p => nodeArray.push(p));
|
|
52
|
+
|
|
53
|
+
return nodeArray.sort((a, b) =>
|
|
54
|
+
// (pitch - a.offset) - (pitch - b.offset)
|
|
55
|
+
Number(b.getAttribute('offset')) - Number(a.getAttribute('offset'))
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Generate x-axis coordinates for the pattern paths
|
|
60
|
+
const generatePositions = (width, diagonal, initial, distance) => {
|
|
61
|
+
const offset = (diagonal - width) / 2;
|
|
62
|
+
const leftDistance = initial + offset;
|
|
63
|
+
const rightDistance = width + offset + distance;
|
|
64
|
+
const units = Math.round(leftDistance / distance) + 1;
|
|
65
|
+
let array = [];
|
|
66
|
+
|
|
67
|
+
for (let i = initial - units * distance; i < rightDistance; i += distance) {
|
|
68
|
+
array.push(i);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return array;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// Turn a path array into a tokenized version of it
|
|
75
|
+
const parsePath = (data) => {
|
|
76
|
+
let array = [];
|
|
77
|
+
let i = 0;
|
|
78
|
+
let len = data.length;
|
|
79
|
+
let last = 0;
|
|
80
|
+
|
|
81
|
+
/*
|
|
82
|
+
* Last state (last) index map
|
|
83
|
+
* 0 => ()
|
|
84
|
+
* 1 => (x y)
|
|
85
|
+
* 2 => (x)
|
|
86
|
+
* 3 => (y)
|
|
87
|
+
* 4 => (x1 y1 x2 y2 x y)
|
|
88
|
+
* 5 => (x2 y2 x y)
|
|
89
|
+
* 6 => (_ _ _ _ _ x y)
|
|
90
|
+
* 7 => (_)
|
|
91
|
+
*/
|
|
92
|
+
|
|
93
|
+
while (i < len) {
|
|
94
|
+
switch (data[i].toUpperCase()) {
|
|
95
|
+
case 'Z':
|
|
96
|
+
array.push(data[i]);
|
|
97
|
+
i += 1;
|
|
98
|
+
last = 0;
|
|
99
|
+
break;
|
|
100
|
+
case 'M':
|
|
101
|
+
case 'L':
|
|
102
|
+
case 'T':
|
|
103
|
+
array.push(data[i], new Point(Number(data[i + 1]), Number(data[i + 2])));
|
|
104
|
+
i += 3;
|
|
105
|
+
last = 1;
|
|
106
|
+
break;
|
|
107
|
+
case 'H':
|
|
108
|
+
array.push(data[i], new Point(Number(data[i + 1]), null));
|
|
109
|
+
i += 2;
|
|
110
|
+
last = 2;
|
|
111
|
+
break;
|
|
112
|
+
case 'V':
|
|
113
|
+
array.push(data[i], new Point(null, Number(data[i + 1])));
|
|
114
|
+
i += 2;
|
|
115
|
+
last = 3;
|
|
116
|
+
break;
|
|
117
|
+
case 'C':
|
|
118
|
+
array.push(
|
|
119
|
+
data[i], new Point(Number(data[i + 1]), Number(data[i + 2])),
|
|
120
|
+
new Point(Number(data[i + 3]), Number(data[i + 4])),
|
|
121
|
+
new Point(Number(data[i + 5]), Number(data[i + 6]))
|
|
122
|
+
);
|
|
123
|
+
i += 7;
|
|
124
|
+
last = 4;
|
|
125
|
+
break;
|
|
126
|
+
case 'S':
|
|
127
|
+
case 'Q':
|
|
128
|
+
array.push(
|
|
129
|
+
data[i], new Point(Number(data[i + 1]), Number(data[i + 2])),
|
|
130
|
+
new Point(Number(data[i + 3]), Number(data[i + 4]))
|
|
131
|
+
);
|
|
132
|
+
i += 5;
|
|
133
|
+
last = 5;
|
|
134
|
+
break;
|
|
135
|
+
case 'A':
|
|
136
|
+
array.push(
|
|
137
|
+
data[i], data[i + 1], data[i + 2], data[i + 3], data[i + 4],
|
|
138
|
+
data[i + 5], new Point(Number(data[i + 6]), Number(data[i + 7]))
|
|
139
|
+
);
|
|
140
|
+
i += 8;
|
|
141
|
+
last = 6;
|
|
142
|
+
break;
|
|
143
|
+
case 'B':
|
|
144
|
+
array.push(data[i], data[i + 1]);
|
|
145
|
+
i += 2;
|
|
146
|
+
last = 7;
|
|
147
|
+
break;
|
|
148
|
+
default:
|
|
149
|
+
switch (last) {
|
|
150
|
+
case 1:
|
|
151
|
+
array.push(new Point(Number(data[i]), Number(data[i + 1])));
|
|
152
|
+
i += 2;
|
|
153
|
+
break;
|
|
154
|
+
case 2:
|
|
155
|
+
array.push(new Point(Number(data[i]), null));
|
|
156
|
+
i += 1;
|
|
157
|
+
break;
|
|
158
|
+
case 3:
|
|
159
|
+
array.push(new Point(null, Number(data[i])));
|
|
160
|
+
i += 1;
|
|
161
|
+
break;
|
|
162
|
+
case 4:
|
|
163
|
+
array.push(
|
|
164
|
+
new Point(Number(data[i]), Number(data[i + 1])),
|
|
165
|
+
new Point(Number(data[i + 2]), Number(data[i + 3])),
|
|
166
|
+
new Point(Number(data[i + 4]), Number(data[i + 5]))
|
|
167
|
+
);
|
|
168
|
+
i += 6;
|
|
169
|
+
break;
|
|
170
|
+
case 5:
|
|
171
|
+
array.push(
|
|
172
|
+
new Point(Number(data[i]), Number(data[i + 1])),
|
|
173
|
+
new Point(Number(data[i + 2]), Number(data[i + 3]))
|
|
174
|
+
);
|
|
175
|
+
i += 4;
|
|
176
|
+
break;
|
|
177
|
+
case 6:
|
|
178
|
+
array.push(
|
|
179
|
+
data[i], data[i + 1], data[i + 2], data[i + 3], data[i + 4],
|
|
180
|
+
new Point(Number(data[i + 5]), Number(data[i + 6]))
|
|
181
|
+
);
|
|
182
|
+
i += 7;
|
|
183
|
+
break;
|
|
184
|
+
default:
|
|
185
|
+
array.push(data[i]);
|
|
186
|
+
i += 1;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return array;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const getYDistance = (hatchpath) => {
|
|
195
|
+
const path = document.createElementNS(svgNS, 'path');
|
|
196
|
+
let d = hatchpath.getAttribute('d');
|
|
197
|
+
|
|
198
|
+
if (d[0].toUpperCase() !== 'M') {
|
|
199
|
+
d = `M 0,0 ${d}`;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
path.setAttribute('d', d);
|
|
203
|
+
|
|
204
|
+
return path.getPointAtLength(path.getTotalLength()).y -
|
|
205
|
+
path.getPointAtLength(0).y;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
// Point class --------------------------------------
|
|
209
|
+
class Point {
|
|
210
|
+
constructor (x, y) {
|
|
211
|
+
this.x = x;
|
|
212
|
+
this.y = y;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
toString () {
|
|
216
|
+
return `${this.x} ${this.y}`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
isPoint () {
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
clone () {
|
|
224
|
+
return new Point(this.x, this.y);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
add (v) {
|
|
228
|
+
return new Point(this.x + v.x, this.y + v.y);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
distSquared (v) {
|
|
232
|
+
let x = this.x - v.x;
|
|
233
|
+
let y = this.y - v.y;
|
|
234
|
+
return (x * x + y * y);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Start of document processing ---------------------
|
|
239
|
+
const shapes = document.querySelectorAll('rect,circle,ellipse,path,text');
|
|
240
|
+
|
|
241
|
+
shapes.forEach((shape, i) => {
|
|
242
|
+
// Get id. If no id, create one.
|
|
243
|
+
let shapeId = shape.getAttribute('id');
|
|
244
|
+
if (!shapeId) {
|
|
245
|
+
shapeId = 'hatch_shape_' + i;
|
|
246
|
+
shape.setAttribute('id', shapeId);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const fill = shape.getAttribute('fill') || shape.style.fill;
|
|
250
|
+
const fillURL = fill.match(/^url\(\s*"?\s*#([^\s"]+)"?\s*\)/);
|
|
251
|
+
|
|
252
|
+
if (fillURL && fillURL[1]) {
|
|
253
|
+
const hatch = document.getElementById(fillURL[1]);
|
|
254
|
+
|
|
255
|
+
if (hatch && hatch.nodeName === 'hatch') {
|
|
256
|
+
const href = hatch.getAttributeNS(xlinkNS, 'href');
|
|
257
|
+
|
|
258
|
+
if (href !== null && href !== '') {
|
|
259
|
+
setReference(hatch, href);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Degenerate hatch, with no hatchpath children
|
|
263
|
+
if (hatch.children.length === 0) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const bbox = shape.getBBox();
|
|
268
|
+
const hatchDiag = Math.ceil(Math.sqrt(
|
|
269
|
+
bbox.width * bbox.width + bbox.height * bbox.height
|
|
270
|
+
));
|
|
271
|
+
|
|
272
|
+
// Hatch variables
|
|
273
|
+
const units = hatch.getAttribute('hatchUnits') || unitObjectBoundingBox;
|
|
274
|
+
const contentUnits = hatch.getAttribute('hatchContentUnits') || unitUserSpace;
|
|
275
|
+
const rotate = Number(hatch.getAttribute('rotate')) || 0;
|
|
276
|
+
const transform = hatch.getAttribute('transform') ||
|
|
277
|
+
hatch.getAttribute('hatchTransform') || '';
|
|
278
|
+
const hatchpaths = orderHatchPaths(hatch.querySelectorAll('hatchpath,hatchPath'));
|
|
279
|
+
const x = units === unitObjectBoundingBox
|
|
280
|
+
? (Number(hatch.getAttribute('x')) * bbox.width) || 0
|
|
281
|
+
: Number(hatch.getAttribute('x')) || 0;
|
|
282
|
+
const y = units === unitObjectBoundingBox
|
|
283
|
+
? (Number(hatch.getAttribute('y')) * bbox.width) || 0
|
|
284
|
+
: Number(hatch.getAttribute('y')) || 0;
|
|
285
|
+
let pitch = units === unitObjectBoundingBox
|
|
286
|
+
? (Number(hatch.getAttribute('pitch')) * bbox.width) || 0
|
|
287
|
+
: Number(hatch.getAttribute('pitch')) || 0;
|
|
288
|
+
|
|
289
|
+
if (contentUnits === unitObjectBoundingBox && bbox.height) {
|
|
290
|
+
pitch /= bbox.height;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// A negative value is an error.
|
|
294
|
+
// A value of zero disables rendering of the element
|
|
295
|
+
if (pitch <= 0) {
|
|
296
|
+
console.error('Non-positive pitch');
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Pattern variables
|
|
301
|
+
const pattern = document.createElementNS(svgNS, 'pattern');
|
|
302
|
+
const patternId = `${fillURL[1]}_pattern`;
|
|
303
|
+
let patternWidth = bbox.width - bbox.width % pitch;
|
|
304
|
+
let patternHeight = 0;
|
|
305
|
+
|
|
306
|
+
const xPositions = generatePositions(patternWidth, hatchDiag, x, pitch);
|
|
307
|
+
|
|
308
|
+
hatchpaths.forEach(hatchpath => {
|
|
309
|
+
let offset = Number(hatchpath.getAttribute('offset')) || 0;
|
|
310
|
+
offset = offset > pitch ? (offset % pitch) : offset;
|
|
311
|
+
const currentXPositions = xPositions.map(p => p + offset);
|
|
312
|
+
|
|
313
|
+
const path = document.createElementNS(svgNS, 'path');
|
|
314
|
+
let d = '';
|
|
315
|
+
|
|
316
|
+
for (let j = 0; j < hatchpath.attributes.length; ++j) {
|
|
317
|
+
const attr = hatchpath.attributes.item(j);
|
|
318
|
+
if (attr.name !== 'd') {
|
|
319
|
+
path.setAttribute(attr.name, attr.value);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (hatchpath.getAttribute('d') === null) {
|
|
324
|
+
d += currentXPositions.reduce(
|
|
325
|
+
(acc, xPos) => `${acc}M ${xPos} ${y} V ${hatchDiag} `, ''
|
|
326
|
+
);
|
|
327
|
+
patternHeight = hatchDiag;
|
|
328
|
+
} else {
|
|
329
|
+
const hatchData = hatchpath.getAttribute('d');
|
|
330
|
+
const data = parsePath(
|
|
331
|
+
hatchData.match(/([+-]?(\d+(\.\d+)?))|[MmZzLlHhVvCcSsQqTtAaBb]/g)
|
|
332
|
+
);
|
|
333
|
+
const len = data.length;
|
|
334
|
+
const startsWithM = data[0] === 'M';
|
|
335
|
+
const relative = data[0].toLowerCase() === data[0];
|
|
336
|
+
const point = new Point(0, 0);
|
|
337
|
+
let yOffset = getYDistance(hatchpath);
|
|
338
|
+
|
|
339
|
+
if (data[len - 1].y !== undefined && yOffset < data[len - 1].y) {
|
|
340
|
+
yOffset = data[len - 1].y;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// The offset must be positive
|
|
344
|
+
if (yOffset <= 0) {
|
|
345
|
+
console.error('y offset is non-positive');
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
patternHeight = bbox.height - bbox.height % yOffset;
|
|
349
|
+
|
|
350
|
+
const currentYPositions = generatePositions(
|
|
351
|
+
patternHeight, hatchDiag, y, yOffset
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
currentXPositions.forEach(xPos => {
|
|
355
|
+
point.x = xPos;
|
|
356
|
+
|
|
357
|
+
if (!startsWithM && !relative) {
|
|
358
|
+
d += `M ${xPos} 0`;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
currentYPositions.forEach(yPos => {
|
|
362
|
+
point.y = yPos;
|
|
363
|
+
|
|
364
|
+
if (relative) {
|
|
365
|
+
// Path is relative, set the first point in each path render
|
|
366
|
+
d += `M ${xPos} ${yPos} ${hatchData}`;
|
|
367
|
+
} else {
|
|
368
|
+
// Path is absolute, translate every point
|
|
369
|
+
d += data.map(e => e.isPoint && e.isPoint() ? e.add(point) : e)
|
|
370
|
+
.map(e => e.isPoint && e.isPoint() ? e.toString() : e)
|
|
371
|
+
.reduce((acc, e) => `${acc} ${e}`, '');
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// The hatchpaths are infinite, so they have no fill
|
|
377
|
+
path.style.fill = 'none';
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
path.setAttribute('d', d);
|
|
381
|
+
pattern.appendChild(path);
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
setAttributes(pattern, {
|
|
385
|
+
'id': patternId,
|
|
386
|
+
'patternUnits': unitUserSpace,
|
|
387
|
+
'patternContentUnits': contentUnits,
|
|
388
|
+
'width': patternWidth,
|
|
389
|
+
'height': patternHeight,
|
|
390
|
+
'x': bbox.x,
|
|
391
|
+
'y': bbox.y,
|
|
392
|
+
'patternTransform': `rotate(${rotate} ${0} ${0}) ${transform}`
|
|
393
|
+
});
|
|
394
|
+
hatch.parentElement.insertBefore(pattern, hatch);
|
|
395
|
+
|
|
396
|
+
shape.style.fill = `url(#${patternId})`;
|
|
397
|
+
shape.setAttribute('fill', `url(#${patternId})`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
})();
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inkscape Hatch Pattern Polyfill (Minified)
|
|
3
|
+
* Source: https://gitlab.com/inkscape/inkscape/-/blob/master/src/extension/internal/polyfill/hatch_compressed.include
|
|
4
|
+
* License: CC0 (Public Domain)
|
|
5
|
+
*
|
|
6
|
+
* Converts SVG 2 hatch elements to SVG 1.1 pattern elements for browser compatibility.
|
|
7
|
+
*/
|
|
8
|
+
!function(){const t="http://www.w3.org/2000/svg",e=(t,e,r,n)=>{const u=(e-t)/2,i=r+u,s=t+u+n;let h=[];for(let t=r-(Math.round(i/n)+1)*n;t<s;t+=n)h.push(t);return h};class r{constructor(t,e){this.x=t,this.y=e}toString(){return`${this.x} ${this.y}`}isPoint(){return!0}clone(){return new r(this.x,this.y)}add(t){return new r(this.x+t.x,this.y+t.y)}distSquared(t){let e=this.x-t.x,r=this.y-t.y;return e*e+r*r}}document.querySelectorAll("rect,circle,ellipse,path,text").forEach((n,u)=>{let i=n.getAttribute("id");i||(i="hatch_shape_"+u,n.setAttribute("id",i));const s=(n.getAttribute("fill")||n.style.fill).match(/^url\(\s*"?\s*#([^\s"]+)"?\s*\)/);if(s&&s[1]){const u=document.getElementById(s[1]);if(u&&"hatch"===u.nodeName){const i=u.getAttributeNS("http://www.w3.org/1999/xlink","href");if(null!==i&&""!==i&&((t,e)=>{const r=["x","y","pitch","rotate","hatchUnits","hatchContentUnits","transform"],n=document.getElementById(e.slice(1));n&&"hatch"===n.nodeName&&(r.forEach(e=>{let r=n.getAttribute(e);null===t.getAttribute(e)&&null!==r&&t.setAttribute(e,r)}),0===t.children.length&&Array.from(n.children).forEach(e=>{t.appendChild(e.cloneNode(!0))}))})(u,i),0===u.children.length)return;const h=n.getBBox(),o=Math.ceil(Math.sqrt(h.width*h.width+h.height*h.height)),a=u.getAttribute("hatchUnits")||"objectBoundingBox",c=u.getAttribute("hatchContentUnits")||"userSpaceOnUse",b=Number(u.getAttribute("rotate"))||0,l=u.getAttribute("transform")||u.getAttribute("hatchTransform")||"",m=(t=>{const e=[];return t.forEach(t=>e.push(t)),e.sort((t,e)=>Number(e.getAttribute("offset"))-Number(t.getAttribute("offset")))})(u.querySelectorAll("hatchpath,hatchPath")),d="objectBoundingBox"===a?Number(u.getAttribute("x"))*h.width||0:Number(u.getAttribute("x"))||0,g="objectBoundingBox"===a?Number(u.getAttribute("y"))*h.width||0:Number(u.getAttribute("y"))||0;let p="objectBoundingBox"===a?Number(u.getAttribute("pitch"))*h.width||0:Number(u.getAttribute("pitch"))||0;if("objectBoundingBox"===c&&h.height&&(p/=h.height),p<=0)return void console.error("Non-positive pitch");const N=document.createElementNS(t,"pattern"),f=`${s[1]}_pattern`;let w=h.width-h.width%p,A=0;const y=e(w,o,d,p);m.forEach(n=>{let u=Number(n.getAttribute("offset"))||0;u=u>p?u%p:u;const i=y.map(t=>t+u),s=document.createElementNS(t,"path");let a="";for(let t=0;t<n.attributes.length;++t){const e=n.attributes.item(t);"d"!==e.name&&s.setAttribute(e.name,e.value)}if(null===n.getAttribute("d"))a+=i.reduce((t,e)=>`${t}M ${e} ${g} V ${o} `,""),A=o;else{const u=n.getAttribute("d"),c=(t=>{let e=[],n=0,u=t.length,i=0;for(;n<u;)switch(t[n].toUpperCase()){case"Z":e.push(t[n]),n+=1,i=0;break;case"M":case"L":case"T":e.push(t[n],new r(Number(t[n+1]),Number(t[n+2]))),n+=3,i=1;break;case"H":e.push(t[n],new r(Number(t[n+1]),null)),n+=2,i=2;break;case"V":e.push(t[n],new r(null,Number(t[n+1]))),n+=2,i=3;break;case"C":e.push(t[n],new r(Number(t[n+1]),Number(t[n+2])),new r(Number(t[n+3]),Number(t[n+4])),new r(Number(t[n+5]),Number(t[n+6]))),n+=7,i=4;break;case"S":case"Q":e.push(t[n],new r(Number(t[n+1]),Number(t[n+2])),new r(Number(t[n+3]),Number(t[n+4]))),n+=5,i=5;break;case"A":e.push(t[n],t[n+1],t[n+2],t[n+3],t[n+4],t[n+5],new r(Number(t[n+6]),Number(t[n+7]))),n+=8,i=6;break;case"B":e.push(t[n],t[n+1]),n+=2,i=7;break;default:switch(i){case 1:e.push(new r(Number(t[n]),Number(t[n+1]))),n+=2;break;case 2:e.push(new r(Number(t[n]),null)),n+=1;break;case 3:e.push(new r(null,Number(t[n]))),n+=1;break;case 4:e.push(new r(Number(t[n]),Number(t[n+1])),new r(Number(t[n+2]),Number(t[n+3])),new r(Number(t[n+4]),Number(t[n+5]))),n+=6;break;case 5:e.push(new r(Number(t[n]),Number(t[n+1])),new r(Number(t[n+2]),Number(t[n+3]))),n+=4;break;case 6:e.push(t[n],t[n+1],t[n+2],t[n+3],t[n+4],new r(Number(t[n+5]),Number(t[n+6]))),n+=7;break;default:e.push(t[n]),n+=1}}return e})(u.match(/([+-]?(\d+(\.\d+)?))|[MmZzLlHhVvCcSsQqTtAaBb]/g)),b=c.length,l="M"===c[0],m=c[0].toLowerCase()===c[0],d=new r(0,0);let p=(e=>{const r=document.createElementNS(t,"path");let n=e.getAttribute("d");return"M"!==n[0].toUpperCase()&&(n=`M 0,0 ${n}`),r.setAttribute("d",n),r.getPointAtLength(r.getTotalLength()).y-r.getPointAtLength(0).y})(n);if(void 0!==c[b-1].y&&p<c[b-1].y&&(p=c[b-1].y),p<=0)return void console.error("y offset is non-positive");A=h.height-h.height%p;const N=e(A,o,g,p);i.forEach(t=>{d.x=t,l||m||(a+=`M ${t} 0`),N.forEach(e=>{d.y=e,a+=m?`M ${t} ${e} ${u}`:c.map(t=>t.isPoint&&t.isPoint()?t.add(d):t).map(t=>t.isPoint&&t.isPoint()?t.toString():t).reduce((t,e)=>`${t} ${e}`,"")})}),s.style.fill="none"}s.setAttribute("d",a),N.appendChild(s)}),((t,e)=>{for(let r in e)t.setAttribute(r,e[r])})(N,{id:f,patternUnits:"userSpaceOnUse",patternContentUnits:c,width:w,height:A,x:h.x,y:h.y,patternTransform:`rotate(${b} 0 0) ${l}`}),u.parentElement.insertBefore(N,u),n.style.fill=`url(#${f})`,n.setAttribute("fill",`url(#${f})`)}}})}();
|