@emasoft/svg-matrix 1.0.27 → 1.0.29
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 +994 -378
- package/bin/svglinter.cjs +4172 -433
- package/bin/svgm.js +744 -184
- 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 +404 -0
- 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 +48 -19
- 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 +16411 -3298
- package/src/svg2-polyfills.js +114 -245
- 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/mask-resolver.js
CHANGED
|
@@ -43,16 +43,16 @@
|
|
|
43
43
|
* @module mask-resolver
|
|
44
44
|
*/
|
|
45
45
|
|
|
46
|
-
import Decimal from
|
|
47
|
-
import { Matrix } from
|
|
48
|
-
import * as
|
|
49
|
-
import * as PolygonClip from
|
|
50
|
-
import * as ClipPathResolver from
|
|
51
|
-
import * as MeshGradient from
|
|
46
|
+
import Decimal from "decimal.js";
|
|
47
|
+
import { Matrix as _Matrix } from "./matrix.js";
|
|
48
|
+
import * as _Transforms2D from "./transforms2d.js";
|
|
49
|
+
import * as PolygonClip from "./polygon-clip.js";
|
|
50
|
+
import * as ClipPathResolver from "./clip-path-resolver.js";
|
|
51
|
+
import * as MeshGradient from "./mesh-gradient.js";
|
|
52
52
|
|
|
53
53
|
Decimal.set({ precision: 80 });
|
|
54
54
|
|
|
55
|
-
const D = x => (x instanceof Decimal ? x : new Decimal(x));
|
|
55
|
+
const D = (x) => (x instanceof Decimal ? x : new Decimal(x));
|
|
56
56
|
|
|
57
57
|
// Default mask bounds (SVG spec: -10% to 120% in each dimension)
|
|
58
58
|
const DEFAULT_MASK_X = -0.1;
|
|
@@ -84,8 +84,8 @@ const DEFAULT_MASK_HEIGHT = 1.2;
|
|
|
84
84
|
* };
|
|
85
85
|
*/
|
|
86
86
|
export const MaskType = {
|
|
87
|
-
LUMINANCE:
|
|
88
|
-
ALPHA:
|
|
87
|
+
LUMINANCE: "luminance",
|
|
88
|
+
ALPHA: "alpha",
|
|
89
89
|
};
|
|
90
90
|
|
|
91
91
|
/**
|
|
@@ -132,25 +132,30 @@ export const MaskType = {
|
|
|
132
132
|
*/
|
|
133
133
|
export function parseMaskElement(maskElement) {
|
|
134
134
|
const data = {
|
|
135
|
-
id: maskElement.getAttribute(
|
|
136
|
-
maskUnits: maskElement.getAttribute(
|
|
137
|
-
maskContentUnits:
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
135
|
+
id: maskElement.getAttribute("id") || "",
|
|
136
|
+
maskUnits: maskElement.getAttribute("maskUnits") || "objectBoundingBox",
|
|
137
|
+
maskContentUnits:
|
|
138
|
+
maskElement.getAttribute("maskContentUnits") || "userSpaceOnUse",
|
|
139
|
+
maskType:
|
|
140
|
+
maskElement.getAttribute("mask-type") ||
|
|
141
|
+
maskElement.style?.maskType ||
|
|
142
|
+
"luminance",
|
|
143
|
+
x: maskElement.getAttribute("x"),
|
|
144
|
+
y: maskElement.getAttribute("y"),
|
|
145
|
+
width: maskElement.getAttribute("width"),
|
|
146
|
+
height: maskElement.getAttribute("height"),
|
|
147
|
+
transform: maskElement.getAttribute("transform") || null,
|
|
148
|
+
children: [],
|
|
146
149
|
};
|
|
147
150
|
|
|
148
151
|
// Set defaults based on maskUnits
|
|
149
|
-
if (data.maskUnits ===
|
|
152
|
+
if (data.maskUnits === "objectBoundingBox") {
|
|
150
153
|
data.x = data.x !== null ? parseFloat(data.x) : DEFAULT_MASK_X;
|
|
151
154
|
data.y = data.y !== null ? parseFloat(data.y) : DEFAULT_MASK_Y;
|
|
152
|
-
data.width =
|
|
153
|
-
|
|
155
|
+
data.width =
|
|
156
|
+
data.width !== null ? parseFloat(data.width) : DEFAULT_MASK_WIDTH;
|
|
157
|
+
data.height =
|
|
158
|
+
data.height !== null ? parseFloat(data.height) : DEFAULT_MASK_HEIGHT;
|
|
154
159
|
} else {
|
|
155
160
|
data.x = data.x !== null ? parseFloat(data.x) : null;
|
|
156
161
|
data.y = data.y !== null ? parseFloat(data.y) : null;
|
|
@@ -163,52 +168,54 @@ export function parseMaskElement(maskElement) {
|
|
|
163
168
|
const tagName = child.tagName.toLowerCase();
|
|
164
169
|
const childData = {
|
|
165
170
|
type: tagName,
|
|
166
|
-
fill: child.getAttribute(
|
|
167
|
-
fillOpacity: parseFloat(
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
171
|
+
fill: child.getAttribute("fill") || child.style?.fill || "black",
|
|
172
|
+
fillOpacity: parseFloat(
|
|
173
|
+
child.getAttribute("fill-opacity") || child.style?.fillOpacity || "1",
|
|
174
|
+
),
|
|
175
|
+
opacity: parseFloat(
|
|
176
|
+
child.getAttribute("opacity") || child.style?.opacity || "1",
|
|
177
|
+
),
|
|
178
|
+
transform: child.getAttribute("transform") || null,
|
|
172
179
|
};
|
|
173
180
|
|
|
174
181
|
// Parse shape-specific attributes
|
|
175
182
|
switch (tagName) {
|
|
176
|
-
case
|
|
177
|
-
childData.x = parseFloat(child.getAttribute(
|
|
178
|
-
childData.y = parseFloat(child.getAttribute(
|
|
179
|
-
childData.width = parseFloat(child.getAttribute(
|
|
180
|
-
childData.height = parseFloat(child.getAttribute(
|
|
181
|
-
childData.rx = parseFloat(child.getAttribute(
|
|
182
|
-
childData.ry = parseFloat(child.getAttribute(
|
|
183
|
+
case "rect":
|
|
184
|
+
childData.x = parseFloat(child.getAttribute("x") || "0");
|
|
185
|
+
childData.y = parseFloat(child.getAttribute("y") || "0");
|
|
186
|
+
childData.width = parseFloat(child.getAttribute("width") || "0");
|
|
187
|
+
childData.height = parseFloat(child.getAttribute("height") || "0");
|
|
188
|
+
childData.rx = parseFloat(child.getAttribute("rx") || "0");
|
|
189
|
+
childData.ry = parseFloat(child.getAttribute("ry") || "0");
|
|
183
190
|
break;
|
|
184
|
-
case
|
|
185
|
-
childData.cx = parseFloat(child.getAttribute(
|
|
186
|
-
childData.cy = parseFloat(child.getAttribute(
|
|
187
|
-
childData.r = parseFloat(child.getAttribute(
|
|
191
|
+
case "circle":
|
|
192
|
+
childData.cx = parseFloat(child.getAttribute("cx") || "0");
|
|
193
|
+
childData.cy = parseFloat(child.getAttribute("cy") || "0");
|
|
194
|
+
childData.r = parseFloat(child.getAttribute("r") || "0");
|
|
188
195
|
break;
|
|
189
|
-
case
|
|
190
|
-
childData.cx = parseFloat(child.getAttribute(
|
|
191
|
-
childData.cy = parseFloat(child.getAttribute(
|
|
192
|
-
childData.rx = parseFloat(child.getAttribute(
|
|
193
|
-
childData.ry = parseFloat(child.getAttribute(
|
|
196
|
+
case "ellipse":
|
|
197
|
+
childData.cx = parseFloat(child.getAttribute("cx") || "0");
|
|
198
|
+
childData.cy = parseFloat(child.getAttribute("cy") || "0");
|
|
199
|
+
childData.rx = parseFloat(child.getAttribute("rx") || "0");
|
|
200
|
+
childData.ry = parseFloat(child.getAttribute("ry") || "0");
|
|
194
201
|
break;
|
|
195
|
-
case
|
|
196
|
-
childData.d = child.getAttribute(
|
|
202
|
+
case "path":
|
|
203
|
+
childData.d = child.getAttribute("d") || "";
|
|
197
204
|
break;
|
|
198
|
-
case
|
|
199
|
-
childData.points = child.getAttribute(
|
|
205
|
+
case "polygon":
|
|
206
|
+
childData.points = child.getAttribute("points") || "";
|
|
200
207
|
break;
|
|
201
|
-
case
|
|
202
|
-
childData.points = child.getAttribute(
|
|
208
|
+
case "polyline":
|
|
209
|
+
childData.points = child.getAttribute("points") || "";
|
|
203
210
|
break;
|
|
204
|
-
case
|
|
211
|
+
case "g":
|
|
205
212
|
// Recursively parse group children
|
|
206
213
|
childData.children = [];
|
|
207
214
|
for (const gc of child.children) {
|
|
208
215
|
// Simplified - just store the tag and basic info
|
|
209
216
|
childData.children.push({
|
|
210
217
|
type: gc.tagName.toLowerCase(),
|
|
211
|
-
fill: gc.getAttribute(
|
|
218
|
+
fill: gc.getAttribute("fill") || "inherit",
|
|
212
219
|
});
|
|
213
220
|
}
|
|
214
221
|
break;
|
|
@@ -266,22 +273,34 @@ export function parseMaskElement(maskElement) {
|
|
|
266
273
|
* // region.y = 50 (absolute)
|
|
267
274
|
*/
|
|
268
275
|
export function getMaskRegion(maskData, targetBBox) {
|
|
269
|
-
if (maskData.maskUnits ===
|
|
276
|
+
if (maskData.maskUnits === "objectBoundingBox") {
|
|
270
277
|
// Coordinates are relative to target bounding box
|
|
271
278
|
return {
|
|
272
279
|
x: D(targetBBox.x).plus(D(maskData.x).mul(targetBBox.width)),
|
|
273
280
|
y: D(targetBBox.y).plus(D(maskData.y).mul(targetBBox.height)),
|
|
274
281
|
width: D(maskData.width).mul(targetBBox.width),
|
|
275
|
-
height: D(maskData.height).mul(targetBBox.height)
|
|
282
|
+
height: D(maskData.height).mul(targetBBox.height),
|
|
276
283
|
};
|
|
277
284
|
}
|
|
278
285
|
|
|
279
286
|
// userSpaceOnUse - use values directly (or defaults if null)
|
|
280
287
|
return {
|
|
281
|
-
x:
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
288
|
+
x:
|
|
289
|
+
maskData.x !== null
|
|
290
|
+
? D(maskData.x)
|
|
291
|
+
: D(targetBBox.x).minus(D(targetBBox.width).mul(0.1)),
|
|
292
|
+
y:
|
|
293
|
+
maskData.y !== null
|
|
294
|
+
? D(maskData.y)
|
|
295
|
+
: D(targetBBox.y).minus(D(targetBBox.height).mul(0.1)),
|
|
296
|
+
width:
|
|
297
|
+
maskData.width !== null
|
|
298
|
+
? D(maskData.width)
|
|
299
|
+
: D(targetBBox.width).mul(1.2),
|
|
300
|
+
height:
|
|
301
|
+
maskData.height !== null
|
|
302
|
+
? D(maskData.height)
|
|
303
|
+
: D(targetBBox.height).mul(1.2),
|
|
285
304
|
};
|
|
286
305
|
}
|
|
287
306
|
|
|
@@ -315,22 +334,27 @@ export function getMaskRegion(maskData, targetBBox) {
|
|
|
315
334
|
* const polygon = maskChildToPolygon(child, bbox, 'userSpaceOnUse', 32);
|
|
316
335
|
* // Result: 32-sided polygon approximating the circle
|
|
317
336
|
*/
|
|
318
|
-
export function maskChildToPolygon(
|
|
337
|
+
export function maskChildToPolygon(
|
|
338
|
+
child,
|
|
339
|
+
targetBBox,
|
|
340
|
+
contentUnits,
|
|
341
|
+
samples = 20,
|
|
342
|
+
) {
|
|
319
343
|
// Create element-like object for ClipPathResolver
|
|
320
344
|
const element = {
|
|
321
345
|
type: child.type,
|
|
322
346
|
...child,
|
|
323
|
-
transform: child.transform
|
|
347
|
+
transform: child.transform,
|
|
324
348
|
};
|
|
325
349
|
|
|
326
350
|
// Get polygon using ClipPathResolver
|
|
327
351
|
let polygon = ClipPathResolver.shapeToPolygon(element, null, samples);
|
|
328
352
|
|
|
329
353
|
// Apply objectBoundingBox scaling if needed
|
|
330
|
-
if (contentUnits ===
|
|
331
|
-
polygon = polygon.map(p => ({
|
|
354
|
+
if (contentUnits === "objectBoundingBox" && polygon.length > 0) {
|
|
355
|
+
polygon = polygon.map((p) => ({
|
|
332
356
|
x: D(targetBBox.x).plus(p.x.mul(targetBBox.width)),
|
|
333
|
-
y: D(targetBBox.y).plus(p.y.mul(targetBBox.height))
|
|
357
|
+
y: D(targetBBox.y).plus(p.y.mul(targetBBox.height)),
|
|
334
358
|
}));
|
|
335
359
|
}
|
|
336
360
|
|
|
@@ -384,16 +408,16 @@ export function maskChildToPolygon(child, targetBBox, contentUnits, samples = 20
|
|
|
384
408
|
* const effectiveOpacity = luminance * maskChild.opacity; // 0.5 * 0.8 = 0.4
|
|
385
409
|
*/
|
|
386
410
|
export function colorToLuminance(colorStr) {
|
|
387
|
-
if (!colorStr || colorStr ===
|
|
411
|
+
if (!colorStr || colorStr === "none" || colorStr === "transparent") {
|
|
388
412
|
return 0;
|
|
389
413
|
}
|
|
390
414
|
|
|
391
415
|
// Parse RGB values
|
|
392
416
|
const rgbMatch = colorStr.match(/rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i);
|
|
393
417
|
if (rgbMatch) {
|
|
394
|
-
const r = parseInt(rgbMatch[1]) / 255;
|
|
395
|
-
const g = parseInt(rgbMatch[2]) / 255;
|
|
396
|
-
const b = parseInt(rgbMatch[3]) / 255;
|
|
418
|
+
const r = parseInt(rgbMatch[1], 10) / 255;
|
|
419
|
+
const g = parseInt(rgbMatch[2], 10) / 255;
|
|
420
|
+
const b = parseInt(rgbMatch[3], 10) / 255;
|
|
397
421
|
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
398
422
|
}
|
|
399
423
|
|
|
@@ -421,7 +445,7 @@ export function colorToLuminance(colorStr) {
|
|
|
421
445
|
cyan: 0.7874,
|
|
422
446
|
magenta: 0.2848,
|
|
423
447
|
gray: 0.5,
|
|
424
|
-
grey: 0.5
|
|
448
|
+
grey: 0.5,
|
|
425
449
|
};
|
|
426
450
|
|
|
427
451
|
const lower = colorStr.toLowerCase();
|
|
@@ -539,7 +563,7 @@ export function resolveMask(maskData, targetBBox, options = {}) {
|
|
|
539
563
|
child,
|
|
540
564
|
targetBBox,
|
|
541
565
|
maskData.maskContentUnits,
|
|
542
|
-
samples
|
|
566
|
+
samples,
|
|
543
567
|
);
|
|
544
568
|
|
|
545
569
|
if (polygon.length >= 3) {
|
|
@@ -612,7 +636,10 @@ export function applyMask(targetPolygon, maskData, targetBBox, options = {}) {
|
|
|
612
636
|
for (const { polygon: maskPoly, opacity } of maskRegions) {
|
|
613
637
|
if (opacity <= 0) continue;
|
|
614
638
|
|
|
615
|
-
const intersection = PolygonClip.polygonIntersection(
|
|
639
|
+
const intersection = PolygonClip.polygonIntersection(
|
|
640
|
+
targetPolygon,
|
|
641
|
+
maskPoly,
|
|
642
|
+
);
|
|
616
643
|
|
|
617
644
|
for (const clippedPoly of intersection) {
|
|
618
645
|
if (clippedPoly.length >= 3) {
|
|
@@ -672,7 +699,12 @@ export function applyMask(targetPolygon, maskData, targetBBox, options = {}) {
|
|
|
672
699
|
* `${i === 0 ? 'M' : 'L'} ${p.x} ${p.y}`
|
|
673
700
|
* ).join(' ') + ' Z';
|
|
674
701
|
*/
|
|
675
|
-
export function maskToClipPath(
|
|
702
|
+
export function maskToClipPath(
|
|
703
|
+
maskData,
|
|
704
|
+
targetBBox,
|
|
705
|
+
opacityThreshold = 0.5,
|
|
706
|
+
options = {},
|
|
707
|
+
) {
|
|
676
708
|
const maskRegions = resolveMask(maskData, targetBBox, options);
|
|
677
709
|
|
|
678
710
|
// Union all regions above threshold
|
|
@@ -737,16 +769,16 @@ export function maskToClipPath(maskData, targetBBox, opacityThreshold = 0.5, opt
|
|
|
737
769
|
export function maskToPathData(maskData, targetBBox, options = {}) {
|
|
738
770
|
const polygon = maskToClipPath(maskData, targetBBox, 0.5, options);
|
|
739
771
|
|
|
740
|
-
if (polygon.length < 3) return
|
|
772
|
+
if (polygon.length < 3) return "";
|
|
741
773
|
|
|
742
|
-
let d =
|
|
774
|
+
let d = "";
|
|
743
775
|
for (let i = 0; i < polygon.length; i++) {
|
|
744
776
|
const p = polygon[i];
|
|
745
777
|
const x = Number(p.x).toFixed(6);
|
|
746
778
|
const y = Number(p.y).toFixed(6);
|
|
747
779
|
d += i === 0 ? `M ${x} ${y}` : ` L ${x} ${y}`;
|
|
748
780
|
}
|
|
749
|
-
d +=
|
|
781
|
+
d += " Z";
|
|
750
782
|
|
|
751
783
|
return d;
|
|
752
784
|
}
|
|
@@ -786,7 +818,7 @@ export function maskToPathData(maskData, targetBBox, options = {}) {
|
|
|
786
818
|
* }
|
|
787
819
|
*/
|
|
788
820
|
export function parseGradientReference(fill) {
|
|
789
|
-
if (!fill || typeof fill !==
|
|
821
|
+
if (!fill || typeof fill !== "string") return null;
|
|
790
822
|
|
|
791
823
|
const match = fill.match(/url\s*\(\s*#([^)\s]+)\s*\)/i);
|
|
792
824
|
return match ? match[1] : null;
|
|
@@ -883,12 +915,19 @@ export function rgbToLuminance(color) {
|
|
|
883
915
|
* console.log(`Region with ${polygon.length} vertices, opacity: ${opacity.toFixed(2)}`);
|
|
884
916
|
* });
|
|
885
917
|
*/
|
|
886
|
-
export function sampleMeshGradientForMask(
|
|
918
|
+
export function sampleMeshGradientForMask(
|
|
919
|
+
meshData,
|
|
920
|
+
shapeBBox,
|
|
921
|
+
maskType = "luminance",
|
|
922
|
+
options = {},
|
|
923
|
+
) {
|
|
887
924
|
const { subdivisions = 4 } = options;
|
|
888
925
|
const result = [];
|
|
889
926
|
|
|
890
927
|
// Get mesh gradient polygons with colors
|
|
891
|
-
const meshPolygons = MeshGradient.meshGradientToPolygons(meshData, {
|
|
928
|
+
const meshPolygons = MeshGradient.meshGradientToPolygons(meshData, {
|
|
929
|
+
subdivisions,
|
|
930
|
+
});
|
|
892
931
|
|
|
893
932
|
for (const { polygon, color } of meshPolygons) {
|
|
894
933
|
if (polygon.length < 3) continue;
|
|
@@ -955,14 +994,28 @@ export function sampleMeshGradientForMask(meshData, shapeBBox, maskType = 'lumin
|
|
|
955
994
|
* renderPolygon(polygon, { fillOpacity: opacity });
|
|
956
995
|
* });
|
|
957
996
|
*/
|
|
958
|
-
export function applyMeshGradientMask(
|
|
959
|
-
|
|
997
|
+
export function applyMeshGradientMask(
|
|
998
|
+
targetPolygon,
|
|
999
|
+
meshData,
|
|
1000
|
+
targetBBox,
|
|
1001
|
+
maskType = "luminance",
|
|
1002
|
+
options = {},
|
|
1003
|
+
) {
|
|
1004
|
+
const meshMaskRegions = sampleMeshGradientForMask(
|
|
1005
|
+
meshData,
|
|
1006
|
+
targetBBox,
|
|
1007
|
+
maskType,
|
|
1008
|
+
options,
|
|
1009
|
+
);
|
|
960
1010
|
const result = [];
|
|
961
1011
|
|
|
962
1012
|
for (const { polygon: maskPoly, opacity } of meshMaskRegions) {
|
|
963
1013
|
if (opacity <= 0) continue;
|
|
964
1014
|
|
|
965
|
-
const intersection = PolygonClip.polygonIntersection(
|
|
1015
|
+
const intersection = PolygonClip.polygonIntersection(
|
|
1016
|
+
targetPolygon,
|
|
1017
|
+
maskPoly,
|
|
1018
|
+
);
|
|
966
1019
|
|
|
967
1020
|
for (const clippedPoly of intersection) {
|
|
968
1021
|
if (clippedPoly.length >= 3) {
|
|
@@ -1038,7 +1091,12 @@ export function applyMeshGradientMask(targetPolygon, meshData, targetBBox, maskT
|
|
|
1038
1091
|
* // Each polygon is a piece of the gradient, clipped by the mask child shape
|
|
1039
1092
|
* });
|
|
1040
1093
|
*/
|
|
1041
|
-
export function resolveMaskWithGradients(
|
|
1094
|
+
export function resolveMaskWithGradients(
|
|
1095
|
+
maskData,
|
|
1096
|
+
targetBBox,
|
|
1097
|
+
gradientDefs = {},
|
|
1098
|
+
options = {},
|
|
1099
|
+
) {
|
|
1042
1100
|
const { samples = 20, subdivisions = 4 } = options;
|
|
1043
1101
|
const maskType = maskData.maskType || MaskType.LUMINANCE;
|
|
1044
1102
|
const result = [];
|
|
@@ -1055,29 +1113,36 @@ export function resolveMaskWithGradients(maskData, targetBBox, gradientDefs = {}
|
|
|
1055
1113
|
child,
|
|
1056
1114
|
targetBBox,
|
|
1057
1115
|
maskData.maskContentUnits,
|
|
1058
|
-
samples
|
|
1116
|
+
samples,
|
|
1059
1117
|
);
|
|
1060
1118
|
|
|
1061
1119
|
if (shapePolygon.length < 3) continue;
|
|
1062
1120
|
|
|
1063
1121
|
// Check if it's a mesh gradient
|
|
1064
|
-
if (gradientData.type ===
|
|
1122
|
+
if (gradientData.type === "meshgradient" || gradientData.patches) {
|
|
1065
1123
|
// Sample mesh gradient within the shape
|
|
1066
1124
|
const meshMaskRegions = sampleMeshGradientForMask(
|
|
1067
1125
|
gradientData,
|
|
1068
1126
|
targetBBox,
|
|
1069
1127
|
maskType,
|
|
1070
|
-
{ subdivisions }
|
|
1128
|
+
{ subdivisions },
|
|
1071
1129
|
);
|
|
1072
1130
|
|
|
1073
1131
|
// Clip mesh regions to the shape
|
|
1074
|
-
for (const {
|
|
1075
|
-
|
|
1132
|
+
for (const {
|
|
1133
|
+
polygon: meshPoly,
|
|
1134
|
+
opacity: meshOpacity,
|
|
1135
|
+
} of meshMaskRegions) {
|
|
1136
|
+
const clipped = PolygonClip.polygonIntersection(
|
|
1137
|
+
shapePolygon,
|
|
1138
|
+
meshPoly,
|
|
1139
|
+
);
|
|
1076
1140
|
|
|
1077
1141
|
for (const clippedPoly of clipped) {
|
|
1078
1142
|
if (clippedPoly.length >= 3) {
|
|
1079
1143
|
// Combine mesh opacity with child opacity
|
|
1080
|
-
const combinedOpacity =
|
|
1144
|
+
const combinedOpacity =
|
|
1145
|
+
meshOpacity * (child.opacity || 1) * (child.fillOpacity || 1);
|
|
1081
1146
|
if (combinedOpacity > 0) {
|
|
1082
1147
|
result.push({ polygon: clippedPoly, opacity: combinedOpacity });
|
|
1083
1148
|
}
|
|
@@ -1092,7 +1157,7 @@ export function resolveMaskWithGradients(maskData, targetBBox, gradientDefs = {}
|
|
|
1092
1157
|
child,
|
|
1093
1158
|
targetBBox,
|
|
1094
1159
|
maskData.maskContentUnits,
|
|
1095
|
-
samples
|
|
1160
|
+
samples,
|
|
1096
1161
|
);
|
|
1097
1162
|
|
|
1098
1163
|
if (polygon.length >= 3) {
|
|
@@ -1158,14 +1223,24 @@ export function resolveMaskWithGradients(maskData, targetBBox, gradientDefs = {}
|
|
|
1158
1223
|
* // Apply region to rendering pipeline
|
|
1159
1224
|
* });
|
|
1160
1225
|
*/
|
|
1161
|
-
export function createMeshGradientMask(
|
|
1162
|
-
|
|
1226
|
+
export function createMeshGradientMask(
|
|
1227
|
+
meshData,
|
|
1228
|
+
bounds,
|
|
1229
|
+
maskType = "luminance",
|
|
1230
|
+
options = {},
|
|
1231
|
+
) {
|
|
1232
|
+
const regions = sampleMeshGradientForMask(
|
|
1233
|
+
meshData,
|
|
1234
|
+
bounds,
|
|
1235
|
+
maskType,
|
|
1236
|
+
options,
|
|
1237
|
+
);
|
|
1163
1238
|
|
|
1164
1239
|
return {
|
|
1165
|
-
type:
|
|
1240
|
+
type: "meshGradientMask",
|
|
1166
1241
|
bounds,
|
|
1167
1242
|
maskType,
|
|
1168
|
-
regions
|
|
1243
|
+
regions,
|
|
1169
1244
|
};
|
|
1170
1245
|
}
|
|
1171
1246
|
|
|
@@ -1237,7 +1312,10 @@ export function getMeshGradientBoundary(meshData, options = {}) {
|
|
|
1237
1312
|
allPoints.push(...rightPoints);
|
|
1238
1313
|
}
|
|
1239
1314
|
if (patch.bottom) {
|
|
1240
|
-
const bottomPoints = MeshGradient.sampleBezierCurve(
|
|
1315
|
+
const bottomPoints = MeshGradient.sampleBezierCurve(
|
|
1316
|
+
patch.bottom,
|
|
1317
|
+
samples,
|
|
1318
|
+
);
|
|
1241
1319
|
allPoints.push(...bottomPoints);
|
|
1242
1320
|
}
|
|
1243
1321
|
if (patch.left) {
|
|
@@ -1311,11 +1389,17 @@ export function getMeshGradientBoundary(meshData, options = {}) {
|
|
|
1311
1389
|
* const clipped = clipWithMeshGradientShape(targetPolygon, meshData, { subdivisions: 6 });
|
|
1312
1390
|
* // All patches unioned into one shape before clipping
|
|
1313
1391
|
*/
|
|
1314
|
-
export function clipWithMeshGradientShape(
|
|
1392
|
+
export function clipWithMeshGradientShape(
|
|
1393
|
+
targetPolygon,
|
|
1394
|
+
meshData,
|
|
1395
|
+
options = {},
|
|
1396
|
+
) {
|
|
1315
1397
|
const { subdivisions = 4 } = options;
|
|
1316
1398
|
|
|
1317
1399
|
// Get all patch polygons (ignoring colors)
|
|
1318
|
-
const meshPolygons = MeshGradient.meshGradientToPolygons(meshData, {
|
|
1400
|
+
const meshPolygons = MeshGradient.meshGradientToPolygons(meshData, {
|
|
1401
|
+
subdivisions,
|
|
1402
|
+
});
|
|
1319
1403
|
|
|
1320
1404
|
if (meshPolygons.length === 0) {
|
|
1321
1405
|
return [];
|
|
@@ -1325,7 +1409,10 @@ export function clipWithMeshGradientShape(targetPolygon, meshData, options = {})
|
|
|
1325
1409
|
let meshShape = meshPolygons[0].polygon;
|
|
1326
1410
|
|
|
1327
1411
|
for (let i = 1; i < meshPolygons.length; i++) {
|
|
1328
|
-
const unionResult = PolygonClip.polygonUnion(
|
|
1412
|
+
const unionResult = PolygonClip.polygonUnion(
|
|
1413
|
+
meshShape,
|
|
1414
|
+
meshPolygons[i].polygon,
|
|
1415
|
+
);
|
|
1329
1416
|
if (unionResult.length > 0 && unionResult[0].length >= 3) {
|
|
1330
1417
|
meshShape = unionResult[0];
|
|
1331
1418
|
}
|
|
@@ -1384,7 +1471,9 @@ export function clipWithMeshGradientShape(targetPolygon, meshData, options = {})
|
|
|
1384
1471
|
export function meshGradientToClipPath(meshData, options = {}) {
|
|
1385
1472
|
const { subdivisions = 4 } = options;
|
|
1386
1473
|
|
|
1387
|
-
const meshPolygons = MeshGradient.meshGradientToPolygons(meshData, {
|
|
1474
|
+
const meshPolygons = MeshGradient.meshGradientToPolygons(meshData, {
|
|
1475
|
+
subdivisions,
|
|
1476
|
+
});
|
|
1388
1477
|
|
|
1389
1478
|
if (meshPolygons.length === 0) {
|
|
1390
1479
|
return [];
|
|
@@ -1405,3 +1494,25 @@ export function meshGradientToClipPath(meshData, options = {}) {
|
|
|
1405
1494
|
|
|
1406
1495
|
return result;
|
|
1407
1496
|
}
|
|
1497
|
+
|
|
1498
|
+
export default {
|
|
1499
|
+
MaskType,
|
|
1500
|
+
parseMaskElement,
|
|
1501
|
+
getMaskRegion,
|
|
1502
|
+
maskChildToPolygon,
|
|
1503
|
+
colorToLuminance,
|
|
1504
|
+
getMaskChildOpacity,
|
|
1505
|
+
resolveMask,
|
|
1506
|
+
applyMask,
|
|
1507
|
+
maskToClipPath,
|
|
1508
|
+
maskToPathData,
|
|
1509
|
+
parseGradientReference,
|
|
1510
|
+
rgbToLuminance,
|
|
1511
|
+
sampleMeshGradientForMask,
|
|
1512
|
+
applyMeshGradientMask,
|
|
1513
|
+
resolveMaskWithGradients,
|
|
1514
|
+
createMeshGradientMask,
|
|
1515
|
+
getMeshGradientBoundary,
|
|
1516
|
+
clipWithMeshGradientShape,
|
|
1517
|
+
meshGradientToClipPath,
|
|
1518
|
+
};
|