@weasel-js/svg 1.11.1 → 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.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Affine } from '@weasel-js/geom';
2
2
  export { pathToD as serializePathD } from '@weasel-js/geom';
3
- import { Path, Paint, ScreenLength, StrokeAlign, MarkerRef, VertexWidth, StrokeNib, StyledRun, TextStyle, TextVerticalAlign, Fill, Stroke, PoseLinear, MarkerEntry, TextPoseLayout, RenderNeeds, TilePatternSpec, ImageNodeData, IngestCtx } from '@weasel-js/core';
3
+ import { Path, Paint, ScreenLength, StrokeAlign, MarkerRef, VertexWidth, StrokeNib, StyledRun, TextStyle, TextVerticalAlign, Fill, Stroke, PoseLinear, MarkerEntry, TextPoseLayout, RenderNeeds, TilePatternSpec, Inherit, ImageNodeData, IngestCtx } from '@weasel-js/core';
4
4
 
5
5
  /**
6
6
  * Media Queries 4 evaluation against a fixed environment.
@@ -134,6 +134,17 @@ interface SvgStroke {
134
134
  vertexWidths?: VertexWidth[];
135
135
  nib?: StrokeNib;
136
136
  }
137
+ /**
138
+ * Which of a node's paints its own element sets none of, so that it takes the
139
+ * paint of the enclosing `<g>` that does. Its `fill` and `stroke` still hold
140
+ * what that resolves to.
141
+ */
142
+ interface SvgInherits {
143
+ fill?: true;
144
+ /** Set only when the element sets no `stroke-*` or `marker-*` property
145
+ * either: the stroke is taken whole. */
146
+ stroke?: true;
147
+ }
137
148
  /**
138
149
  * Leaf node: a path geometry plus fill/stroke. Other shapes (`<rect>`,
139
150
  * `<ellipse>`, etc.) are lowered to this on parse.
@@ -143,6 +154,7 @@ interface SvgPathNode {
143
154
  path: Path;
144
155
  fill: SvgPaint;
145
156
  stroke?: SvgStroke;
157
+ inherits?: SvgInherits;
146
158
  /** Element-level opacity (`opacity="..."`), 0..1. */
147
159
  opacity?: number;
148
160
  /** Element-level rotation in **radians**, pivoting around the AABB
@@ -172,6 +184,12 @@ interface SvgGroupNode {
172
184
  * unions them and a `Path` holds one outline. */
173
185
  clip?: Path;
174
186
  opacity?: number;
187
+ /** The fill its children take when they set none — written as the group's
188
+ * own `fill`. Absent when neither it nor an enclosing group sets one. */
189
+ fill?: SvgPaint;
190
+ /** As `fill`, for the stroke; absent too when the one in effect is none. */
191
+ stroke?: SvgStroke;
192
+ inherits?: SvgInherits;
175
193
  /** Kept by `ParseOptions.keepHidden` from content SVG does not render:
176
194
  * `display="none"` here, or a leaf under `visibility="hidden"`. Written
177
195
  * back as `display="none"`. */
