@weasel-js/svg 1.4.4 → 1.5.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 +6 -13
- package/dist/index.js +42 -30
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
package/dist/index.d.ts
CHANGED
|
@@ -130,16 +130,6 @@ interface SvgGroupNode {
|
|
|
130
130
|
/** Opaque per-element bag for declared namespaces. See `NamespaceMeta`. */
|
|
131
131
|
meta?: NamespaceMeta;
|
|
132
132
|
}
|
|
133
|
-
/**
|
|
134
|
-
* Width given to an external `<text>` that carries no `data-weasel-width`:
|
|
135
|
-
* large enough that line wrapping never fires, since SVG text flows from a
|
|
136
|
-
* baseline and the source says nothing about a box.
|
|
137
|
-
*
|
|
138
|
-
* It is a wrap sentinel, not a measurement. Anything that treats a text
|
|
139
|
-
* node's width as geometry — a union AABB, a fit-clamp — has to recognize it
|
|
140
|
-
* and substitute an estimate, or one text node swamps the whole file.
|
|
141
|
-
*/
|
|
142
|
-
declare const UNBOUNDED_TEXT_WIDTH = 99999;
|
|
143
133
|
/**
|
|
144
134
|
* Text leaf node. Mirrors the kit's `TextPose` shape — a bounding box plus
|
|
145
135
|
* text content, optional rich-text `runs` (per-range styling), and an
|
|
@@ -150,9 +140,12 @@ declare const UNBOUNDED_TEXT_WIDTH = 99999;
|
|
|
150
140
|
* `dominant-baseline="text-before-edge"` so `y` is the top of the text
|
|
151
141
|
* box, plus `data-weasel-width` / `data-weasel-height` so weasel's
|
|
152
142
|
* explicit box dimensions round-trip losslessly. External SVG text
|
|
153
|
-
* (lacking the data-* attrs) imports with
|
|
154
|
-
*
|
|
143
|
+
* (lacking the data-* attrs) imports with the width the kit's text layout
|
|
144
|
+
* measures — an em-based estimate when no registered font can — and
|
|
155
145
|
* `height = fontSize * lineHeight * (lineCount || 1)`.
|
|
146
|
+
*
|
|
147
|
+
* `x` here is always the box's left edge. In the SVG it is the
|
|
148
|
+
* `text-anchor` point, and the parser and serializer convert between them.
|
|
156
149
|
*/
|
|
157
150
|
interface SvgTextNode {
|
|
158
151
|
kind: 'text';
|
|
@@ -401,4 +394,4 @@ declare function svgNodesToKitDrafts(nodes: readonly SvgNode[], nextId: () => st
|
|
|
401
394
|
*/
|
|
402
395
|
declare function unpackSvgFiles(files: File[], ctx: IngestCtx): Promise<void>;
|
|
403
396
|
|
|
404
|
-
export { IDENTITY_MATRIX, type Matrix, type NamespaceMeta, type NamespacedElement, type ParseOptions, type ParseResult, type SerializeOptions, type SvgDraftBounds, type SvgGroupNode, type SvgImageNode, type SvgNode, type SvgPaint, type SvgPathNode, type SvgSceneDraft, type SvgStroke, type SvgTextNode,
|
|
397
|
+
export { IDENTITY_MATRIX, type Matrix, type NamespaceMeta, type NamespacedElement, type ParseOptions, type ParseResult, type SerializeOptions, type SvgDraftBounds, type SvgGroupNode, type SvgImageNode, type SvgNode, type SvgPaint, type SvgPathNode, type SvgSceneDraft, type SvgStroke, type SvgTextNode, parseSvg, serializeSvg, strokeDataFromSvg, svgNodesToKitDrafts, tilePreviewCssUrl, tilePreviewSvg, unpackSvgFiles };
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import { pathFromD, PATH_M, PATH_L, PATH_Z, boundsOfPath, getMarker, SCRIPT_METRICS, dwarn, createInsertOp, PathBuilder, parseColor, rgbaToHex, PATH_C, PATH_Q, solid, fillToBoundsFrame,
|
|
1
|
+
import { pathFromD, PATH_M, PATH_L, PATH_Z, boundsOfPath, getMarker, SCRIPT_METRICS, dwarn, createInsertOp, PathBuilder, parseColor, rgbaToHex, resolveTextStyle, layoutRuns, resolveRuns, resolveAlign, PATH_C, PATH_Q, solid, fillToBoundsFrame, getPaintKind } from '@weasel-js/core';
|
|
2
|
+
import { invert } from '@weasel-js/geom';
|
|
2
3
|
import { tileGeometry } from '@weasel-js/core/patterns-builtin';
|
|
3
4
|
|
|
4
5
|
// src/parse.ts
|
|
5
6
|
|
|
6
7
|
// src/types.ts
|
|
7
8
|
var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
|
|
8
|
-
var UNBOUNDED_TEXT_WIDTH = 99999;
|
|
9
|
-
|
|
10
|
-
// src/transform.ts
|
|
11
9
|
function multiply(a, b) {
|
|
12
10
|
return [
|
|
13
11
|
a[0] * b[0] + a[2] * b[1],
|
|
@@ -78,15 +76,11 @@ function parseTransform(attr, onWarn) {
|
|
|
78
76
|
}
|
|
79
77
|
return result;
|
|
80
78
|
}
|
|
81
|
-
function invertMatrix(m) {
|
|
82
|
-
const det = m[0] * m[3] - m[1] * m[2];
|
|
83
|
-
if (!Number.isFinite(det) || Math.abs(det) < 1e-12) return null;
|
|
84
|
-
const a = m[3] / det, b = -m[1] / det, c = -m[2] / det, d = m[0] / det;
|
|
85
|
-
return [a, b, c, d, -(a * m[4] + c * m[5]), -(b * m[4] + d * m[5])];
|
|
86
|
-
}
|
|
87
79
|
function rebaseTransform(ctm, local) {
|
|
88
|
-
const inv =
|
|
89
|
-
|
|
80
|
+
const inv = invert([...ctm]);
|
|
81
|
+
if (!inv) return null;
|
|
82
|
+
const [a, b, c, d, e, f] = inv;
|
|
83
|
+
return multiply(multiply(ctm, local), [a, b, c, d, e, f]);
|
|
90
84
|
}
|
|
91
85
|
function decomposeRotation(m, cx, cy, eps = 1e-4) {
|
|
92
86
|
const [a, b, c, d, e, f] = m;
|
|
@@ -236,6 +230,10 @@ function polygonToPath(points, m = IDENTITY_MATRIX) {
|
|
|
236
230
|
b.close();
|
|
237
231
|
return transformPath(b.build(), m);
|
|
238
232
|
}
|
|
233
|
+
function anchorOffset(style, width) {
|
|
234
|
+
const edge = resolveAlign(style?.align ?? "left", style?.direction ?? "ltr");
|
|
235
|
+
return edge === "center" ? width / 2 : edge === "right" ? width : 0;
|
|
236
|
+
}
|
|
239
237
|
function parsePaintAttr(raw) {
|
|
240
238
|
if (raw == null) return null;
|
|
241
239
|
const s = raw.trim();
|
|
@@ -866,8 +864,10 @@ function parseElement(el, ctm, style, gradients, onWarn, uriToPrefix) {
|
|
|
866
864
|
const bounds = pathAabb(path);
|
|
867
865
|
const cx = bounds.x + bounds.width / 2;
|
|
868
866
|
const cy = bounds.y + bounds.height / 2;
|
|
869
|
-
const angle = decomposeRotation(worldLocal, cx, cy);
|
|
870
|
-
if (
|
|
867
|
+
const angle = worldLocal && decomposeRotation(worldLocal, cx, cy);
|
|
868
|
+
if (!worldLocal) {
|
|
869
|
+
onWarn("leaf transform sits under a singular parent transform; dropped");
|
|
870
|
+
} else if (angle != null) {
|
|
871
871
|
rotation = angle;
|
|
872
872
|
} else {
|
|
873
873
|
path = transformPath(path, worldLocal);
|
|
@@ -1187,8 +1187,10 @@ function parseImageElement(el, ctm, onWarn) {
|
|
|
1187
1187
|
const worldLocal = rebaseTransform(ctm, localTransform);
|
|
1188
1188
|
const cx = node.x + node.width / 2;
|
|
1189
1189
|
const cy = node.y + node.height / 2;
|
|
1190
|
-
const angle = decomposeRotation(worldLocal, cx, cy);
|
|
1191
|
-
if (
|
|
1190
|
+
const angle = worldLocal && decomposeRotation(worldLocal, cx, cy);
|
|
1191
|
+
if (!worldLocal) {
|
|
1192
|
+
onWarn("<image> transform sits under a singular parent transform; dropped");
|
|
1193
|
+
} else if (angle != null) {
|
|
1192
1194
|
node.rotation = angle;
|
|
1193
1195
|
} else {
|
|
1194
1196
|
const rotComp = rotationComponent(worldLocal);
|
|
@@ -1238,6 +1240,19 @@ function collapseRunWhitespace(runs) {
|
|
|
1238
1240
|
if (runs[i].text === "") runs.splice(i, 1);
|
|
1239
1241
|
}
|
|
1240
1242
|
}
|
|
1243
|
+
var ESTIMATED_GLYPH_ADVANCE_EM = 0.6;
|
|
1244
|
+
function laidOutWidth(runs, style, plain) {
|
|
1245
|
+
const resolved = resolveTextStyle(style);
|
|
1246
|
+
const { bounds } = layoutRuns(resolveRuns(runs.length > 0 ? runs : [{ text: plain }], resolved), {
|
|
1247
|
+
maxWidth: Infinity,
|
|
1248
|
+
lineHeight: resolved.lineHeight,
|
|
1249
|
+
align: resolved.align,
|
|
1250
|
+
direction: resolved.direction
|
|
1251
|
+
});
|
|
1252
|
+
if (bounds.width > 0) return bounds.width;
|
|
1253
|
+
const longest = plain.split("\n").reduce((max, line) => Math.max(max, line.length), 0);
|
|
1254
|
+
return longest * resolved.fontSize * ESTIMATED_GLYPH_ADVANCE_EM;
|
|
1255
|
+
}
|
|
1241
1256
|
function parseTextElement(el, ctm, style, gradients, onWarn) {
|
|
1242
1257
|
const num2 = (raw, fallback) => {
|
|
1243
1258
|
if (raw == null) return fallback;
|
|
@@ -1283,13 +1298,13 @@ function parseTextElement(el, ctm, style, gradients, onWarn) {
|
|
|
1283
1298
|
const plain = runs.map((r) => r.text).join("");
|
|
1284
1299
|
const dataW = num2(el.getAttribute("data-weasel-width"), NaN);
|
|
1285
1300
|
const dataH = num2(el.getAttribute("data-weasel-height"), NaN);
|
|
1286
|
-
const width = Number.isFinite(dataW) ? dataW :
|
|
1301
|
+
const width = Number.isFinite(dataW) ? dataW : laidOutWidth(runs, textStyle, plain);
|
|
1287
1302
|
const lines = (plain.match(/\n/g)?.length ?? 0) + 1;
|
|
1288
1303
|
const height = Number.isFinite(dataH) ? dataH : fontSize * lineHeight * lines;
|
|
1289
1304
|
const opacity = readOpacityAttr(el, "opacity");
|
|
1290
1305
|
const node = {
|
|
1291
1306
|
kind: "text",
|
|
1292
|
-
x: ax,
|
|
1307
|
+
x: ax - anchorOffset(textStyle, width),
|
|
1293
1308
|
y: topY,
|
|
1294
1309
|
width,
|
|
1295
1310
|
height,
|
|
@@ -1299,6 +1314,7 @@ function parseTextElement(el, ctm, style, gradients, onWarn) {
|
|
|
1299
1314
|
(r) => Object.entries(r).some(([k, v]) => k !== "text" && v !== void 0)
|
|
1300
1315
|
);
|
|
1301
1316
|
if (hasStyling) node.runs = runs;
|
|
1317
|
+
if (el.getAttribute("data-weasel-wrap") === "true") textStyle.wrap = true;
|
|
1302
1318
|
if (Object.keys(textStyle).length > 0) node.style = textStyle;
|
|
1303
1319
|
if (textPaint.fill !== void 0) node.fill = textPaint.fill;
|
|
1304
1320
|
if (textPaint.stroke != null) node.stroke = textPaint.stroke;
|
|
@@ -1307,8 +1323,10 @@ function parseTextElement(el, ctm, style, gradients, onWarn) {
|
|
|
1307
1323
|
const worldLocal = rebaseTransform(ctm, localTransform);
|
|
1308
1324
|
const cx = node.x + node.width / 2;
|
|
1309
1325
|
const cy = node.y + node.height / 2;
|
|
1310
|
-
const angle = decomposeRotation(worldLocal, cx, cy);
|
|
1311
|
-
if (
|
|
1326
|
+
const angle = worldLocal && decomposeRotation(worldLocal, cx, cy);
|
|
1327
|
+
if (!worldLocal) {
|
|
1328
|
+
onWarn("<text> transform sits under a singular parent transform; dropped");
|
|
1329
|
+
} else if (angle != null) {
|
|
1312
1330
|
node.rotation = angle;
|
|
1313
1331
|
} else {
|
|
1314
1332
|
const rotComp = rotationComponent(worldLocal);
|
|
@@ -1719,7 +1737,7 @@ function rotateAboutCenter(ctm, b, angle) {
|
|
|
1719
1737
|
}
|
|
1720
1738
|
function textXml(node, registry, namespaces) {
|
|
1721
1739
|
const attrs = [
|
|
1722
|
-
`x="${trimNumber(node.x)}"`,
|
|
1740
|
+
`x="${trimNumber(node.x + anchorOffset(node.style, node.width))}"`,
|
|
1723
1741
|
`y="${trimNumber(node.y)}"`,
|
|
1724
1742
|
`dominant-baseline="text-before-edge"`,
|
|
1725
1743
|
// The runs model stores real line breaks; SVG's default whitespace
|
|
@@ -1745,6 +1763,7 @@ function textXml(node, registry, namespaces) {
|
|
|
1745
1763
|
}
|
|
1746
1764
|
const decoration = textDecorationValue(style?.underline, style?.strikethrough, style?.overline);
|
|
1747
1765
|
if (decoration) attrs.push(`text-decoration="${decoration}"`);
|
|
1766
|
+
if (style?.wrap) attrs.push('data-weasel-wrap="true"');
|
|
1748
1767
|
if (node.fill === null) {
|
|
1749
1768
|
attrs.push('fill="none"');
|
|
1750
1769
|
} else if (node.fill) {
|
|
@@ -1909,7 +1928,7 @@ function svgNodesToKitDrafts(nodes, nextId) {
|
|
|
1909
1928
|
const pose2 = {
|
|
1910
1929
|
x: n.x,
|
|
1911
1930
|
y: n.y,
|
|
1912
|
-
width:
|
|
1931
|
+
width: n.width,
|
|
1913
1932
|
height: n.height
|
|
1914
1933
|
};
|
|
1915
1934
|
if (n.rotation) pose2.rotation = n.rotation;
|
|
@@ -1974,13 +1993,6 @@ function svgNodesToKitDrafts(nodes, nextId) {
|
|
|
1974
1993
|
for (const n of nodes) visit(n, null);
|
|
1975
1994
|
return drafts;
|
|
1976
1995
|
}
|
|
1977
|
-
var ESTIMATED_GLYPH_ADVANCE_EM = 0.6;
|
|
1978
|
-
function textBoxWidth(n) {
|
|
1979
|
-
if (n.width !== UNBOUNDED_TEXT_WIDTH) return n.width;
|
|
1980
|
-
const fontSize = resolveTextStyle(n.style).fontSize;
|
|
1981
|
-
const longest = n.text.split("\n").reduce((max, line) => Math.max(max, line.length), 0);
|
|
1982
|
-
return longest * fontSize * ESTIMATED_GLYPH_ADVANCE_EM;
|
|
1983
|
-
}
|
|
1984
1996
|
function scaleTextData(data, scale) {
|
|
1985
1997
|
if (scale === 1 || typeof data.text !== "string") return data;
|
|
1986
1998
|
const style = data.style ?? {};
|
|
@@ -2064,6 +2076,6 @@ async function unpackSvgFiles(files, ctx) {
|
|
|
2064
2076
|
}
|
|
2065
2077
|
}
|
|
2066
2078
|
|
|
2067
|
-
export { IDENTITY_MATRIX,
|
|
2079
|
+
export { IDENTITY_MATRIX, parseSvg, serializeSvg, strokeDataFromSvg, svgNodesToKitDrafts, tilePreviewCssUrl, tilePreviewSvg, unpackSvgFiles };
|
|
2068
2080
|
//# sourceMappingURL=index.js.map
|
|
2069
2081
|
//# sourceMappingURL=index.js.map
|