@weasel-js/svg 1.11.0 → 1.12.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.
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
- import { identity, multiply, invert, splitByClosure, fillsOpenSubpaths, rotateAboutPoint, hasOpenSubpath, splitSubpaths } from '@weasel-js/geom';
2
- import { resolveCorners, PATH_M, PATH_L, PATH_C, PATH_Q, PATH_Z, isTileKind, boundsOfPath, transformPath, isSolidPaint, warmFonts, warmPaintKinds, isPaintKindKnown, solid, fillToBoundsFrame, getMarker, registerMarker, dwarn, unionAABB, createInsertOp, fillInPoseFrame, pathThroughPoseFrame, DEFAULT_SHAPE_FILL, pathInPoseFrame, poseLinearOf, poseFrameOf, DEFAULT_TEXT_STYLE, resolveScreenLength, pathFromD, isPatternPaint, resolveTextStyle, strokeToPath, toRuns, resolveRunFace, parseColor, rgbaToHex, layoutRuns, resolveRuns, resolveAlign, PathBuilder, scaleVertexWidth, getPaintKind, resolveMarkerSize, paragraphDirection, scriptMetricsFor, vertexHalfWidths, isTilePaint, tileSpecOf } from '@weasel-js/core';
1
+ import { identity, multiply, pathToD, invert, splitByClosure, fillsOpenSubpaths, rotateAboutPoint, hasOpenSubpath, splitSubpaths } from '@weasel-js/geom';
2
+ export { pathToD as serializePathD } from '@weasel-js/geom';
3
+ import { isTileKind, boundsOfPath, transformPath, isSolidPaint, warmFonts, warmPaintKinds, isPaintKindKnown, solid, fillToBoundsFrame, getMarker, registerMarker, dwarn, unionAABB, createInsertOp, fillInPoseFrame, resolveStroke, pathThroughPoseFrame, resolveFill, pathInPoseFrame, poseLinearOf, poseFrameOf, INHERIT, DEFAULT_TEXT_FILL, resolveScreenLength, pathFromD, isPatternPaint, resolveTextStyle, strokeToPath, toRuns, resolveRunFace, parseColor, rgbaToHex, layoutRuns, resolveRuns, resolveAlign, PathBuilder, PATH_M, PATH_L, PATH_Z, scaleVertexWidth, getPaintKind, resolveMarkerSize, paragraphDirection, scriptMetricsFor, vertexHalfWidths, isTilePaint, tileSpecOf } from '@weasel-js/core';
3
4
  import { tileGeometry } from '@weasel-js/core/patterns-builtin';
4
5
 
5
6
  // src/types.ts
@@ -1383,40 +1384,6 @@ function readPattern(el, onWarn) {
1383
1384
  if (!isIdentity([m[0], m[1], m[2], m[3], 0, 0])) paint.transform = [m[0], m[1], m[2], m[3]];
1384
1385
  return paint;
1385
1386
  }
1386
- function serializePathD(given) {
1387
- const path = resolveCorners(given);
1388
- if (path.kind === "rect") {
1389
- const { x, y, width: width2, height: height2 } = path;
1390
- return `M${trimNumber(x)} ${trimNumber(y)}h${trimNumber(width2)}v${trimNumber(height2)}h${trimNumber(-width2)}Z`;
1391
- }
1392
- const cmds = path.commands;
1393
- const xs = path.coords;
1394
- const out = [];
1395
- let ci = 0;
1396
- for (let i = 0; i < cmds.length; i++) {
1397
- const c = cmds[i];
1398
- if (c === PATH_M) {
1399
- out.push(`M${trimNumber(xs[ci])} ${trimNumber(xs[ci + 1])}`);
1400
- ci += 2;
1401
- } else if (c === PATH_L) {
1402
- out.push(`L${trimNumber(xs[ci])} ${trimNumber(xs[ci + 1])}`);
1403
- ci += 2;
1404
- } else if (c === PATH_C) {
1405
- out.push(
1406
- `C${trimNumber(xs[ci])} ${trimNumber(xs[ci + 1])} ${trimNumber(xs[ci + 2])} ${trimNumber(xs[ci + 3])} ${trimNumber(xs[ci + 4])} ${trimNumber(xs[ci + 5])}`
1407
- );
1408
- ci += 6;
1409
- } else if (c === PATH_Q) {
1410
- out.push(
1411
- `Q${trimNumber(xs[ci])} ${trimNumber(xs[ci + 1])} ${trimNumber(xs[ci + 2])} ${trimNumber(xs[ci + 3])}`
1412
- );
1413
- ci += 4;
1414
- } else if (c === PATH_Z) {
1415
- out.push("Z");
1416
- }
1417
- }
1418
- return out.join("");
1419
- }
1420
1387
 
1421
1388
  // src/gradients.ts
1422
1389
  var WEASEL_NS = "urn:weasel-js:svg";
@@ -1722,7 +1689,7 @@ var PaintServerRegistry = class {
1722
1689
  }
1723
1690
  for (const path of this.clips) {
1724
1691
  parts.push(
1725
- `<clipPath id="${this.clipIds.get(path)}"><path d="${serializePathD(path)}"/></clipPath>`
1692
+ `<clipPath id="${this.clipIds.get(path)}"><path d="${pathToD(path)}"/></clipPath>`
1726
1693
  );
1727
1694
  }
1728
1695
  parts.push("</defs>");
