@pixerate/editor 0.100.0 → 0.101.0

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.
@@ -75,6 +75,25 @@ var VECTOR_GRADIENT_SPREADS = ["pad", "reflect", "repeat"];
75
75
  var VECTOR_GRADIENT_INTERPOLATIONS = ["srgb", "linear", "oklab"];
76
76
  var VECTOR_STROKE_GRADIENT_MODES = ["within", "along", "across"];
77
77
  var VECTOR_ARROWHEADS = ["triangle", "arrow", "open", "circle", "square", "bar"];
78
+ var VECTOR_BLEND_MODES = [
79
+ "normal",
80
+ "darken",
81
+ "multiply",
82
+ "color-burn",
83
+ "lighten",
84
+ "screen",
85
+ "color-dodge",
86
+ "linear-dodge",
87
+ "overlay",
88
+ "soft-light",
89
+ "hard-light",
90
+ "difference",
91
+ "exclusion",
92
+ "hue",
93
+ "saturation",
94
+ "color",
95
+ "luminosity"
96
+ ];
78
97
  var VECTOR_BLEND_SPACINGS = ["steps", "distance", "smoothColor"];
79
98
  var VECTOR_TOOLS = [
80
99
  "select",
@@ -100,6 +119,7 @@ var VECTOR_TRANSFORM_HANDLES = ["nw", "n", "ne", "e", "se", "s", "sw", "w", "rot
100
119
  var VECTOR_PATHFINDER_OPERATIONS = ["divide", "trim", "merge", "crop", "outline", "minusBack"];
101
120
  var VECTOR_EFFECT_TYPES = ["trim", "roundCorners", "puckerBloat", "twist", "zigzag", "wiggle", "offset", "merge", "repeater", "envelope"];
102
121
  var VECTOR_MERGE_MODES = ["merge", "add", "subtract", "intersect", "exclude"];
122
+ var VECTOR_FILTER_TYPES = ["dropShadow", "innerShadow", "outerGlow", "innerGlow", "layerBlur", "feather"];
103
123
 
104
124
  // src/vector/gradient.ts
105
125
  var clamp = (v2, lo, hi) => Math.min(hi, Math.max(lo, v2));
@@ -1255,8 +1275,8 @@ function removeWidthPoints(points, indices) {
1255
1275
  }
1256
1276
  function ease(u, e) {
1257
1277
  const x = Math.min(Math.max(u, 0), 1);
1258
- const round = Math.sqrt(Math.max(0, 1 - (1 - x) * (1 - x)));
1259
- return x + (round - x) * Math.min(Math.max(e, 0), 1);
1278
+ const round3 = Math.sqrt(Math.max(0, 1 - (1 - x) * (1 - x)));
1279
+ return x + (round3 - x) * Math.min(Math.max(e, 0), 1);
1260
1280
  }
1261
1281
  function taperWaveFactor(t, taper, wave) {
1262
1282
  let k = 1;
@@ -1880,6 +1900,278 @@ function vectorStrokeReach(style) {
1880
1900
  return reach;
1881
1901
  }
1882
1902
 
1903
+ // src/vector/paintLayers.ts
1904
+ var VECTOR_STROKE_SETTING_KEYS = [
1905
+ "strokeWidth",
1906
+ "strokeLinecap",
1907
+ "strokeLinejoin",
1908
+ "strokeDasharray",
1909
+ "strokeAlign",
1910
+ "startArrow",
1911
+ "endArrow",
1912
+ "arrowAlign",
1913
+ "startArrowScale",
1914
+ "endArrowScale",
1915
+ "strokeWidthProfile"
1916
+ ];
1917
+ var VECTOR_BLEND_MODE_LABELS = {
1918
+ normal: "Normal",
1919
+ darken: "Darken",
1920
+ multiply: "Multiply",
1921
+ "color-burn": "Color Burn",
1922
+ lighten: "Lighten",
1923
+ screen: "Screen",
1924
+ "color-dodge": "Color Dodge",
1925
+ "linear-dodge": "Linear Dodge (Add)",
1926
+ overlay: "Overlay",
1927
+ "soft-light": "Soft Light",
1928
+ "hard-light": "Hard Light",
1929
+ difference: "Difference",
1930
+ exclusion: "Exclusion",
1931
+ hue: "Hue",
1932
+ saturation: "Saturation",
1933
+ color: "Color",
1934
+ luminosity: "Luminosity"
1935
+ };
1936
+ function isVectorBlendMode(value) {
1937
+ return typeof value === "string" && VECTOR_BLEND_MODES.includes(value);
1938
+ }
1939
+ function vectorBlendModeCss(mode) {
1940
+ if (!mode || mode === "normal" || !isVectorBlendMode(mode)) return void 0;
1941
+ return mode === "linear-dodge" ? "plus-lighter" : mode;
1942
+ }
1943
+ function vectorBlendModeFromCss(value) {
1944
+ const v2 = (value ?? "").trim().toLowerCase();
1945
+ if (v2 === "plus-lighter") return "linear-dodge";
1946
+ return isVectorBlendMode(v2) ? v2 : null;
1947
+ }
1948
+ var draws = (paint) => {
1949
+ if (paint === void 0 || paint === null) return false;
1950
+ if (typeof paint === "string") {
1951
+ const p = paint.trim().toLowerCase();
1952
+ return p !== "" && p !== "none" && p !== "transparent";
1953
+ }
1954
+ return true;
1955
+ };
1956
+ function isPlainVectorPaintLayer(layer) {
1957
+ return (layer.opacity ?? 1) >= 1 && (layer.blendMode ?? "normal") === "normal" && layer.visible !== false && (layer.kind ?? "paint") === "paint";
1958
+ }
1959
+ function isDrawnVectorPaintLayer(layer) {
1960
+ return layer.visible !== false && (layer.kind ?? "paint") === "paint" && draws(layer.paint) && (layer.opacity ?? 1) > 0;
1961
+ }
1962
+ function vectorFills(style) {
1963
+ if (Array.isArray(style.fills)) return style.fills;
1964
+ return draws(style.fill) ? [{ paint: style.fill }] : [];
1965
+ }
1966
+ function vectorStrokeSettings(style) {
1967
+ const out = {};
1968
+ for (const key of VECTOR_STROKE_SETTING_KEYS) {
1969
+ const v2 = style[key];
1970
+ if (v2 !== void 0 && v2 !== null) out[key] = v2;
1971
+ }
1972
+ return out;
1973
+ }
1974
+ function vectorStrokes(style) {
1975
+ if (Array.isArray(style.strokes)) return style.strokes;
1976
+ return draws(style.stroke) ? [{ paint: style.stroke, ...vectorStrokeSettings(style) }] : [];
1977
+ }
1978
+ function hasVectorPaintStacks(style) {
1979
+ return Array.isArray(style.fills) || Array.isArray(style.strokes);
1980
+ }
1981
+ function withVectorFills(node, fills) {
1982
+ const next = { ...node };
1983
+ if (fills.length === 0 || fills.length === 1 && isPlainVectorPaintLayer(fills[0])) {
1984
+ delete next.fills;
1985
+ next.fill = fills[0]?.paint ?? null;
1986
+ return next;
1987
+ }
1988
+ next.fills = fills.map((l) => ({ ...l }));
1989
+ next.fill = fills[fills.length - 1].paint;
1990
+ return next;
1991
+ }
1992
+ function withVectorStrokes(node, strokes) {
1993
+ const next = { ...node };
1994
+ if (strokes.length === 0) {
1995
+ delete next.strokes;
1996
+ next.stroke = null;
1997
+ return next;
1998
+ }
1999
+ const top = strokes[strokes.length - 1];
2000
+ for (const key of VECTOR_STROKE_SETTING_KEYS) delete next[key];
2001
+ Object.assign(next, vectorStrokeSettings(top));
2002
+ next.stroke = top.paint;
2003
+ if (strokes.length === 1 && isPlainVectorPaintLayer(top)) delete next.strokes;
2004
+ else next.strokes = strokes.map((l) => ({ ...l }));
2005
+ return next;
2006
+ }
2007
+ var json = (v2) => JSON.stringify(v2 ?? null);
2008
+ function strokeShorthandDiff(node, top) {
2009
+ const out = [];
2010
+ if (json(node.stroke) !== json(top?.paint)) out.push("stroke");
2011
+ for (const key of VECTOR_STROKE_SETTING_KEYS) if (json(node[key]) !== json(top?.[key])) out.push(key);
2012
+ return out;
2013
+ }
2014
+ var checked = /* @__PURE__ */ new WeakSet();
2015
+ function normalizeVectorPaintStacks(node, prefer = "shorthand") {
2016
+ if (!hasVectorPaintStacks(node)) {
2017
+ if (!("fills" in node) && !("strokes" in node)) return node;
2018
+ const next2 = { ...node };
2019
+ delete next2.fills;
2020
+ delete next2.strokes;
2021
+ return next2;
2022
+ }
2023
+ if (checked.has(node)) return node;
2024
+ let next = node;
2025
+ if (Array.isArray(node.fills)) {
2026
+ let fills = node.fills;
2027
+ const top = fills[fills.length - 1];
2028
+ if (prefer === "shorthand" && json(node.fill) !== json(top?.paint)) {
2029
+ if (!draws(node.fill)) fills = [];
2030
+ else fills = top ? [...fills.slice(0, -1), { ...top, paint: node.fill }] : [{ paint: node.fill }];
2031
+ }
2032
+ next = withVectorFills(next, fills);
2033
+ }
2034
+ if (Array.isArray(node.strokes)) {
2035
+ let strokes = node.strokes;
2036
+ const top = strokes[strokes.length - 1];
2037
+ const diff = prefer === "shorthand" ? strokeShorthandDiff(node, top) : [];
2038
+ if (diff.length) {
2039
+ if (diff.includes("stroke") && !draws(node.stroke)) strokes = [];
2040
+ else {
2041
+ const layer = top ? { ...top } : { paint: node.stroke ?? null };
2042
+ for (const key of diff) {
2043
+ if (key === "stroke") layer.paint = node.stroke ?? null;
2044
+ else if (node[key] === void 0 || node[key] === null) delete layer[key];
2045
+ else layer[key] = node[key];
2046
+ }
2047
+ strokes = top ? [...strokes.slice(0, -1), layer] : [layer];
2048
+ }
2049
+ }
2050
+ next = withVectorStrokes(next, strokes);
2051
+ }
2052
+ const keys = ["fill", "fills", "stroke", "strokes", ...VECTOR_STROKE_SETTING_KEYS];
2053
+ const same2 = keys.every((k) => k in next === k in node && json(next[k]) === json(node[k]));
2054
+ const out = same2 ? node : next;
2055
+ checked.add(out);
2056
+ return out;
2057
+ }
2058
+ function sanitizePaint(value) {
2059
+ if (value === null || value === void 0) return null;
2060
+ if (typeof value === "string") return value;
2061
+ return sanitizeVectorGradient(value);
2062
+ }
2063
+ function sanitizeLayerBase(raw) {
2064
+ const out = { paint: sanitizePaint(raw.paint) };
2065
+ if (typeof raw.opacity === "number" && Number.isFinite(raw.opacity) && raw.opacity < 1) out.opacity = Math.max(0, raw.opacity);
2066
+ if (isVectorBlendMode(raw.blendMode) && raw.blendMode !== "normal") out.blendMode = raw.blendMode;
2067
+ if (raw.visible === false) out.visible = false;
2068
+ return out;
2069
+ }
2070
+ function sanitizeVectorFills(value) {
2071
+ if (!Array.isArray(value)) return [];
2072
+ return value.filter((l) => !!l && typeof l === "object").map(sanitizeLayerBase);
2073
+ }
2074
+ var ARROW_ALIGNS = ["extend", "tip"];
2075
+ function sanitizeVectorStrokeSettings(raw) {
2076
+ const out = {};
2077
+ const num2 = (v2) => typeof v2 === "number" && Number.isFinite(v2) ? v2 : void 0;
2078
+ const width = num2(raw.strokeWidth);
2079
+ if (width !== void 0 && width >= 0) out.strokeWidth = width;
2080
+ if (raw.strokeLinecap === "butt" || raw.strokeLinecap === "round" || raw.strokeLinecap === "square") out.strokeLinecap = raw.strokeLinecap;
2081
+ if (raw.strokeLinejoin === "miter" || raw.strokeLinejoin === "round" || raw.strokeLinejoin === "bevel") out.strokeLinejoin = raw.strokeLinejoin;
2082
+ if (Array.isArray(raw.strokeDasharray)) {
2083
+ const dash = raw.strokeDasharray.filter((n) => typeof n === "number" && Number.isFinite(n) && n >= 0);
2084
+ if (dash.length && dash.some((n) => n > 0)) out.strokeDasharray = dash;
2085
+ }
2086
+ if (raw.strokeAlign === "center" || raw.strokeAlign === "inside" || raw.strokeAlign === "outside") out.strokeAlign = raw.strokeAlign;
2087
+ for (const key of ["startArrow", "endArrow"]) if (VECTOR_ARROWHEADS.includes(raw[key])) out[key] = raw[key];
2088
+ if (ARROW_ALIGNS.includes(raw.arrowAlign)) out.arrowAlign = raw.arrowAlign;
2089
+ for (const key of ["startArrowScale", "endArrowScale"]) {
2090
+ const n = num2(raw[key]);
2091
+ if (n !== void 0 && n > 0) out[key] = n;
2092
+ }
2093
+ if (Array.isArray(raw.strokeWidthProfile)) {
2094
+ const p = sanitizeWidthProfile(raw.strokeWidthProfile);
2095
+ if (p.length) out.strokeWidthProfile = p;
2096
+ }
2097
+ return out;
2098
+ }
2099
+ function sanitizeVectorStrokes(value) {
2100
+ if (!Array.isArray(value)) return [];
2101
+ return value.filter((l) => !!l && typeof l === "object").map((raw) => {
2102
+ if (raw.kind !== void 0 && raw.kind !== "paint") return { ...raw };
2103
+ return { ...sanitizeLayerBase(raw), ...sanitizeVectorStrokeSettings(raw) };
2104
+ });
2105
+ }
2106
+ var layerNodeCache = /* @__PURE__ */ new WeakMap();
2107
+ function vectorPaintLayerNodes(node) {
2108
+ const hit = layerNodeCache.get(node);
2109
+ if (hit) return hit;
2110
+ const { fills: _f, strokes: _s, ...base } = node;
2111
+ const plain2 = { ...base };
2112
+ for (const key of VECTOR_STROKE_SETTING_KEYS) delete plain2[key];
2113
+ const out = [];
2114
+ vectorFills(node).forEach((layer, index) => {
2115
+ out.push({ kind: "fill", index, layer, node: { ...plain2, fill: layer.paint, stroke: null }, drawn: isDrawnVectorPaintLayer(layer) });
2116
+ });
2117
+ vectorStrokes(node).forEach((layer, index) => {
2118
+ const settings = (layer.kind ?? "paint") === "paint" ? vectorStrokeSettings(layer) : {};
2119
+ out.push({ kind: "stroke", index, layer, node: { ...plain2, fill: null, ...settings, stroke: layer.paint }, drawn: isDrawnVectorPaintLayer(layer) });
2120
+ });
2121
+ layerNodeCache.set(node, out);
2122
+ return out;
2123
+ }
2124
+ function needsVectorPaintLayers(node) {
2125
+ if (!hasVectorPaintStacks(node)) return false;
2126
+ const fills = vectorFills(node);
2127
+ const strokes = vectorStrokes(node);
2128
+ return fills.length > 1 || strokes.length > 1 || fills.some((l) => !isPlainVectorPaintLayer(l)) || strokes.some((l) => !isPlainVectorPaintLayer(l));
2129
+ }
2130
+ function vectorStrokeStackReach(node) {
2131
+ let reach = 0;
2132
+ for (const l of vectorPaintLayerNodes(node)) if (l.kind === "stroke" && l.drawn) reach = Math.max(reach, vectorStrokeReach(l.node));
2133
+ return reach;
2134
+ }
2135
+ function hitTestVectorPaintLayer(node, path, p, tolerance = 0) {
2136
+ if (!path.length) return null;
2137
+ const layers = vectorPaintLayerNodes(node);
2138
+ for (let i = layers.length - 1; i >= 0; i--) {
2139
+ const l = layers[i];
2140
+ if (!l.drawn) continue;
2141
+ if (l.kind === "stroke") {
2142
+ if ((l.node.strokeWidth ?? 1) > 0 && hitTestVectorStroke(path, p, l.node, tolerance)) return { kind: "stroke", index: l.index };
2143
+ } else if (pointInBezierPath(path, [p.x, p.y], node.fillRule ?? "nonzero")) return { kind: "fill", index: l.index };
2144
+ }
2145
+ return null;
2146
+ }
2147
+ function hitTestVectorPaintStack(node, path, p, tolerance = 0) {
2148
+ if (hitTestVectorPaintLayer(node, path, p, tolerance)) return true;
2149
+ if (vectorPaintLayerNodes(node).some((l) => l.kind === "stroke" && l.drawn)) return false;
2150
+ return distanceToBezierPath(path, [p.x, p.y]) <= tolerance;
2151
+ }
2152
+ function vectorStacksHaveGradient(style) {
2153
+ return [...style.fills ?? [], ...style.strokes ?? []].some((l) => isVectorGradient(l.paint));
2154
+ }
2155
+ function newVectorStrokeLayer(node, patch = {}) {
2156
+ const top = vectorStrokes(node).at(-1);
2157
+ const settings = top && (top.kind ?? "paint") === "paint" ? vectorStrokeSettings(top) : { strokeWidth: node.strokeWidth ?? 1 };
2158
+ return { paint: "#000000", ...settings, ...patch };
2159
+ }
2160
+ function vectorTargetPaint(style, target, layer = null) {
2161
+ if (layer === null) return style[target];
2162
+ const stack = target === "fill" ? vectorFills(style) : vectorStrokes(style);
2163
+ return stack[layer] ? stack[layer].paint : style[target];
2164
+ }
2165
+ function withVectorTargetPaint(node, target, layer, paint) {
2166
+ const stack = target === "fill" ? vectorFills(node) : vectorStrokes(node);
2167
+ if (layer === null || !stack[layer]) {
2168
+ if (!stack.length) return { ...node, [target]: paint };
2169
+ layer = stack.length - 1;
2170
+ }
2171
+ const next = stack.map((l, i) => i === layer ? { ...l, paint } : l);
2172
+ return target === "fill" ? withVectorFills(node, next) : withVectorStrokes(node, next);
2173
+ }
2174
+
1883
2175
  // src/vector/pathMap.ts
1884
2176
  var MAX_PIECES = 1 << 20;
1885
2177
  var MAX_DEPTH = 6;
@@ -2616,6 +2908,9 @@ function shapeNode(shape, id) {
2616
2908
  locked: _l,
2617
2909
  visible: _v,
2618
2910
  opacity: _o,
2911
+ // The node's own blend mode and filters stay on the node, not on each shape it draws.
2912
+ blendMode: _bm,
2913
+ filters: _fl,
2619
2914
  ...style
2620
2915
  } = shape.source;
2621
2916
  return { ...style, id, type: "path", subpaths: bezierToSubpaths(shape.path), ...shape.opacity < 1 ? { opacity: shape.opacity } : {} };
@@ -2627,7 +2922,9 @@ function placement(node) {
2627
2922
  ...node.visible === false ? { visible: false } : {},
2628
2923
  ...node.locked ? { locked: true } : {},
2629
2924
  ...node.blendStep ? { blendStep: true } : {},
2630
- ...node.booleanOperation ? { booleanOperation: node.booleanOperation } : {}
2925
+ ...node.booleanOperation ? { booleanOperation: node.booleanOperation } : {},
2926
+ ...node.blendMode ? { blendMode: node.blendMode } : {},
2927
+ ...node.filters?.length ? { filters: node.filters } : {}
2631
2928
  };
2632
2929
  }
2633
2930
  function vectorAppearanceNodes(node) {
@@ -2942,10 +3239,7 @@ function hitTestVectorNode(node, p, tolerance = 0, parent = IDENTITY_MATRIX, opt
2942
3239
  }
2943
3240
  const path = vectorPathWorldBezier(node, parent);
2944
3241
  if (path.length === 0) return false;
2945
- const point = [p.x, p.y];
2946
- if (hasPaint(node.fill) && pointInBezierPath(path, point, node.fillRule ?? "nonzero")) return true;
2947
- if (hasPaint(node.stroke)) return hitTestVectorStroke(path, p, node, tolerance);
2948
- return distanceToBezierPath(path, point) <= tolerance;
3242
+ return hitTestVectorPaintStack(node, path, p, tolerance);
2949
3243
  }
2950
3244
  function vectorStrokeOutline(node, parent = IDENTITY_MATRIX) {
2951
3245
  if (!hasPaint(node.stroke) || !((node.strokeWidth ?? 1) > 0)) return [];
@@ -2995,13 +3289,21 @@ function bakeIntoGeometry(node, m) {
2995
3289
  return { ...group, children, blend: { ...group.blend, spine: moved } };
2996
3290
  }
2997
3291
  const path = node;
2998
- const mirrored = m[0] * m[3] - m[1] * m[2] < 0 && path.strokeWidthProfile?.length;
3292
+ const flips = m[0] * m[3] - m[1] * m[2] < 0;
3293
+ const mirrored = flips && path.strokeWidthProfile?.length;
3294
+ const layer = (l) => ({
3295
+ ...l,
3296
+ ...isVectorGradient(l.paint) ? { paint: transformVectorPaint(l.paint, m) } : {},
3297
+ ...flips && l.strokeWidthProfile?.length ? { strokeWidthProfile: mirrorWidthProfile(l.strokeWidthProfile) } : {}
3298
+ });
2999
3299
  return {
3000
3300
  ...path,
3001
3301
  ...mirrored ? { strokeWidthProfile: mirrorWidthProfile(path.strokeWidthProfile) } : {},
3002
3302
  // Gradients in the node's own space move with its geometry.
3003
3303
  ...isVectorGradient(path.fill) ? { fill: transformVectorPaint(path.fill, m) } : {},
3004
3304
  ...isVectorGradient(path.stroke) ? { stroke: transformVectorPaint(path.stroke, m) } : {},
3305
+ ...Array.isArray(path.fills) ? { fills: path.fills.map(layer) } : {},
3306
+ ...Array.isArray(path.strokes) ? { strokes: path.strokes.map(layer) } : {},
3005
3307
  subpaths: path.subpaths.map((sp) => ({
3006
3308
  closed: sp.closed,
3007
3309
  anchors: sp.anchors.map((a) => mapAnchor(a, m))
@@ -4267,6 +4569,201 @@ function evaluateVectorPathKeyframes(keyframes, time, options = {}) {
4267
4569
  return morph.at(u);
4268
4570
  }
4269
4571
 
4572
+ // src/vector/filters.ts
4573
+ var VECTOR_FILTER_DEFAULTS = {
4574
+ dropShadow: { color: "#000000", opacity: 0.25, offsetX: 0, offsetY: 4, blur: 4, spread: 0 },
4575
+ innerShadow: { color: "#000000", opacity: 0.25, offsetX: 0, offsetY: 4, blur: 4, spread: 0 },
4576
+ outerGlow: { color: "#ffffbe", opacity: 0.75, blur: 10, spread: 0 },
4577
+ innerGlow: { color: "#ffffbe", opacity: 0.75, blur: 10, spread: 0 },
4578
+ layerBlur: { radius: 4 },
4579
+ feather: { radius: 8 }
4580
+ };
4581
+ var VECTOR_FILTER_LABELS = {
4582
+ dropShadow: "Drop Shadow",
4583
+ innerShadow: "Inner Shadow",
4584
+ outerGlow: "Outer Glow",
4585
+ innerGlow: "Inner Glow",
4586
+ layerBlur: "Layer Blur",
4587
+ feather: "Feather"
4588
+ };
4589
+ var SHADOW_SPECS = [
4590
+ { key: "color", label: "Color", kind: "color" },
4591
+ { key: "opacity", label: "Opacity", kind: "number", min: 0, max: 1, step: 0.01, slider: true },
4592
+ { key: "offsetX", label: "X", kind: "number", min: -1e3, max: 1e3, step: 1 },
4593
+ { key: "offsetY", label: "Y", kind: "number", min: -1e3, max: 1e3, step: 1 },
4594
+ { key: "blur", label: "Blur", kind: "number", min: 0, max: 500, step: 1 },
4595
+ { key: "spread", label: "Spread", kind: "number", min: 0, max: 500, step: 1 }
4596
+ ];
4597
+ var GLOW_SPECS = SHADOW_SPECS.filter((s) => s.key !== "offsetX" && s.key !== "offsetY");
4598
+ var VECTOR_FILTER_PARAM_SPECS = {
4599
+ dropShadow: SHADOW_SPECS,
4600
+ innerShadow: SHADOW_SPECS.map((s) => s.key === "spread" ? { ...s, label: "Choke" } : s),
4601
+ outerGlow: GLOW_SPECS,
4602
+ innerGlow: GLOW_SPECS.map((s) => s.key === "spread" ? { ...s, label: "Choke" } : s),
4603
+ layerBlur: [{ key: "radius", label: "Radius", kind: "number", min: 0, max: 500, step: 1, slider: true }],
4604
+ feather: [{ key: "radius", label: "Radius", kind: "number", min: 0, max: 500, step: 1, slider: true }]
4605
+ };
4606
+ function isVectorFilterType(value) {
4607
+ return typeof value === "string" && VECTOR_FILTER_TYPES.includes(value);
4608
+ }
4609
+ function normalizeVectorFilterParams(type, params, base) {
4610
+ if (!isVectorFilterType(type)) return null;
4611
+ const out = { ...VECTOR_FILTER_DEFAULTS[type], ...base ?? {} };
4612
+ for (const spec of VECTOR_FILTER_PARAM_SPECS[type]) {
4613
+ const v2 = params?.[spec.key];
4614
+ if (v2 === void 0) continue;
4615
+ if (spec.kind === "number" && typeof v2 === "number" && Number.isFinite(v2)) out[spec.key] = Math.min(spec.max ?? Infinity, Math.max(spec.min ?? -Infinity, v2));
4616
+ else if (spec.kind === "color" && typeof v2 === "string" && v2.trim()) out[spec.key] = v2.trim();
4617
+ }
4618
+ return out;
4619
+ }
4620
+ function vectorFilterParamKeys(type) {
4621
+ return VECTOR_FILTER_PARAM_SPECS[type].map((s) => s.key);
4622
+ }
4623
+ function createVectorFilter(type, id, params) {
4624
+ const p = normalizeVectorFilterParams(type, params);
4625
+ return p ? { id, type, params: p } : null;
4626
+ }
4627
+ function sanitizeVectorFilters(value) {
4628
+ if (!Array.isArray(value)) return [];
4629
+ const out = [];
4630
+ const used = /* @__PURE__ */ new Set();
4631
+ for (const raw of value) {
4632
+ if (!raw || typeof raw !== "object") continue;
4633
+ const r = raw;
4634
+ if (!isVectorFilterType(r.type)) continue;
4635
+ let id = typeof r.id === "string" && r.id && !used.has(r.id) ? r.id : "";
4636
+ for (let k = out.length + 1; !id; k++) if (!used.has(`filter${k}`)) id = `filter${k}`;
4637
+ used.add(id);
4638
+ const filter = createVectorFilter(r.type, id, r.params ?? {});
4639
+ out.push(r.enabled === false ? { ...filter, enabled: false } : filter);
4640
+ }
4641
+ return out;
4642
+ }
4643
+ function activeVectorFilters(node) {
4644
+ return (node.filters ?? []).filter((f) => {
4645
+ if (f.enabled === false || !isVectorFilterType(f.type)) return false;
4646
+ if (f.type === "layerBlur" || f.type === "feather") return f.params.radius > 0;
4647
+ return f.params.opacity > 0;
4648
+ });
4649
+ }
4650
+ function vectorFilterReach(filters) {
4651
+ let outer = 0;
4652
+ let blur = 0;
4653
+ for (const f of filters) {
4654
+ switch (f.type) {
4655
+ case "dropShadow":
4656
+ case "innerShadow":
4657
+ outer = Math.max(outer, Math.max(Math.abs(f.params.offsetX), Math.abs(f.params.offsetY)) + f.params.blur * 1.5 + f.params.spread);
4658
+ break;
4659
+ case "outerGlow":
4660
+ case "innerGlow":
4661
+ outer = Math.max(outer, f.params.blur * 1.5 + f.params.spread);
4662
+ break;
4663
+ case "layerBlur":
4664
+ blur += f.params.radius * 1.5;
4665
+ break;
4666
+ }
4667
+ }
4668
+ return outer + blur;
4669
+ }
4670
+ var round = (n) => Math.round(n * 1e3) / 1e3;
4671
+ var fe = (tag, attrs, children) => ({ tag, attrs, ...children ? { children } : {} });
4672
+ function vectorFilterSvg(filters, id, bounds) {
4673
+ const active = filters.filter((f) => activeVectorFilters({ filters: [f] }).length);
4674
+ if (!active.length || !bounds) return null;
4675
+ const primitives = [];
4676
+ let body = "SourceGraphic";
4677
+ let n = 0;
4678
+ const name = (part) => `${part}${n}`;
4679
+ for (const f of active) {
4680
+ if (f.type !== "innerShadow" && f.type !== "innerGlow") continue;
4681
+ n++;
4682
+ const p = f.params;
4683
+ primitives.push(fe("feComponentTransfer", { in: "SourceAlpha", result: name("inv") }, [fe("feFuncA", { type: "table", tableValues: "1 0" })]));
4684
+ let last = name("inv");
4685
+ if (f.type === "innerShadow" && (f.params.offsetX || f.params.offsetY)) {
4686
+ primitives.push(fe("feOffset", { in: last, dx: round(f.params.offsetX), dy: round(f.params.offsetY), result: name("off") }));
4687
+ last = name("off");
4688
+ }
4689
+ if (p.spread > 0) {
4690
+ primitives.push(fe("feMorphology", { in: last, operator: "dilate", radius: round(p.spread), result: name("grow") }));
4691
+ last = name("grow");
4692
+ }
4693
+ if (p.blur > 0) {
4694
+ primitives.push(fe("feGaussianBlur", { in: last, stdDeviation: round(p.blur / 2), result: name("blur") }));
4695
+ last = name("blur");
4696
+ }
4697
+ primitives.push(fe("feFlood", { "flood-color": p.color, "flood-opacity": round(p.opacity), result: name("color") }));
4698
+ primitives.push(fe("feComposite", { in: name("color"), in2: last, operator: "in", result: name("tint") }));
4699
+ primitives.push(fe("feComposite", { in: name("tint"), in2: "SourceAlpha", operator: "in", result: name("inner") }));
4700
+ primitives.push(fe("feComposite", { in: name("inner"), in2: body, operator: "over", result: name("body") }));
4701
+ body = name("body");
4702
+ }
4703
+ for (const f of active) {
4704
+ if (f.type !== "feather") continue;
4705
+ n++;
4706
+ const r = f.params.radius;
4707
+ primitives.push(fe("feMorphology", { in: "SourceAlpha", operator: "erode", radius: round(r / 2), result: name("core") }));
4708
+ primitives.push(fe("feGaussianBlur", { in: name("core"), stdDeviation: round(r / 4), result: name("fade") }));
4709
+ primitives.push(fe("feComposite", { in: body, in2: name("fade"), operator: "in", result: name("body") }));
4710
+ body = name("body");
4711
+ }
4712
+ const under = [];
4713
+ for (const f of active) {
4714
+ if (f.type !== "dropShadow" && f.type !== "outerGlow") continue;
4715
+ n++;
4716
+ const p = f.params;
4717
+ let last = "SourceAlpha";
4718
+ if (p.spread > 0) {
4719
+ primitives.push(fe("feMorphology", { in: last, operator: "dilate", radius: round(p.spread), result: name("grow") }));
4720
+ last = name("grow");
4721
+ }
4722
+ if (p.blur > 0) {
4723
+ primitives.push(fe("feGaussianBlur", { in: last, stdDeviation: round(p.blur / 2), result: name("blur") }));
4724
+ last = name("blur");
4725
+ }
4726
+ if (f.type === "dropShadow" && (f.params.offsetX || f.params.offsetY)) {
4727
+ primitives.push(fe("feOffset", { in: last, dx: round(f.params.offsetX), dy: round(f.params.offsetY), result: name("off") }));
4728
+ last = name("off");
4729
+ }
4730
+ primitives.push(fe("feFlood", { "flood-color": p.color, "flood-opacity": round(p.opacity), result: name("color") }));
4731
+ primitives.push(fe("feComposite", { in: name("color"), in2: last, operator: "in", result: name("shadow") }));
4732
+ under.push(name("shadow"));
4733
+ }
4734
+ if (under.length) {
4735
+ n++;
4736
+ primitives.push(fe("feMerge", { result: name("merged") }, [...under, body].map((i) => fe("feMergeNode", { in: i }))));
4737
+ body = name("merged");
4738
+ }
4739
+ for (const f of active) {
4740
+ if (f.type !== "layerBlur") continue;
4741
+ n++;
4742
+ primitives.push(fe("feGaussianBlur", { in: body, stdDeviation: round(f.params.radius / 2), result: name("body") }));
4743
+ body = name("body");
4744
+ }
4745
+ const lastResult = primitives[primitives.length - 1]?.attrs.result;
4746
+ if (lastResult !== body) primitives.push(fe("feMerge", {}, [fe("feMergeNode", { in: body })]));
4747
+ const pad = vectorFilterReach(active) + 2;
4748
+ return {
4749
+ id,
4750
+ x: round(bounds.x - pad),
4751
+ y: round(bounds.y - pad),
4752
+ width: round(bounds.width + 2 * pad),
4753
+ height: round(bounds.height + 2 * pad),
4754
+ primitives
4755
+ };
4756
+ }
4757
+ var escapeAttr = (value) => value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
4758
+ function primitiveMarkup(p) {
4759
+ const attrs = Object.entries(p.attrs).map(([k, v2]) => ` ${k}="${escapeAttr(String(v2))}"`).join("");
4760
+ return p.children?.length ? `<${p.tag}${attrs}>${p.children.map(primitiveMarkup).join("")}</${p.tag}>` : `<${p.tag}${attrs}/>`;
4761
+ }
4762
+ function vectorFilterMarkup(filter, extra = "") {
4763
+ return `<filter id="${escapeAttr(filter.id)}" filterUnits="userSpaceOnUse" x="${filter.x}" y="${filter.y}" width="${filter.width}" height="${filter.height}" color-interpolation-filters="sRGB"${extra}>${filter.primitives.map(primitiveMarkup).join("")}</filter>`;
4764
+ }
4765
+ var VECTOR_FILTER_MENU = VECTOR_FILTER_TYPES.map((type) => ({ type, label: VECTOR_FILTER_LABELS[type] }));
4766
+
4270
4767
  // src/vector/gradientStroke.ts
4271
4768
  var SHARP = 1.25;
4272
4769
  var TOLERANCE = 0.2;
@@ -4450,7 +4947,7 @@ function applyVectorCornersAttribute(subpaths, value) {
4450
4947
  }
4451
4948
  return subpaths;
4452
4949
  }
4453
- var escapeAttr = (value) => value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
4950
+ var escapeAttr2 = (value) => value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
4454
4951
  function vectorPathLocalBounds(node) {
4455
4952
  const b = bezierPathBounds(subpathsToBezier(node.subpaths));
4456
4953
  return b ? { x: b.minX, y: b.minY, width: b.maxX - b.minX, height: b.maxY - b.minY } : null;
@@ -4506,6 +5003,26 @@ function computeStrokeSvgParts(node, clipId, precision) {
4506
5003
  heads: parts.heads.map((h) => bezierPathToSvg(h.path, { precision }))
4507
5004
  };
4508
5005
  }
5006
+ function vectorNodeLocalPaintBounds(node) {
5007
+ const drawn = expandVectorAppearance(node);
5008
+ const reach = (n) => n.type === "path" ? vectorStrokeStackReach(n) : Math.max(0, ...n.children.map(reach));
5009
+ let b;
5010
+ if (drawn.type === "path") b = vectorPathLocalBounds(drawn);
5011
+ else b = unionBounds(drawn.children.filter((c) => c.visible !== false).map((c) => vectorNodeBounds(c, IDENTITY_MATRIX)));
5012
+ if (!b) return null;
5013
+ const pad = reach(drawn);
5014
+ return { x: b.x - pad, y: b.y - pad, width: b.width + 2 * pad, height: b.height + 2 * pad };
5015
+ }
5016
+ var filterCache = /* @__PURE__ */ new WeakMap();
5017
+ function vectorNodeFilterSvg(node, id) {
5018
+ if (!node.filters?.length) return null;
5019
+ const hit = filterCache.get(node);
5020
+ if (hit && hit.id === id) return hit.filter;
5021
+ const active = activeVectorFilters(node);
5022
+ const filter = active.length ? vectorFilterSvg(active, id, vectorNodeLocalPaintBounds(node)) : null;
5023
+ filterCache.set(node, { id, filter });
5024
+ return filter;
5025
+ }
4509
5026
  function widthProfileToAttribute(points, precision = 4) {
4510
5027
  return points.map((p) => [p.t, p.left, p.right].map((n) => formatNumber(n, precision)).join(",")).join(";");
4511
5028
  }
@@ -4521,16 +5038,16 @@ function widthProfileFromAttribute(value) {
4521
5038
  var round2 = (n) => Math.round(n * 100) / 100;
4522
5039
  function gradientMarkup(def, extra = "") {
4523
5040
  const tag = def.type === "radial" ? "radialGradient" : "linearGradient";
4524
- const attrs = Object.entries(def.attrs).map(([k, v2]) => ` ${k}="${escapeAttr(String(v2))}"`).join("");
4525
- const stops = def.stops.map((s) => `<stop offset="${s.offset}" stop-color="${escapeAttr(s.stopColor)}"${s.stopOpacity !== void 0 ? ` stop-opacity="${s.stopOpacity}"` : ""}/>`).join("");
4526
- return `<${tag} id="${escapeAttr(def.id)}"${attrs}${extra}>${stops}</${tag}>`;
5041
+ const attrs = Object.entries(def.attrs).map(([k, v2]) => ` ${k}="${escapeAttr2(String(v2))}"`).join("");
5042
+ const stops = def.stops.map((s) => `<stop offset="${s.offset}" stop-color="${escapeAttr2(s.stopColor)}"${s.stopOpacity !== void 0 ? ` stop-opacity="${s.stopOpacity}"` : ""}/>`).join("");
5043
+ return `<${tag} id="${escapeAttr2(def.id)}"${attrs}${extra}>${stops}</${tag}>`;
4527
5044
  }
4528
5045
  function paintDefMarkup(def, gradient) {
4529
- const extra = gradient ? ` data-vector-gradient="${escapeAttr(JSON.stringify(gradient))}"` : "";
5046
+ const extra = gradient ? ` data-vector-gradient="${escapeAttr2(JSON.stringify(gradient))}"` : "";
4530
5047
  if (def.type !== "pattern") return gradientMarkup(def, extra);
4531
- const attrs = Object.entries(def.attrs).map(([k, v2]) => ` ${k}="${escapeAttr(String(v2))}"`).join("");
4532
- const inner = def.gradients.map((g) => gradientMarkup(g)).join("") + def.tiles.map((t) => `<path d="${t.d}" fill="${escapeAttr(t.fill)}"/>`).join("");
4533
- return `<pattern id="${escapeAttr(def.id)}"${attrs}${extra}>${inner}</pattern>`;
5048
+ const attrs = Object.entries(def.attrs).map(([k, v2]) => ` ${k}="${escapeAttr2(String(v2))}"`).join("");
5049
+ const inner = def.gradients.map((g) => gradientMarkup(g)).join("") + def.tiles.map((t) => `<path d="${t.d}" fill="${escapeAttr2(t.fill)}"/>`).join("");
5050
+ return `<pattern id="${escapeAttr2(def.id)}"${attrs}${extra}>${inner}</pattern>`;
4534
5051
  }
4535
5052
  var paintMarkup = (paint, defs) => defs.map((d, i) => paintDefMarkup(d, i === 0 && isVectorGradient(paint) ? paint : void 0));
4536
5053
  function blendFromAttribute(value, m) {
@@ -4569,25 +5086,113 @@ function vectorDocumentToSvg(doc, options = {}) {
4569
5086
  const num2 = (n) => formatNumber(n, precision);
4570
5087
  const indent = (depth) => pretty ? " ".repeat(depth) : "";
4571
5088
  const common = (node) => {
4572
- let out2 = ` id="${escapeAttr(node.id)}"`;
5089
+ let out2 = ` id="${escapeAttr2(node.id)}"`;
4573
5090
  if (node.transform) out2 += ` transform="matrix(${node.transform.map((n) => formatNumber(n, 6)).join(" ")})"`;
4574
5091
  if (node.opacity !== void 0 && node.opacity < 1) out2 += ` opacity="${num2(node.opacity)}"`;
4575
5092
  if (node.visible === false) out2 += ` display="none"`;
4576
5093
  if (node.blendStep) out2 += ` data-vector-blend-step=""`;
4577
- if (node.type === "group" && node.blend) out2 += ` data-vector-blend="${escapeAttr(JSON.stringify(node.blend))}"`;
5094
+ if (node.type === "group" && node.blend) out2 += ` data-vector-blend="${escapeAttr2(JSON.stringify(node.blend))}"`;
4578
5095
  if (node.booleanOperation) out2 += ` data-vector-boolean-operation="${node.booleanOperation}"`;
5096
+ const blend = vectorBlendModeCss(node.blendMode);
5097
+ if (blend) out2 += ` style="mix-blend-mode:${blend}"`;
5098
+ if (node.filters?.length) {
5099
+ const filter = vectorNodeFilterSvg(node, `${prefix}filter-${node.id}`);
5100
+ const data = ` data-vector-filters="${escapeAttr2(JSON.stringify(node.filters))}"`;
5101
+ if (filter) {
5102
+ defs.push(vectorFilterMarkup(filter, data));
5103
+ out2 += ` filter="url(#${escapeAttr2(filter.id)})"`;
5104
+ } else out2 += data;
5105
+ }
4579
5106
  return out2;
4580
5107
  };
5108
+ const layerAttrs = (layer) => {
5109
+ let out2 = "";
5110
+ if (layer.opacity !== void 0 && layer.opacity < 1) out2 += ` opacity="${num2(layer.opacity)}"`;
5111
+ const blend = vectorBlendModeCss(layer.blendMode);
5112
+ if (blend) out2 += ` style="mix-blend-mode:${blend}"`;
5113
+ return out2;
5114
+ };
5115
+ const strokePartLines = (node, parts, strokeValue, d, lineAttrs, depth) => {
5116
+ const out2 = [];
5117
+ if (options.strokes === "outline") {
5118
+ const area = bezierPathToSvg(vectorStrokeOutlinePath(subpathsToBezier(node.subpaths), node), { precision });
5119
+ out2.push(`${indent(depth)}<path data-vector-part="stroke-outline" d="${area}" fill="${escapeAttr2(strokeValue)}"/>`);
5120
+ return out2;
5121
+ }
5122
+ let clip = "";
5123
+ if (parts.clip) {
5124
+ const rule = node.fillRule === "evenodd" ? "evenodd" : "nonzero";
5125
+ const id = escapeAttr2(parts.clip.id);
5126
+ if (parts.clip.kind === "clip") {
5127
+ defs.push(`<clipPath id="${id}"><path d="${d}" clip-rule="${rule}"/></clipPath>`);
5128
+ clip = ` clip-path="url(#${id})"`;
5129
+ } else {
5130
+ const b = parts.clip.bounds;
5131
+ const box = `x="${num2(b.x)}" y="${num2(b.y)}" width="${num2(b.width)}" height="${num2(b.height)}"`;
5132
+ defs.push(`<mask id="${id}" maskUnits="userSpaceOnUse" ${box}><rect ${box} fill="#fff"/><path d="${d}" fill="#000" fill-rule="${rule}"/></mask>`);
5133
+ clip = ` mask="url(#${id})"`;
5134
+ }
5135
+ }
5136
+ if (parts.outline !== void 0) out2.push(`${indent(depth)}<path data-vector-part="stroke-outline" d="${parts.outline}" fill="${escapeAttr2(strokeValue)}"${clip}/>`);
5137
+ else out2.push(`${indent(depth)}<path data-vector-part="stroke" d="${parts.d}" fill="none"${lineAttrs(parts.width)}${clip}/>`);
5138
+ for (const h2 of parts.heads) out2.push(`${indent(depth)}<path data-vector-part="head" d="${h2}" fill="${escapeAttr2(strokeValue)}"/>`);
5139
+ return out2;
5140
+ };
5141
+ const lineAttrsOf = (node, strokeValue) => (width) => {
5142
+ let out2 = ` stroke="${escapeAttr2(strokeValue)}" stroke-width="${num2(width)}"`;
5143
+ if (node.strokeLinecap && node.strokeLinecap !== "butt") out2 += ` stroke-linecap="${node.strokeLinecap}"`;
5144
+ if (node.strokeLinejoin && node.strokeLinejoin !== "miter") out2 += ` stroke-linejoin="${node.strokeLinejoin}"`;
5145
+ if (node.strokeDasharray?.length) out2 += ` stroke-dasharray="${node.strokeDasharray.map(num2).join(" ")}"`;
5146
+ return out2;
5147
+ };
5148
+ const liveCorners = (node) => {
5149
+ const corners = vectorCornersAttribute(node.subpaths, precision);
5150
+ return corners ? ` data-vector-d="${bezierPathToSvg(subpathsToBezier(node.subpaths, { corners: false }), { precision })}" data-vector-corners="${corners}"` : "";
5151
+ };
5152
+ const emitStacked = (node, depth) => {
5153
+ const d = vectorPathData(node, precision);
5154
+ const rule = node.fillRule === "evenodd" ? ` fill-rule="evenodd"` : "";
5155
+ let data = ` data-vector-path="" data-vector-fills="${escapeAttr2(JSON.stringify(vectorFills(node)))}" data-vector-strokes="${escapeAttr2(JSON.stringify(vectorStrokes(node)))}"`;
5156
+ if (node.fillRule === "evenodd") data += ` data-vector-fill-rule="evenodd"`;
5157
+ lines.push(`${indent(depth)}<g${common(node)}${data}>`);
5158
+ const layers = vectorPaintLayerNodes(node).filter((l) => l.drawn);
5159
+ let geometry = liveCorners(node);
5160
+ if (!layers.some((l) => l.kind === "fill")) {
5161
+ lines.push(`${indent(depth + 1)}<path data-vector-part="fill" d="${d}" fill="none"${rule}${geometry}/>`);
5162
+ geometry = "";
5163
+ }
5164
+ for (const l of layers) {
5165
+ const id = `${prefix}${l.kind}-${node.id}-${l.index}`;
5166
+ const paint = vectorPaintToSvg(l.layer.paint, id, { node: l.node, precision, ...l.kind === "stroke" ? { role: "stroke" } : {} });
5167
+ defs.push(...paintMarkup(l.layer.paint, paint.defs));
5168
+ const attrs = ` data-vector-layer="${l.index}"${layerAttrs(l.layer)}`;
5169
+ if (l.kind === "fill") {
5170
+ lines.push(`${indent(depth + 1)}<path data-vector-part="fill"${attrs} d="${d}" fill="${escapeAttr2(paint.value)}"${rule}${geometry}/>`);
5171
+ geometry = "";
5172
+ continue;
5173
+ }
5174
+ const lineAttrs = lineAttrsOf(l.node, paint.value);
5175
+ const parts = vectorStrokeSvgParts(l.node, `${prefix}clip-${node.id}-${l.index}`, precision);
5176
+ if (!parts) {
5177
+ lines.push(`${indent(depth + 1)}<path data-vector-part="stroke"${attrs} d="${d}" fill="none"${lineAttrs(l.node.strokeWidth ?? 1)}/>`);
5178
+ continue;
5179
+ }
5180
+ lines.push(`${indent(depth + 1)}<g data-vector-part="stroke-layer"${attrs}>`);
5181
+ lines.push(...strokePartLines(l.node, parts, paint.value, d, lineAttrs, depth + 2));
5182
+ lines.push(`${indent(depth + 1)}</g>`);
5183
+ }
5184
+ lines.push(`${indent(depth)}</g>`);
5185
+ };
4581
5186
  const emit2 = (node, depth) => {
4582
5187
  if (node.visible === false && !options.includeHidden) return;
4583
5188
  const wrappers = options.wrap?.[node.id] ?? [];
4584
5189
  wrappers.forEach((w2, i) => {
4585
- let attrs = w2.id !== void 0 ? ` id="${escapeAttr(w2.id)}"` : "";
4586
- if (w2.transform) attrs += ` transform="${escapeAttr(w2.transform)}"`;
5190
+ let attrs = w2.id !== void 0 ? ` id="${escapeAttr2(w2.id)}"` : "";
5191
+ if (w2.transform) attrs += ` transform="${escapeAttr2(w2.transform)}"`;
4587
5192
  if (w2.clip) {
4588
5193
  const shape = Object.entries(w2.clip.attrs).map(([k, v2]) => ` ${k}="${formatNumber(v2, 4)}"`).join("");
4589
- defs.push(`<clipPath id="${escapeAttr(w2.clip.id)}" clipPathUnits="objectBoundingBox"><${w2.clip.shape}${w2.clip.shapeId !== void 0 ? ` id="${escapeAttr(w2.clip.shapeId)}"` : ""}${shape}/></clipPath>`);
4590
- attrs += ` clip-path="url(#${escapeAttr(w2.clip.id)})"`;
5194
+ defs.push(`<clipPath id="${escapeAttr2(w2.clip.id)}" clipPathUnits="objectBoundingBox"><${w2.clip.shape}${w2.clip.shapeId !== void 0 ? ` id="${escapeAttr2(w2.clip.shapeId)}"` : ""}${shape}/></clipPath>`);
5195
+ attrs += ` clip-path="url(#${escapeAttr2(w2.clip.id)})"`;
4591
5196
  }
4592
5197
  lines.push(`${indent(depth + i)}<g${attrs}>`);
4593
5198
  });
@@ -4601,13 +5206,13 @@ function vectorDocumentToSvg(doc, options = {}) {
4601
5206
  ...node.effects?.length ? { effects: node.effects } : {},
4602
5207
  ...node.type === "group" && node.liveBoolean ? { liveBoolean: node.liveBoolean } : {}
4603
5208
  };
4604
- lines.push(`${indent(depth)}<g${common(node)} data-vector-live="${escapeAttr(JSON.stringify(live2))}">`);
5209
+ lines.push(`${indent(depth)}<g${common(node)} data-vector-live="${escapeAttr2(JSON.stringify(live2))}">`);
4605
5210
  lines.push(`${indent(depth + 1)}<g data-vector-part="appearance">`);
4606
5211
  for (const shape of vectorAppearanceNodes(node)) emit2(shape, depth + 2);
4607
5212
  lines.push(`${indent(depth + 1)}</g>`);
4608
5213
  lines.push(`${indent(depth + 1)}<g data-vector-part="source" display="none">`);
4609
5214
  if (node.type === "path") {
4610
- const { effects: _e, transform: _t, opacity: _o, visible: _v, booleanOperation: _b, blendStep: _s, ...source } = node;
5215
+ const { effects: _e, transform: _t, opacity: _o, visible: _v, booleanOperation: _b, blendStep: _s, blendMode: _bm, filters: _f, ...source } = node;
4611
5216
  emit2({ ...source, id: `${node.id}~src` }, depth + 2);
4612
5217
  } else for (const child of node.children) emit2(child, depth + 2);
4613
5218
  lines.push(`${indent(depth + 1)}</g>`);
@@ -4620,20 +5225,17 @@ function vectorDocumentToSvg(doc, options = {}) {
4620
5225
  lines.push(`${indent(depth)}</g>`);
4621
5226
  return;
4622
5227
  }
5228
+ if (needsVectorPaintLayers(node)) {
5229
+ emitStacked(node, depth);
5230
+ return;
5231
+ }
4623
5232
  const fill = vectorPaintToSvg(node.fill, `${prefix}fill-${node.id}`, { node, precision });
4624
5233
  const stroke = vectorPaintToSvg(node.stroke, `${prefix}stroke-${node.id}`, { node, role: "stroke", precision });
4625
5234
  defs.push(...paintMarkup(node.fill, fill.defs), ...paintMarkup(node.stroke, stroke.defs));
4626
5235
  const d = vectorPathData(node, precision);
4627
- const corners = vectorCornersAttribute(node.subpaths, precision);
4628
- const live = corners ? ` data-vector-d="${bezierPathToSvg(subpathsToBezier(node.subpaths, { corners: false }), { precision })}" data-vector-corners="${corners}"` : "";
4629
- const fillAttrs = ` fill="${escapeAttr(fill.value)}"${node.fillRule === "evenodd" ? ` fill-rule="evenodd"` : ""}`;
4630
- const lineAttrs = (width) => {
4631
- let out2 = ` stroke="${escapeAttr(stroke.value)}" stroke-width="${num2(width)}"`;
4632
- if (node.strokeLinecap && node.strokeLinecap !== "butt") out2 += ` stroke-linecap="${node.strokeLinecap}"`;
4633
- if (node.strokeLinejoin && node.strokeLinejoin !== "miter") out2 += ` stroke-linejoin="${node.strokeLinejoin}"`;
4634
- if (node.strokeDasharray?.length) out2 += ` stroke-dasharray="${node.strokeDasharray.map(num2).join(" ")}"`;
4635
- return out2;
4636
- };
5236
+ const live = liveCorners(node);
5237
+ const fillAttrs = ` fill="${escapeAttr2(fill.value)}"${node.fillRule === "evenodd" ? ` fill-rule="evenodd"` : ""}`;
5238
+ const lineAttrs = lineAttrsOf(node, stroke.value);
4637
5239
  const parts = stroke.value !== "none" ? vectorStrokeSvgParts(node, `${prefix}clip-${node.id}`, precision) : void 0;
4638
5240
  if (!parts) {
4639
5241
  lines.push(`${indent(depth)}<path${common(node)} d="${d}"${fillAttrs}${stroke.value !== "none" ? lineAttrs(node.strokeWidth ?? 1) : ""}${live}/>`);
@@ -4655,28 +5257,7 @@ function vectorDocumentToSvg(doc, options = {}) {
4655
5257
  }
4656
5258
  lines.push(`${indent(depth)}<g${common(node)}${data}>`);
4657
5259
  lines.push(`${indent(depth + 1)}<path data-vector-part="fill" d="${d}"${fillAttrs}${live}/>`);
4658
- if (options.strokes === "outline") {
4659
- const area = bezierPathToSvg(vectorStrokeOutlinePath(subpathsToBezier(node.subpaths), node), { precision });
4660
- lines.push(`${indent(depth + 1)}<path data-vector-part="stroke-outline" d="${area}" fill="${escapeAttr(stroke.value)}"/>`);
4661
- } else {
4662
- let clip = "";
4663
- if (parts.clip) {
4664
- const rule = node.fillRule === "evenodd" ? "evenodd" : "nonzero";
4665
- const id = escapeAttr(parts.clip.id);
4666
- if (parts.clip.kind === "clip") {
4667
- defs.push(`<clipPath id="${id}"><path d="${d}" clip-rule="${rule}"/></clipPath>`);
4668
- clip = ` clip-path="url(#${id})"`;
4669
- } else {
4670
- const b = parts.clip.bounds;
4671
- const box = `x="${num2(b.x)}" y="${num2(b.y)}" width="${num2(b.width)}" height="${num2(b.height)}"`;
4672
- defs.push(`<mask id="${id}" maskUnits="userSpaceOnUse" ${box}><rect ${box} fill="#fff"/><path d="${d}" fill="#000" fill-rule="${rule}"/></mask>`);
4673
- clip = ` mask="url(#${id})"`;
4674
- }
4675
- }
4676
- if (parts.outline !== void 0) lines.push(`${indent(depth + 1)}<path data-vector-part="stroke-outline" d="${parts.outline}" fill="${escapeAttr(stroke.value)}"${clip}/>`);
4677
- else lines.push(`${indent(depth + 1)}<path data-vector-part="stroke" d="${parts.d}" fill="none"${lineAttrs(parts.width)}${clip}/>`);
4678
- for (const h2 of parts.heads) lines.push(`${indent(depth + 1)}<path data-vector-part="head" d="${h2}" fill="${escapeAttr(stroke.value)}"/>`);
4679
- }
5260
+ lines.push(...strokePartLines(node, parts, stroke.value, d, lineAttrs, depth + 1));
4680
5261
  lines.push(`${indent(depth)}</g>`);
4681
5262
  };
4682
5263
  for (const node of doc.nodes) emit2(node, 1);
@@ -4686,7 +5267,7 @@ function vectorDocumentToSvg(doc, options = {}) {
4686
5267
  const out = [`<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">`];
4687
5268
  if (defs.length) out.push(`${indent(1)}<defs>${defs.join("")}</defs>`);
4688
5269
  if (doc.background && options.background !== false && hasPaint(doc.background)) {
4689
- out.push(`${indent(1)}<rect width="${w}" height="${h}" fill="${escapeAttr(doc.background)}"/>`);
5270
+ out.push(`${indent(1)}<rect width="${w}" height="${h}" fill="${escapeAttr2(doc.background)}"/>`);
4690
5271
  }
4691
5272
  out.push(...lines, "</svg>");
4692
5273
  return out.join(nl);
@@ -4777,10 +5358,15 @@ var STYLE_KEYS2 = {
4777
5358
  "stroke-linecap": "strokeLinecap",
4778
5359
  "stroke-linejoin": "strokeLinejoin",
4779
5360
  "stroke-dasharray": "strokeDasharray",
4780
- "fill-rule": "fillRule"
5361
+ "fill-rule": "fillRule",
5362
+ "fill-opacity": "fillOpacity",
5363
+ "stroke-opacity": "strokeOpacity",
5364
+ "mix-blend-mode": "mixBlendMode",
5365
+ filter: "filter"
4781
5366
  };
4782
5367
  function readStyle(el, parent) {
4783
- const out = { ...parent };
5368
+ const { mixBlendMode: _m, filter: _f, ...inherited } = parent;
5369
+ const out = { ...inherited };
4784
5370
  for (const [attr, key] of Object.entries(STYLE_KEYS2)) if (el.attrs[attr] !== void 0) out[key] = el.attrs[attr].trim();
4785
5371
  for (const decl of (el.attrs.style ?? "").split(";")) {
4786
5372
  const i = decl.indexOf(":");
@@ -4816,6 +5402,45 @@ function collectGradients(el, raw) {
4816
5402
  if (PAINT_SERVERS.has(el.name) && el.attrs.id) raw.set(el.attrs.id, el);
4817
5403
  for (const c of el.children) collectGradients(c, raw);
4818
5404
  }
5405
+ function collectFilters(el, raw) {
5406
+ if (el.name === "filter" && el.attrs.id) raw.set(el.attrs.id, el);
5407
+ for (const c of el.children) collectFilters(c, raw);
5408
+ }
5409
+ function filtersFromElement(el) {
5410
+ const own = el.attrs["data-vector-filters"];
5411
+ if (own !== void 0) {
5412
+ try {
5413
+ return sanitizeVectorFilters(JSON.parse(own));
5414
+ } catch {
5415
+ return [];
5416
+ }
5417
+ }
5418
+ const prims = el.children.filter((c) => c.name.startsWith("fe"));
5419
+ const out = [];
5420
+ for (const p of prims) {
5421
+ const std = numberList(p.attrs.stdDeviation)[0] ?? 0;
5422
+ if (p.name === "feDropShadow") {
5423
+ const style = Object.fromEntries(
5424
+ (p.attrs.style ?? "").split(";").map((d) => d.split(":").map((x) => x.trim())).filter((d) => d.length === 2)
5425
+ );
5426
+ const opacity = parseFloat(style["flood-opacity"] ?? p.attrs["flood-opacity"] ?? "1");
5427
+ out.push({
5428
+ type: "dropShadow",
5429
+ params: {
5430
+ color: style["flood-color"] ?? p.attrs["flood-color"] ?? "#000000",
5431
+ opacity: Number.isFinite(opacity) ? opacity : 1,
5432
+ offsetX: length(p.attrs.dx, 2),
5433
+ offsetY: length(p.attrs.dy, 2),
5434
+ blur: std * 2,
5435
+ spread: 0
5436
+ }
5437
+ });
5438
+ } else if (p.name === "feGaussianBlur" && prims.length === 1 && (p.attrs.in === void 0 || p.attrs.in === "SourceGraphic")) {
5439
+ out.push({ type: "layerBlur", params: { radius: std * 2 } });
5440
+ } else return [];
5441
+ }
5442
+ return sanitizeVectorFilters(out);
5443
+ }
4819
5444
  function gradientFromAttribute(value) {
4820
5445
  if (!value) return null;
4821
5446
  try {
@@ -4913,6 +5538,13 @@ function parseVectorSvg(svg, options = {}) {
4913
5538
  };
4914
5539
  const rawGradients = /* @__PURE__ */ new Map();
4915
5540
  collectGradients(root, rawGradients);
5541
+ const rawFilters = /* @__PURE__ */ new Map();
5542
+ collectFilters(root, rawFilters);
5543
+ const filtersOf = (value) => {
5544
+ const url = /^url\(\s*['"]?#([^'")\s]+)['"]?\s*\)/.exec((value ?? "").trim());
5545
+ const el = url ? rawFilters.get(url[1]) : void 0;
5546
+ return el ? filtersFromElement(el) : [];
5547
+ };
4916
5548
  const paint = (value) => {
4917
5549
  const v2 = value.trim();
4918
5550
  const url = /^url\(\s*['"]?#([^'")\s]+)['"]?\s*\)/.exec(v2);
@@ -4979,8 +5611,55 @@ function parseVectorSvg(svg, options = {}) {
4979
5611
  if (m === IDENTITY_MATRIX) return node;
4980
5612
  const next = transformVectorNode(node, m);
4981
5613
  const strokeScale = Math.sqrt(Math.abs(m[0] * m[3] - m[1] * m[2]));
4982
- if (hasPaint(next.stroke) && strokeScale > 0 && Math.abs(strokeScale - 1) > 1e-9) next.strokeWidth = (next.strokeWidth ?? 1) * strokeScale;
4983
- return next;
5614
+ if (strokeScale > 0 && Math.abs(strokeScale - 1) > 1e-9) {
5615
+ if (hasPaint(next.stroke)) next.strokeWidth = (next.strokeWidth ?? 1) * strokeScale;
5616
+ if (Array.isArray(next.strokes)) next.strokes = next.strokes.map((l) => (l.kind ?? "paint") === "paint" ? { ...l, strokeWidth: (l.strokeWidth ?? 1) * strokeScale } : l);
5617
+ }
5618
+ return normalizeVectorPaintStacks(next, "stack");
5619
+ };
5620
+ const withOpacities = (node, s) => {
5621
+ let out = node;
5622
+ const fo = s.fillOpacity !== void 0 ? fraction(s.fillOpacity, 1) : 1;
5623
+ const so = s.strokeOpacity !== void 0 ? fraction(s.strokeOpacity, 1) : 1;
5624
+ if (fo < 1 && hasPaint(out.fill)) out = withVectorFills(out, [{ paint: out.fill, opacity: Math.max(0, fo) }]);
5625
+ if (so < 1 && hasPaint(out.stroke)) out = withVectorStrokes(out, vectorStrokes(out).map((l) => ({ ...l, opacity: Math.max(0, so) })));
5626
+ return out;
5627
+ };
5628
+ const stackedPath = (g, _style, m, meta) => {
5629
+ const fillEl = g.children.find((c) => c.name === "path" && c.attrs["data-vector-part"] === "fill");
5630
+ const subpaths = fillEl ? shapeOf(fillEl) : null;
5631
+ if (!subpaths?.length) return null;
5632
+ const parse = (v2) => {
5633
+ try {
5634
+ return v2 ? JSON.parse(v2) : [];
5635
+ } catch {
5636
+ return [];
5637
+ }
5638
+ };
5639
+ let node = { id: idFor(g), type: "path", ...meta, subpaths, fill: null, stroke: null };
5640
+ if (g.attrs["data-vector-fill-rule"] === "evenodd") node.fillRule = "evenodd";
5641
+ node = withVectorFills(node, sanitizeVectorFills(parse(g.attrs["data-vector-fills"])));
5642
+ node = withVectorStrokes(node, sanitizeVectorStrokes(parse(g.attrs["data-vector-strokes"])));
5643
+ return placed(node, m);
5644
+ };
5645
+ const mergeStacked = (nodes2) => {
5646
+ const out = [];
5647
+ const key = (n) => JSON.stringify([n.subpaths, n.transform ?? null, n.opacity ?? 1, n.visible !== false, n.filters ?? null, n.blendMode ?? "normal", n.fillRule ?? "nonzero", n.booleanOperation ?? null]);
5648
+ for (const n of nodes2) {
5649
+ const prev = out[out.length - 1];
5650
+ if (prev?.type === "path" && n.type === "path" && key(prev) === key(n)) {
5651
+ const fills = vectorFills(n);
5652
+ const strokesBefore = vectorStrokes(prev);
5653
+ if (!(fills.length && strokesBefore.length)) {
5654
+ let merged = withVectorFills(prev, [...vectorFills(prev), ...fills]);
5655
+ merged = withVectorStrokes(merged, [...strokesBefore, ...vectorStrokes(n)]);
5656
+ out[out.length - 1] = merged;
5657
+ continue;
5658
+ }
5659
+ }
5660
+ out.push(n);
5661
+ }
5662
+ return out;
4984
5663
  };
4985
5664
  const mergedPath = (g, style, m, meta) => {
4986
5665
  const part = (name) => g.children.find((c) => c.name === "path" && c.attrs["data-vector-part"] === name);
@@ -5063,10 +5742,14 @@ function parseVectorSvg(svg, options = {}) {
5063
5742
  const s = readStyle(child, style);
5064
5743
  const opacity = child.attrs.opacity !== void 0 ? parseFloat(child.attrs.opacity) : void 0;
5065
5744
  const hidden = child.attrs.display === "none" || child.attrs.visibility === "hidden";
5745
+ const blendMode = vectorBlendModeFromCss(s.mixBlendMode);
5746
+ const filters = filtersOf(s.filter);
5066
5747
  const meta = {
5067
5748
  ...child.attrs["inkscape:label"] || child.attrs["data-name"] ? { name: child.attrs["inkscape:label"] ?? child.attrs["data-name"] } : {},
5068
5749
  ...opacity !== void 0 && Number.isFinite(opacity) && opacity < 1 ? { opacity } : {},
5069
- ...hidden ? { visible: false } : {}
5750
+ ...hidden ? { visible: false } : {},
5751
+ ...blendMode && blendMode !== "normal" ? { blendMode } : {},
5752
+ ...filters.length ? { filters } : {}
5070
5753
  };
5071
5754
  if (child.attrs["data-vector-blend-step"] !== void 0) continue;
5072
5755
  const operation = child.attrs["data-vector-boolean-operation"];
@@ -5076,6 +5759,11 @@ function parseVectorSvg(svg, options = {}) {
5076
5759
  if (node2) out.push(node2);
5077
5760
  continue;
5078
5761
  }
5762
+ if (child.name === "g" && child.attrs["data-vector-path"] !== void 0 && child.attrs["data-vector-fills"] !== void 0) {
5763
+ const node2 = stackedPath(child, s, m, meta);
5764
+ if (node2) out.push(node2);
5765
+ continue;
5766
+ }
5079
5767
  if (child.name === "g" && child.attrs["data-vector-path"] !== void 0) {
5080
5768
  const node2 = mergedPath(child, s, m, meta);
5081
5769
  if (node2) out.push(node2);
@@ -5093,20 +5781,23 @@ function parseVectorSvg(svg, options = {}) {
5093
5781
  const subpaths = shapeOf(child);
5094
5782
  if (!subpaths || !subpaths.length) continue;
5095
5783
  const node = placed(
5096
- {
5097
- id: idFor(child),
5098
- type: "path",
5099
- ...meta,
5100
- subpaths,
5101
- fill: child.name === "line" ? null : paint(s.fill),
5102
- stroke: paint(s.stroke),
5103
- ...strokeStyle(s)
5104
- },
5784
+ withOpacities(
5785
+ {
5786
+ id: idFor(child),
5787
+ type: "path",
5788
+ ...meta,
5789
+ subpaths,
5790
+ fill: child.name === "line" ? null : paint(s.fill),
5791
+ stroke: paint(s.stroke),
5792
+ ...strokeStyle(s)
5793
+ },
5794
+ s
5795
+ ),
5105
5796
  m
5106
5797
  );
5107
5798
  out.push(node);
5108
5799
  }
5109
- return out;
5800
+ return options.mergeStackedShapes ? mergeStacked(out) : out;
5110
5801
  };
5111
5802
  const nodes = visit(root, readStyle(root, { fill: "#000", stroke: "none", strokeWidth: "" }), rootMatrix);
5112
5803
  return { width, height, nodes };
@@ -5119,12 +5810,14 @@ export {
5119
5810
  VECTOR_GRADIENT_INTERPOLATIONS,
5120
5811
  VECTOR_STROKE_GRADIENT_MODES,
5121
5812
  VECTOR_ARROWHEADS,
5813
+ VECTOR_BLEND_MODES,
5122
5814
  VECTOR_BLEND_SPACINGS,
5123
5815
  VECTOR_TOOLS,
5124
5816
  VECTOR_TRANSFORM_HANDLES,
5125
5817
  VECTOR_PATHFINDER_OPERATIONS,
5126
5818
  VECTOR_EFFECT_TYPES,
5127
5819
  VECTOR_MERGE_MODES,
5820
+ VECTOR_FILTER_TYPES,
5128
5821
  isVectorGradient,
5129
5822
  parseVectorColor,
5130
5823
  VECTOR_GRADIENT_MIN_STOPS,
@@ -5223,6 +5916,32 @@ export {
5223
5916
  vectorStrokeOutlinePath,
5224
5917
  hitTestVectorStroke,
5225
5918
  vectorStrokeReach,
5919
+ VECTOR_STROKE_SETTING_KEYS,
5920
+ VECTOR_BLEND_MODE_LABELS,
5921
+ isVectorBlendMode,
5922
+ vectorBlendModeCss,
5923
+ vectorBlendModeFromCss,
5924
+ isPlainVectorPaintLayer,
5925
+ isDrawnVectorPaintLayer,
5926
+ vectorFills,
5927
+ vectorStrokeSettings,
5928
+ vectorStrokes,
5929
+ hasVectorPaintStacks,
5930
+ withVectorFills,
5931
+ withVectorStrokes,
5932
+ normalizeVectorPaintStacks,
5933
+ sanitizeVectorFills,
5934
+ sanitizeVectorStrokeSettings,
5935
+ sanitizeVectorStrokes,
5936
+ vectorPaintLayerNodes,
5937
+ needsVectorPaintLayers,
5938
+ vectorStrokeStackReach,
5939
+ hitTestVectorPaintLayer,
5940
+ hitTestVectorPaintStack,
5941
+ vectorStacksHaveGradient,
5942
+ newVectorStrokeLayer,
5943
+ vectorTargetPaint,
5944
+ withVectorTargetPaint,
5226
5945
  effectPathLength,
5227
5946
  effectPathVertexCount,
5228
5947
  trimIntervals,
@@ -5331,16 +6050,31 @@ export {
5331
6050
  createVectorMorph,
5332
6051
  interpolateVectorPaths,
5333
6052
  evaluateVectorPathKeyframes,
6053
+ VECTOR_FILTER_DEFAULTS,
6054
+ VECTOR_FILTER_LABELS,
6055
+ VECTOR_FILTER_PARAM_SPECS,
6056
+ isVectorFilterType,
6057
+ normalizeVectorFilterParams,
6058
+ vectorFilterParamKeys,
6059
+ createVectorFilter,
6060
+ sanitizeVectorFilters,
6061
+ activeVectorFilters,
6062
+ vectorFilterReach,
6063
+ vectorFilterSvg,
6064
+ vectorFilterMarkup,
6065
+ VECTOR_FILTER_MENU,
5334
6066
  vectorStrokeGradientDefs,
5335
6067
  vectorCornersAttribute,
5336
6068
  applyVectorCornersAttribute,
5337
6069
  vectorPathLocalBounds,
5338
6070
  vectorPaintToSvg,
5339
6071
  vectorStrokeSvgParts,
6072
+ vectorNodeLocalPaintBounds,
6073
+ vectorNodeFilterSvg,
5340
6074
  widthProfileToAttribute,
5341
6075
  widthProfileFromAttribute,
5342
6076
  vectorDocumentToSvg,
5343
6077
  parseSvgTransform,
5344
6078
  parseVectorSvg
5345
6079
  };
5346
- //# sourceMappingURL=chunk-YGZNGDIT.js.map
6080
+ //# sourceMappingURL=chunk-63EFQCT5.js.map