@weasel-js/svg 1.4.3 → 1.4.4

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
@@ -72,7 +72,12 @@ type SvgPaint = {
72
72
  /** Stroke description: a paint plus structural line parameters. */
73
73
  interface SvgStroke {
74
74
  paint: SvgPaint;
75
- width: number;
75
+ /** World units, or `{ px }` for a width that holds its rendered thickness
76
+ * however the document is scaled — SVG's `vector-effect="non-scaling-stroke"`,
77
+ * and the same unit system as the kit's `Stroke.width`. */
78
+ width: number | {
79
+ px: number;
80
+ };
76
81
  opacity?: number;
77
82
  /** `stroke-linecap`. Default per SVG spec is `'butt'`. */
78
83
  cap?: 'butt' | 'round' | 'square';
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { pathFromD, PATH_M, PATH_L, PATH_Z, boundsOfPath, getMarker, SCRIPT_METRICS, dwarn, createInsertOp, PathBuilder, parseColor, rgbaToHex, PATH_C, PATH_Q, resolveStrokeWidth, solid, fillToBoundsFrame, resolveTextStyle, getPaintKind } from '@weasel-js/core';
1
+ import { pathFromD, PATH_M, PATH_L, PATH_Z, boundsOfPath, getMarker, SCRIPT_METRICS, dwarn, createInsertOp, PathBuilder, parseColor, rgbaToHex, PATH_C, PATH_Q, solid, fillToBoundsFrame, resolveTextStyle, getPaintKind } from '@weasel-js/core';
2
2
  import { tileGeometry } from '@weasel-js/core/patterns-builtin';
3
3
 
4
4
  // src/parse.ts
@@ -879,7 +879,7 @@ function parseElement(el, ctm, style, gradients, onWarn, uriToPrefix) {
879
879
  }
880
880
  const leafStyle = deriveStyle(style, el);
881
881
  const fill = readPaint(leafStyle, "fill", "#000000", gradients, onWarn);
882
- const stroke = readStroke(leafStyle, gradients, onWarn);
882
+ const stroke = readStroke(leafStyle, gradients, onWarn, isNonScalingStroke(el));
883
883
  const opacity = readOpacityAttr(el, "opacity");
884
884
  const fillRuleRaw = leafStyle["fill-rule"] ?? null;
885
885
  if (fillRuleRaw === "evenodd" && path.kind === "polygon") {
@@ -988,14 +988,14 @@ function readPaint(style, attr, defaultColor, gradients, onWarn) {
988
988
  if (a != null) out.opacity = a;
989
989
  return out;
990
990
  }
991
- function readStroke(style, gradients, onWarn) {
991
+ function readStroke(style, gradients, onWarn, nonScaling = false) {
992
992
  const inheritedStroke = style["stroke"] ?? null;
993
993
  const inheritedWidth = style["stroke-width"] ?? null;
994
994
  if (inheritedStroke == null && inheritedWidth == null) return void 0;
995
995
  const paint = readPaint(style, "stroke", "#000000", gradients, onWarn);
996
996
  if (paint.kind === "none") return void 0;
997
- const width = inheritedWidth != null ? parseFloat(inheritedWidth) : 1;
998
- const stroke = { paint, width };
997
+ const n = inheritedWidth != null ? parseFloat(inheritedWidth) : 1;
998
+ const stroke = { paint, width: nonScaling ? { px: n } : n };
999
999
  const opacityRaw = style["stroke-opacity"] ?? null;
1000
1000
  if (opacityRaw != null) {
1001
1001
  const a = clamp01(parseFloat(opacityRaw));
@@ -1062,6 +1062,9 @@ var STROKE_KEYS = [
1062
1062
  "marker-mid",
1063
1063
  "marker-end"
1064
1064
  ];
1065
+ function isNonScalingStroke(el) {
1066
+ return ownProp(el, "vector-effect") === "non-scaling-stroke";
1067
+ }
1065
1068
  function ownStrokeStyle(el) {
1066
1069
  const out = {};
1067
1070
  for (const k of STROKE_KEYS) {
@@ -1249,7 +1252,7 @@ function parseTextElement(el, ctm, style, gradients, onWarn) {
1249
1252
  const ay = m[1] * rawX + m[3] * rawY + m[5];
1250
1253
  const leafStyle = deriveStyle(style, el);
1251
1254
  const textStyle = readTextStyle(leafStyle, onWarn);
1252
- const textPaint = readTextPaint(leafStyle, gradients, onWarn);
1255
+ const textPaint = readTextPaint(leafStyle, gradients, onWarn, isNonScalingStroke(el));
1253
1256
  const fontSize = textStyle.fontSize ?? 16;
1254
1257
  const lineHeight = textStyle.lineHeight ?? 1.2;
1255
1258
  const dominantBaseline = el.getAttribute("dominant-baseline");
@@ -1357,7 +1360,7 @@ function readTspanRun(el, gradients, style, onWarn) {
1357
1360
  if (paint) run.fill = paint;
1358
1361
  }
1359
1362
  }
1360
- const stroke = coreStroke(readStroke(ownStrokeStyle(el), gradients, onWarn));
1363
+ const stroke = coreStroke(readStroke(ownStrokeStyle(el), gradients, onWarn, isNonScalingStroke(el)));
1361
1364
  if (stroke) run.stroke = stroke;
1362
1365
  return run;
1363
1366
  }
@@ -1394,7 +1397,7 @@ function readTextStyle(style, onWarn) {
1394
1397
  if (decoration.overline) out.overline = true;
1395
1398
  return out;
1396
1399
  }
1397
- function readTextPaint(style, gradients, onWarn) {
1400
+ function readTextPaint(style, gradients, onWarn, nonScalingStroke = false) {
1398
1401
  const out = {};
1399
1402
  const fillRaw = resolveCurrentColor(style["fill"] ?? null, style);
1400
1403
  if (fillRaw) {
@@ -1408,7 +1411,7 @@ function readTextPaint(style, gradients, onWarn) {
1408
1411
  out.fill = null;
1409
1412
  }
1410
1413
  }
1411
- const stroke = coreStroke(readStroke(style, gradients, onWarn));
1414
+ const stroke = coreStroke(readStroke(style, gradients, onWarn, nonScalingStroke));
1412
1415
  if (stroke) out.stroke = stroke;
1413
1416
  return out;
1414
1417
  }
@@ -1608,11 +1611,15 @@ function paintAttrs(paint, name, registry, includeOpacity = true) {
1608
1611
  const id = registry.register(paint.paint);
1609
1612
  return [`${name}="url(#${id})"`];
1610
1613
  }
1614
+ function strokeWidthAttrs(width) {
1615
+ if (typeof width === "number") return [`stroke-width="${trimNumber(width)}"`];
1616
+ return [`stroke-width="${trimNumber(width.px)}"`, 'vector-effect="non-scaling-stroke"'];
1617
+ }
1611
1618
  function coreStrokeAttrs(stroke, registry) {
1612
1619
  const paint = stroke?.paint;
1613
1620
  if (!stroke || !paint) return [];
1614
- const width = resolveStrokeWidth(stroke.width ?? 1, 1);
1615
- if (!(width > 0)) return [];
1621
+ const width = stroke.width ?? 1;
1622
+ if (!((typeof width === "object" ? width.px : width) > 0)) return [];
1616
1623
  const attrs = [];
1617
1624
  if ("color" in paint) {
1618
1625
  attrs.push(`stroke="${paint.color}"`);
@@ -1622,7 +1629,7 @@ function coreStrokeAttrs(stroke, registry) {
1622
1629
  } else {
1623
1630
  attrs.push(`stroke="url(#${registry.register(paint)})"`);
1624
1631
  }
1625
- attrs.push(`stroke-width="${trimNumber(width)}"`);
1632
+ attrs.push(...strokeWidthAttrs(width));
1626
1633
  if (stroke.cap) attrs.push(`stroke-linecap="${stroke.cap}"`);
1627
1634
  if (stroke.join) attrs.push(`stroke-linejoin="${stroke.join}"`);
1628
1635
  if (stroke.dash && stroke.dash.length > 0) {
@@ -1645,7 +1652,7 @@ function coreStrokeAttrs(stroke, registry) {
1645
1652
  }
1646
1653
  function strokeAttrsFor(stroke, registry) {
1647
1654
  const attrs = paintAttrs(stroke.paint, "stroke", registry, false);
1648
- attrs.push(`stroke-width="${trimNumber(stroke.width)}"`);
1655
+ attrs.push(...strokeWidthAttrs(stroke.width));
1649
1656
  const opacity = stroke.opacity ?? (stroke.paint.kind === "solid" ? stroke.paint.opacity : void 0);
1650
1657
  if (opacity != null && opacity !== 1) {
1651
1658
  attrs.push(`stroke-opacity="${trimNumber(opacity)}"`);
@@ -1860,6 +1867,21 @@ function strokeDataFromSvg(stroke, box) {
1860
1867
  ...stroke.markerEnd !== void 0 ? { markerEnd: stroke.markerEnd } : {}
1861
1868
  };
1862
1869
  }
1870
+ function fillInBoxFrame(fill, box) {
1871
+ const units = "units" in fill ? fill.units : void 0;
1872
+ return units === "world" ? fillToBoundsFrame(fill, box) : fill;
1873
+ }
1874
+ function runInBoxFrame(run, box) {
1875
+ const fill = run.fill ? fillInBoxFrame(run.fill, box) : run.fill;
1876
+ const stroke = run.stroke ? strokeInBoxFrame(run.stroke, box) : run.stroke;
1877
+ if (fill === run.fill && stroke === run.stroke) return run;
1878
+ return { ...run, ...fill ? { fill } : {}, ...stroke ? { stroke } : {} };
1879
+ }
1880
+ function strokeInBoxFrame(stroke, box) {
1881
+ if (!stroke.paint) return stroke;
1882
+ const paint = fillInBoxFrame(stroke.paint, box);
1883
+ return paint === stroke.paint ? stroke : { ...stroke, paint };
1884
+ }
1863
1885
  function svgNodesToKitDrafts(nodes, nextId) {
1864
1886
  const drafts = [];
1865
1887
  const visit = (n, parentId) => {
@@ -1891,12 +1913,26 @@ function svgNodesToKitDrafts(nodes, nextId) {
1891
1913
  height: n.height
1892
1914
  };
1893
1915
  if (n.rotation) pose2.rotation = n.rotation;
1916
+ const box = {
1917
+ x: pose2.x,
1918
+ y: pose2.y,
1919
+ width: pose2.width,
1920
+ height: pose2.height
1921
+ };
1894
1922
  drafts.push({
1895
1923
  kind: "leaf",
1896
1924
  id: nextId(),
1897
1925
  parentId,
1898
1926
  pose: pose2,
1899
- data: { text: n.text, ...n.style ? { style: n.style } : {} }
1927
+ data: {
1928
+ text: n.text,
1929
+ ...n.style ? { style: n.style } : {},
1930
+ ...n.runs ? { runs: n.runs.map((r) => runInBoxFrame(r, box)) } : {},
1931
+ // `!== undefined`, not a truthiness test: `null` is the document
1932
+ // saying `fill="none"`, and absent takes the painter's default.
1933
+ ...n.fill !== void 0 ? { fill: n.fill === null ? null : fillInBoxFrame(n.fill, box) } : {},
1934
+ ...n.stroke !== void 0 ? { stroke: strokeInBoxFrame(n.stroke, box) } : {}
1935
+ }
1900
1936
  });
1901
1937
  return pose2;
1902
1938
  }
@@ -1948,8 +1984,12 @@ function textBoxWidth(n) {
1948
1984
  function scaleTextData(data, scale) {
1949
1985
  if (scale === 1 || typeof data.text !== "string") return data;
1950
1986
  const style = data.style ?? {};
1987
+ const runs = data.runs;
1951
1988
  return {
1952
1989
  ...data,
1990
+ ...runs ? {
1991
+ runs: runs.map((r) => r.fontSize !== void 0 ? { ...r, fontSize: r.fontSize * scale } : r)
1992
+ } : {},
1953
1993
  style: { ...style, fontSize: resolveTextStyle(style).fontSize * scale }
1954
1994
  };
1955
1995
  }