@weasel-js/svg 1.10.1 → 1.11.1
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/README.md +18 -0
- package/dist/index.d.ts +65 -13
- package/dist/index.js +301 -114
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { identity, multiply, invert, splitByClosure, fillsOpenSubpaths, rotateAboutPoint, hasOpenSubpath, splitSubpaths } from '@weasel-js/geom';
|
|
2
|
-
|
|
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, 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
4
|
import { tileGeometry } from '@weasel-js/core/patterns-builtin';
|
|
4
5
|
|
|
5
6
|
// src/types.ts
|
|
@@ -177,9 +178,11 @@ function scaleLength(v, scale) {
|
|
|
177
178
|
return typeof v === "number" ? v * scale : v;
|
|
178
179
|
}
|
|
179
180
|
function scaleStroke(stroke, scale) {
|
|
180
|
-
const { width: width2 } = stroke;
|
|
181
|
-
if (scale === 1
|
|
182
|
-
const
|
|
181
|
+
const { width: width2, vertexWidths } = stroke;
|
|
182
|
+
if (scale === 1) return stroke;
|
|
183
|
+
const scaledVertices = vertexWidths ? { vertexWidths: vertexWidths.map((n) => scaleVertexWidth(n, scale)) } : {};
|
|
184
|
+
if (typeof width2 === "object") return vertexWidths ? { ...stroke, ...scaledVertices } : stroke;
|
|
185
|
+
const out = { ...stroke, ...scaledVertices };
|
|
183
186
|
if (width2 !== void 0) out.width = width2 * scale;
|
|
184
187
|
if (out.dash) out.dash = out.dash.map((n) => n * scale);
|
|
185
188
|
return out;
|
|
@@ -1381,39 +1384,6 @@ function readPattern(el, onWarn) {
|
|
|
1381
1384
|
if (!isIdentity([m[0], m[1], m[2], m[3], 0, 0])) paint.transform = [m[0], m[1], m[2], m[3]];
|
|
1382
1385
|
return paint;
|
|
1383
1386
|
}
|
|
1384
|
-
function serializePathD(path) {
|
|
1385
|
-
if (path.kind === "rect") {
|
|
1386
|
-
const { x, y, width: width2, height: height2 } = path;
|
|
1387
|
-
return `M${trimNumber(x)} ${trimNumber(y)}h${trimNumber(width2)}v${trimNumber(height2)}h${trimNumber(-width2)}Z`;
|
|
1388
|
-
}
|
|
1389
|
-
const cmds = path.commands;
|
|
1390
|
-
const xs = path.coords;
|
|
1391
|
-
const out = [];
|
|
1392
|
-
let ci = 0;
|
|
1393
|
-
for (let i = 0; i < cmds.length; i++) {
|
|
1394
|
-
const c = cmds[i];
|
|
1395
|
-
if (c === PATH_M) {
|
|
1396
|
-
out.push(`M${trimNumber(xs[ci])} ${trimNumber(xs[ci + 1])}`);
|
|
1397
|
-
ci += 2;
|
|
1398
|
-
} else if (c === PATH_L) {
|
|
1399
|
-
out.push(`L${trimNumber(xs[ci])} ${trimNumber(xs[ci + 1])}`);
|
|
1400
|
-
ci += 2;
|
|
1401
|
-
} else if (c === PATH_C) {
|
|
1402
|
-
out.push(
|
|
1403
|
-
`C${trimNumber(xs[ci])} ${trimNumber(xs[ci + 1])} ${trimNumber(xs[ci + 2])} ${trimNumber(xs[ci + 3])} ${trimNumber(xs[ci + 4])} ${trimNumber(xs[ci + 5])}`
|
|
1404
|
-
);
|
|
1405
|
-
ci += 6;
|
|
1406
|
-
} else if (c === PATH_Q) {
|
|
1407
|
-
out.push(
|
|
1408
|
-
`Q${trimNumber(xs[ci])} ${trimNumber(xs[ci + 1])} ${trimNumber(xs[ci + 2])} ${trimNumber(xs[ci + 3])}`
|
|
1409
|
-
);
|
|
1410
|
-
ci += 4;
|
|
1411
|
-
} else if (c === PATH_Z) {
|
|
1412
|
-
out.push("Z");
|
|
1413
|
-
}
|
|
1414
|
-
}
|
|
1415
|
-
return out.join("");
|
|
1416
|
-
}
|
|
1417
1387
|
|
|
1418
1388
|
// src/gradients.ts
|
|
1419
1389
|
var WEASEL_NS = "urn:weasel-js:svg";
|
|
@@ -1719,7 +1689,7 @@ var PaintServerRegistry = class {
|
|
|
1719
1689
|
}
|
|
1720
1690
|
for (const path of this.clips) {
|
|
1721
1691
|
parts.push(
|
|
1722
|
-
`<clipPath id="${this.clipIds.get(path)}"><path d="${
|
|
1692
|
+
`<clipPath id="${this.clipIds.get(path)}"><path d="${pathToD(path)}"/></clipPath>`
|
|
1723
1693
|
);
|
|
1724
1694
|
}
|
|
1725
1695
|
parts.push("</defs>");
|
|
@@ -1733,7 +1703,7 @@ function defaultMarkerXml(id, entry, onWarn, key, size) {
|
|
|
1733
1703
|
const name = key ?? id;
|
|
1734
1704
|
const unit = size === void 0 ? 1 : resolveMarkerSize({ key: name, size }, 1);
|
|
1735
1705
|
const path = entry.path({ size: unit, stroke: { paint: { kind: "solid", color: "#000" } } });
|
|
1736
|
-
const d =
|
|
1706
|
+
const d = pathToD(path);
|
|
1737
1707
|
const fill = markerPaintAttrs("fill", entry.fill, id, onWarn);
|
|
1738
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"` : "";
|
|
1739
1709
|
const orient = typeof entry.orient === "number" ? trimNumber(entry.orient * 180 / Math.PI) : "auto-start-reverse";
|
|
@@ -2096,6 +2066,97 @@ function coreStroke(stroke) {
|
|
|
2096
2066
|
if (stroke.markerEnd) out.markerEnd = stroke.markerEnd;
|
|
2097
2067
|
return out;
|
|
2098
2068
|
}
|
|
2069
|
+
var VERTEX_WIDTHS = "vertex-widths";
|
|
2070
|
+
var NIB = "nib";
|
|
2071
|
+
var WIDTH = "stroke-width";
|
|
2072
|
+
var ROLE = "role";
|
|
2073
|
+
var OUTLINE = "stroke-outline";
|
|
2074
|
+
var GROUP = "stroke-group";
|
|
2075
|
+
var MARKER_ATTR = /^(marker-(?:start|mid|end))=/;
|
|
2076
|
+
var UNSET = "-";
|
|
2077
|
+
var SIDES = "/";
|
|
2078
|
+
function attrOf(el, name) {
|
|
2079
|
+
return el.getAttributeNS(WEASEL_NS, name) ?? el.getAttribute(`${WEASEL_NS_PREFIX}:${name}`);
|
|
2080
|
+
}
|
|
2081
|
+
function hasVariableWidth(stroke) {
|
|
2082
|
+
return (stroke?.vertexWidths?.length ?? 0) > 0 || stroke?.nib !== void 0;
|
|
2083
|
+
}
|
|
2084
|
+
function writesVariableWidth(nodes) {
|
|
2085
|
+
return nodes.some((n) => n.kind === "group" ? writesVariableWidth(n.children) : n.kind === "path" && hasVariableWidth(n.stroke));
|
|
2086
|
+
}
|
|
2087
|
+
function widthToken(entry) {
|
|
2088
|
+
const { left, right } = vertexHalfWidths(entry, NaN);
|
|
2089
|
+
const side = (v) => Number.isNaN(v) ? UNSET : trimNumber(v);
|
|
2090
|
+
if (typeof entry === "number") return Number.isNaN(left) ? UNSET : trimNumber(left + right);
|
|
2091
|
+
return Number.isNaN(left) && Number.isNaN(right) ? UNSET : `${side(left)}${SIDES}${side(right)}`;
|
|
2092
|
+
}
|
|
2093
|
+
function widthOfToken(token) {
|
|
2094
|
+
const side = (v) => v === UNSET ? NaN : Number(v);
|
|
2095
|
+
if (!token.includes(SIDES)) return side(token);
|
|
2096
|
+
const [left, right] = token.split(SIDES);
|
|
2097
|
+
return { left: side(left), right: side(right) };
|
|
2098
|
+
}
|
|
2099
|
+
function variableWidthAttrs(stroke, strokeAttrs) {
|
|
2100
|
+
const width2 = typeof stroke.width === "number" ? stroke.width : stroke.width.px;
|
|
2101
|
+
const out = [
|
|
2102
|
+
...strokeAttrs.map((a) => a.startsWith("stroke-width=") ? 'stroke-width="0"' : MARKER_ATTR.test(a) ? `${WEASEL_NS_PREFIX}:${a}` : a),
|
|
2103
|
+
`${WEASEL_NS_PREFIX}:${WIDTH}="${trimNumber(width2)}"`
|
|
2104
|
+
];
|
|
2105
|
+
if (stroke.vertexWidths?.length) {
|
|
2106
|
+
const list = stroke.vertexWidths.map(widthToken).join(" ");
|
|
2107
|
+
out.push(`${WEASEL_NS_PREFIX}:${VERTEX_WIDTHS}="${list}"`);
|
|
2108
|
+
}
|
|
2109
|
+
if (stroke.nib) {
|
|
2110
|
+
out.push(`${WEASEL_NS_PREFIX}:${NIB}="${String(stroke.nib.angle)} ${String(stroke.nib.ratio)}"`);
|
|
2111
|
+
}
|
|
2112
|
+
return out;
|
|
2113
|
+
}
|
|
2114
|
+
function strokeOutlineXml(path, stroke, fillAttrs, transform) {
|
|
2115
|
+
const kit = {
|
|
2116
|
+
width: stroke.width,
|
|
2117
|
+
...stroke.vertexWidths ? { vertexWidths: stroke.vertexWidths } : {},
|
|
2118
|
+
...stroke.nib ? { nib: stroke.nib } : {},
|
|
2119
|
+
...stroke.cap ? { cap: stroke.cap } : {},
|
|
2120
|
+
...stroke.join ? { join: stroke.join } : {},
|
|
2121
|
+
...stroke.dash ? { dash: stroke.dash } : {},
|
|
2122
|
+
...stroke.miterLimit != null ? { miterLimit: stroke.miterLimit } : {},
|
|
2123
|
+
...stroke.align ? { align: stroke.align } : {},
|
|
2124
|
+
...stroke.markerStart ? { markerStart: stroke.markerStart } : {},
|
|
2125
|
+
...stroke.markerMid ? { markerMid: stroke.markerMid } : {},
|
|
2126
|
+
...stroke.markerEnd ? { markerEnd: stroke.markerEnd } : {}
|
|
2127
|
+
};
|
|
2128
|
+
const outline = strokeToPath(path, kit);
|
|
2129
|
+
const attrs = [`d="${pathToD(outline)}"`, ...fillAttrs, 'stroke="none"'];
|
|
2130
|
+
if (transform) attrs.push(transform);
|
|
2131
|
+
attrs.push(`${WEASEL_NS_PREFIX}:${ROLE}="${OUTLINE}"`);
|
|
2132
|
+
return `<path ${attrs.join(" ")}/>`;
|
|
2133
|
+
}
|
|
2134
|
+
function strokeGroupXml(opacity, inner) {
|
|
2135
|
+
return `<g ${opacity} ${WEASEL_NS_PREFIX}:${ROLE}="${GROUP}">${inner}</g>`;
|
|
2136
|
+
}
|
|
2137
|
+
function unwrapStrokeGroup(el, node) {
|
|
2138
|
+
if (attrOf(el, ROLE) !== GROUP || !node || Array.isArray(node) || node.kind !== "group") return node;
|
|
2139
|
+
const [only] = node.children;
|
|
2140
|
+
if (node.children.length !== 1 || only.kind !== "path") return node;
|
|
2141
|
+
return node.opacity != null ? { ...only, opacity: node.opacity * (only.opacity ?? 1) } : only;
|
|
2142
|
+
}
|
|
2143
|
+
function isStrokeOutline(el) {
|
|
2144
|
+
return attrOf(el, ROLE) === OUTLINE;
|
|
2145
|
+
}
|
|
2146
|
+
function withVariableWidth(el, stroke) {
|
|
2147
|
+
const width2 = parseFloat(attrOf(el, WIDTH) ?? "");
|
|
2148
|
+
if (!stroke || !Number.isFinite(width2)) return stroke;
|
|
2149
|
+
const out = { ...stroke, width: typeof stroke.width === "number" ? width2 : { px: width2 } };
|
|
2150
|
+
const list = attrOf(el, VERTEX_WIDTHS);
|
|
2151
|
+
if (list) out.vertexWidths = list.trim().split(/\s+/).map(widthOfToken);
|
|
2152
|
+
const [angle, ratio] = (attrOf(el, NIB) ?? "").trim().split(/\s+/).map(Number);
|
|
2153
|
+
if (Number.isFinite(angle) && Number.isFinite(ratio)) out.nib = { angle, ratio };
|
|
2154
|
+
for (const [attr, field] of [["marker-start", "markerStart"], ["marker-mid", "markerMid"], ["marker-end", "markerEnd"]]) {
|
|
2155
|
+
const id = /^url\(\s*#([^)\s]+)\s*\)$/.exec(attrOf(el, attr)?.trim() ?? "")?.[1];
|
|
2156
|
+
if (id) out[field] = id;
|
|
2157
|
+
}
|
|
2158
|
+
return out;
|
|
2159
|
+
}
|
|
2099
2160
|
var near = (v, target) => Math.abs(v - target) <= 1e-9;
|
|
2100
2161
|
function placeBox(box, m, stretch) {
|
|
2101
2162
|
const [a, b, c, d] = m;
|
|
@@ -2239,6 +2300,7 @@ function anchorOffset(style, width2, text) {
|
|
|
2239
2300
|
|
|
2240
2301
|
// src/tspanLines.ts
|
|
2241
2302
|
var LINES_ATTR = "data-weasel-lines";
|
|
2303
|
+
var LINE_HEIGHT_ATTR = "data-weasel-line-height";
|
|
2242
2304
|
var statesLines = (tspan) => tspan.hasAttribute(LINES_ATTR);
|
|
2243
2305
|
function linesXml(pieces, x, pitch) {
|
|
2244
2306
|
let out = "";
|
|
@@ -2286,9 +2348,9 @@ var LineTracker = class {
|
|
|
2286
2348
|
const dy = readLength(tspan, "dy", "y", 0, this.onWarn);
|
|
2287
2349
|
this.at = (y != null ? readLength(tspan, "y", "y", from, this.onWarn) : from) + dy;
|
|
2288
2350
|
if (!tspan.hasAttribute("x") || !(movedY || dy !== 0)) return 0;
|
|
2289
|
-
const stated = Number(tspan.getAttribute(LINES_ATTR));
|
|
2290
|
-
const lines = Number.isInteger(stated) && stated
|
|
2291
|
-
if (afterText && this.at > from) this.pitches.push((this.at - from) / lines);
|
|
2351
|
+
const stated = Number(tspan.getAttribute(LINES_ATTR) ?? NaN);
|
|
2352
|
+
const lines = Number.isInteger(stated) && stated >= 0 ? stated : 1;
|
|
2353
|
+
if (afterText && lines > 0 && this.at > from) this.pitches.push((this.at - from) / lines);
|
|
2292
2354
|
return lines;
|
|
2293
2355
|
}
|
|
2294
2356
|
/**
|
|
@@ -2308,13 +2370,17 @@ var LineTracker = class {
|
|
|
2308
2370
|
return Math.abs(lineHeight - DEFAULT_LINE_HEIGHT) < 1e-6 ? void 0 : lineHeight;
|
|
2309
2371
|
}
|
|
2310
2372
|
};
|
|
2373
|
+
var holdsOnlyText = (el) => !Array.from(el.childNodes).some(
|
|
2374
|
+
(child) => child.nodeType === 1
|
|
2375
|
+
/* ELEMENT_NODE */
|
|
2376
|
+
);
|
|
2311
2377
|
function warnUnreadPositions(el, onWarn) {
|
|
2312
2378
|
const tag = el.tagName.toLowerCase();
|
|
2313
2379
|
for (const name of ["x", "y"]) {
|
|
2314
2380
|
const raw = el.getAttribute(name)?.trim() ?? "";
|
|
2315
2381
|
if (/[\s,]/.test(raw)) onWarn(`<${tag} ${name}="${raw}"> positions each glyph; only the first value is read`);
|
|
2316
2382
|
}
|
|
2317
|
-
for (const name of tag === "tspan" ? ["rotate"] : ["dx", "rotate"]) {
|
|
2383
|
+
for (const name of tag === "tspan" || holdsOnlyText(el) ? ["rotate"] : ["dx", "rotate"]) {
|
|
2318
2384
|
const raw = el.getAttribute(name)?.trim() ?? "";
|
|
2319
2385
|
if (raw.split(/[\s,]+/).some((v) => parseFloat(v) !== 0 && v !== "")) {
|
|
2320
2386
|
onWarn(`<${tag} ${name}="${raw}"> is not read; the glyphs keep their own advance and angle`);
|
|
@@ -2347,6 +2413,76 @@ function joinLines(lines, leading = 0) {
|
|
|
2347
2413
|
}
|
|
2348
2414
|
return out;
|
|
2349
2415
|
}
|
|
2416
|
+
|
|
2417
|
+
// src/laidLines.ts
|
|
2418
|
+
var LAID_ATTR = "data-weasel-layout";
|
|
2419
|
+
function layoutMeasured(layout, text) {
|
|
2420
|
+
return layout.laid.bounds.width > 0 || text.trim() === "";
|
|
2421
|
+
}
|
|
2422
|
+
function anchorX(cells, { anchor, rtl }) {
|
|
2423
|
+
const left = Math.min(...cells.map((c) => c.x));
|
|
2424
|
+
const right = Math.max(...cells.map((c) => c.x + c.advance));
|
|
2425
|
+
if (anchor === "middle") return (left + right) / 2;
|
|
2426
|
+
return anchor === "start" === !rtl ? left : right;
|
|
2427
|
+
}
|
|
2428
|
+
function wordStarts(text, start, end) {
|
|
2429
|
+
const out = [start];
|
|
2430
|
+
for (let i = start + 1; i < end; i++) {
|
|
2431
|
+
if (!/\s/.test(text[i]) && /\s/.test(text[i - 1])) out.push(i);
|
|
2432
|
+
}
|
|
2433
|
+
return out;
|
|
2434
|
+
}
|
|
2435
|
+
function rangeXml(pieces, offsets, start, end, lead) {
|
|
2436
|
+
let out = "";
|
|
2437
|
+
let first = true;
|
|
2438
|
+
pieces.forEach((piece, k) => {
|
|
2439
|
+
const from = Math.max(start, offsets[k]);
|
|
2440
|
+
const to = Math.min(end, offsets[k] + piece.text.length);
|
|
2441
|
+
if (from >= to) return;
|
|
2442
|
+
const text = piece.text.slice(from - offsets[k], to - offsets[k]);
|
|
2443
|
+
const attrs = [...piece.attrs ?? [], ...first ? lead : []];
|
|
2444
|
+
first = false;
|
|
2445
|
+
out += piece.attrs === null && attrs.length === 0 ? escapeText(text) : `<tspan${attrs.map((a) => ` ${a}`).join("")}>${escapeText(text)}</tspan>`;
|
|
2446
|
+
});
|
|
2447
|
+
return out;
|
|
2448
|
+
}
|
|
2449
|
+
var HARD_BREAKS = /\r\n|[\n\v\f\r\u0085\u2028\u2029]/g;
|
|
2450
|
+
var breaksIn = (text) => text.match(HARD_BREAKS)?.length ?? 0;
|
|
2451
|
+
function laidLinesXml(pieces, layout, anchor) {
|
|
2452
|
+
const text = pieces.map((p) => p.text).join("");
|
|
2453
|
+
const offsets = [];
|
|
2454
|
+
pieces.reduce((at, p) => {
|
|
2455
|
+
offsets.push(at);
|
|
2456
|
+
return at + p.text.length;
|
|
2457
|
+
}, 0);
|
|
2458
|
+
const px = (x) => `x="${trimNumber(layout.x + x)}"`;
|
|
2459
|
+
const py = (line) => `y="${trimNumber(layout.y + line.baselineY)}"`;
|
|
2460
|
+
let out = "";
|
|
2461
|
+
let wrote = false;
|
|
2462
|
+
let prevEnd = 0;
|
|
2463
|
+
const count = (lines) => {
|
|
2464
|
+
const stated = lines !== 1 || !wrote ? [`${LINES_ATTR}="${lines}"`] : [];
|
|
2465
|
+
wrote = true;
|
|
2466
|
+
return stated;
|
|
2467
|
+
};
|
|
2468
|
+
for (const line of layout.laid.lines) {
|
|
2469
|
+
if (line.cells.length === 0) continue;
|
|
2470
|
+
const start = Math.min(...line.cells.map((c) => c.srcIndex));
|
|
2471
|
+
const stepped = breaksIn(text.slice(prevEnd, start));
|
|
2472
|
+
prevEnd = line.srcEnd;
|
|
2473
|
+
const starts = line.justified ? wordStarts(text, start, line.srcEnd) : [start];
|
|
2474
|
+
starts.forEach((from, k) => {
|
|
2475
|
+
const to = starts[k + 1] ?? line.srcEnd;
|
|
2476
|
+
const cells = line.cells.filter((c) => c.srcIndex >= from && c.srcIndex < to);
|
|
2477
|
+
const lead = [px(anchorX(cells, anchor)), ...k === 0 ? [py(line), ...count(stepped)] : []];
|
|
2478
|
+
out += rangeXml(pieces, offsets, from, to, lead);
|
|
2479
|
+
});
|
|
2480
|
+
}
|
|
2481
|
+
const trailing = breaksIn(text.slice(prevEnd));
|
|
2482
|
+
const last = layout.laid.lines[layout.laid.lines.length - 1];
|
|
2483
|
+
if (trailing > 0 && last) out += `<tspan ${[px(last.x0), py(last), ...count(trailing)].join(" ")}></tspan>`;
|
|
2484
|
+
return out;
|
|
2485
|
+
}
|
|
2350
2486
|
var COLLAPSIBLE = /^\s|\s$|\s\s|[\n\t\r]/;
|
|
2351
2487
|
function readDx(el, onWarn) {
|
|
2352
2488
|
const raw = el.getAttribute("dx")?.trim();
|
|
@@ -2375,8 +2511,9 @@ function placeTspanRun(tspan, run, lines, inherited2, onWarn) {
|
|
|
2375
2511
|
return 0;
|
|
2376
2512
|
}
|
|
2377
2513
|
const raw = tspan.getAttribute("dx");
|
|
2514
|
+
const tag = tspan.tagName.toLowerCase();
|
|
2378
2515
|
if (COLLAPSIBLE.test(run.text)) {
|
|
2379
|
-
onWarn(
|
|
2516
|
+
onWarn(`<${tag} dx="${raw}"> is not read: its text has white space SVG collapses, which moves the glyphs the list names`);
|
|
2380
2517
|
line.push(run);
|
|
2381
2518
|
return 0;
|
|
2382
2519
|
}
|
|
@@ -2389,7 +2526,7 @@ function placeTspanRun(tspan, run, lines, inherited2, onWarn) {
|
|
|
2389
2526
|
} else if (dx[0] !== 0 && lines.length === 1) {
|
|
2390
2527
|
lead = dx[0];
|
|
2391
2528
|
} else if (dx[0] !== 0) {
|
|
2392
|
-
onWarn(
|
|
2529
|
+
onWarn(`<${tag} dx="${raw}"> opens a line; the line starts at the box edge, and the rest of the list is read`);
|
|
2393
2530
|
}
|
|
2394
2531
|
line.push(...spread(run, dx.slice(1), inherited2));
|
|
2395
2532
|
return lead;
|
|
@@ -2455,6 +2592,10 @@ function laidOutWidth(runs, style, plain) {
|
|
|
2455
2592
|
const longest = plain.split("\n").reduce((max, line) => Math.max(max, line.length), 0);
|
|
2456
2593
|
return longest * resolved.fontSize * ESTIMATED_GLYPH_ADVANCE_EM;
|
|
2457
2594
|
}
|
|
2595
|
+
function numberAttr(el, name) {
|
|
2596
|
+
const n = parseFloat(el.getAttribute(name) ?? "");
|
|
2597
|
+
return Number.isFinite(n) ? n : void 0;
|
|
2598
|
+
}
|
|
2458
2599
|
function placeTextNode(node, m, onWarn) {
|
|
2459
2600
|
const cx = node.x + node.width / 2;
|
|
2460
2601
|
const cy = node.y + node.height / 2;
|
|
@@ -2496,13 +2637,14 @@ function parseTextElement(el, ctm, style, gradients, onWarn) {
|
|
|
2496
2637
|
const textPaint = readTextPaint(leafStyle, gradients, onWarn, isNonScalingStroke(el));
|
|
2497
2638
|
const fontSize = resolveScreenLength(textStyle.fontSize ?? 16, 1);
|
|
2498
2639
|
const dominantBaseline = el.getAttribute("dominant-baseline");
|
|
2499
|
-
const
|
|
2640
|
+
const laid = el.hasAttribute(LAID_ATTR);
|
|
2641
|
+
const explicitTopAnchor = laid || dominantBaseline === "text-before-edge" || dominantBaseline === "hanging";
|
|
2500
2642
|
const topY = explicitTopAnchor ? ay : ay - fontSize;
|
|
2501
2643
|
const inheritedShift = ownValue(el, "baseline-shift", onWarn) ?? {};
|
|
2502
2644
|
const tracker = new LineTracker(el, onWarn);
|
|
2503
2645
|
const { runs, lead } = readRuns(el, tracker, inheritedShift, textStyle.letterSpacing, gradients, leafStyle, onWarn);
|
|
2504
2646
|
const plain = runs.map((r) => r.text).join("");
|
|
2505
|
-
const pitched = tracker.lineHeight(fontSize);
|
|
2647
|
+
const pitched = laid ? numberAttr(el, LINE_HEIGHT_ATTR) : tracker.lineHeight(fontSize);
|
|
2506
2648
|
if (pitched !== void 0) textStyle.lineHeight = pitched;
|
|
2507
2649
|
const lineHeight = textStyle.lineHeight ?? DEFAULT_LINE_HEIGHT;
|
|
2508
2650
|
const dataW = num2(el.getAttribute("data-weasel-width"), NaN);
|
|
@@ -2546,7 +2688,10 @@ function readRuns(el, tracker, inheritedShift, letterSpacing, gradients, leafSty
|
|
|
2546
2688
|
const push = (run) => {
|
|
2547
2689
|
runLines[runLines.length - 1].push(run);
|
|
2548
2690
|
};
|
|
2549
|
-
|
|
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++) {
|
|
2550
2695
|
const child = el.childNodes[i];
|
|
2551
2696
|
if (child.nodeType === 3) {
|
|
2552
2697
|
const t = child.textContent ?? "";
|
|
@@ -3017,10 +3162,10 @@ function parseChildren(parent, ctm, style, gradients, clips, onWarn, uriToPrefix
|
|
|
3017
3162
|
for (let i = 0; i < parent.children.length; i++) {
|
|
3018
3163
|
const el = parent.children[i];
|
|
3019
3164
|
const ns = el.namespaceURI;
|
|
3020
|
-
if (ns && ns !== "http://www.w3.org/2000/svg") continue;
|
|
3165
|
+
if (ns && ns !== "http://www.w3.org/2000/svg" || isStrokeOutline(el)) continue;
|
|
3021
3166
|
const hidden = isHiddenElement(el, style);
|
|
3022
3167
|
if (hidden && !keepHidden) continue;
|
|
3023
|
-
const node = parseElement(el, ctm, style, gradients, clips, onWarn, uriToPrefix, keepHidden);
|
|
3168
|
+
const node = unwrapStrokeGroup(el, parseElement(el, ctm, style, gradients, clips, onWarn, uriToPrefix, keepHidden));
|
|
3024
3169
|
if (!node) continue;
|
|
3025
3170
|
const parsed = Array.isArray(node) ? node : [node];
|
|
3026
3171
|
if (hidden) for (const n of parsed) n.hidden = true;
|
|
@@ -3113,7 +3258,7 @@ function parseElement(el, ctm, style, gradients, clips, onWarn, uriToPrefix, kee
|
|
|
3113
3258
|
}
|
|
3114
3259
|
const leafStyle = deriveStyle(style, el);
|
|
3115
3260
|
const fill = paintThroughMatrix(readPaint2(leafStyle, "fill", "#000000", gradients, onWarn), baked);
|
|
3116
|
-
const ownStroke = readStroke(leafStyle, gradients, onWarn, isNonScalingStroke(el));
|
|
3261
|
+
const ownStroke = withVariableWidth(el, readStroke(leafStyle, gradients, onWarn, isNonScalingStroke(el)));
|
|
3117
3262
|
const stroke = ownStroke && strokeThroughMatrix(ownStroke, baked);
|
|
3118
3263
|
if (stroke && typeof stroke.width === "number" && isNonUniform(baked)) {
|
|
3119
3264
|
onWarn(`<${tag}> stroke sits under a skew or non-uniform scale; its width takes the uniform part only`);
|
|
@@ -3292,6 +3437,12 @@ function paintAttrs(paint, name, registry, includeOpacity = true) {
|
|
|
3292
3437
|
if (includeOpacity) out.push(...opacityAttr(`${name}-opacity`, svgPaintOpacity(paint)));
|
|
3293
3438
|
return out;
|
|
3294
3439
|
}
|
|
3440
|
+
function strokePaintAsFillAttrs(stroke, registry) {
|
|
3441
|
+
return [
|
|
3442
|
+
...paintAttrs(stroke.paint, "fill", registry, false),
|
|
3443
|
+
...opacityAttr("fill-opacity", stroke.opacity ?? svgPaintOpacity(stroke.paint))
|
|
3444
|
+
];
|
|
3445
|
+
}
|
|
3295
3446
|
function svgPaintOpacity(paint) {
|
|
3296
3447
|
if (paint.kind === "solid") return paint.opacity;
|
|
3297
3448
|
return paint.kind === "gradient" ? paint.paint.opacity : void 0;
|
|
@@ -3361,12 +3512,13 @@ function strokeAttrsFor(stroke, registry, warn) {
|
|
|
3361
3512
|
}
|
|
3362
3513
|
return attrs;
|
|
3363
3514
|
}
|
|
3364
|
-
function textXml(node, registry, namespaces, warn) {
|
|
3515
|
+
function textXml(node, registry, namespaces, warn, given) {
|
|
3516
|
+
const layout = given && layoutMeasured(given, node.text) ? given : null;
|
|
3365
3517
|
const x = node.x + anchorOffset(node.style, node.width, node.text);
|
|
3366
3518
|
const attrs = [
|
|
3367
3519
|
`x="${trimNumber(x)}"`,
|
|
3368
3520
|
`y="${trimNumber(node.y)}"`,
|
|
3369
|
-
`dominant-baseline="text-before-edge"`,
|
|
3521
|
+
layout ? `${LAID_ATTR}="lines"` : `dominant-baseline="text-before-edge"`,
|
|
3370
3522
|
// SVG's default whitespace handling would collapse indentation and runs
|
|
3371
3523
|
// of spaces away on re-import.
|
|
3372
3524
|
`xml:space="preserve"`,
|
|
@@ -3381,9 +3533,10 @@ function textXml(node, registry, namespaces, warn) {
|
|
|
3381
3533
|
if (style?.fontVariantCaps === "small-caps") attrs.push('font-variant="small-caps"');
|
|
3382
3534
|
const direction = svgDirection(style, node.text);
|
|
3383
3535
|
if (direction === "rtl" || style?.direction === "ltr") attrs.push(`direction="${direction}"`);
|
|
3536
|
+
let anchor = "start";
|
|
3384
3537
|
if (style?.align != null || direction === "rtl") {
|
|
3385
3538
|
const align = style?.align ?? "left";
|
|
3386
|
-
|
|
3539
|
+
anchor = align === "center" ? "middle" : align === "start" || align === "justify" ? "start" : align === "end" ? "end" : align === "left" === (direction === "ltr") ? "start" : "end";
|
|
3387
3540
|
if (anchor !== "start") attrs.push(`text-anchor="${anchor}"`);
|
|
3388
3541
|
if (align === "justify") attrs.push('data-weasel-align="justify"');
|
|
3389
3542
|
}
|
|
@@ -3398,13 +3551,14 @@ function textXml(node, registry, namespaces, warn) {
|
|
|
3398
3551
|
}
|
|
3399
3552
|
if (style?.wrap) {
|
|
3400
3553
|
attrs.push('data-weasel-wrap="true"');
|
|
3401
|
-
warn("text that wraps at its box width is written unwrapped: SVG text does not wrap, so other readers draw one line per hard break (weasel reads the wrap back)");
|
|
3554
|
+
if (!layout) warn("text that wraps at its box width is written unwrapped: SVG text does not wrap, so other readers draw one line per hard break (weasel reads the wrap back)");
|
|
3402
3555
|
}
|
|
3403
3556
|
if (style?.tabSize != null) attrs.push(`data-weasel-tab-size="${trimNumber(style.tabSize)}"`);
|
|
3404
3557
|
if (node.verticalAlign && node.verticalAlign !== "top") {
|
|
3405
3558
|
attrs.push(`data-weasel-vertical-align="${node.verticalAlign}"`);
|
|
3406
|
-
warn(`text aligned to the ${node.verticalAlign} of its box is written top-aligned: SVG text has no box, so other readers draw it at the top (weasel reads the alignment back)`);
|
|
3559
|
+
if (!layout) warn(`text aligned to the ${node.verticalAlign} of its box is written top-aligned: SVG text has no box, so other readers draw it at the top (weasel reads the alignment back)`);
|
|
3407
3560
|
}
|
|
3561
|
+
if (layout && style?.lineHeight != null) attrs.push(`${LINE_HEIGHT_ATTR}="${trimNumber(style.lineHeight)}"`);
|
|
3408
3562
|
if (node.fill !== void 0) {
|
|
3409
3563
|
const paint = node.fill?.paint;
|
|
3410
3564
|
if (!paint) attrs.push('fill="none"');
|
|
@@ -3421,7 +3575,7 @@ function textXml(node, registry, namespaces, warn) {
|
|
|
3421
3575
|
if (frame) attrs.push(frame);
|
|
3422
3576
|
const pieces = node.runs && node.runs.length > 0 ? node.runs.map((r) => ({ text: r.text, attrs: runAttrs(r, style, registry, warn) })) : [{ text: node.text, attrs: null }];
|
|
3423
3577
|
const resolved = resolveTextStyle(style);
|
|
3424
|
-
const body = linesXml(pieces, x, resolved.fontSize * resolved.lineHeight);
|
|
3578
|
+
const body = layout ? laidLinesXml(pieces, layout, { anchor, rtl: direction === "rtl" }) : linesXml(pieces, x, resolved.fontSize * resolved.lineHeight);
|
|
3425
3579
|
const metaAttrs = metaAttrsXml(node.meta, namespaces);
|
|
3426
3580
|
const metaEls = metaElementsXml(node.meta, namespaces);
|
|
3427
3581
|
return `<text ${attrs.join(" ")}${metaAttrs}>${body}${metaEls}</text>`;
|
|
@@ -3518,7 +3672,7 @@ function serializeSvg(nodes, opts = {}) {
|
|
|
3518
3672
|
const vb = `${trimNumber(bounds.x)} ${trimNumber(bounds.y)} ${trimNumber(bounds.width)} ${trimNumber(bounds.height)}`;
|
|
3519
3673
|
const namespaces = opts.namespaces ?? {};
|
|
3520
3674
|
const rootAttrs = [`xmlns="http://www.w3.org/2000/svg"`];
|
|
3521
|
-
if (registry.usesPrivateNamespace() && namespaces[WEASEL_NS_PREFIX] == null) {
|
|
3675
|
+
if ((registry.usesPrivateNamespace() || writesVariableWidth(nodes)) && namespaces[WEASEL_NS_PREFIX] == null) {
|
|
3522
3676
|
rootAttrs.push(`xmlns:${WEASEL_NS_PREFIX}="${WEASEL_NS}"`);
|
|
3523
3677
|
}
|
|
3524
3678
|
for (const [prefix, uri] of Object.entries(namespaces)) {
|
|
@@ -3544,7 +3698,7 @@ function serializeSvg(nodes, opts = {}) {
|
|
|
3544
3698
|
said.add(m);
|
|
3545
3699
|
opts.onWarn(m);
|
|
3546
3700
|
};
|
|
3547
|
-
const bodyXml = nodes.map((n) => nodeXml(n, registry, namespaces, warn)).join("");
|
|
3701
|
+
const bodyXml = nodes.map((n) => nodeXml(n, registry, namespaces, warn, opts.textLayout)).join("");
|
|
3548
3702
|
const titleXml = opts.title && opts.title.length > 0 ? `<title>${escapeText(opts.title)}</title>` : "";
|
|
3549
3703
|
const xml = `<svg ${rootAttrs.join(" ")}>${titleXml}${defsXml}${docMetaXml}${bodyXml}</svg>`;
|
|
3550
3704
|
return opts.pretty ? prettyXml(xml) : xml;
|
|
@@ -3578,16 +3732,17 @@ function registerMarkers(stroke, registry) {
|
|
|
3578
3732
|
if (ref !== void 0) registry.markerId(ref);
|
|
3579
3733
|
}
|
|
3580
3734
|
}
|
|
3581
|
-
function nodeXml(node, registry, namespaces, warn) {
|
|
3582
|
-
const xml = kindXml(node, registry, namespaces, warn);
|
|
3583
|
-
|
|
3735
|
+
function nodeXml(node, registry, namespaces, warn, layoutOf) {
|
|
3736
|
+
const xml = kindXml(node, registry, namespaces, warn, layoutOf);
|
|
3737
|
+
if (!node.hidden) return xml;
|
|
3738
|
+
return xml.replace(/^<[\w:]+/, (open) => `${open} display="none"`).replace(/<path (?=[^>]*:role="stroke-outline")/, '<path display="none" ');
|
|
3584
3739
|
}
|
|
3585
|
-
function kindXml(node, registry, namespaces, warn) {
|
|
3586
|
-
if (node.kind === "group") return groupXml(node, registry, namespaces, warn);
|
|
3587
|
-
if (node.kind === "text") return textXml(node, registry, namespaces, warn);
|
|
3740
|
+
function kindXml(node, registry, namespaces, warn, layoutOf) {
|
|
3741
|
+
if (node.kind === "group") return groupXml(node, registry, namespaces, warn, layoutOf);
|
|
3742
|
+
if (node.kind === "text") return textXml(node, registry, namespaces, warn, layoutOf?.(node));
|
|
3588
3743
|
if (node.kind === "image") return imageXml(node, namespaces);
|
|
3589
3744
|
const drawn = pathAsSvgDraws(node);
|
|
3590
|
-
return drawn.kind === "group" ? groupXml(drawn, registry, namespaces, warn) : pathXml(drawn, registry, namespaces, warn);
|
|
3745
|
+
return drawn.kind === "group" ? groupXml(drawn, registry, namespaces, warn, layoutOf) : pathXml(drawn, registry, namespaces, warn);
|
|
3591
3746
|
}
|
|
3592
3747
|
function imageXml(node, namespaces) {
|
|
3593
3748
|
const box = `x="${trimNumber(node.x)}" y="${trimNumber(node.y)}" width="${trimNumber(node.width)}" height="${trimNumber(node.height)}"`;
|
|
@@ -3611,7 +3766,7 @@ function imageXml(node, namespaces) {
|
|
|
3611
3766
|
const head = ['<g data-weasel-image="true"', ...outer].join(" ");
|
|
3612
3767
|
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>`;
|
|
3613
3768
|
}
|
|
3614
|
-
function groupXml(node, registry, namespaces, warn) {
|
|
3769
|
+
function groupXml(node, registry, namespaces, warn, layoutOf) {
|
|
3615
3770
|
const attrs = [];
|
|
3616
3771
|
if (node.transform) {
|
|
3617
3772
|
const m = formatMatrix(node.transform);
|
|
@@ -3625,50 +3780,56 @@ function groupXml(node, registry, namespaces, warn) {
|
|
|
3625
3780
|
}
|
|
3626
3781
|
const metaAttrs = metaAttrsXml(node.meta, namespaces);
|
|
3627
3782
|
const head = attrs.length > 0 ? `<g ${attrs.join(" ")}${metaAttrs}>` : `<g${metaAttrs}>`;
|
|
3628
|
-
const body = node.children.map((c) => nodeXml(c, registry, namespaces, warn)).join("");
|
|
3783
|
+
const body = node.children.map((c) => nodeXml(c, registry, namespaces, warn, layoutOf)).join("");
|
|
3629
3784
|
return `${head}${body}${metaElementsXml(node.meta, namespaces)}</g>`;
|
|
3630
3785
|
}
|
|
3631
3786
|
function pathXml(node, registry, namespaces, warn) {
|
|
3632
|
-
const attrs = [`d="${
|
|
3787
|
+
const attrs = [`d="${pathToD(node.path)}"`];
|
|
3633
3788
|
const fillAttrs = paintAttrs(node.fill, "fill", registry);
|
|
3634
3789
|
for (const a of fillAttrs) attrs.push(a);
|
|
3635
3790
|
if (node.path.kind === "polygon" && node.path.fillRule === "evenodd") {
|
|
3636
3791
|
attrs.push(`fill-rule="evenodd"`);
|
|
3637
3792
|
}
|
|
3793
|
+
const varies = hasVariableWidth(node.stroke);
|
|
3638
3794
|
if (node.stroke) {
|
|
3639
|
-
const strokeAttrs = strokeAttrsFor(node.stroke, registry,
|
|
3640
|
-
|
|
3795
|
+
const strokeAttrs = strokeAttrsFor(node.stroke, registry, varies ? () => {
|
|
3796
|
+
} : warn);
|
|
3797
|
+
for (const a of varies ? variableWidthAttrs(node.stroke, strokeAttrs) : strokeAttrs) attrs.push(a);
|
|
3641
3798
|
} else {
|
|
3642
3799
|
attrs.push('stroke="none"');
|
|
3643
3800
|
}
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
if (
|
|
3648
|
-
const b = pathBounds(node.path);
|
|
3649
|
-
const cx = b.minX + (b.maxX - b.minX) / 2;
|
|
3650
|
-
const cy = b.minY + (b.maxY - b.minY) / 2;
|
|
3651
|
-
const deg = node.rotation * 180 / Math.PI;
|
|
3652
|
-
attrs.push(`transform="rotate(${trimNumber(deg)} ${trimNumber(cx)} ${trimNumber(cy)})"`);
|
|
3653
|
-
}
|
|
3801
|
+
const opacity = node.opacity != null && node.opacity !== 1 ? `opacity="${trimNumber(node.opacity)}"` : null;
|
|
3802
|
+
if (opacity && !varies) attrs.push(opacity);
|
|
3803
|
+
const transform = rotationAttr(node);
|
|
3804
|
+
if (transform) attrs.push(transform);
|
|
3654
3805
|
const metaAttrs = metaAttrsXml(node.meta, namespaces);
|
|
3655
3806
|
const metaEls = metaElementsXml(node.meta, namespaces);
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
return
|
|
3807
|
+
const xml = metaEls ? `<path ${attrs.join(" ")}${metaAttrs}>${metaEls}</path>` : `<path ${attrs.join(" ")}${metaAttrs}/>`;
|
|
3808
|
+
if (!varies) return xml;
|
|
3809
|
+
const both = xml + strokeOutlineXml(node.path, node.stroke, strokePaintAsFillAttrs(node.stroke, registry), transform);
|
|
3810
|
+
return opacity ? strokeGroupXml(opacity, both) : both;
|
|
3811
|
+
}
|
|
3812
|
+
function rotationAttr(node) {
|
|
3813
|
+
if (node.rotation == null || node.rotation === 0) return null;
|
|
3814
|
+
const b = pathBounds(node.path);
|
|
3815
|
+
const cx = b.minX + (b.maxX - b.minX) / 2;
|
|
3816
|
+
const cy = b.minY + (b.maxY - b.minY) / 2;
|
|
3817
|
+
const deg = node.rotation * 180 / Math.PI;
|
|
3818
|
+
return `transform="rotate(${trimNumber(deg)} ${trimNumber(cx)} ${trimNumber(cy)})"`;
|
|
3660
3819
|
}
|
|
3661
|
-
function svgNeeds(nodes) {
|
|
3820
|
+
function svgNeeds(nodes, opts = {}) {
|
|
3662
3821
|
const registry = new PaintServerRegistry();
|
|
3663
3822
|
registerPaintServers(nodes, registry);
|
|
3664
3823
|
const fonts = /* @__PURE__ */ new Map();
|
|
3824
|
+
const add = (face) => fonts.set(`${face.family}|${face.weight}|${face.style}`, face);
|
|
3665
3825
|
const walk = (list) => {
|
|
3666
3826
|
for (const n of list) {
|
|
3667
3827
|
if (n.kind === "group") walk(n.children);
|
|
3668
3828
|
else if (n.kind === "text") {
|
|
3829
|
+
if (opts.textLayout) layoutFaces(n).forEach(add);
|
|
3669
3830
|
for (const run of n.runs ?? []) {
|
|
3670
3831
|
const face = scriptSizeFace(run, n.style);
|
|
3671
|
-
if (face)
|
|
3832
|
+
if (face) add(face);
|
|
3672
3833
|
}
|
|
3673
3834
|
}
|
|
3674
3835
|
}
|
|
@@ -3676,8 +3837,15 @@ function svgNeeds(nodes) {
|
|
|
3676
3837
|
walk(nodes);
|
|
3677
3838
|
return { fonts: [...fonts.values()], paintKinds: registry.paintKinds() };
|
|
3678
3839
|
}
|
|
3679
|
-
function
|
|
3680
|
-
const
|
|
3840
|
+
function layoutFaces(node) {
|
|
3841
|
+
const style = resolveTextStyle(node.style);
|
|
3842
|
+
return toRuns(node.runs && node.runs.length > 0 ? node.runs : node.text).map((run) => {
|
|
3843
|
+
const face = resolveRunFace(run, style);
|
|
3844
|
+
return { family: face.fontFamily, weight: face.fontWeight, style: face.fontStyle };
|
|
3845
|
+
});
|
|
3846
|
+
}
|
|
3847
|
+
function warmSvg(nodes, opts = {}) {
|
|
3848
|
+
const needs = svgNeeds(nodes, opts);
|
|
3681
3849
|
return Promise.all([
|
|
3682
3850
|
warmFonts(needs.fonts),
|
|
3683
3851
|
warmPaintKinds(needs.paintKinds.filter(isPaintKindKnown))
|
|
@@ -3730,7 +3898,9 @@ function strokeDataFromSvg(stroke, box) {
|
|
|
3730
3898
|
...stroke.miterLimit !== void 0 ? { miterLimit: stroke.miterLimit } : {},
|
|
3731
3899
|
...stroke.markerStart !== void 0 ? { markerStart: stroke.markerStart } : {},
|
|
3732
3900
|
...stroke.markerMid !== void 0 ? { markerMid: stroke.markerMid } : {},
|
|
3733
|
-
...stroke.markerEnd !== void 0 ? { markerEnd: stroke.markerEnd } : {}
|
|
3901
|
+
...stroke.markerEnd !== void 0 ? { markerEnd: stroke.markerEnd } : {},
|
|
3902
|
+
...stroke.vertexWidths !== void 0 ? { vertexWidths: stroke.vertexWidths } : {},
|
|
3903
|
+
...stroke.nib !== void 0 ? { nib: stroke.nib } : {}
|
|
3734
3904
|
};
|
|
3735
3905
|
}
|
|
3736
3906
|
function fillInBoxFrame(fill, box) {
|
|
@@ -3773,9 +3943,12 @@ function svgNodesToKitDrafts(input, nextId, options = {}) {
|
|
|
3773
3943
|
...n.hidden ? { hidden: true } : {}
|
|
3774
3944
|
};
|
|
3775
3945
|
drafts.push(draft);
|
|
3946
|
+
const at = drafts.length - 1;
|
|
3776
3947
|
const acc = unionAABB(n.children.map((c) => visit(c, draft.id, k)));
|
|
3777
3948
|
if (!acc) {
|
|
3778
|
-
drafts.
|
|
3949
|
+
const kept = drafts.length > at + 1 || (options.keepEmpty?.(n) ?? false);
|
|
3950
|
+
if (!kept) drafts.splice(at, 1);
|
|
3951
|
+
else if (n.clip) draft.pose = boxOfPath(n.clip);
|
|
3779
3952
|
return null;
|
|
3780
3953
|
}
|
|
3781
3954
|
draft.pose = n.clip ? boxOfPath(n.clip) : acc;
|
|
@@ -3859,7 +4032,7 @@ async function unpackSvgFiles(files, ctx) {
|
|
|
3859
4032
|
for (const file of files) {
|
|
3860
4033
|
try {
|
|
3861
4034
|
const text = await readFileText(file);
|
|
3862
|
-
const parsed = parseSvg(text);
|
|
4035
|
+
const parsed = parseSvg(text, { keepHidden: true });
|
|
3863
4036
|
for (const w of parsed.warnings) dwarn("ingest", `svg "${file.name}":`, w);
|
|
3864
4037
|
let drafts = svgNodesToKitDrafts(parsed, () => ctx.scene.mintId());
|
|
3865
4038
|
if (drafts.length === 0) {
|
|
@@ -3904,7 +4077,8 @@ async function unpackSvgFiles(files, ctx) {
|
|
|
3904
4077
|
layer,
|
|
3905
4078
|
pose: place(d.pose),
|
|
3906
4079
|
data: d.kind === "leaf" ? scaleLeafData(d.data, scale) : d.data,
|
|
3907
|
-
parent: d.parentId
|
|
4080
|
+
parent: d.parentId,
|
|
4081
|
+
...d.hidden ? { hidden: true } : {}
|
|
3908
4082
|
},
|
|
3909
4083
|
label: "Insert SVG"
|
|
3910
4084
|
})
|
|
@@ -3939,7 +4113,9 @@ function svgStrokeFromKit(stroke, box) {
|
|
|
3939
4113
|
...stroke.align !== void 0 ? { align: stroke.align } : {},
|
|
3940
4114
|
...stroke.markerStart !== void 0 ? { markerStart: stroke.markerStart } : {},
|
|
3941
4115
|
...stroke.markerMid !== void 0 ? { markerMid: stroke.markerMid } : {},
|
|
3942
|
-
...stroke.markerEnd !== void 0 ? { markerEnd: stroke.markerEnd } : {}
|
|
4116
|
+
...stroke.markerEnd !== void 0 ? { markerEnd: stroke.markerEnd } : {},
|
|
4117
|
+
...stroke.vertexWidths?.length ? { vertexWidths: stroke.vertexWidths } : {},
|
|
4118
|
+
...stroke.nib ? { nib: stroke.nib } : {}
|
|
3943
4119
|
};
|
|
3944
4120
|
}
|
|
3945
4121
|
function svgImageFromKit(image, pose) {
|
|
@@ -3973,22 +4149,12 @@ function runInPoseFrame(run, box) {
|
|
|
3973
4149
|
function svgLeafFromKit(data, pose) {
|
|
3974
4150
|
if (data.image) return svgImageFromKit(data.image, pose);
|
|
3975
4151
|
if (data.text != null) {
|
|
3976
|
-
const
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
text: data.text
|
|
3983
|
-
};
|
|
3984
|
-
if (data.style) node2.style = data.style;
|
|
3985
|
-
if (data.runs) node2.runs = data.runs.map((r) => runInPoseFrame(r, pose));
|
|
3986
|
-
if (data.verticalAlign) node2.verticalAlign = data.verticalAlign;
|
|
3987
|
-
if (data.fill !== void 0) node2.fill = data.fill && mapFillPaint(data.fill, (p) => fillInPoseFrame(p, pose));
|
|
3988
|
-
if (data.stroke) node2.stroke = strokeInPoseFrame(data.stroke, pose);
|
|
3989
|
-
if (pose.rotation) node2.rotation = pose.rotation;
|
|
3990
|
-
if (pose.transform) node2.transform = pose.transform;
|
|
3991
|
-
return node2;
|
|
4152
|
+
const text = svgTextFromKit({ ...data, text: data.text }, pose);
|
|
4153
|
+
const boxPath = { kind: "rect", x: pose.x, y: pose.y, width: pose.width, height: pose.height };
|
|
4154
|
+
const clip = data.overflow === "clip" ? { clip: pathThroughPoseFrame(boxPath, pose) } : {};
|
|
4155
|
+
if (!data.background?.paint) return clip.clip ? { kind: "group", children: [text], ...clip } : text;
|
|
4156
|
+
const box = svgLeafFromKit({ path: boxPath, fill: data.background }, pose);
|
|
4157
|
+
return { kind: "group", children: [box, text], ...clip };
|
|
3992
4158
|
}
|
|
3993
4159
|
if (!data.path) return null;
|
|
3994
4160
|
const stroke = svgStrokeFromKit(data.stroke, pose);
|
|
@@ -4007,12 +4173,33 @@ function svgLeafFromKit(data, pose) {
|
|
|
4007
4173
|
} else if (pose.rotation) node.rotation = pose.rotation;
|
|
4008
4174
|
return node;
|
|
4009
4175
|
}
|
|
4176
|
+
function svgTextFromKit(data, pose) {
|
|
4177
|
+
const pad = Math.max(0, Math.min(data.padding ?? 0, pose.width / 2, pose.height / 2));
|
|
4178
|
+
const node = {
|
|
4179
|
+
kind: "text",
|
|
4180
|
+
x: pose.x + pad,
|
|
4181
|
+
y: pose.y + pad,
|
|
4182
|
+
width: pose.width - 2 * pad,
|
|
4183
|
+
height: pose.height - 2 * pad,
|
|
4184
|
+
text: data.text
|
|
4185
|
+
};
|
|
4186
|
+
if (data.style) node.style = data.style;
|
|
4187
|
+
if (data.runs) node.runs = data.runs.map((r) => runInPoseFrame(r, pose));
|
|
4188
|
+
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);
|
|
4191
|
+
if (pose.rotation) node.rotation = pose.rotation;
|
|
4192
|
+
if (pose.transform) node.transform = pose.transform;
|
|
4193
|
+
return node;
|
|
4194
|
+
}
|
|
4010
4195
|
function svgNodesFromKit(tree, options = {}) {
|
|
4011
4196
|
const { include, leaf, group } = options;
|
|
4012
4197
|
const emit = (id) => {
|
|
4013
|
-
if (include && !include(id)) return null;
|
|
4014
4198
|
const node = tree.get(id);
|
|
4015
|
-
|
|
4199
|
+
const out2 = node && (!include || include(id)) ? lower(id, node) : null;
|
|
4200
|
+
return out2 && node?.hidden === true ? { ...out2, hidden: true } : out2;
|
|
4201
|
+
};
|
|
4202
|
+
const lower = (id, node) => {
|
|
4016
4203
|
if (node.kind === "container") {
|
|
4017
4204
|
const emitChildren = () => {
|
|
4018
4205
|
const children = [];
|
|
@@ -4045,6 +4232,6 @@ function svgNodesFromKit(tree, options = {}) {
|
|
|
4045
4232
|
return out;
|
|
4046
4233
|
}
|
|
4047
4234
|
|
|
4048
|
-
export { DEFAULT_MEDIA_ENVIRONMENT, IDENTITY_MATRIX, evaluateMediaQuery, evaluateSupports, fillDataFromSvg, nativeSvgKind, nativeSvgSpace, parseSvg, serializeSvg, strokeDataFromSvg, svgImageFromKit, svgLeafFromKit, svgNeeds, svgNodesFromKit, svgNodesToKitDrafts, svgPaintFromKit, svgStrokeFromKit, tilePreviewCssUrl, tilePreviewSvg, unpackSvgFiles, warmSvg };
|
|
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 };
|
|
4049
4236
|
//# sourceMappingURL=index.js.map
|
|
4050
4237
|
//# sourceMappingURL=index.js.map
|