@@ -1736,7 +1703,7 @@ function defaultMarkerXml(id, entry, onWarn, key, size) {
1736
1703
  const name = key ?? id;
1737
1704
  const unit = size === void 0 ? 1 : resolveMarkerSize({ key: name, size }, 1);
1738
1705
  const path = entry.path({ size: unit, stroke: { paint: { kind: "solid", color: "#000" } } });
1739
- const d = serializePathD(path);
1706
+ const d = pathToD(path);
1740
1707
  const fill = markerPaintAttrs("fill", entry.fill, id, onWarn);
1741
1708
  const outline = entry.outline ? ` ${markerPaintAttrs("stroke", entry.outline.paint, id, onWarn)} stroke-width="${trimNumber(entry.outline.width * unit)}" stroke-linecap="round" stroke-linejoin="round"` : "";
1742
1709
  const orient = typeof entry.orient === "number" ? trimNumber(entry.orient * 180 / Math.PI) : "auto-start-reverse";
@@ -2159,7 +2126,7 @@ function strokeOutlineXml(path, stroke, fillAttrs, transform) {
2159
2126
  ...stroke.markerEnd ? { markerEnd: stroke.markerEnd } : {}
2160
2127
  };
2161
2128
  const outline = strokeToPath(path, kit);
2162
- const attrs = [`d="${serializePathD(outline)}"`, ...fillAttrs, 'stroke="none"'];
2129
+ const attrs = [`d="${pathToD(outline)}"`, ...fillAttrs, 'stroke="none"'];
2163
2130
  if (transform) attrs.push(transform);
2164
2131
  attrs.push(`${WEASEL_NS_PREFIX}:${ROLE}="${OUTLINE}"`);
2165
2132
  return `<path ${attrs.join(" ")}/>`;
@@ -2403,13 +2370,17 @@ var LineTracker = class {
2403
2370
  return Math.abs(lineHeight - DEFAULT_LINE_HEIGHT) < 1e-6 ? void 0 : lineHeight;
2404
2371
  }
2405
2372
  };
