@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
package/src/svg-boolean-ops.js
CHANGED
|
@@ -13,25 +13,25 @@
|
|
|
13
13
|
* @module svg-boolean-ops
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import Decimal from
|
|
17
|
-
import
|
|
16
|
+
import Decimal from "decimal.js";
|
|
17
|
+
import * as PolygonClip from "./polygon-clip.js";
|
|
18
18
|
|
|
19
19
|
Decimal.set({ precision: 80 });
|
|
20
20
|
|
|
21
|
-
const D = x => (x instanceof Decimal ? x : new Decimal(x));
|
|
22
|
-
const EPSILON = new Decimal(
|
|
21
|
+
const D = (x) => (x instanceof Decimal ? x : new Decimal(x));
|
|
22
|
+
const EPSILON = new Decimal("1e-40");
|
|
23
23
|
|
|
24
24
|
const {
|
|
25
25
|
point,
|
|
26
|
-
pointsEqual,
|
|
26
|
+
pointsEqual: _pointsEqual,
|
|
27
27
|
cross,
|
|
28
|
-
polygonArea,
|
|
28
|
+
polygonArea: _polygonArea,
|
|
29
29
|
polygonIntersection,
|
|
30
30
|
polygonUnion,
|
|
31
31
|
polygonDifference,
|
|
32
|
-
isCounterClockwise,
|
|
33
|
-
ensureCCW,
|
|
34
|
-
segmentIntersection
|
|
32
|
+
isCounterClockwise: _isCounterClockwise,
|
|
33
|
+
ensureCCW: _ensureCCW,
|
|
34
|
+
segmentIntersection: _segmentIntersection,
|
|
35
35
|
} = PolygonClip;
|
|
36
36
|
|
|
37
37
|
// ============================================================================
|
|
@@ -42,8 +42,8 @@ const {
|
|
|
42
42
|
* Fill rule enumeration matching SVG spec.
|
|
43
43
|
*/
|
|
44
44
|
export const FillRule = {
|
|
45
|
-
NONZERO:
|
|
46
|
-
EVENODD:
|
|
45
|
+
NONZERO: "nonzero",
|
|
46
|
+
EVENODD: "evenodd",
|
|
47
47
|
};
|
|
48
48
|
|
|
49
49
|
/**
|
|
@@ -54,7 +54,11 @@ export const FillRule = {
|
|
|
54
54
|
* @param {string} fillRule - 'nonzero' or 'evenodd'
|
|
55
55
|
* @returns {number} 1 inside, 0 on boundary, -1 outside
|
|
56
56
|
*/
|
|
57
|
-
export function pointInPolygonWithRule(
|
|
57
|
+
export function pointInPolygonWithRule(
|
|
58
|
+
pt,
|
|
59
|
+
polygon,
|
|
60
|
+
fillRule = FillRule.NONZERO,
|
|
61
|
+
) {
|
|
58
62
|
const n = polygon.length;
|
|
59
63
|
if (n < 3) return -1;
|
|
60
64
|
|
|
@@ -99,6 +103,10 @@ export function pointInPolygonWithRule(pt, polygon, fillRule = FillRule.NONZERO)
|
|
|
99
103
|
|
|
100
104
|
/**
|
|
101
105
|
* Check if a point lies on a line segment.
|
|
106
|
+
* @param {Object} pt - Point to test {x, y}
|
|
107
|
+
* @param {Object} a - Segment start point {x, y}
|
|
108
|
+
* @param {Object} b - Segment end point {x, y}
|
|
109
|
+
* @returns {boolean} True if point is on the segment
|
|
102
110
|
*/
|
|
103
111
|
function pointOnSegment(pt, a, b) {
|
|
104
112
|
const crossVal = cross(a, b, pt);
|
|
@@ -111,8 +119,12 @@ function pointOnSegment(pt, a, b) {
|
|
|
111
119
|
const minY = Decimal.min(a.y, b.y);
|
|
112
120
|
const maxY = Decimal.max(a.y, b.y);
|
|
113
121
|
|
|
114
|
-
return
|
|
115
|
-
|
|
122
|
+
return (
|
|
123
|
+
pt.x.gte(minX.minus(EPSILON)) &&
|
|
124
|
+
pt.x.lte(maxX.plus(EPSILON)) &&
|
|
125
|
+
pt.y.gte(minY.minus(EPSILON)) &&
|
|
126
|
+
pt.y.lte(maxY.plus(EPSILON))
|
|
127
|
+
);
|
|
116
128
|
}
|
|
117
129
|
|
|
118
130
|
// ============================================================================
|
|
@@ -139,7 +151,7 @@ export function rectToPolygon(rect) {
|
|
|
139
151
|
point(x, y),
|
|
140
152
|
point(x.plus(w), y),
|
|
141
153
|
point(x.plus(w), y.plus(h)),
|
|
142
|
-
point(x, y.plus(h))
|
|
154
|
+
point(x, y.plus(h)),
|
|
143
155
|
];
|
|
144
156
|
}
|
|
145
157
|
|
|
@@ -154,37 +166,57 @@ export function rectToPolygon(rect) {
|
|
|
154
166
|
// Top-right corner
|
|
155
167
|
for (let i = 0; i <= segments; i++) {
|
|
156
168
|
const angle = Math.PI * 1.5 + (Math.PI / 2) * (i / segments);
|
|
157
|
-
vertices.push(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
169
|
+
vertices.push(
|
|
170
|
+
point(
|
|
171
|
+
x
|
|
172
|
+
.plus(w)
|
|
173
|
+
.minus(actualRx)
|
|
174
|
+
.plus(actualRx.times(Math.cos(angle))),
|
|
175
|
+
y.plus(actualRy).plus(actualRy.times(Math.sin(angle))),
|
|
176
|
+
),
|
|
177
|
+
);
|
|
161
178
|
}
|
|
162
179
|
|
|
163
180
|
// Bottom-right corner
|
|
164
181
|
for (let i = 0; i <= segments; i++) {
|
|
165
182
|
const angle = 0 + (Math.PI / 2) * (i / segments);
|
|
166
|
-
vertices.push(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
183
|
+
vertices.push(
|
|
184
|
+
point(
|
|
185
|
+
x
|
|
186
|
+
.plus(w)
|
|
187
|
+
.minus(actualRx)
|
|
188
|
+
.plus(actualRx.times(Math.cos(angle))),
|
|
189
|
+
y
|
|
190
|
+
.plus(h)
|
|
191
|
+
.minus(actualRy)
|
|
192
|
+
.plus(actualRy.times(Math.sin(angle))),
|
|
193
|
+
),
|
|
194
|
+
);
|
|
170
195
|
}
|
|
171
196
|
|
|
172
197
|
// Bottom-left corner
|
|
173
198
|
for (let i = 0; i <= segments; i++) {
|
|
174
199
|
const angle = Math.PI / 2 + (Math.PI / 2) * (i / segments);
|
|
175
|
-
vertices.push(
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
200
|
+
vertices.push(
|
|
201
|
+
point(
|
|
202
|
+
x.plus(actualRx).plus(actualRx.times(Math.cos(angle))),
|
|
203
|
+
y
|
|
204
|
+
.plus(h)
|
|
205
|
+
.minus(actualRy)
|
|
206
|
+
.plus(actualRy.times(Math.sin(angle))),
|
|
207
|
+
),
|
|
208
|
+
);
|
|
179
209
|
}
|
|
180
210
|
|
|
181
211
|
// Top-left corner
|
|
182
212
|
for (let i = 0; i <= segments; i++) {
|
|
183
213
|
const angle = Math.PI + (Math.PI / 2) * (i / segments);
|
|
184
|
-
vertices.push(
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
214
|
+
vertices.push(
|
|
215
|
+
point(
|
|
216
|
+
x.plus(actualRx).plus(actualRx.times(Math.cos(angle))),
|
|
217
|
+
y.plus(actualRy).plus(actualRy.times(Math.sin(angle))),
|
|
218
|
+
),
|
|
219
|
+
);
|
|
188
220
|
}
|
|
189
221
|
|
|
190
222
|
return vertices;
|
|
@@ -205,10 +237,12 @@ export function circleToPolygon(circle, segments = 32) {
|
|
|
205
237
|
const vertices = [];
|
|
206
238
|
for (let i = 0; i < segments; i++) {
|
|
207
239
|
const angle = (2 * Math.PI * i) / segments;
|
|
208
|
-
vertices.push(
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
240
|
+
vertices.push(
|
|
241
|
+
point(
|
|
242
|
+
cx.plus(r.times(Math.cos(angle))),
|
|
243
|
+
cy.plus(r.times(Math.sin(angle))),
|
|
244
|
+
),
|
|
245
|
+
);
|
|
212
246
|
}
|
|
213
247
|
|
|
214
248
|
return vertices;
|
|
@@ -230,10 +264,12 @@ export function ellipseToPolygon(ellipse, segments = 32) {
|
|
|
230
264
|
const vertices = [];
|
|
231
265
|
for (let i = 0; i < segments; i++) {
|
|
232
266
|
const angle = (2 * Math.PI * i) / segments;
|
|
233
|
-
vertices.push(
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
267
|
+
vertices.push(
|
|
268
|
+
point(
|
|
269
|
+
cx.plus(rx.times(Math.cos(angle))),
|
|
270
|
+
cy.plus(ry.times(Math.sin(angle))),
|
|
271
|
+
),
|
|
272
|
+
);
|
|
237
273
|
}
|
|
238
274
|
|
|
239
275
|
return vertices;
|
|
@@ -246,7 +282,7 @@ export function ellipseToPolygon(ellipse, segments = 32) {
|
|
|
246
282
|
* @param {Object} stroke - {width, linecap}
|
|
247
283
|
* @returns {Array} Polygon vertices representing stroked line
|
|
248
284
|
*/
|
|
249
|
-
export function lineToPolygon(line, stroke = { width: 1, linecap:
|
|
285
|
+
export function lineToPolygon(line, stroke = { width: 1, linecap: "butt" }) {
|
|
250
286
|
const x1 = D(line.x1);
|
|
251
287
|
const y1 = D(line.y1);
|
|
252
288
|
const x2 = D(line.x2);
|
|
@@ -269,7 +305,7 @@ export function lineToPolygon(line, stroke = { width: 1, linecap: 'butt' }) {
|
|
|
269
305
|
|
|
270
306
|
const vertices = [];
|
|
271
307
|
|
|
272
|
-
if (stroke.linecap ===
|
|
308
|
+
if (stroke.linecap === "square") {
|
|
273
309
|
// Extend endpoints by half width
|
|
274
310
|
const ex = dx.div(len).times(halfWidth);
|
|
275
311
|
const ey = dy.div(len).times(halfWidth);
|
|
@@ -278,9 +314,9 @@ export function lineToPolygon(line, stroke = { width: 1, linecap: 'butt' }) {
|
|
|
278
314
|
point(x1.minus(ex).minus(nx), y1.minus(ey).minus(ny)),
|
|
279
315
|
point(x2.plus(ex).minus(nx), y2.plus(ey).minus(ny)),
|
|
280
316
|
point(x2.plus(ex).plus(nx), y2.plus(ey).plus(ny)),
|
|
281
|
-
point(x1.minus(ex).plus(nx), y1.minus(ey).plus(ny))
|
|
317
|
+
point(x1.minus(ex).plus(nx), y1.minus(ey).plus(ny)),
|
|
282
318
|
);
|
|
283
|
-
} else if (stroke.linecap ===
|
|
319
|
+
} else if (stroke.linecap === "round") {
|
|
284
320
|
// Add semicircles at endpoints in CCW order
|
|
285
321
|
// Start from right side of start point, go around start cap, along left side,
|
|
286
322
|
// around end cap, and back along right side
|
|
@@ -290,19 +326,23 @@ export function lineToPolygon(line, stroke = { width: 1, linecap: 'butt' }) {
|
|
|
290
326
|
// Start cap (semicircle) - going CCW from right side (-normal) to left side (+normal)
|
|
291
327
|
for (let i = 0; i <= segments; i++) {
|
|
292
328
|
const angle = startAngle - Math.PI / 2 - Math.PI * (i / segments);
|
|
293
|
-
vertices.push(
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
329
|
+
vertices.push(
|
|
330
|
+
point(
|
|
331
|
+
x1.plus(halfWidth.times(Math.cos(angle))),
|
|
332
|
+
y1.plus(halfWidth.times(Math.sin(angle))),
|
|
333
|
+
),
|
|
334
|
+
);
|
|
297
335
|
}
|
|
298
336
|
|
|
299
337
|
// End cap (semicircle) - continuing CCW from left side to right side
|
|
300
338
|
for (let i = 0; i <= segments; i++) {
|
|
301
339
|
const angle = startAngle + Math.PI / 2 - Math.PI * (i / segments);
|
|
302
|
-
vertices.push(
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
340
|
+
vertices.push(
|
|
341
|
+
point(
|
|
342
|
+
x2.plus(halfWidth.times(Math.cos(angle))),
|
|
343
|
+
y2.plus(halfWidth.times(Math.sin(angle))),
|
|
344
|
+
),
|
|
345
|
+
);
|
|
306
346
|
}
|
|
307
347
|
} else {
|
|
308
348
|
// butt (default) - simple rectangle
|
|
@@ -311,7 +351,7 @@ export function lineToPolygon(line, stroke = { width: 1, linecap: 'butt' }) {
|
|
|
311
351
|
point(x1.minus(nx), y1.minus(ny)),
|
|
312
352
|
point(x2.minus(nx), y2.minus(ny)),
|
|
313
353
|
point(x2.plus(nx), y2.plus(ny)),
|
|
314
|
-
point(x1.plus(nx), y1.plus(ny))
|
|
354
|
+
point(x1.plus(nx), y1.plus(ny)),
|
|
315
355
|
);
|
|
316
356
|
}
|
|
317
357
|
|
|
@@ -326,11 +366,14 @@ export function lineToPolygon(line, stroke = { width: 1, linecap: 'butt' }) {
|
|
|
326
366
|
*/
|
|
327
367
|
export function svgPolygonToPolygon(points) {
|
|
328
368
|
if (Array.isArray(points)) {
|
|
329
|
-
return points.map(p => point(p.x, p.y));
|
|
369
|
+
return points.map((p) => point(p.x, p.y));
|
|
330
370
|
}
|
|
331
371
|
|
|
332
372
|
// Parse SVG points string
|
|
333
|
-
const coords = points
|
|
373
|
+
const coords = points
|
|
374
|
+
.trim()
|
|
375
|
+
.split(/[\s,]+/)
|
|
376
|
+
.map(Number);
|
|
334
377
|
const vertices = [];
|
|
335
378
|
for (let i = 0; i < coords.length; i += 2) {
|
|
336
379
|
vertices.push(point(coords[i], coords[i + 1]));
|
|
@@ -355,7 +398,7 @@ export function svgPolygonToPolygon(points) {
|
|
|
355
398
|
*/
|
|
356
399
|
export function offsetPolygon(polygon, distance, options = {}) {
|
|
357
400
|
const dist = D(distance);
|
|
358
|
-
const linejoin = options.linejoin ||
|
|
401
|
+
const linejoin = options.linejoin || "miter";
|
|
359
402
|
const miterLimit = D(options.miterLimit || 4);
|
|
360
403
|
|
|
361
404
|
if (polygon.length < 3) {
|
|
@@ -416,19 +459,31 @@ export function offsetPolygon(polygon, distance, options = {}) {
|
|
|
416
459
|
if (sinHalfAngle.gt(EPSILON)) {
|
|
417
460
|
const miterDist = dist.div(sinHalfAngle);
|
|
418
461
|
|
|
419
|
-
if (linejoin ===
|
|
462
|
+
if (linejoin === "miter" && miterDist.lte(dist.times(miterLimit))) {
|
|
420
463
|
actualDist = miterDist;
|
|
421
|
-
} else if (linejoin ===
|
|
464
|
+
} else if (linejoin === "bevel" || miterDist.gt(dist.times(miterLimit))) {
|
|
422
465
|
// Bevel: add two points instead of one
|
|
423
|
-
const outerPt1 = point(
|
|
424
|
-
|
|
466
|
+
const outerPt1 = point(
|
|
467
|
+
curr.x.plus(nx1.times(dist)),
|
|
468
|
+
curr.y.plus(ny1.times(dist)),
|
|
469
|
+
);
|
|
470
|
+
const outerPt2 = point(
|
|
471
|
+
curr.x.plus(nx2.times(dist)),
|
|
472
|
+
curr.y.plus(ny2.times(dist)),
|
|
473
|
+
);
|
|
425
474
|
outerVertices.push(outerPt1, outerPt2);
|
|
426
475
|
|
|
427
|
-
const innerPt1 = point(
|
|
428
|
-
|
|
476
|
+
const innerPt1 = point(
|
|
477
|
+
curr.x.minus(nx1.times(dist)),
|
|
478
|
+
curr.y.minus(ny1.times(dist)),
|
|
479
|
+
);
|
|
480
|
+
const innerPt2 = point(
|
|
481
|
+
curr.x.minus(nx2.times(dist)),
|
|
482
|
+
curr.y.minus(ny2.times(dist)),
|
|
483
|
+
);
|
|
429
484
|
innerVertices.push(innerPt1, innerPt2);
|
|
430
485
|
continue;
|
|
431
|
-
} else if (linejoin ===
|
|
486
|
+
} else if (linejoin === "round") {
|
|
432
487
|
// Round: add arc segments
|
|
433
488
|
const startAngle = Math.atan2(ny1.toNumber(), nx1.toNumber());
|
|
434
489
|
const endAngle = Math.atan2(ny2.toNumber(), nx2.toNumber());
|
|
@@ -436,30 +491,47 @@ export function offsetPolygon(polygon, distance, options = {}) {
|
|
|
436
491
|
if (angleDiff < -Math.PI) angleDiff += 2 * Math.PI;
|
|
437
492
|
if (angleDiff > Math.PI) angleDiff -= 2 * Math.PI;
|
|
438
493
|
|
|
439
|
-
const segments = Math.max(
|
|
494
|
+
const segments = Math.max(
|
|
495
|
+
2,
|
|
496
|
+
Math.ceil(Math.abs(angleDiff) / (Math.PI / 8)),
|
|
497
|
+
);
|
|
440
498
|
for (let j = 0; j <= segments; j++) {
|
|
441
499
|
const angle = startAngle + angleDiff * (j / segments);
|
|
442
|
-
outerVertices.push(
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
500
|
+
outerVertices.push(
|
|
501
|
+
point(
|
|
502
|
+
curr.x.plus(dist.times(Math.cos(angle))),
|
|
503
|
+
curr.y.plus(dist.times(Math.sin(angle))),
|
|
504
|
+
),
|
|
505
|
+
);
|
|
506
|
+
innerVertices.push(
|
|
507
|
+
point(
|
|
508
|
+
curr.x.minus(dist.times(Math.cos(angle))),
|
|
509
|
+
curr.y.minus(dist.times(Math.sin(angle))),
|
|
510
|
+
),
|
|
511
|
+
);
|
|
450
512
|
}
|
|
451
513
|
continue;
|
|
452
514
|
}
|
|
453
515
|
}
|
|
454
516
|
|
|
455
517
|
// Single offset point
|
|
456
|
-
outerVertices.push(
|
|
457
|
-
|
|
518
|
+
outerVertices.push(
|
|
519
|
+
point(
|
|
520
|
+
curr.x.plus(nx.times(actualDist)),
|
|
521
|
+
curr.y.plus(ny.times(actualDist)),
|
|
522
|
+
),
|
|
523
|
+
);
|
|
524
|
+
innerVertices.push(
|
|
525
|
+
point(
|
|
526
|
+
curr.x.minus(nx.times(actualDist)),
|
|
527
|
+
curr.y.minus(ny.times(actualDist)),
|
|
528
|
+
),
|
|
529
|
+
);
|
|
458
530
|
}
|
|
459
531
|
|
|
460
532
|
return {
|
|
461
533
|
outer: outerVertices,
|
|
462
|
-
inner: innerVertices.reverse() // Reverse for consistent winding
|
|
534
|
+
inner: innerVertices.reverse(), // Reverse for consistent winding
|
|
463
535
|
};
|
|
464
536
|
}
|
|
465
537
|
|
|
@@ -500,9 +572,10 @@ export function applyDashArray(polygon, dashArray, dashOffset = 0) {
|
|
|
500
572
|
}
|
|
501
573
|
|
|
502
574
|
// Normalize dash array (must have even length)
|
|
503
|
-
const dashes =
|
|
504
|
-
|
|
505
|
-
|
|
575
|
+
const dashes =
|
|
576
|
+
dashArray.length % 2 === 0
|
|
577
|
+
? dashArray.map((d) => D(d))
|
|
578
|
+
: [...dashArray, ...dashArray].map((d) => D(d));
|
|
506
579
|
|
|
507
580
|
const segments = [];
|
|
508
581
|
let currentSegment = [];
|
|
@@ -544,10 +617,9 @@ export function applyDashArray(polygon, dashArray, dashOffset = 0) {
|
|
|
544
617
|
if (remaining.lte(remainingInDash)) {
|
|
545
618
|
// Rest of edge fits in current dash/gap
|
|
546
619
|
if (drawing) {
|
|
547
|
-
currentSegment.push(
|
|
548
|
-
p1.x.plus(dx.times(t)),
|
|
549
|
-
|
|
550
|
-
));
|
|
620
|
+
currentSegment.push(
|
|
621
|
+
point(p1.x.plus(dx.times(t)), p1.y.plus(dy.times(t))),
|
|
622
|
+
);
|
|
551
623
|
currentSegment.push(point(p2.x, p2.y));
|
|
552
624
|
}
|
|
553
625
|
remainingInDash = remainingInDash.minus(remaining);
|
|
@@ -567,14 +639,12 @@ export function applyDashArray(polygon, dashArray, dashOffset = 0) {
|
|
|
567
639
|
const tEnd = t.plus(remainingInDash.div(edgeLen));
|
|
568
640
|
|
|
569
641
|
if (drawing) {
|
|
570
|
-
currentSegment.push(
|
|
571
|
-
p1.x.plus(dx.times(t)),
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
p1.y.plus(dy.times(tEnd))
|
|
577
|
-
));
|
|
642
|
+
currentSegment.push(
|
|
643
|
+
point(p1.x.plus(dx.times(t)), p1.y.plus(dy.times(t))),
|
|
644
|
+
);
|
|
645
|
+
currentSegment.push(
|
|
646
|
+
point(p1.x.plus(dx.times(tEnd)), p1.y.plus(dy.times(tEnd))),
|
|
647
|
+
);
|
|
578
648
|
segments.push(currentSegment);
|
|
579
649
|
currentSegment = [];
|
|
580
650
|
}
|
|
@@ -627,40 +697,44 @@ export class SVGRegion {
|
|
|
627
697
|
let polygon;
|
|
628
698
|
|
|
629
699
|
switch (type) {
|
|
630
|
-
case
|
|
700
|
+
case "rect":
|
|
631
701
|
polygon = rectToPolygon(props);
|
|
632
702
|
break;
|
|
633
|
-
case
|
|
703
|
+
case "circle":
|
|
634
704
|
polygon = circleToPolygon(props);
|
|
635
705
|
break;
|
|
636
|
-
case
|
|
706
|
+
case "ellipse":
|
|
637
707
|
polygon = ellipseToPolygon(props);
|
|
638
708
|
break;
|
|
639
|
-
case
|
|
709
|
+
case "polygon":
|
|
640
710
|
polygon = svgPolygonToPolygon(props.points);
|
|
641
711
|
break;
|
|
642
|
-
case
|
|
712
|
+
case "line":
|
|
643
713
|
// Lines have no fill, only stroke
|
|
644
714
|
polygon = null;
|
|
645
715
|
break;
|
|
646
716
|
default:
|
|
647
|
-
throw new Error(
|
|
717
|
+
throw new Error("Unsupported element type: " + type);
|
|
648
718
|
}
|
|
649
719
|
|
|
650
720
|
const region = new SVGRegion({
|
|
651
|
-
fillRule: style.fillRule || FillRule.NONZERO
|
|
721
|
+
fillRule: style.fillRule || FillRule.NONZERO,
|
|
652
722
|
});
|
|
653
723
|
|
|
654
724
|
// Add fill region if element has fill
|
|
655
|
-
if (polygon && style.fill !==
|
|
725
|
+
if (polygon && style.fill !== "none") {
|
|
656
726
|
region.fillPolygons = [polygon];
|
|
657
727
|
}
|
|
658
728
|
|
|
659
729
|
// Add stroke region if element has stroke
|
|
660
|
-
if (style.stroke !==
|
|
661
|
-
const sourcePolygon =
|
|
662
|
-
|
|
663
|
-
|
|
730
|
+
if (style.stroke !== "none" && style.strokeWidth > 0) {
|
|
731
|
+
const sourcePolygon =
|
|
732
|
+
type === "line"
|
|
733
|
+
? lineToPolygon(props, {
|
|
734
|
+
width: style.strokeWidth,
|
|
735
|
+
linecap: style.strokeLinecap,
|
|
736
|
+
})
|
|
737
|
+
: polygon;
|
|
664
738
|
|
|
665
739
|
if (sourcePolygon) {
|
|
666
740
|
let strokePolygons;
|
|
@@ -670,24 +744,26 @@ export class SVGRegion {
|
|
|
670
744
|
const dashedSegments = applyDashArray(
|
|
671
745
|
sourcePolygon,
|
|
672
746
|
style.strokeDasharray,
|
|
673
|
-
style.strokeDashoffset || 0
|
|
747
|
+
style.strokeDashoffset || 0,
|
|
674
748
|
);
|
|
675
|
-
strokePolygons = dashedSegments.map(seg =>
|
|
749
|
+
strokePolygons = dashedSegments.map((seg) =>
|
|
676
750
|
strokeToFilledPolygon(seg, {
|
|
677
751
|
width: style.strokeWidth,
|
|
678
752
|
linejoin: style.strokeLinejoin,
|
|
679
|
-
miterLimit: style.strokeMiterlimit
|
|
680
|
-
})
|
|
753
|
+
miterLimit: style.strokeMiterlimit,
|
|
754
|
+
}),
|
|
681
755
|
);
|
|
682
756
|
} else {
|
|
683
|
-
strokePolygons = [
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
757
|
+
strokePolygons = [
|
|
758
|
+
strokeToFilledPolygon(sourcePolygon, {
|
|
759
|
+
width: style.strokeWidth,
|
|
760
|
+
linejoin: style.strokeLinejoin,
|
|
761
|
+
miterLimit: style.strokeMiterlimit,
|
|
762
|
+
}),
|
|
763
|
+
];
|
|
688
764
|
}
|
|
689
765
|
|
|
690
|
-
region.strokePolygons = strokePolygons.filter(p => p.length >= 3);
|
|
766
|
+
region.strokePolygons = strokePolygons.filter((p) => p.length >= 3);
|
|
691
767
|
}
|
|
692
768
|
}
|
|
693
769
|
|
|
@@ -758,7 +834,7 @@ export function regionIntersection(regionA, regionB) {
|
|
|
758
834
|
|
|
759
835
|
return new SVGRegion({
|
|
760
836
|
fillPolygons: resultPolygons,
|
|
761
|
-
fillRule: FillRule.NONZERO // Result is always simple polygons
|
|
837
|
+
fillRule: FillRule.NONZERO, // Result is always simple polygons
|
|
762
838
|
});
|
|
763
839
|
}
|
|
764
840
|
|
|
@@ -770,7 +846,7 @@ export function regionIntersection(regionA, regionB) {
|
|
|
770
846
|
* @returns {SVGRegion} Union region
|
|
771
847
|
*/
|
|
772
848
|
export function regionUnion(regionA, regionB) {
|
|
773
|
-
const
|
|
849
|
+
const _resultPolygons = [];
|
|
774
850
|
|
|
775
851
|
const polygonsA = regionA.getAllPolygons();
|
|
776
852
|
const polygonsB = regionB.getAllPolygons();
|
|
@@ -811,7 +887,7 @@ export function regionUnion(regionA, regionB) {
|
|
|
811
887
|
|
|
812
888
|
return new SVGRegion({
|
|
813
889
|
fillPolygons: combined,
|
|
814
|
-
fillRule: FillRule.NONZERO
|
|
890
|
+
fillRule: FillRule.NONZERO,
|
|
815
891
|
});
|
|
816
892
|
}
|
|
817
893
|
|
|
@@ -823,7 +899,7 @@ export function regionUnion(regionA, regionB) {
|
|
|
823
899
|
* @returns {SVGRegion} Difference region
|
|
824
900
|
*/
|
|
825
901
|
export function regionDifference(regionA, regionB) {
|
|
826
|
-
let resultPolygons = regionA.getAllPolygons().map(p => [...p]);
|
|
902
|
+
let resultPolygons = regionA.getAllPolygons().map((p) => [...p]);
|
|
827
903
|
|
|
828
904
|
const polygonsB = regionB.getAllPolygons();
|
|
829
905
|
|
|
@@ -845,7 +921,7 @@ export function regionDifference(regionA, regionB) {
|
|
|
845
921
|
|
|
846
922
|
return new SVGRegion({
|
|
847
923
|
fillPolygons: resultPolygons,
|
|
848
|
-
fillRule: FillRule.NONZERO
|
|
924
|
+
fillRule: FillRule.NONZERO,
|
|
849
925
|
});
|
|
850
926
|
}
|
|
851
927
|
|
|
@@ -862,7 +938,7 @@ export function regionXOR(regionA, regionB) {
|
|
|
862
938
|
|
|
863
939
|
return new SVGRegion({
|
|
864
940
|
fillPolygons: [...diffAB.fillPolygons, ...diffBA.fillPolygons],
|
|
865
|
-
fillRule: FillRule.NONZERO
|
|
941
|
+
fillRule: FillRule.NONZERO,
|
|
866
942
|
});
|
|
867
943
|
}
|
|
868
944
|
|
|
@@ -894,5 +970,5 @@ export default {
|
|
|
894
970
|
regionIntersection,
|
|
895
971
|
regionUnion,
|
|
896
972
|
regionDifference,
|
|
897
|
-
regionXOR
|
|
973
|
+
regionXOR,
|
|
898
974
|
};
|