@weasel-js/svg 1.4.2 → 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 +10 -2
- package/dist/index.js +67 -22
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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
|
|
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';
|
|
@@ -166,8 +171,11 @@ interface SvgTextNode {
|
|
|
166
171
|
* in `data.fill` / `data.stroke`. Not `SvgPaint`, which is the path
|
|
167
172
|
* nodes' shape — a text paint reaches the renderer through the runs, and
|
|
168
173
|
* `StyledRun.fill` / `.stroke` override these per range.
|
|
174
|
+
*
|
|
175
|
+
* `null` is SVG's `fill="none"`: outline-only text, painted by its stroke
|
|
176
|
+
* alone. Absent takes the default black.
|
|
169
177
|
*/
|
|
170
|
-
fill?: FillStyle;
|
|
178
|
+
fill?: FillStyle | null;
|
|
171
179
|
stroke?: Stroke;
|
|
172
180
|
/** Element-level opacity (`opacity="..."`), 0..1. */
|
|
173
181
|
opacity?: number;
|
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,
|
|
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
|
|
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");
|
|
@@ -1297,7 +1300,7 @@ function parseTextElement(el, ctm, style, gradients, onWarn) {
|
|
|
1297
1300
|
);
|
|
1298
1301
|
if (hasStyling) node.runs = runs;
|
|
1299
1302
|
if (Object.keys(textStyle).length > 0) node.style = textStyle;
|
|
1300
|
-
if (textPaint.fill
|
|
1303
|
+
if (textPaint.fill !== void 0) node.fill = textPaint.fill;
|
|
1301
1304
|
if (textPaint.stroke != null) node.stroke = textPaint.stroke;
|
|
1302
1305
|
if (opacity != null) node.opacity = opacity;
|
|
1303
1306
|
if (!isIdentity(localTransform)) {
|
|
@@ -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) {
|
|
@@ -1404,9 +1407,11 @@ function readTextPaint(style, gradients, onWarn) {
|
|
|
1404
1407
|
} else if (parsed?.kind === "ref") {
|
|
1405
1408
|
const paint = gradients.get(parsed.id);
|
|
1406
1409
|
if (paint) out.fill = paint;
|
|
1410
|
+
} else if (parsed?.kind === "none") {
|
|
1411
|
+
out.fill = null;
|
|
1407
1412
|
}
|
|
1408
1413
|
}
|
|
1409
|
-
const stroke = coreStroke(readStroke(style, gradients, onWarn));
|
|
1414
|
+
const stroke = coreStroke(readStroke(style, gradients, onWarn, nonScalingStroke));
|
|
1410
1415
|
if (stroke) out.stroke = stroke;
|
|
1411
1416
|
return out;
|
|
1412
1417
|
}
|
|
@@ -1606,20 +1611,25 @@ function paintAttrs(paint, name, registry, includeOpacity = true) {
|
|
|
1606
1611
|
const id = registry.register(paint.paint);
|
|
1607
1612
|
return [`${name}="url(#${id})"`];
|
|
1608
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
|
+
}
|
|
1609
1618
|
function coreStrokeAttrs(stroke, registry) {
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1619
|
+
const paint = stroke?.paint;
|
|
1620
|
+
if (!stroke || !paint) return [];
|
|
1621
|
+
const width = stroke.width ?? 1;
|
|
1622
|
+
if (!((typeof width === "object" ? width.px : width) > 0)) return [];
|
|
1613
1623
|
const attrs = [];
|
|
1614
|
-
if ("color" in
|
|
1615
|
-
attrs.push(`stroke="${
|
|
1616
|
-
if (
|
|
1617
|
-
attrs.push(`stroke-opacity="${trimNumber(
|
|
1624
|
+
if ("color" in paint) {
|
|
1625
|
+
attrs.push(`stroke="${paint.color}"`);
|
|
1626
|
+
if (paint.opacity != null && paint.opacity !== 1) {
|
|
1627
|
+
attrs.push(`stroke-opacity="${trimNumber(paint.opacity)}"`);
|
|
1618
1628
|
}
|
|
1619
1629
|
} else {
|
|
1620
|
-
attrs.push(`stroke="url(#${registry.register(
|
|
1630
|
+
attrs.push(`stroke="url(#${registry.register(paint)})"`);
|
|
1621
1631
|
}
|
|
1622
|
-
attrs.push(
|
|
1632
|
+
attrs.push(...strokeWidthAttrs(width));
|
|
1623
1633
|
if (stroke.cap) attrs.push(`stroke-linecap="${stroke.cap}"`);
|
|
1624
1634
|
if (stroke.join) attrs.push(`stroke-linejoin="${stroke.join}"`);
|
|
1625
1635
|
if (stroke.dash && stroke.dash.length > 0) {
|
|
@@ -1642,7 +1652,7 @@ function coreStrokeAttrs(stroke, registry) {
|
|
|
1642
1652
|
}
|
|
1643
1653
|
function strokeAttrsFor(stroke, registry) {
|
|
1644
1654
|
const attrs = paintAttrs(stroke.paint, "stroke", registry, false);
|
|
1645
|
-
attrs.push(
|
|
1655
|
+
attrs.push(...strokeWidthAttrs(stroke.width));
|
|
1646
1656
|
const opacity = stroke.opacity ?? (stroke.paint.kind === "solid" ? stroke.paint.opacity : void 0);
|
|
1647
1657
|
if (opacity != null && opacity !== 1) {
|
|
1648
1658
|
attrs.push(`stroke-opacity="${trimNumber(opacity)}"`);
|
|
@@ -1735,7 +1745,9 @@ function textXml(node, registry, namespaces) {
|
|
|
1735
1745
|
}
|
|
1736
1746
|
const decoration = textDecorationValue(style?.underline, style?.strikethrough, style?.overline);
|
|
1737
1747
|
if (decoration) attrs.push(`text-decoration="${decoration}"`);
|
|
1738
|
-
if (node.fill) {
|
|
1748
|
+
if (node.fill === null) {
|
|
1749
|
+
attrs.push('fill="none"');
|
|
1750
|
+
} else if (node.fill) {
|
|
1739
1751
|
if ("color" in node.fill) {
|
|
1740
1752
|
attrs.push(`fill="${node.fill.color}"`);
|
|
1741
1753
|
if (node.fill.opacity != null && node.fill.opacity !== 1) {
|
|
@@ -1855,6 +1867,21 @@ function strokeDataFromSvg(stroke, box) {
|
|
|
1855
1867
|
...stroke.markerEnd !== void 0 ? { markerEnd: stroke.markerEnd } : {}
|
|
1856
1868
|
};
|
|
1857
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
|
+
}
|
|
1858
1885
|
function svgNodesToKitDrafts(nodes, nextId) {
|
|
1859
1886
|
const drafts = [];
|
|
1860
1887
|
const visit = (n, parentId) => {
|
|
@@ -1886,12 +1913,26 @@ function svgNodesToKitDrafts(nodes, nextId) {
|
|
|
1886
1913
|
height: n.height
|
|
1887
1914
|
};
|
|
1888
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
|
+
};
|
|
1889
1922
|
drafts.push({
|
|
1890
1923
|
kind: "leaf",
|
|
1891
1924
|
id: nextId(),
|
|
1892
1925
|
parentId,
|
|
1893
1926
|
pose: pose2,
|
|
1894
|
-
data: {
|
|
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
|
+
}
|
|
1895
1936
|
});
|
|
1896
1937
|
return pose2;
|
|
1897
1938
|
}
|
|
@@ -1943,8 +1984,12 @@ function textBoxWidth(n) {
|
|
|
1943
1984
|
function scaleTextData(data, scale) {
|
|
1944
1985
|
if (scale === 1 || typeof data.text !== "string") return data;
|
|
1945
1986
|
const style = data.style ?? {};
|
|
1987
|
+
const runs = data.runs;
|
|
1946
1988
|
return {
|
|
1947
1989
|
...data,
|
|
1990
|
+
...runs ? {
|
|
1991
|
+
runs: runs.map((r) => r.fontSize !== void 0 ? { ...r, fontSize: r.fontSize * scale } : r)
|
|
1992
|
+
} : {},
|
|
1948
1993
|
style: { ...style, fontSize: resolveTextStyle(style).fontSize * scale }
|
|
1949
1994
|
};
|
|
1950
1995
|
}
|