2373
+ var holdsOnlyText = (el) => !Array.from(el.childNodes).some(
2374
+ (child) => child.nodeType === 1
2375
+ /* ELEMENT_NODE */
2376
+ );
2406
2377
  function warnUnreadPositions(el, onWarn) {
2407
2378
  const tag = el.tagName.toLowerCase();
2408
2379
  for (const name of ["x", "y"]) {
2409
2380
  const raw = el.getAttribute(name)?.trim() ?? "";
2410
2381
  if (/[\s,]/.test(raw)) onWarn(`<${tag} ${name}="${raw}"> positions each glyph; only the first value is read`);
2411
2382
  }
2412
- for (const name of tag === "tspan" ? ["rotate"] : ["dx", "rotate"]) {
2383
+ for (const name of tag === "tspan" || holdsOnlyText(el) ? ["rotate"] : ["dx", "rotate"]) {
2413
2384
  const raw = el.getAttribute(name)?.trim() ?? "";
2414
2385
  if (raw.split(/[\s,]+/).some((v) => parseFloat(v) !== 0 && v !== "")) {
2415
2386
  onWarn(`<${tag} ${name}="${raw}"> is not read; the glyphs keep their own advance and angle`);
@@ -2540,8 +2511,9 @@ function placeTspanRun(tspan, run, lines, inherited2, onWarn) {
2540
2511
  return 0;
2541
2512
  }
2542
2513
  const raw = tspan.getAttribute("dx");
2514
+ const tag = tspan.tagName.toLowerCase();
2543
2515
  if (COLLAPSIBLE.test(run.text)) {
2544
- onWarn(`<tspan dx="${raw}"> is not read: its text has white space SVG collapses, which moves the glyphs the list names`);
2516
+ onWarn(`<${tag} dx="${raw}"> is not read: its text has white space SVG collapses, which moves the glyphs the list names`);
2545
2517
  line.push(run);
2546
2518
  return 0;
2547
2519
  }
@@ -2554,7 +2526,7 @@ function placeTspanRun(tspan, run, lines, inherited2, onWarn) {
2554
2526
  } else if (dx[0] !== 0 && lines.length === 1) {
2555
2527
  lead = dx[0];
2556
2528
  } else if (dx[0] !== 0) {
2557
- onWarn(`<tspan dx="${raw}"> opens a line; the line starts at the box edge, and the rest of the list is read`);
2529
+ onWarn(`<${tag} dx="${raw}"> opens a line; the line starts at the box edge, and the rest of the list is read`);
2558
2530
  }
2559
2531
  line.push(...spread(run, dx.slice(1), inherited2));
2560
2532
  return lead;
@@ -2716,7 +2688,10 @@ function readRuns(el, tracker, inheritedShift, letterSpacing, gradients, leafSty
2716
2688
  const push = (run) => {
2717
2689
  runLines[runLines.length - 1].push(run);
2718
2690
  };
2719
- for (let i = 0; i < el.childNodes.length; i++) {
2691
+ if (holdsOnlyText(el) && el.hasAttribute("dx")) {
2692
+ const t = el.textContent ?? "";
2693
+ if (t) lead = placeTspanRun(el, { text: t, ...inheritedShift }, runLines, letterSpacing, onWarn);
2694
+ } else for (let i = 0; i < el.childNodes.length; i++) {
2720
2695
  const child = el.childNodes[i];
2721
2696
  if (child.nodeType === 3) {
2722
2697
  const t = child.textContent ?? "";
@@ -3049,6 +3024,32 @@ function markerEntryFrom(el, id, role, strokeWidth, gradients, onWarn) {
3049
3024
  };
3050
3025
  }
3051
3026
 
3027
+ // src/groupPaint.ts
3028
+ var NO_GROUP_PAINT = { fill: false, stroke: false };
3029
+ var STROKE_PROPS = INHERITED_PROPERTIES.filter((p) => p.startsWith("stroke") || p.startsWith("marker"));
3030
+ var sets = (el, prop) => {
3031
+ const v = ownProp(el, prop);
3032
+ return v != null && v.trim() !== "inherit";
3033
+ };
3034
+ var setsFill = (el) => sets(el, "fill");
3035
+ var setsStroke = (el) => STROKE_PROPS.some((p) => sets(el, p));
3036
+ function viewportPaintFrom(el, from) {
3037
+ return { fill: from.fill && !setsFill(el), stroke: from.stroke && !setsStroke(el) };
3038
+ }
3039
+ function inheritsFrom(el, from) {
3040
+ const fill = from.fill && !setsFill(el);
3041
+ const stroke = from.stroke && !setsStroke(el);
3042
+ return fill || stroke ? { inherits: { ...fill ? { fill: true } : {}, ...stroke ? { stroke: true } : {} } } : {};
3043
+ }
3044
+ function groupPaint(el, from, style, ctm, gradients, onWarn) {
3045
+ const children = { fill: from.fill || setsFill(el), stroke: from.stroke || setsStroke(el) };
3046
+ const paint = {};
3047
+ if (children.fill) paint.fill = paintThroughMatrix(readPaint2(style, "fill", "#000000", gradients, onWarn), ctm);
3048
+ const stroke = children.stroke ? readStroke(style, gradients, onWarn) : void 0;
3049
+ if (stroke) paint.stroke = strokeThroughMatrix(stroke, ctm);
3050
+ return { paint: { ...paint, ...inheritsFrom(el, from) }, children };
3051
+ }
3052
+
3052
3053
  // src/parse.ts
3053
3054
  var SUPPORTED_GROUP_TAGS = /* @__PURE__ */ new Set(["g", "svg"]);
3054
3055
  function parseSvg(svg, opts = {}) {
@@ -3081,7 +3082,7 @@ function parseSvg(svg, opts = {}) {
3081
3082
  for (const [id, paint] of collectPatterns(root, onWarn)) gradients.set(id, paint);
3082
3083
  const rootStyle = deriveStyle(EMPTY_STYLE, root);
3083
3084
  const clips = collectClipPaths(root, onWarn);
3084
- const nodes = parseChildren(root, IDENTITY_MATRIX, rootStyle, gradients, clips, onWarn, uriToPrefix, opts.keepHidden === true);
3085
+ const nodes = parseChildren(root, IDENTITY_MATRIX, rootStyle, NO_GROUP_PAINT, gradients, clips, onWarn, uriToPrefix, opts.keepHidden === true);
3085
3086
  const markers = ingestMarkers(root, nodes, gradients, onWarn);
3086
3087
  const result = { nodes, warnings };
3087
3088
  if (markers.length > 0) result.markers = markers;
@@ -3182,7 +3183,7 @@ function collectElementMeta(el, uriToPrefix) {
3182
3183
  }
3183
3184
  return Object.keys(meta).length > 0 ? meta : void 0;
3184
3185
  }
3185
- function parseChildren(parent, ctm, style, gradients, clips, onWarn, uriToPrefix, keepHidden) {
3186
+ function parseChildren(parent, ctm, style, from, gradients, clips, onWarn, uriToPrefix, keepHidden) {
3186
3187
  const out = [];
3187
3188
  for (let i = 0; i < parent.children.length; i++) {
3188
3189
  const el = parent.children[i];
@@ -3190,7 +3191,7 @@ function parseChildren(parent, ctm, style, gradients, clips, onWarn, uriToPrefix
3190
3191
  if (ns && ns !== "http://www.w3.org/2000/svg" || isStrokeOutline(el)) continue;
3191
3192
  const hidden = isHiddenElement(el, style);
3192
3193
  if (hidden && !keepHidden) continue;
3193
- const node = unwrapStrokeGroup(el, parseElement(el, ctm, style, gradients, clips, onWarn, uriToPrefix, keepHidden));
3194
+ const node = unwrapStrokeGroup(el, parseElement(el, ctm, style, from, gradients, clips, onWarn, uriToPrefix, keepHidden));
3194
3195
  if (!node) continue;
3195
3196
  const parsed = Array.isArray(node) ? node : [node];
3196
3197
  if (hidden) for (const n of parsed) n.hidden = true;
@@ -3198,7 +3199,7 @@ function parseChildren(parent, ctm, style, gradients, clips, onWarn, uriToPrefix
3198
3199
  }
3199
3200
  return out;
3200
3201
  }
3201
- function parseElement(el, ctm, style, gradients, clips, onWarn, uriToPrefix, keepHidden) {
3202
+ function parseElement(el, ctm, style, from, gradients, clips, onWarn, uriToPrefix, keepHidden) {
3202
3203
  const tag = el.tagName.toLowerCase();
3203
3204
  if (IGNORED_TAGS.has(tag)) return null;
3204
3205
  if (tag === "g" && el.hasAttribute("data-weasel-image")) {
@@ -3213,20 +3214,7 @@ function parseElement(el, ctm, style, gradients, clips, onWarn, uriToPrefix, kee
3213
3214
  if (clipRaw != null && readProperty("clip-path", clipRaw) !== "none") {
3214
3215
  onWarn(`clip-path on <${tag}> is not modeled; only a <g> clips, so it is drawn unclipped`);
3215
3216
  }
3216
- if (tag === "g") {
3217
- const local = parseTransform(el.getAttribute("transform"), onWarn);
3218
- const childCtm = multiply(ctm, local);
3219
- const childStyle = deriveStyle(style, el);
3220
- const children = parseChildren(el, childCtm, childStyle, gradients, clips, onWarn, uriToPrefix, keepHidden);
3221
- const opacity2 = ownValue(el, "opacity");
3222
- const group = { kind: "group", children };
3223
- const clip = clipFor(el, childCtm, clips, onWarn);
3224
- if (clip) group.clip = clip;
3225
- if (opacity2 != null) group.opacity = opacity2;
3226
- const meta2 = collectElementMeta(el, uriToPrefix);
3227
- if (meta2) group.meta = meta2;
3228
- return group;
3229
- }
3217
+ if (tag === "g") return parseGroup(el, ctm, style, from, gradients, clips, onWarn, uriToPrefix, keepHidden);
3230
3218
  if (SUPPORTED_GROUP_TAGS.has(tag)) {
3231
3219
  const childStyle = deriveStyle(style, el);
3232
3220
  const x = parseFloat(el.getAttribute("x") ?? "0");
@@ -3235,13 +3223,14 @@ function parseElement(el, ctm, style, gradients, clips, onWarn, uriToPrefix, kee
3235
3223
  if (el.hasAttribute("viewBox")) {
3236
3224
  onWarn("nested <svg> viewBox is not modeled; its children are not rescaled");
3237
3225
  }
3238
- return parseChildren(el, multiply(ctm, offset), childStyle, gradients, clips, onWarn, uriToPrefix, keepHidden);
3226
+ return parseChildren(el, multiply(ctm, offset), childStyle, viewportPaintFrom(el, from), gradients, clips, onWarn, uriToPrefix, keepHidden);
3239
3227
  }
3240
3228
  if (tag === "text") {
3241
3229
  const textNode = parseTextElement(el, ctm, style, gradients, onWarn);
3242
3230
  if (textNode && !Array.isArray(textNode) && textNode.kind === "text") {
3243
3231
  const meta2 = collectElementMeta(el, uriToPrefix);
3244
3232
  if (meta2) textNode.meta = meta2;
3233
+ Object.assign(textNode, inheritsFrom(el, from));
3245
3234
  }
3246
3235
  return textNode;
3247
3236
  }
@@ -3296,6 +3285,7 @@ function parseElement(el, ctm, style, gradients, clips, onWarn, uriToPrefix, kee
3296
3285
  if (stroke) node.stroke = stroke;
3297
3286
  if (opacity != null) node.opacity = opacity;
3298
3287
  if (rotation != null) node.rotation = rotation;
3288
+ Object.assign(node, inheritsFrom(el, from));
3299
3289
  if (tag === "line" && (leafStyle["fill"] ?? null) == null) {
3300
3290
  node.fill = { kind: "none" };
3301
3291
  }
@@ -3303,6 +3293,20 @@ function parseElement(el, ctm, style, gradients, clips, onWarn, uriToPrefix, kee
3303
3293
  if (meta) node.meta = meta;
3304
3294
  return node;
3305
3295
  }
3296
+ function parseGroup(el, ctm, style, from, gradients, clips, onWarn, uriToPrefix, keepHidden) {
3297
+ const childCtm = multiply(ctm, parseTransform(el.getAttribute("transform"), onWarn));
3298
+ const childStyle = deriveStyle(style, el);
3299
+ const paint = groupPaint(el, from, childStyle, childCtm, gradients, onWarn);
3300
+ const children = parseChildren(el, childCtm, childStyle, paint.children, gradients, clips, onWarn, uriToPrefix, keepHidden);
3301
+ const opacity = ownValue(el, "opacity");
3302
+ const group = { kind: "group", children, ...paint.paint };
3303
+ const clip = clipFor(el, childCtm, clips, onWarn);
3304
+ if (clip) group.clip = clip;
3305
+ if (opacity != null) group.opacity = opacity;
3306
+ const meta = collectElementMeta(el, uriToPrefix);
3307
+ if (meta) group.meta = meta;
3308
+ return group;
3309
+ }
3306
3310
  function isHiddenElement(el, style) {
3307
3311
  const tag = el.tagName.toLowerCase();
3308
3312
  if (IGNORED_TAGS.has(tag)) return false;
@@ -3537,6 +3541,61 @@ function strokeAttrsFor(stroke, registry, warn) {
3537
3541
  }
3538
3542
  return attrs;
3539
3543
  }
3544
+
3545
+ // src/groupPaintXml.ts
3546
+ var FILL_ATTRS = ["fill", "fill-opacity"];
3547
+ var STROKE_ATTRS = [
3548
+ "stroke",
3549
+ "stroke-width",
3550
+ "stroke-opacity",
3551
+ "stroke-linecap",
3552
+ "stroke-linejoin",
3553
+ "stroke-dasharray",
3554
+ "stroke-dashoffset",
3555
+ "stroke-miterlimit",
3556
+ "marker-start",
3557
+ "marker-mid",
3558
+ "marker-end"
3559
+ ];
3560
+ var INITIAL = {
3561
+ "fill": "#000000",
3562
+ "fill-opacity": "1",
3563
+ "stroke": "none",
3564
+ "stroke-width": "1",
3565
+ "stroke-opacity": "1",
3566
+ "stroke-linecap": "butt",
3567
+ "stroke-linejoin": "miter",
3568
+ "stroke-dasharray": "none",
3569
+ "stroke-dashoffset": "0",
3570
+ "stroke-miterlimit": "4",
3571
+ "marker-start": "none",
3572
+ "marker-mid": "none",
3573
+ "marker-end": "none"
3574
+ };
3575
+ function groupPaintAttrs(node, registry, warn) {
3576
+ const out = [];
3577
+ if (node.fill && !node.inherits?.fill) out.push(...paintAttrs(node.fill, "fill", registry));
3578
+ if (node.stroke && !node.inherits?.stroke) out.push(...strokeAttrsFor(node.stroke, registry, warn));
3579
+ return out;
3580
+ }
3581
+ function attrNames(attrs) {
3582
+ return attrs.map((a) => a.slice(0, a.indexOf("=")));
3583
+ }
3584
+ var START_TAG = /<(path|text)\b([^>]*?)(\/?)>/g;
3585
+ var ATTR = /\s([\w:-]+)="[^"]*"/g;
3586
+ function fitLeafXml(xml, inherits, painted) {
3587
+ if (painted.size === 0 && !inherits) return xml;
3588
+ const dropped = /* @__PURE__ */ new Set([...inherits?.fill ? FILL_ATTRS : [], ...inherits?.stroke ? STROKE_ATTRS : []]);
3589
+ let first = true;
3590
+ return xml.replace(START_TAG, (_tag, name, body, close2) => {
3591
+ let attrs = body;
3592
+ if (first) attrs = attrs.replace(ATTR, (whole, n) => dropped.has(n) ? "" : whole);
3593
+ first = false;
3594
+ const has = new Set([...attrs.matchAll(ATTR)].map((m) => m[1]));
3595
+ const resets = [...painted].filter((n) => !has.has(n) && !dropped.has(n) && n in INITIAL);
3596
+ return `<${name}${attrs}${resets.map((n) => ` ${n}="${INITIAL[n]}"`).join("")}${close2}>`;
3597
+ });
3598
+ }
3540
3599
  function textXml(node, registry, namespaces, warn, given) {
3541
3600
  const layout = given && layoutMeasured(given, node.text) ? given : null;
3542
3601
  const x = node.x + anchorOffset(node.style, node.width, node.text);
@@ -3690,6 +3749,7 @@ function prettyXml(xml) {
3690
3749
  }
3691
3750
 
3692
3751
  // src/serialize.ts
3752
+ var NOTHING_PAINTED = /* @__PURE__ */ new Set();
3693
3753
  function serializeSvg(nodes, opts = {}) {
3694
3754
  const registry = new PaintServerRegistry();
3695
3755
  registerPaintServers(nodes, registry);
@@ -3732,6 +3792,8 @@ function registerPaintServers(nodes, registry) {
3732
3792
  for (const n of nodes) {
3733
3793
  if (n.kind === "group") {
3734
3794
  if (n.clip) registry.clipId(n.clip);
3795
+ if (n.fill?.kind === "gradient" && !n.inherits?.fill) registry.register(n.fill.paint);
3796
+ if (n.stroke?.paint.kind === "gradient" && !n.inherits?.stroke) registry.register(n.stroke.paint.paint);
3735
3797
  registerPaintServers(n.children, registry);
3736
3798
  } else if (n.kind === "path") {
3737
3799
  if (n.fill.kind === "gradient") registry.register(n.fill.paint);
@@ -3757,17 +3819,18 @@ function registerMarkers(stroke, registry) {
3757
3819
  if (ref !== void 0) registry.markerId(ref);
3758
3820
  }
3759
3821
  }
3760
- function nodeXml(node, registry, namespaces, warn, layoutOf) {
3761
- const xml = kindXml(node, registry, namespaces, warn, layoutOf);
3822
+ function nodeXml(node, registry, namespaces, warn, layoutOf, painted = NOTHING_PAINTED) {
3823
+ const kind = kindXml(node, registry, namespaces, warn, layoutOf, painted);
3824
+ const xml = node.kind === "path" || node.kind === "text" ? fitLeafXml(kind, node.inherits, painted) : kind;
3762
3825
  if (!node.hidden) return xml;
3763
3826
  return xml.replace(/^<[\w:]+/, (open) => `${open} display="none"`).replace(/<path (?=[^>]*:role="stroke-outline")/, '<path display="none" ');
3764
3827
  }
3765
- function kindXml(node, registry, namespaces, warn, layoutOf) {
3766
- if (node.kind === "group") return groupXml(node, registry, namespaces, warn, layoutOf);
3828
+ function kindXml(node, registry, namespaces, warn, layoutOf, painted) {
3829
+ if (node.kind === "group") return groupXml(node, registry, namespaces, warn, layoutOf, painted);
3767
3830
  if (node.kind === "text") return textXml(node, registry, namespaces, warn, layoutOf?.(node));
3768
3831
  if (node.kind === "image") return imageXml(node, namespaces);
3769
3832
  const drawn = pathAsSvgDraws(node);
3770
- return drawn.kind === "group" ? groupXml(drawn, registry, namespaces, warn, layoutOf) : pathXml(drawn, registry, namespaces, warn);
3833
+ return drawn.kind === "group" ? groupXml(drawn, registry, namespaces, warn, layoutOf, painted) : pathXml(drawn, registry, namespaces, warn);
3771
3834
  }
3772
3835
  function imageXml(node, namespaces) {
3773
3836
  const box = `x="${trimNumber(node.x)}" y="${trimNumber(node.y)}" width="${trimNumber(node.width)}" height="${trimNumber(node.height)}"`;
@@ -3791,8 +3854,11 @@ function imageXml(node, namespaces) {
3791
3854
  const head = ['<g data-weasel-image="true"', ...outer].join(" ");
3792
3855
  return `${head}${metaAttrs}><svg ${box} viewBox="${viewBox}" preserveAspectRatio="none"><image ${href} x="0" y="0" width="1" height="1" preserveAspectRatio="none"${flip}/></svg>${metaEls}</g>`;
3793
3856
  }
3794
- function groupXml(node, registry, namespaces, warn, layoutOf) {
3857
+ function groupXml(node, registry, namespaces, warn, layoutOf, painted = NOTHING_PAINTED) {
3795
3858
  const attrs = [];
3859
+ const paint = groupPaintAttrs(node, registry, warn);
3860
+ for (const a of paint) attrs.push(a);
3861
+ const below = paint.length === 0 ? painted : /* @__PURE__ */ new Set([...painted, ...attrNames(paint)]);
3796
3862
  if (node.transform) {
3797
3863
  const m = formatMatrix(node.transform);
3798
3864
  if (m) attrs.push(`transform="${m}"`);
@@ -3805,11 +3871,11 @@ function groupXml(node, registry, namespaces, warn, layoutOf) {
3805
3871
  }
3806
3872
  const metaAttrs = metaAttrsXml(node.meta, namespaces);
3807
3873
  const head = attrs.length > 0 ? `<g ${attrs.join(" ")}${metaAttrs}>` : `<g${metaAttrs}>`;
3808
- const body = node.children.map((c) => nodeXml(c, registry, namespaces, warn, layoutOf)).join("");
3874
+ const body = node.children.map((c) => nodeXml(c, registry, namespaces, warn, layoutOf, below)).join("");
3809
3875
  return `${head}${body}${metaElementsXml(node.meta, namespaces)}</g>`;
3810
3876
  }
3811
3877
  function pathXml(node, registry, namespaces, warn) {
3812
- const attrs = [`d="${serializePathD(node.path)}"`];
3878
+ const attrs = [`d="${pathToD(node.path)}"`];
3813
3879
  const fillAttrs = paintAttrs(node.fill, "fill", registry);
3814
3880
  for (const a of fillAttrs) attrs.push(a);
3815
3881
  if (node.path.kind === "polygon" && node.path.fillRule === "evenodd") {
@@ -3892,6 +3958,19 @@ function fadeRun(run, k) {
3892
3958
  ...run.stroke ? { stroke: fadeStroke(run.stroke, k) } : {}
3893
3959
  };
3894
3960
  }
3961
+
3962
+ // src/draftTakes.ts
3963
+ var NO_TAKES = {};
3964
+ function takesOf(n, from, k) {
3965
+ const inherits = n.kind === "path" || n.kind === "text" || n.kind === "group" ? n.inherits : void 0;
3966
+ return { fill: inherits?.fill === true && from.fill === k, stroke: inherits?.stroke === true && from.stroke === k };
3967
+ }
3968
+ var solidOrNone = (p) => p === void 0 || p.kind === "solid" || p.kind === "none";
3969
+ function groupTakes(group, takes, k) {
3970
+ const fill = group.inherits?.fill ? takes.fill : solidOrNone(group.fill);
3971
+ const stroke = group.inherits?.stroke ? takes.stroke : solidOrNone(group.stroke?.paint);
3972
+ return { ...fill ? { fill: k } : {}, ...stroke ? { stroke: k } : {} };
3973
+ }
3895
3974
  var CASCADE_OFFSET_PX = 24;
3896
3975
  var VIEWPORT_FIT = 0.9;
3897
3976
  function readFileText(file) {
@@ -3956,8 +4035,9 @@ function svgNodesToKitDrafts(input, nextId, options = {}) {
3956
4035
  drafts.push({ kind: "leaf", id, parentId, pose, data: out, ...source.hidden ? { hidden: true } : {} });
3957
4036
  return pose;
3958
4037
  };
3959
- const visit = (n, parentId, inherited2) => {
4038
+ const visit = (n, parentId, inherited2, from) => {
3960
4039
  const k = inherited2 * (n.opacity ?? 1);
4040
+ const takes = takesOf(n, from, k);
3961
4041
  if (n.kind === "group") {
3962
4042
  const draft = {
3963
4043
  kind: "container",
@@ -3969,7 +4049,7 @@ function svgNodesToKitDrafts(input, nextId, options = {}) {
3969
4049
  };
3970
4050
  drafts.push(draft);
3971
4051
  const at = drafts.length - 1;
3972
- const acc = unionAABB(n.children.map((c) => visit(c, draft.id, k)));
4052
+ const acc = unionAABB(n.children.map((c) => visit(c, draft.id, k, groupTakes(n, takes, k))));
3973
4053
  if (!acc) {
3974
4054
  const kept = drafts.length > at + 1 || (options.keepEmpty?.(n) ?? false);
3975
4055
  if (!kept) drafts.splice(at, 1);
@@ -3977,6 +4057,7 @@ function svgNodesToKitDrafts(input, nextId, options = {}) {
3977
4057
  return null;
3978
4058
  }
3979
4059
  draft.pose = n.clip ? boxOfPath(n.clip) : acc;
4060
+ Object.assign(draft.data, containerPaint(n, takes, draft.pose, k));
3980
4061
  return draft.pose;
3981
4062
  }
3982
4063
  if (n.kind === "text") {
@@ -3994,16 +4075,15 @@ function svgNodesToKitDrafts(input, nextId, options = {}) {
3994
4075
  width: pose2.width,
3995
4076
  height: pose2.height
3996
4077
  };
3997
- const fill2 = n.fill === void 0 && k !== 1 ? DEFAULT_TEXT_STYLE.fill : n.fill;
4078
+ const fill2 = n.fill === void 0 ? DEFAULT_TEXT_FILL : n.fill;
3998
4079
  return pushLeaf(n, parentId, pose2, {
3999
4080
  text: n.text,
4000
4081
  ...n.style ? { style: n.style } : {},
4001
4082
  ...n.verticalAlign ? { verticalAlign: n.verticalAlign } : {},
4002
4083
  ...n.runs ? { runs: n.runs.map((r) => fadeRun(runInBoxFrame(r, box), k)) } : {},
4003
- // `!== undefined`, not a truthiness test: `null` is the document
4004
- // saying `fill="none"`, and absent takes the painter's default.
4005
- ...fill2 !== void 0 ? { fill: fill2 && mapFillPaint(fill2, (p) => fadeFill(fillInBoxFrame(p, box), k)) } : {},
4006
- ...n.stroke !== void 0 ? { stroke: fadeStroke(strokeInBoxFrame(n.stroke, box), k) } : {}
4084
+ // `null` is the document saying `fill="none"`.
4085
+ ...takes.fill ? { fill: INHERIT } : { fill: fill2 && mapFillPaint(fill2, (p) => fadeFill(fillInBoxFrame(p, box), k)) },
4086
+ ...takes.stroke ? { stroke: INHERIT } : n.stroke !== void 0 ? { stroke: fadeStroke(strokeInBoxFrame(n.stroke, box), k) } : {}
4007
4087
  });
4008
4088
  }
4009
4089
  if (n.kind === "image") {
@@ -4027,13 +4107,21 @@ function svgNodesToKitDrafts(input, nextId, options = {}) {
4027
4107
  const strokeFromSvg = strokeDataFromSvg(n.stroke, b);
4028
4108
  return pushLeaf(n, parentId, pose, {
4029
4109
  path: n.path,
4030
- ...fill !== void 0 ? { fill: fill && { paint: fadeFill(fill, k) } } : {},
4031
- ...strokeFromSvg !== void 0 ? { stroke: fadeStroke(strokeFromSvg, k) } : {}
4110
+ ...takes.fill ? { fill: INHERIT } : fill !== void 0 ? { fill: fill && { paint: fadeFill(fill, k) } } : {},
4111
+ ...takes.stroke ? { stroke: INHERIT } : strokeFromSvg !== void 0 ? { stroke: fadeStroke(strokeFromSvg, k) } : {}
4032
4112
  });
4033
4113
  };
4034
- for (const n of nodes) visit(n, null, 1);
4114
+ for (const n of nodes) visit(n, null, 1, NO_TAKES);
4035
4115
  return drafts;
4036
4116
  }
4117
+ function containerPaint(n, takes, box, k) {
4118
+ const fill = fillDataFromSvg(n.fill, box);
4119
+ const stroke = strokeDataFromSvg(n.stroke, box);
4120
+ return {
4121
+ ...takes.fill ? { fill: INHERIT } : fill ? { fill: { paint: fadeFill(fill, k) } } : {},
4122
+ ...takes.stroke ? { stroke: INHERIT } : stroke ? { stroke: fadeStroke(stroke, k) } : {}
4123
+ };
4124
+ }
4037
4125
  function boxOfPath(path) {
4038
4126
  const b = boundsOfPath(path);
4039
4127
  return { x: b.x, y: b.y, width: b.width, height: b.height };
@@ -4122,8 +4210,9 @@ function svgPaintFromKit(fill, box) {
4122
4210
  }
4123
4211
  return { kind: "gradient", paint: fillInPoseFrame(fill, box) };
4124
4212
  }
4125
- function svgStrokeFromKit(stroke, box) {
4126
- if (!stroke?.paint) return void 0;
4213
+ function svgStrokeFromKit(raw, box) {
4214
+ const stroke = resolveStroke(raw);
4215
+ if (!stroke) return void 0;
4127
4216
  const width2 = stroke.width ?? 1;
4128
4217
  if (!((typeof width2 === "object" ? width2.px : width2) > 0)) return void 0;
4129
4218
  const opacity = stroke.paint.opacity;
@@ -4177,17 +4266,16 @@ function svgLeafFromKit(data, pose) {
4177
4266
  const text = svgTextFromKit({ ...data, text: data.text }, pose);
4178
4267
  const boxPath = { kind: "rect", x: pose.x, y: pose.y, width: pose.width, height: pose.height };
4179
4268
  const clip = data.overflow === "clip" ? { clip: pathThroughPoseFrame(boxPath, pose) } : {};
4180
- if (!data.background?.paint) return clip.clip ? { kind: "group", children: [text], ...clip } : text;
4269
+ if (!resolveFill(data.background)) return clip.clip ? { kind: "group", children: [text], ...clip } : text;
4181
4270
  const box = svgLeafFromKit({ path: boxPath, fill: data.background }, pose);
4182
4271
  return { kind: "group", children: [box, text], ...clip };
4183
4272
  }
4184
4273
  if (!data.path) return null;
4185
- const stroke = svgStrokeFromKit(data.stroke, pose);
4186
- const fill = data.fill !== void 0 ? data.fill : stroke ? null : DEFAULT_SHAPE_FILL;
4274
+ const stroke = svgStrokeFromKit(resolveStroke(data.stroke), pose);
4187
4275
  const node = {
4188
4276
  kind: "path",
4189
4277
  path: pathInPoseFrame(data.path, pose),
4190
- fill: svgPaintFromKit(fill?.paint ?? null, pose)
4278
+ fill: svgPaintFromKit(resolveFill(data.fill)?.paint ?? null, pose)
4191
4279
  };
4192
4280
  if (stroke) node.stroke = stroke;
4193
4281
  const frame = poseLinearOf(pose) && poseFrameOf(pose);
@@ -4211,25 +4299,32 @@ function svgTextFromKit(data, pose) {
4211
4299
  if (data.style) node.style = data.style;
4212
4300
  if (data.runs) node.runs = data.runs.map((r) => runInPoseFrame(r, pose));
4213
4301
  if (data.verticalAlign) node.verticalAlign = data.verticalAlign;
4214
- if (data.fill !== void 0) node.fill = data.fill && mapFillPaint(data.fill, (p) => fillInPoseFrame(p, pose));
4215
- if (data.stroke) node.stroke = strokeInPoseFrame(data.stroke, pose);
4302
+ const fill = resolveFill(data.fill);
4303
+ node.fill = fill && mapFillPaint(fill, (p) => fillInPoseFrame(p, pose));
4304
+ const stroke = resolveStroke(data.stroke);
4305
+ if (stroke) node.stroke = strokeInPoseFrame(stroke, pose);
4216
4306
  if (pose.rotation) node.rotation = pose.rotation;
4217
4307
  if (pose.transform) node.transform = pose.transform;
4218
4308
  return node;
4219
4309
  }
4220
4310
  function svgNodesFromKit(tree, options = {}) {
4221
4311
  const { include, leaf, group } = options;
4222
- const emit = (id) => {
4312
+ const emit = (id, up) => {
4223
4313
  const node = tree.get(id);
4224
- const out2 = node && (!include || include(id)) ? lower(id, node) : null;
4314
+ const out2 = node && (!include || include(id)) ? lower(id, node, up) : null;
4225
4315
  return out2 && node?.hidden === true ? { ...out2, hidden: true } : out2;
4226
4316
  };
4227
- const lower = (id, node) => {
4317
+ const lower = (id, node, up) => {
4228
4318
  if (node.kind === "container") {
4319
+ const own2 = node.data;
4320
+ const below = {
4321
+ fill: own2?.fill === INHERIT ? up.fill : resolveFill(own2?.fill),
4322
+ stroke: own2?.stroke === INHERIT ? up.stroke : resolveStroke(own2?.stroke)
4323
+ };
4229
4324
  const emitChildren = () => {
4230
4325
  const children = [];
4231
4326
  for (const c of tree.childrenOf(id)) {
4232
- const child = emit(c);
4327
+ const child = emit(c, below);
4233
4328
  if (child) children.push(child);
4234
4329
  }
4235
4330
  return children;
@@ -4238,7 +4333,7 @@ function svgNodesFromKit(tree, options = {}) {
4238
4333
  const clip = path ? pathThroughPoseFrame(pathInPoseFrame(path, node.pose), node.pose) : void 0;
4239
4334
  const copies = node.copiesFromPose?.(node.pose, node) ?? [];
4240
4335
  const content = () => ({ kind: "group", children: emitChildren(), ...clip ? { clip } : {} });
4241
- let g = content();
4336
+ let g = { ...content(), ...groupPaintFromKit(own2, below, node.pose) };
4242
4337
  if (copies.length > 0) {
4243
4338
  g = {
4244
4339
  kind: "group",
@@ -4247,16 +4342,44 @@ function svgNodesFromKit(tree, options = {}) {
4247
4342
  }
4248
4343
  return group ? group(id, g) : g;
4249
4344
  }
4250
- return leaf ? leaf(id, node) : svgLeafFromKit(node.data, node.pose);
4345
+ const data = node.data ?? {};
4346
+ return markInherits(leaf ? leaf(id, node, up) : svgLeafFromKit(resolvedData(data, up), node.pose), data, up);
4251
4347
  };
4252
4348
  const out = [];
4253
4349
  for (const id of options.roots ?? tree.roots) {
4254
- const n = emit(id);
4350
+ const n = emit(id, NO_UP_PAINT);
4255
4351
  if (n) out.push(n);
4256
4352
  }
4257
4353
  return out;
4258
4354
  }
4355
+ var NO_UP_PAINT = { fill: null, stroke: null };
4356
+ var carries = (p) => p != null && isSolidPaint(p.paint);
4357
+ function groupPaintFromKit(own2, below, pose) {
4358
+ const out = {};
4359
+ if (below.fill?.paint) out.fill = svgPaintFromKit(below.fill.paint, pose);
4360
+ const stroke = svgStrokeFromKit(below.stroke, pose);
4361
+ if (stroke) out.stroke = stroke;
4362
+ const fill = own2?.fill === INHERIT && carries(below.fill);
4363
+ const strokes = own2?.stroke === INHERIT && carries(below.stroke);
4364
+ if (fill || strokes) out.inherits = { ...fill ? { fill: true } : {}, ...strokes ? { stroke: true } : {} };
4365
+ return out;
4366
+ }
4367
+ function resolvedData(data, up) {
4368
+ if (data.fill !== INHERIT && data.stroke !== INHERIT) return data;
4369
+ return {
4370
+ ...data,
4371
+ ...data.fill === INHERIT ? { fill: up.fill } : {},
4372
+ ...data.stroke === INHERIT ? { stroke: up.stroke } : {}
4373
+ };
4374
+ }
4375
+ function markInherits(out, data, up) {
4376
+ if (out === null || out.kind !== "path" && out.kind !== "text") return out;
4377
+ const fill = data.fill === INHERIT && carries(up.fill);
4378
+ const stroke = data.stroke === INHERIT && carries(up.stroke);
4379
+ if (fill || stroke) out.inherits = { ...fill ? { fill: true } : {}, ...stroke ? { stroke: true } : {} };
4380
+ return out;
4381
+ }
4259
4382
 
4260
- export { DEFAULT_MEDIA_ENVIRONMENT, IDENTITY_MATRIX, evaluateMediaQuery, evaluateSupports, fillDataFromSvg, nativeSvgKind, nativeSvgSpace, parseSvg, serializePathD, serializeSvg, strokeDataFromSvg, svgImageFromKit, svgLeafFromKit, svgNeeds, svgNodesFromKit, svgNodesToKitDrafts, svgPaintFromKit, svgStrokeFromKit, tilePreviewCssUrl, tilePreviewSvg, unpackSvgFiles, warmSvg };
4383
+ export { DEFAULT_MEDIA_ENVIRONMENT, IDENTITY_MATRIX, compareSpecificity, escapeAttr, escapeText, evaluateMediaQuery, evaluateSupports, fillDataFromSvg, nativeSvgKind, nativeSvgSpace, parseSvg, serializeSvg, specificity, splitTopLevel, strokeDataFromSvg, svgImageFromKit, svgLeafFromKit, svgNeeds, svgNodesFromKit, svgNodesToKitDrafts, svgPaintFromKit, svgStrokeFromKit, tilePreviewCssUrl, tilePreviewSvg, unpackSvgFiles, warmSvg };
4261
4384
  //# sourceMappingURL=index.js.map
4262
4385
  //# sourceMappingURL=index.js.map