@@ -218,6 +236,7 @@ interface SvgTextNode {
218
236
  */
219
237
  fill?: Fill | null;
220
238
  stroke?: Stroke;
239
+ inherits?: SvgInherits;
221
240
  /** Element-level opacity (`opacity="..."`), 0..1. */
222
241
  opacity?: number;
223
242
  /** Element-level rotation in **radians**, pivoting around the unrotated
@@ -491,6 +510,21 @@ declare function tilePreviewSvg(spec: TilePatternSpec, background?: string): str
491
510
  /** `tilePreviewSvg` packed as a `url(...)` value for CSS `background-image`. */
492
511
  declare function tilePreviewCssUrl(spec: TilePatternSpec, background?: string): string;
493
512
 
513
+ /** CSS Selectors 4 specificity. */
514
+ /** A selector's specificity as (ids, classes, types). */
515
+ type Specificity = readonly [number, number, number];
516
+ /** Negative when `a` is less specific than `b`, positive when more, 0 when equal. */
517
+ declare function compareSpecificity(a: Specificity, b: Specificity): number;
518
+ /** CSS Selectors 4 specificity of one complex selector (no comma list). */
519
+ declare function specificity(selector: string): Specificity;
520
+
521
+ /**
522
+ * Split `s` on `sep` wherever it sits outside strings and (), [] nesting.
523
+ * With `dropBlocks`, a `{…}` block at depth zero is discarded along with the
524
+ * prelude before it, back to the previous separator.
525
+ */
526
+ declare function splitTopLevel(s: string, sep: string, dropBlocks?: boolean): string[];
527
+
494
528
  /**
495
529
  * `@supports` evaluation. A declaration test asks whether this parser would
496
530
  * honor the declaration ({@link isDeclarationHonored}), not whether a browser
@@ -532,8 +566,9 @@ interface SvgKitPose {
532
566
  * `svgNodesToKitDrafts` writes. */
533
567
  interface SvgKitLeafData {
534
568
  path?: Path;
535
- fill?: Fill | null;
536
- stroke?: Stroke | null;
569
+ /** `'inherit'` takes the enclosing container's. */
570
+ fill?: Fill | Inherit | null;
571
+ stroke?: Stroke | Inherit | null;
537
572
  text?: string;
538
573
  style?: TextStyle;
539
574
  runs?: readonly StyledRun[];
@@ -552,7 +587,7 @@ interface SvgKitLeafData {
552
587
  declare function svgPaintFromKit(fill: Paint | null, box: SvgKitPose): SvgPaint;
553
588
  /** A kit `Stroke` as an `SvgStroke` — the inverse of `strokeDataFromSvg`.
554
589
  * One with no paint or no width draws nothing and is written as no stroke. */
555
- declare function svgStrokeFromKit(stroke: Stroke | null | undefined, box: SvgKitPose): SvgStroke | undefined;
590
+ declare function svgStrokeFromKit(raw: Stroke | null | undefined, box: SvgKitPose): SvgStroke | undefined;
556
591
  /** Write a `kit:image` leaf as an `SvgImageNode` — the inverse of the image
557
592
  * arm of `svgNodesToKitDrafts`. The pose is the image's box. */
558
593
  declare function svgImageFromKit(image: ImageNodeData['image'], pose: SvgKitPose): SvgImageNode;
@@ -562,10 +597,12 @@ declare function svgImageFromKit(image: ImageNodeData['image'], pose: SvgKitPose
562
597
  * draws. A text node with a `background` is a group: the box, then the text.
563
598
  * One that clips is a group clipped to its box as it is drawn, turned with it.
564
599
  *
565
- * A path with no `fill` takes `kit:path`'s fallback: the default shape fill
566
- * when it has no stroke, and no fill when it has one. Under a pose `transform`
567
- * its stroke width takes the uniform part of the stretch, as `parseSvg` reads
568
- * a stroke under a skewed element.
600
+ * An `'inherit'` fill or stroke is none here, with no container to read;
601
+ * {@link svgNodesFromKit} resolves it first.
602
+ *
603
+ * A fill or stroke that paints nothing is written `none`. Under a pose
604
+ * `transform` a stroke's width takes the uniform part of the stretch, as
605
+ * `parseSvg` reads a stroke under a skewed element.
569
606
  */
570
607
  declare function svgLeafFromKit(data: SvgKitLeafData, pose: SvgKitPose): SvgPathNode | SvgTextNode | SvgImageNode | SvgGroupNode | null;
571
608
  /** One node as {@link svgNodesFromKit} reads it. */
@@ -593,8 +630,11 @@ interface SvgNodesFromKitOptions<TId extends string = string> {
593
630
  roots?: readonly TId[];
594
631
  /** `false` leaves a node out, with everything under it — a hidden layer. */
595
632
  include?(id: TId): boolean;
596
- /** Lower a leaf yourself; `null` leaves it out. Defaults to {@link svgLeafFromKit}. */
597
- leaf?(id: TId, node: SvgKitTreeNode): SvgNode | null;
633
+ /** Lower a leaf yourself; `null` leaves it out. Defaults to {@link svgLeafFromKit}.
634
+ * `inherited` is what the leaf's `'inherit'` fields resolve to; write those
635
+ * as these, and the walk marks the result as inheriting where SVG's
636
+ * cascade can carry them. */
637
+ leaf?(id: TId, node: SvgKitTreeNode, inherited: SvgKitInherited): SvgNode | null;
598
638
  /** Decorate the group written for a container, e.g. with `meta`. */
599
639
  group?(id: TId, group: SvgGroupNode): SvgGroupNode;
600
640
  }
@@ -605,6 +645,12 @@ interface SvgNodesFromKitOptions<TId extends string = string> {
605
645
  * otherwise. A hidden node is written hidden, with its subtree as it is.
606
646
  */
607
647
  declare function svgNodesFromKit<TId extends string>(tree: SvgKitTree<TId>, options?: SvgNodesFromKitOptions<TId>): SvgNode[];
648
+ /** What a leaf's `'inherit'` fill and stroke resolve to — its nearest
649
+ * enclosing container's; `null` for none. */
650
+ interface SvgKitInherited {
651
+ fill: Fill | null;
652
+ stroke: Stroke | null;
653
+ }
608
654
 
609
655
  /** Axis-aligned box, in the coordinate space of the SVG being unpacked. Used
610
656
  * for the bounds a draft occupies before it becomes a scene node. */
@@ -703,4 +749,4 @@ declare function svgNodesToKitDrafts<TData = Record<string, unknown>>(input: Par
703
749
  */
704
750
  declare function unpackSvgFiles(files: File[], ctx: IngestCtx): Promise<void>;
705
751
 
706
- export { DEFAULT_MEDIA_ENVIRONMENT, IDENTITY_MATRIX, type Matrix, type NamespaceMeta, type NamespacedElement, type ParseOptions, type ParseResult, type SerializeOptions, type SupportsOptions, type SvgDraftBounds, type SvgDraftPose, type SvgGroupNode, type SvgImageNode, type SvgKitLeafData, type SvgKitPose, type SvgKitTree, type SvgKitTreeNode, type SvgLeafNode, type SvgMediaEnvironment, type SvgNode, type SvgNodesFromKitOptions, type SvgNodesToKitDraftsOptions, type SvgPaint, type SvgPathNode, type SvgSceneDraft, type SvgStroke, type SvgTextNode, escapeAttr, escapeText, evaluateMediaQuery, evaluateSupports, fillDataFromSvg, nativeSvgKind, nativeSvgSpace, parseSvg, serializeSvg, strokeDataFromSvg, svgImageFromKit, svgLeafFromKit, svgNeeds, svgNodesFromKit, svgNodesToKitDrafts, svgPaintFromKit, svgStrokeFromKit, tilePreviewCssUrl, tilePreviewSvg, unpackSvgFiles, warmSvg };
752
+ export { DEFAULT_MEDIA_ENVIRONMENT, IDENTITY_MATRIX, type Matrix, type NamespaceMeta, type NamespacedElement, type ParseOptions, type ParseResult, type SerializeOptions, type Specificity, type SupportsOptions, type SvgDraftBounds, type SvgDraftPose, type SvgGroupNode, type SvgImageNode, type SvgKitLeafData, type SvgKitPose, type SvgKitTree, type SvgKitTreeNode, type SvgLeafNode, type SvgMediaEnvironment, type SvgNode, type SvgNodesFromKitOptions, type SvgNodesToKitDraftsOptions, type SvgPaint, type SvgPathNode, type SvgSceneDraft, type SvgStroke, type SvgTextNode, compareSpecificity, escapeAttr, escapeText, evaluateMediaQuery, evaluateSupports, fillDataFromSvg, nativeSvgKind, nativeSvgSpace, parseSvg, serializeSvg, specificity, splitTopLevel, strokeDataFromSvg, svgImageFromKit, svgLeafFromKit, svgNeeds, svgNodesFromKit, svgNodesToKitDrafts, svgPaintFromKit, svgStrokeFromKit, tilePreviewCssUrl, tilePreviewSvg, unpackSvgFiles, warmSvg };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { identity, multiply, pathToD, invert, splitByClosure, fillsOpenSubpaths, rotateAboutPoint, hasOpenSubpath, splitSubpaths } from '@weasel-js/geom';
2
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, pathThroughPoseFrame, DEFAULT_SHAPE_FILL, pathInPoseFrame, poseLinearOf, poseFrameOf, DEFAULT_TEXT_STYLE, 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
+ 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';
4
4
  import { tileGeometry } from '@weasel-js/core/patterns-builtin';
5
5
 
6
6
  // src/types.ts
@@ -3024,6 +3024,32 @@ function markerEntryFrom(el, id, role, strokeWidth, gradients, onWarn) {
3024
3024
  };
3025
3025
  }
3026
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
+
3027
3053
  // src/parse.ts
3028
3054
  var SUPPORTED_GROUP_TAGS = /* @__PURE__ */ new Set(["g", "svg"]);
3029
3055
  function parseSvg(svg, opts = {}) {
@@ -3056,7 +3082,7 @@ function parseSvg(svg, opts = {}) {
3056
3082
  for (const [id, paint] of collectPatterns(root, onWarn)) gradients.set(id, paint);
3057
3083
  const rootStyle = deriveStyle(EMPTY_STYLE, root);
3058
3084
  const clips = collectClipPaths(root, onWarn);
3059
- 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);
3060
3086
  const markers = ingestMarkers(root, nodes, gradients, onWarn);
3061
3087
  const result = { nodes, warnings };
3062
3088
  if (markers.length > 0) result.markers = markers;
@@ -3157,7 +3183,7 @@ function collectElementMeta(el, uriToPrefix) {
3157
3183
  }
3158
3184
  return Object.keys(meta).length > 0 ? meta : void 0;
3159
3185
  }
3160
- function parseChildren(parent, ctm, style, gradients, clips, onWarn, uriToPrefix, keepHidden) {
3186
+ function parseChildren(parent, ctm, style, from, gradients, clips, onWarn, uriToPrefix, keepHidden) {
3161
3187
  const out = [];
3162
3188
  for (let i = 0; i < parent.children.length; i++) {
3163
3189
  const el = parent.children[i];
@@ -3165,7 +3191,7 @@ function parseChildren(parent, ctm, style, gradients, clips, onWarn, uriToPrefix
3165
3191
  if (ns && ns !== "http://www.w3.org/2000/svg" || isStrokeOutline(el)) continue;
3166
3192
  const hidden = isHiddenElement(el, style);
3167
3193
  if (hidden && !keepHidden) continue;
3168
- 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));
3169
3195
  if (!node) continue;
3170
3196
  const parsed = Array.isArray(node) ? node : [node];
3171
3197
  if (hidden) for (const n of parsed) n.hidden = true;
@@ -3173,7 +3199,7 @@ function parseChildren(parent, ctm, style, gradients, clips, onWarn, uriToPrefix
3173
3199
  }
3174
3200
  return out;
3175
3201
  }
3176
- function parseElement(el, ctm, style, gradients, clips, onWarn, uriToPrefix, keepHidden) {
3202
+ function parseElement(el, ctm, style, from, gradients, clips, onWarn, uriToPrefix, keepHidden) {
3177
3203
  const tag = el.tagName.toLowerCase();
3178
3204
  if (IGNORED_TAGS.has(tag)) return null;
3179
3205
  if (tag === "g" && el.hasAttribute("data-weasel-image")) {
@@ -3188,20 +3214,7 @@ function parseElement(el, ctm, style, gradients, clips, onWarn, uriToPrefix, kee
3188
3214
  if (clipRaw != null && readProperty("clip-path", clipRaw) !== "none") {
3189
3215
  onWarn(`clip-path on <${tag}> is not modeled; only a <g> clips, so it is drawn unclipped`);
3190
3216
  }
3191
- if (tag === "g") {
3192
- const local = parseTransform(el.getAttribute("transform"), onWarn);
3193
- const childCtm = multiply(ctm, local);
3194
- const childStyle = deriveStyle(style, el);
3195
- const children = parseChildren(el, childCtm, childStyle, gradients, clips, onWarn, uriToPrefix, keepHidden);
3196
- const opacity2 = ownValue(el, "opacity");
3197
- const group = { kind: "group", children };
3198
- const clip = clipFor(el, childCtm, clips, onWarn);
3199
- if (clip) group.clip = clip;
3200
- if (opacity2 != null) group.opacity = opacity2;
3201
- const meta2 = collectElementMeta(el, uriToPrefix);
3202
- if (meta2) group.meta = meta2;
3203
- return group;
3204
- }
3217
+ if (tag === "g") return parseGroup(el, ctm, style, from, gradients, clips, onWarn, uriToPrefix, keepHidden);
3205
3218
  if (SUPPORTED_GROUP_TAGS.has(tag)) {
3206
3219
  const childStyle = deriveStyle(style, el);
3207
3220
  const x = parseFloat(el.getAttribute("x") ?? "0");
@@ -3210,13 +3223,14 @@ function parseElement(el, ctm, style, gradients, clips, onWarn, uriToPrefix, kee
3210
3223
  if (el.hasAttribute("viewBox")) {
3211
3224
  onWarn("nested <svg> viewBox is not modeled; its children are not rescaled");
3212
3225
  }
3213
- 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);
3214
3227
  }
3215
3228
  if (tag === "text") {
3216
3229
  const textNode = parseTextElement(el, ctm, style, gradients, onWarn);
3217
3230
  if (textNode && !Array.isArray(textNode) && textNode.kind === "text") {
3218
3231
  const meta2 = collectElementMeta(el, uriToPrefix);
3219
3232
  if (meta2) textNode.meta = meta2;
3233
+ Object.assign(textNode, inheritsFrom(el, from));
3220
3234
  }
3221
3235
  return textNode;
3222
3236
  }
@@ -3271,6 +3285,7 @@ function parseElement(el, ctm, style, gradients, clips, onWarn, uriToPrefix, kee
3271
3285
  if (stroke) node.stroke = stroke;
3272
3286
  if (opacity != null) node.opacity = opacity;
3273
3287
  if (rotation != null) node.rotation = rotation;
3288
+ Object.assign(node, inheritsFrom(el, from));
3274
3289
  if (tag === "line" && (leafStyle["fill"] ?? null) == null) {
3275
3290
  node.fill = { kind: "none" };
3276
3291
  }
@@ -3278,6 +3293,20 @@ function parseElement(el, ctm, style, gradients, clips, onWarn, uriToPrefix, kee
3278
3293
  if (meta) node.meta = meta;
3279
3294
  return node;
3280
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
+ }
3281
3310
  function isHiddenElement(el, style) {
3282
3311
  const tag = el.tagName.toLowerCase();
3283
3312
  if (IGNORED_TAGS.has(tag)) return false;
@@ -3512,6 +3541,61 @@ function strokeAttrsFor(stroke, registry, warn) {
3512
3541
  }
3513
3542
  return attrs;
3514
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
+ }
3515
3599
  function textXml(node, registry, namespaces, warn, given) {
3516
3600
  const layout = given && layoutMeasured(given, node.text) ? given : null;
3517
3601
  const x = node.x + anchorOffset(node.style, node.width, node.text);
@@ -3665,6 +3749,7 @@ function prettyXml(xml) {
3665
3749
  }
3666
3750
 
3667
3751
  // src/serialize.ts
3752
+ var NOTHING_PAINTED = /* @__PURE__ */ new Set();
3668
3753
  function serializeSvg(nodes, opts = {}) {
3669
3754
  const registry = new PaintServerRegistry();
3670
3755
  registerPaintServers(nodes, registry);
@@ -3707,6 +3792,8 @@ function registerPaintServers(nodes, registry) {
3707
3792
  for (const n of nodes) {
3708
3793
  if (n.kind === "group") {
3709
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);
3710
3797
  registerPaintServers(n.children, registry);
3711
3798
  } else if (n.kind === "path") {
3712
3799
  if (n.fill.kind === "gradient") registry.register(n.fill.paint);
@@ -3732,17 +3819,18 @@ function registerMarkers(stroke, registry) {
3732
3819
  if (ref !== void 0) registry.markerId(ref);
3733
3820
  }
3734
3821
  }
3735
- function nodeXml(node, registry, namespaces, warn, layoutOf) {
3736
- 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;
3737
3825
  if (!node.hidden) return xml;
3738
3826
  return xml.replace(/^<[\w:]+/, (open) => `${open} display="none"`).replace(/<path (?=[^>]*:role="stroke-outline")/, '<path display="none" ');
3739
3827
  }
3740
- function kindXml(node, registry, namespaces, warn, layoutOf) {
3741
- 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);
3742
3830
  if (node.kind === "text") return textXml(node, registry, namespaces, warn, layoutOf?.(node));
3743
3831
  if (node.kind === "image") return imageXml(node, namespaces);
3744
3832
  const drawn = pathAsSvgDraws(node);
3745
- 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);
3746
3834
  }
3747
3835
  function imageXml(node, namespaces) {
3748
3836
  const box = `x="${trimNumber(node.x)}" y="${trimNumber(node.y)}" width="${trimNumber(node.width)}" height="${trimNumber(node.height)}"`;
@@ -3766,8 +3854,11 @@ function imageXml(node, namespaces) {
3766
3854
  const head = ['<g data-weasel-image="true"', ...outer].join(" ");
3767
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>`;
3768
3856
  }
3769
- function groupXml(node, registry, namespaces, warn, layoutOf) {
3857
+ function groupXml(node, registry, namespaces, warn, layoutOf, painted = NOTHING_PAINTED) {
3770
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)]);
3771
3862
  if (node.transform) {
3772
3863
  const m = formatMatrix(node.transform);
3773
3864
  if (m) attrs.push(`transform="${m}"`);
@@ -3780,7 +3871,7 @@ function groupXml(node, registry, namespaces, warn, layoutOf) {
3780
3871
  }
3781
3872
  const metaAttrs = metaAttrsXml(node.meta, namespaces);
3782
3873
  const head = attrs.length > 0 ? `<g ${attrs.join(" ")}${metaAttrs}>` : `<g${metaAttrs}>`;
3783
- 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("");
3784
3875
  return `${head}${body}${metaElementsXml(node.meta, namespaces)}</g>`;
3785
3876
  }
3786
3877
  function pathXml(node, registry, namespaces, warn) {
@@ -3867,6 +3958,19 @@ function fadeRun(run, k) {
3867
3958
  ...run.stroke ? { stroke: fadeStroke(run.stroke, k) } : {}
3868
3959
  };
3869
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
+ }
3870
3974
  var CASCADE_OFFSET_PX = 24;
3871
3975
  var VIEWPORT_FIT = 0.9;
3872
3976
  function readFileText(file) {
@@ -3931,8 +4035,9 @@ function svgNodesToKitDrafts(input, nextId, options = {}) {
3931
4035
  drafts.push({ kind: "leaf", id, parentId, pose, data: out, ...source.hidden ? { hidden: true } : {} });
3932
4036
  return pose;
3933
4037
  };
3934
- const visit = (n, parentId, inherited2) => {
4038
+ const visit = (n, parentId, inherited2, from) => {
3935
4039
  const k = inherited2 * (n.opacity ?? 1);
4040
+ const takes = takesOf(n, from, k);
3936
4041
  if (n.kind === "group") {
3937
4042
  const draft = {
3938
4043
  kind: "container",
@@ -3944,7 +4049,7 @@ function svgNodesToKitDrafts(input, nextId, options = {}) {
3944
4049
  };
3945
4050
  drafts.push(draft);
3946
4051
  const at = drafts.length - 1;
3947
- 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))));
3948
4053
  if (!acc) {
3949
4054
  const kept = drafts.length > at + 1 || (options.keepEmpty?.(n) ?? false);
3950
4055
  if (!kept) drafts.splice(at, 1);
@@ -3952,6 +4057,7 @@ function svgNodesToKitDrafts(input, nextId, options = {}) {
3952
4057
  return null;
3953
4058
  }
3954
4059
  draft.pose = n.clip ? boxOfPath(n.clip) : acc;
4060
+ Object.assign(draft.data, containerPaint(n, takes, draft.pose, k));
3955
4061
  return draft.pose;
3956
4062
  }
3957
4063
  if (n.kind === "text") {
@@ -3969,16 +4075,15 @@ function svgNodesToKitDrafts(input, nextId, options = {}) {
3969
4075
  width: pose2.width,
3970
4076
  height: pose2.height
3971
4077
  };
3972
- 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;
3973
4079
  return pushLeaf(n, parentId, pose2, {
3974
4080
  text: n.text,
3975
4081
  ...n.style ? { style: n.style } : {},
3976
4082
  ...n.verticalAlign ? { verticalAlign: n.verticalAlign } : {},
3977
4083
  ...n.runs ? { runs: n.runs.map((r) => fadeRun(runInBoxFrame(r, box), k)) } : {},
3978
- // `!== undefined`, not a truthiness test: `null` is the document
3979
- // saying `fill="none"`, and absent takes the painter's default.
3980
- ...fill2 !== void 0 ? { fill: fill2 && mapFillPaint(fill2, (p) => fadeFill(fillInBoxFrame(p, box), k)) } : {},
3981
- ...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) } : {}
3982
4087
  });
3983
4088
  }
3984
4089
  if (n.kind === "image") {
@@ -4002,13 +4107,21 @@ function svgNodesToKitDrafts(input, nextId, options = {}) {
4002
4107
  const strokeFromSvg = strokeDataFromSvg(n.stroke, b);
4003
4108
  return pushLeaf(n, parentId, pose, {
4004
4109
  path: n.path,
4005
- ...fill !== void 0 ? { fill: fill && { paint: fadeFill(fill, k) } } : {},
4006
- ...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) } : {}
4007
4112
  });
4008
4113
  };
4009
- for (const n of nodes) visit(n, null, 1);
4114
+ for (const n of nodes) visit(n, null, 1, NO_TAKES);
4010
4115
  return drafts;
4011
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
+ }
4012
4125
  function boxOfPath(path) {
4013
4126
  const b = boundsOfPath(path);
4014
4127
  return { x: b.x, y: b.y, width: b.width, height: b.height };
@@ -4097,8 +4210,9 @@ function svgPaintFromKit(fill, box) {
4097
4210
  }
4098
4211
  return { kind: "gradient", paint: fillInPoseFrame(fill, box) };
4099
4212
  }
4100
- function svgStrokeFromKit(stroke, box) {
4101
- if (!stroke?.paint) return void 0;
4213
+ function svgStrokeFromKit(raw, box) {
4214
+ const stroke = resolveStroke(raw);
4215
+ if (!stroke) return void 0;
4102
4216
  const width2 = stroke.width ?? 1;
4103
4217
  if (!((typeof width2 === "object" ? width2.px : width2) > 0)) return void 0;
4104
4218
  const opacity = stroke.paint.opacity;
@@ -4152,17 +4266,16 @@ function svgLeafFromKit(data, pose) {
4152
4266
  const text = svgTextFromKit({ ...data, text: data.text }, pose);
4153
4267
  const boxPath = { kind: "rect", x: pose.x, y: pose.y, width: pose.width, height: pose.height };
4154
4268
  const clip = data.overflow === "clip" ? { clip: pathThroughPoseFrame(boxPath, pose) } : {};
4155
- 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;
4156
4270
  const box = svgLeafFromKit({ path: boxPath, fill: data.background }, pose);
4157
4271
  return { kind: "group", children: [box, text], ...clip };
4158
4272
  }
4159
4273
  if (!data.path) return null;
4160
- const stroke = svgStrokeFromKit(data.stroke, pose);
4161
- const fill = data.fill !== void 0 ? data.fill : stroke ? null : DEFAULT_SHAPE_FILL;
4274
+ const stroke = svgStrokeFromKit(resolveStroke(data.stroke), pose);
4162
4275
  const node = {
4163
4276
  kind: "path",
4164
4277
  path: pathInPoseFrame(data.path, pose),
4165
- fill: svgPaintFromKit(fill?.paint ?? null, pose)
4278
+ fill: svgPaintFromKit(resolveFill(data.fill)?.paint ?? null, pose)
4166
4279
  };
4167
4280
  if (stroke) node.stroke = stroke;
4168
4281
  const frame = poseLinearOf(pose) && poseFrameOf(pose);
@@ -4186,25 +4299,32 @@ function svgTextFromKit(data, pose) {
4186
4299
  if (data.style) node.style = data.style;
4187
4300
  if (data.runs) node.runs = data.runs.map((r) => runInPoseFrame(r, pose));
4188
4301
  if (data.verticalAlign) node.verticalAlign = data.verticalAlign;
4189
- if (data.fill !== void 0) node.fill = data.fill && mapFillPaint(data.fill, (p) => fillInPoseFrame(p, pose));
4190
- 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);
4191
4306
  if (pose.rotation) node.rotation = pose.rotation;
4192
4307
  if (pose.transform) node.transform = pose.transform;
4193
4308
  return node;
4194
4309
  }
4195
4310
  function svgNodesFromKit(tree, options = {}) {
4196
4311
  const { include, leaf, group } = options;
4197
- const emit = (id) => {
4312
+ const emit = (id, up) => {
4198
4313
  const node = tree.get(id);
4199
- const out2 = node && (!include || include(id)) ? lower(id, node) : null;
4314
+ const out2 = node && (!include || include(id)) ? lower(id, node, up) : null;
4200
4315
  return out2 && node?.hidden === true ? { ...out2, hidden: true } : out2;
4201
4316
  };
4202
- const lower = (id, node) => {
4317
+ const lower = (id, node, up) => {
4203
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
+ };
4204
4324
  const emitChildren = () => {
4205
4325
  const children = [];
4206
4326
  for (const c of tree.childrenOf(id)) {
4207
- const child = emit(c);
4327
+ const child = emit(c, below);
4208
4328
  if (child) children.push(child);
4209
4329
  }
4210
4330
  return children;
@@ -4213,7 +4333,7 @@ function svgNodesFromKit(tree, options = {}) {
4213
4333
  const clip = path ? pathThroughPoseFrame(pathInPoseFrame(path, node.pose), node.pose) : void 0;
4214
4334
  const copies = node.copiesFromPose?.(node.pose, node) ?? [];
4215
4335
  const content = () => ({ kind: "group", children: emitChildren(), ...clip ? { clip } : {} });
4216
- let g = content();
4336
+ let g = { ...content(), ...groupPaintFromKit(own2, below, node.pose) };
4217
4337
  if (copies.length > 0) {
4218
4338
  g = {
4219
4339
  kind: "group",
@@ -4222,16 +4342,44 @@ function svgNodesFromKit(tree, options = {}) {
4222
4342
  }
4223
4343
  return group ? group(id, g) : g;
4224
4344
  }
4225
- 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);
4226
4347
  };
4227
4348
  const out = [];
4228
4349
  for (const id of options.roots ?? tree.roots) {
4229
- const n = emit(id);
4350
+ const n = emit(id, NO_UP_PAINT);
4230
4351
  if (n) out.push(n);
4231
4352
  }
4232
4353
  return out;
4233
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
+ }
4234
4382
 
4235
- export { DEFAULT_MEDIA_ENVIRONMENT, IDENTITY_MATRIX, escapeAttr, escapeText, evaluateMediaQuery, evaluateSupports, fillDataFromSvg, nativeSvgKind, nativeSvgSpace, parseSvg, 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 };
4236
4384
  //# sourceMappingURL=index.js.map
4237
4385
  //# sourceMappingURL=index.js.map