@weasel-js/svg 1.9.4 → 1.10.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 +11 -9
- package/dist/index.d.ts +139 -147
- package/dist/index.js +738 -424
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { identity, multiply, invert } from '@weasel-js/geom';
|
|
2
|
-
import { boundsOfPath, transformPath,
|
|
1
|
+
import { identity, multiply, invert, splitByClosure, fillsOpenSubpaths, rotateAboutPoint, hasOpenSubpath, splitSubpaths } from '@weasel-js/geom';
|
|
2
|
+
import { isTileKind, boundsOfPath, transformPath, isSolidPaint, warmFonts, warmPaintKinds, isPaintKindKnown, solid, fillToBoundsFrame, getMarker, registerMarker, dwarn, unionAABB, createInsertOp, fillInPoseFrame, DEFAULT_SHAPE_FILL, pathInPoseFrame, poseLinearOf, poseFrameOf, pathThroughPoseFrame, DEFAULT_TEXT_STYLE, resolveScreenLength, pathFromD, isPatternPaint, resolveTextStyle, PATH_M, PATH_L, PATH_C, PATH_Q, PATH_Z, resolveRunFace, parseColor, rgbaToHex, layoutRuns, resolveRuns, resolveAlign, PathBuilder, getPaintKind, resolveMarkerSize, paragraphDirection, scriptMetricsFor, isTilePaint, tileSpecOf } from '@weasel-js/core';
|
|
3
3
|
import { tileGeometry } from '@weasel-js/core/patterns-builtin';
|
|
4
4
|
|
|
5
5
|
// src/types.ts
|
|
@@ -210,13 +210,13 @@ function isNonUniform(m) {
|
|
|
210
210
|
return Math.abs(a * c + b * d) > 1e-4 * n * n || Math.abs(Math.hypot(a, b) - Math.hypot(c, d)) > 1e-4 * n;
|
|
211
211
|
}
|
|
212
212
|
function fillThroughMatrix(fill, m) {
|
|
213
|
-
if (!("units" in fill) || fill.units !== "world") return fill;
|
|
213
|
+
if (!("units" in fill) || fill.units !== "world" && fill.units !== "local") return fill;
|
|
214
214
|
const at = (p) => applyMatrix(m, p.x, p.y);
|
|
215
|
-
if (fill.
|
|
216
|
-
if (fill.
|
|
215
|
+
if (fill.kind === "linear-gradient") return { ...fill, from: at(fill.from), to: at(fill.to) };
|
|
216
|
+
if (fill.kind === "radial-gradient") {
|
|
217
217
|
return { ...fill, center: at(fill.center), radius: fill.radius * uniformScaleOf(m) };
|
|
218
218
|
}
|
|
219
|
-
if (fill.
|
|
219
|
+
if (fill.kind === "conic-gradient") return { ...fill, center: at(fill.center) };
|
|
220
220
|
return fill;
|
|
221
221
|
}
|
|
222
222
|
function paintThroughMatrix(paint, m) {
|
|
@@ -635,6 +635,106 @@ function evaluateMediaQuery(list, env) {
|
|
|
635
635
|
}
|
|
636
636
|
});
|
|
637
637
|
}
|
|
638
|
+
|
|
639
|
+
// src/container.ts
|
|
640
|
+
function queryContainerOf(declared, media) {
|
|
641
|
+
const [shortNames, shortType] = (declared("container") ?? "").split("/").map((part) => part.trim());
|
|
642
|
+
const type = (declared("container-type") ?? shortType ?? "").trim().toLowerCase();
|
|
643
|
+
if (type !== "size" && type !== "inline-size") return null;
|
|
644
|
+
const names = (declared("container-name") ?? shortNames ?? "").trim().split(/\s+/).filter((n) => n !== "" && n.toLowerCase() !== "none");
|
|
645
|
+
return { names, type, width: media.width, height: media.height };
|
|
646
|
+
}
|
|
647
|
+
var NAME = /^(-?[a-zA-Z_\u0080-][\w\u0080--]*)(?![\w(-])\s*/;
|
|
648
|
+
var BLOCK_AXIS = /\b(?:height|block-size|aspect-ratio|orientation)\b/;
|
|
649
|
+
var NOT_A_SIZE_FEATURE = /\b(?:device-|resolution|color|monochrome|prefers-|update|hover|pointer)/;
|
|
650
|
+
function containerQueryHolds(prelude, container) {
|
|
651
|
+
if (!container) return false;
|
|
652
|
+
let condition2 = prelude.trim();
|
|
653
|
+
const named = NAME.exec(condition2);
|
|
654
|
+
if (named && named[1].toLowerCase() !== "not") {
|
|
655
|
+
if (!container.names.includes(named[1])) return false;
|
|
656
|
+
condition2 = condition2.slice(named[0].length);
|
|
657
|
+
}
|
|
658
|
+
condition2 = condition2.toLowerCase();
|
|
659
|
+
if (NOT_A_SIZE_FEATURE.test(condition2)) return false;
|
|
660
|
+
if (container.type === "inline-size" && BLOCK_AXIS.test(condition2)) return false;
|
|
661
|
+
const physical = condition2.replace(/\binline-size\b/g, "width").replace(/\bblock-size\b/g, "height");
|
|
662
|
+
return evaluateMediaQuery(physical, {
|
|
663
|
+
...DEFAULT_MEDIA_ENVIRONMENT,
|
|
664
|
+
width: container.width,
|
|
665
|
+
height: container.height
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// src/specificity.ts
|
|
670
|
+
function identEnd(s, i) {
|
|
671
|
+
while (i < s.length) {
|
|
672
|
+
const ch = s[i];
|
|
673
|
+
if (ch === "\\") i += 2;
|
|
674
|
+
else if (/[\w-]/.test(ch) || ch.charCodeAt(0) > 127) i++;
|
|
675
|
+
else break;
|
|
676
|
+
}
|
|
677
|
+
return i;
|
|
678
|
+
}
|
|
679
|
+
var LEGACY_PSEUDO_ELEMENTS = /* @__PURE__ */ new Set(["before", "after", "first-line", "first-letter"]);
|
|
680
|
+
var MAX_ARG_PSEUDOS = /* @__PURE__ */ new Set(["not", "is", "matches", "-webkit-any", "has"]);
|
|
681
|
+
function compareSpecificity(a, b) {
|
|
682
|
+
return a[0] - b[0] || a[1] - b[1] || a[2] - b[2];
|
|
683
|
+
}
|
|
684
|
+
function maxSpecificity(list) {
|
|
685
|
+
let best = [0, 0, 0];
|
|
686
|
+
for (const sel of splitTopLevel(list, ",")) {
|
|
687
|
+
const s = specificity(sel);
|
|
688
|
+
if (compareSpecificity(s, best) > 0) best = s;
|
|
689
|
+
}
|
|
690
|
+
return best;
|
|
691
|
+
}
|
|
692
|
+
function specificity(selector) {
|
|
693
|
+
let a = 0, b = 0, c = 0;
|
|
694
|
+
const s = selector;
|
|
695
|
+
let i = 0;
|
|
696
|
+
while (i < s.length) {
|
|
697
|
+
const ch = s[i];
|
|
698
|
+
if (ch === "#") {
|
|
699
|
+
a++;
|
|
700
|
+
i = identEnd(s, i + 1);
|
|
701
|
+
} else if (ch === ".") {
|
|
702
|
+
b++;
|
|
703
|
+
i = identEnd(s, i + 1);
|
|
704
|
+
} else if (ch === "[") {
|
|
705
|
+
b++;
|
|
706
|
+
while (i < s.length && s[i] !== "]") {
|
|
707
|
+
i = s[i] === '"' || s[i] === "'" ? skipString(s, i) : i + 1;
|
|
708
|
+
}
|
|
709
|
+
i++;
|
|
710
|
+
} else if (ch === ":") {
|
|
711
|
+
const element = s[i + 1] === ":";
|
|
712
|
+
const nameStart = i + (element ? 2 : 1);
|
|
713
|
+
const nameEnd = identEnd(s, nameStart);
|
|
714
|
+
const name = s.slice(nameStart, nameEnd).toLowerCase();
|
|
715
|
+
let arg = null;
|
|
716
|
+
i = nameEnd;
|
|
717
|
+
if (s[i] === "(") {
|
|
718
|
+
const end = parenEnd(s, i);
|
|
719
|
+
arg = s.slice(i + 1, end - 1);
|
|
720
|
+
i = end;
|
|
721
|
+
}
|
|
722
|
+
if (element || LEGACY_PSEUDO_ELEMENTS.has(name)) c++;
|
|
723
|
+
else if (name === "where") ; else if (MAX_ARG_PSEUDOS.has(name) && arg != null) {
|
|
724
|
+
const m = maxSpecificity(arg);
|
|
725
|
+
a += m[0];
|
|
726
|
+
b += m[1];
|
|
727
|
+
c += m[2];
|
|
728
|
+
} else b++;
|
|
729
|
+
} else if (/[a-zA-Z_\\-]/.test(ch) || ch.charCodeAt(0) > 127) {
|
|
730
|
+
c++;
|
|
731
|
+
i = identEnd(s, i);
|
|
732
|
+
} else {
|
|
733
|
+
i++;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
return [a, b, c];
|
|
737
|
+
}
|
|
638
738
|
function parsePaintAttr(raw) {
|
|
639
739
|
if (raw == null) return null;
|
|
640
740
|
const s = raw.trim();
|
|
@@ -888,74 +988,6 @@ function evaluateSupports(condition2, opts = {}) {
|
|
|
888
988
|
|
|
889
989
|
// src/cascade.ts
|
|
890
990
|
var EMPTY_STYLE = {};
|
|
891
|
-
function identEnd(s, i) {
|
|
892
|
-
while (i < s.length) {
|
|
893
|
-
const ch = s[i];
|
|
894
|
-
if (ch === "\\") i += 2;
|
|
895
|
-
else if (/[\w-]/.test(ch) || ch.charCodeAt(0) > 127) i++;
|
|
896
|
-
else break;
|
|
897
|
-
}
|
|
898
|
-
return i;
|
|
899
|
-
}
|
|
900
|
-
var LEGACY_PSEUDO_ELEMENTS = /* @__PURE__ */ new Set(["before", "after", "first-line", "first-letter"]);
|
|
901
|
-
var MAX_ARG_PSEUDOS = /* @__PURE__ */ new Set(["not", "is", "matches", "-webkit-any", "has"]);
|
|
902
|
-
function compareSpecificity(a, b) {
|
|
903
|
-
return a[0] - b[0] || a[1] - b[1] || a[2] - b[2];
|
|
904
|
-
}
|
|
905
|
-
function maxSpecificity(list) {
|
|
906
|
-
let best = [0, 0, 0];
|
|
907
|
-
for (const sel of splitTopLevel(list, ",")) {
|
|
908
|
-
const s = specificity(sel);
|
|
909
|
-
if (compareSpecificity(s, best) > 0) best = s;
|
|
910
|
-
}
|
|
911
|
-
return best;
|
|
912
|
-
}
|
|
913
|
-
function specificity(selector) {
|
|
914
|
-
let a = 0, b = 0, c = 0;
|
|
915
|
-
const s = selector;
|
|
916
|
-
let i = 0;
|
|
917
|
-
while (i < s.length) {
|
|
918
|
-
const ch = s[i];
|
|
919
|
-
if (ch === "#") {
|
|
920
|
-
a++;
|
|
921
|
-
i = identEnd(s, i + 1);
|
|
922
|
-
} else if (ch === ".") {
|
|
923
|
-
b++;
|
|
924
|
-
i = identEnd(s, i + 1);
|
|
925
|
-
} else if (ch === "[") {
|
|
926
|
-
b++;
|
|
927
|
-
while (i < s.length && s[i] !== "]") {
|
|
928
|
-
i = s[i] === '"' || s[i] === "'" ? skipString(s, i) : i + 1;
|
|
929
|
-
}
|
|
930
|
-
i++;
|
|
931
|
-
} else if (ch === ":") {
|
|
932
|
-
const element = s[i + 1] === ":";
|
|
933
|
-
const nameStart = i + (element ? 2 : 1);
|
|
934
|
-
const nameEnd = identEnd(s, nameStart);
|
|
935
|
-
const name = s.slice(nameStart, nameEnd).toLowerCase();
|
|
936
|
-
let arg = null;
|
|
937
|
-
i = nameEnd;
|
|
938
|
-
if (s[i] === "(") {
|
|
939
|
-
const end = parenEnd(s, i);
|
|
940
|
-
arg = s.slice(i + 1, end - 1);
|
|
941
|
-
i = end;
|
|
942
|
-
}
|
|
943
|
-
if (element || LEGACY_PSEUDO_ELEMENTS.has(name)) c++;
|
|
944
|
-
else if (name === "where") ; else if (MAX_ARG_PSEUDOS.has(name) && arg != null) {
|
|
945
|
-
const m = maxSpecificity(arg);
|
|
946
|
-
a += m[0];
|
|
947
|
-
b += m[1];
|
|
948
|
-
c += m[2];
|
|
949
|
-
} else b++;
|
|
950
|
-
} else if (/[a-zA-Z_\\-]/.test(ch) || ch.charCodeAt(0) > 127) {
|
|
951
|
-
c++;
|
|
952
|
-
i = identEnd(s, i);
|
|
953
|
-
} else {
|
|
954
|
-
i++;
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
return [a, b, c];
|
|
958
|
-
}
|
|
959
991
|
var CascadeLayers = class {
|
|
960
992
|
children = /* @__PURE__ */ new Map();
|
|
961
993
|
anonymous = 0;
|
|
@@ -1044,6 +1076,11 @@ function parseRuleList(src, from, to, layer, ctx, rules, state) {
|
|
|
1044
1076
|
if (names && names.length <= 1) {
|
|
1045
1077
|
const inner = names.length ? state.layers.declare(layer, names[0]) : state.layers.declareAnonymous(layer);
|
|
1046
1078
|
parseRuleList(src, i + 1, end - 1, inner, ctx, rules, state);
|
|
1079
|
+
} else if (name === "container") {
|
|
1080
|
+
const outer = state.containers;
|
|
1081
|
+
state.containers = [...outer ?? [], at[2]];
|
|
1082
|
+
parseRuleList(src, i + 1, end - 1, layer, ctx, rules, state);
|
|
1083
|
+
state.containers = outer;
|
|
1047
1084
|
} else if (conditionHolds(name, at[2], ctx)) {
|
|
1048
1085
|
parseRuleList(src, i + 1, end - 1, layer, ctx, rules, state);
|
|
1049
1086
|
}
|
|
@@ -1052,7 +1089,15 @@ function parseRuleList(src, from, to, layer, ctx, rules, state) {
|
|
|
1052
1089
|
for (const raw of splitTopLevel(prelude, ",")) {
|
|
1053
1090
|
const selector = raw.trim();
|
|
1054
1091
|
if (selector) {
|
|
1055
|
-
|
|
1092
|
+
const { containers } = state;
|
|
1093
|
+
rules.push({
|
|
1094
|
+
selector,
|
|
1095
|
+
declarations,
|
|
1096
|
+
specificity: specificity(selector),
|
|
1097
|
+
order: state.order++,
|
|
1098
|
+
layer,
|
|
1099
|
+
...containers ? { containers } : {}
|
|
1100
|
+
});
|
|
1056
1101
|
}
|
|
1057
1102
|
}
|
|
1058
1103
|
}
|
|
@@ -1072,15 +1117,23 @@ function appliesAsCss(style, ctx) {
|
|
|
1072
1117
|
}
|
|
1073
1118
|
var sheetCache = /* @__PURE__ */ new WeakMap();
|
|
1074
1119
|
function bindStylesheets(doc, media, onWarn) {
|
|
1075
|
-
sheetCache.set(doc, collectRules(doc, media, onWarn));
|
|
1120
|
+
sheetCache.set(doc, { rules: collectRules(doc, media, onWarn), media });
|
|
1076
1121
|
}
|
|
1077
|
-
function
|
|
1122
|
+
function documentSheet(doc) {
|
|
1078
1123
|
const cached = sheetCache.get(doc);
|
|
1079
1124
|
if (cached) return cached;
|
|
1080
1125
|
const root = doc.documentElement;
|
|
1081
|
-
const
|
|
1082
|
-
|
|
1083
|
-
|
|
1126
|
+
const media = root ? mediaEnvironmentFor(root) : DEFAULT_MEDIA_ENVIRONMENT;
|
|
1127
|
+
const sheet = { rules: collectRules(doc, media), media };
|
|
1128
|
+
sheetCache.set(doc, sheet);
|
|
1129
|
+
return sheet;
|
|
1130
|
+
}
|
|
1131
|
+
function containersHold(el, containers, media) {
|
|
1132
|
+
const root = el.ownerDocument?.documentElement;
|
|
1133
|
+
if (!root || el === root) return false;
|
|
1134
|
+
const declared = authorDeclarations(root);
|
|
1135
|
+
const container = queryContainerOf((prop) => declared.get(prop), media);
|
|
1136
|
+
return containers.every((prelude) => containerQueryHolds(prelude, container));
|
|
1084
1137
|
}
|
|
1085
1138
|
function collectRules(doc, media, onWarn) {
|
|
1086
1139
|
const ctx = { media, onWarn, selector: (s) => isValidSelector(doc, s) };
|
|
@@ -1118,8 +1171,8 @@ var elementCache = /* @__PURE__ */ new WeakMap();
|
|
|
1118
1171
|
function authorDeclarations(el) {
|
|
1119
1172
|
const cached = elementCache.get(el);
|
|
1120
1173
|
if (cached) return cached;
|
|
1121
|
-
const
|
|
1122
|
-
const matched = rules.filter((r) => matches(el, r.selector));
|
|
1174
|
+
const sheet = el.ownerDocument && documentSheet(el.ownerDocument);
|
|
1175
|
+
const matched = (sheet ? sheet.rules : []).filter((r) => matches(el, r.selector) && (!r.containers || containersHold(el, r.containers, sheet.media)));
|
|
1123
1176
|
const inline = parseDeclarations(el.getAttribute("style") ?? "");
|
|
1124
1177
|
const out = /* @__PURE__ */ new Map();
|
|
1125
1178
|
const importantOrder = [...matched].sort((x, y) => y.rank - x.rank || compareCascade(x, y));
|
|
@@ -1242,15 +1295,12 @@ function collectElementsByTag(root, tags) {
|
|
|
1242
1295
|
}
|
|
1243
1296
|
|
|
1244
1297
|
// src/patterns.ts
|
|
1245
|
-
function patternSpecOf(paint) {
|
|
1246
|
-
return "tile" in paint.pattern ? paint.pattern : null;
|
|
1247
|
-
}
|
|
1248
1298
|
function patternXml(id, paint, onWarn) {
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
onWarn?.("pattern fill carries a TextureHandle, which has no vector form \u2014 omitted from <defs>");
|
|
1299
|
+
if (!isTilePaint(paint)) {
|
|
1300
|
+
onWarn?.("texture fill carries a TextureHandle, which has no vector form \u2014 omitted from <defs>");
|
|
1252
1301
|
return "";
|
|
1253
1302
|
}
|
|
1303
|
+
const spec = tileSpecOf(paint);
|
|
1254
1304
|
const geometry = tileGeometry(spec);
|
|
1255
1305
|
const body = geometry.shapes.map(shapeXml).join("");
|
|
1256
1306
|
const origin = paint.origin ?? { x: 0, y: 0 };
|
|
@@ -1318,7 +1368,7 @@ function readPattern(el, onWarn) {
|
|
|
1318
1368
|
onWarn?.(`<pattern id="${el.getAttribute("id")}"> has malformed data-weasel-tile \u2014 dropped`);
|
|
1319
1369
|
return null;
|
|
1320
1370
|
}
|
|
1321
|
-
if (
|
|
1371
|
+
if (!isTileKind(spec?.kind) || typeof spec?.color !== "string") {
|
|
1322
1372
|
onWarn?.(`<pattern id="${el.getAttribute("id")}"> data-weasel-tile is not a tile spec \u2014 dropped`);
|
|
1323
1373
|
return null;
|
|
1324
1374
|
}
|
|
@@ -1326,7 +1376,7 @@ function readPattern(el, onWarn) {
|
|
|
1326
1376
|
const y = parseFloat(el.getAttribute("y") ?? "0");
|
|
1327
1377
|
const m = parseTransform(el.getAttribute("patternTransform"), onWarn);
|
|
1328
1378
|
const origin = applyMatrix(m, x, y);
|
|
1329
|
-
const paint = {
|
|
1379
|
+
const paint = { ...spec };
|
|
1330
1380
|
if (origin.x !== 0 || origin.y !== 0) paint.origin = origin;
|
|
1331
1381
|
if (!isIdentity([m[0], m[1], m[2], m[3], 0, 0])) paint.transform = [m[0], m[1], m[2], m[3]];
|
|
1332
1382
|
return paint;
|
|
@@ -1369,10 +1419,10 @@ function serializePathD(path) {
|
|
|
1369
1419
|
var WEASEL_NS = "urn:weasel-js:svg";
|
|
1370
1420
|
var WEASEL_NS_PREFIX = "wzl";
|
|
1371
1421
|
function nativeSvgKind(kind) {
|
|
1372
|
-
return kind === "solid" || kind === "linear-gradient" || kind === "radial-gradient" || kind === "
|
|
1422
|
+
return kind === "solid" || kind === "linear-gradient" || kind === "radial-gradient" || kind === "texture" || isTileKind(kind);
|
|
1373
1423
|
}
|
|
1374
1424
|
function hasNativeSvgForm(paint) {
|
|
1375
|
-
return nativeSvgKind(paint.
|
|
1425
|
+
return nativeSvgKind(paint.kind ?? "solid");
|
|
1376
1426
|
}
|
|
1377
1427
|
var INTERPOLATE_SPACES = /* @__PURE__ */ new Set(["rgb", "oklab", "oklch"]);
|
|
1378
1428
|
function nativeSvgSpace(space) {
|
|
@@ -1498,9 +1548,9 @@ function warnUnmodeled(el, elements, onWarn) {
|
|
|
1498
1548
|
break;
|
|
1499
1549
|
}
|
|
1500
1550
|
}
|
|
1501
|
-
const
|
|
1502
|
-
if (
|
|
1503
|
-
onWarn?.(`spreadMethod="${
|
|
1551
|
+
const spread2 = inheritedAttr(el, elements, "spreadMethod");
|
|
1552
|
+
if (spread2 === "reflect" || spread2 === "repeat") {
|
|
1553
|
+
onWarn?.(`spreadMethod="${spread2}" is not modeled; the gradient pads`);
|
|
1504
1554
|
}
|
|
1505
1555
|
}
|
|
1506
1556
|
function coord(el, elements, name, axis, fraction, units, onWarn) {
|
|
@@ -1520,7 +1570,7 @@ function readLinearGradient(el, elements, onWarn) {
|
|
|
1520
1570
|
const units = readGradientUnits(el, elements);
|
|
1521
1571
|
const at = (name, axis, fraction) => coord(el, elements, name, axis, fraction, units, onWarn);
|
|
1522
1572
|
return {
|
|
1523
|
-
|
|
1573
|
+
kind: "linear-gradient",
|
|
1524
1574
|
from: { x: at("x1", "x", 0), y: at("y1", "y", 0) },
|
|
1525
1575
|
to: { x: at("x2", "x", 1), y: at("y2", "y", 0) },
|
|
1526
1576
|
stops: inheritedStops(el, elements, onWarn),
|
|
@@ -1534,7 +1584,7 @@ function readRadialGradient(el, elements, onWarn) {
|
|
|
1534
1584
|
const units = readGradientUnits(el, elements);
|
|
1535
1585
|
const at = (name, axis, fraction) => coord(el, elements, name, axis, fraction, units, onWarn);
|
|
1536
1586
|
return {
|
|
1537
|
-
|
|
1587
|
+
kind: "radial-gradient",
|
|
1538
1588
|
center: { x: at("cx", "x", 0.5), y: at("cy", "y", 0.5) },
|
|
1539
1589
|
radius: at("r", "other", 0.5),
|
|
1540
1590
|
stops: inheritedStops(el, elements, onWarn),
|
|
@@ -1545,7 +1595,7 @@ function readRadialGradient(el, elements, onWarn) {
|
|
|
1545
1595
|
function readConicGradient(el, elements, onWarn) {
|
|
1546
1596
|
warnGradientTransform(el, elements, onWarn);
|
|
1547
1597
|
return {
|
|
1548
|
-
|
|
1598
|
+
kind: "conic-gradient",
|
|
1549
1599
|
center: {
|
|
1550
1600
|
x: num(inheritedAttr(el, elements, "cx"), 0.5),
|
|
1551
1601
|
y: num(inheritedAttr(el, elements, "cy"), 0.5)
|
|
@@ -1576,7 +1626,7 @@ function readMeshGradient(el, onWarn) {
|
|
|
1576
1626
|
if (patches.length === 0) return null;
|
|
1577
1627
|
const space = el.getAttribute("interpolate") ?? el.getAttributeNS(WEASEL_NS, "interpolate");
|
|
1578
1628
|
return {
|
|
1579
|
-
|
|
1629
|
+
kind: "mesh-gradient",
|
|
1580
1630
|
patches,
|
|
1581
1631
|
units: el.getAttribute("gradientUnits") === "objectBoundingBox" ? "bounds" : "world",
|
|
1582
1632
|
...space === "oklab" || space === "oklch" ? { interpolate: space } : {}
|
|
@@ -1600,7 +1650,7 @@ var PaintServerRegistry = class {
|
|
|
1600
1650
|
register(paint) {
|
|
1601
1651
|
const existing = this.byPaint.get(paint);
|
|
1602
1652
|
if (existing) return existing;
|
|
1603
|
-
const id = paint
|
|
1653
|
+
const id = isPatternPaint(paint) ? `pat${this.counter++}` : `grad${this.counter++}`;
|
|
1604
1654
|
this.byPaint.set(paint, id);
|
|
1605
1655
|
this.order.push(paint);
|
|
1606
1656
|
return id;
|
|
@@ -1619,7 +1669,7 @@ var PaintServerRegistry = class {
|
|
|
1619
1669
|
}
|
|
1620
1670
|
/** The paint kind of each registered paint, once, in first-use order. */
|
|
1621
1671
|
paintKinds() {
|
|
1622
|
-
return [...new Set(this.order.map((paint) => paint.
|
|
1672
|
+
return [...new Set(this.order.map((paint) => paint.kind ?? "solid"))];
|
|
1623
1673
|
}
|
|
1624
1674
|
/** Whether any registered paint serializes outside SVG's own vocabulary, so
|
|
1625
1675
|
* the root has to declare {@link WEASEL_NS}. */
|
|
@@ -1682,7 +1732,7 @@ function writesInset(entry) {
|
|
|
1682
1732
|
function defaultMarkerXml(id, entry, onWarn, key, size) {
|
|
1683
1733
|
const name = key ?? id;
|
|
1684
1734
|
const unit = size === void 0 ? 1 : resolveMarkerSize({ key: name, size }, 1);
|
|
1685
|
-
const path = entry.path({ size: unit, stroke: { paint: {
|
|
1735
|
+
const path = entry.path({ size: unit, stroke: { paint: { kind: "solid", color: "#000" } } });
|
|
1686
1736
|
const d = serializePathD(path);
|
|
1687
1737
|
const fill = markerPaintAttrs("fill", entry.fill, id, onWarn);
|
|
1688
1738
|
const outline = entry.outline ? ` ${markerPaintAttrs("stroke", entry.outline.paint, id, onWarn)} stroke-width="${trimNumber(entry.outline.width * unit)}" stroke-linecap="round" stroke-linejoin="round"` : "";
|
|
@@ -1694,43 +1744,43 @@ function defaultMarkerXml(id, entry, onWarn, key, size) {
|
|
|
1694
1744
|
function markerPaintAttrs(attr, paint, id, onWarn) {
|
|
1695
1745
|
if (paint === "none") return `${attr}="none"`;
|
|
1696
1746
|
if (paint === void 0 || paint === "line") return `${attr}="context-stroke"`;
|
|
1697
|
-
if ("color" in paint && (paint.
|
|
1747
|
+
if ("color" in paint && (paint.kind === void 0 || paint.kind === "solid")) {
|
|
1698
1748
|
return paint.opacity != null && paint.opacity !== 1 ? `${attr}="${paint.color}" ${attr}-opacity="${trimNumber(paint.opacity)}"` : `${attr}="${paint.color}"`;
|
|
1699
1749
|
}
|
|
1700
1750
|
onWarn?.(`marker "${id}" ${attr} is a paint server, which a <marker> def cannot reference here; written as the line's paint`);
|
|
1701
1751
|
return `${attr}="context-stroke"`;
|
|
1702
1752
|
}
|
|
1703
1753
|
function paintServerXml(id, paint, onWarn) {
|
|
1704
|
-
if (paint
|
|
1754
|
+
if (isPatternPaint(paint)) return patternXml(id, paint, onWarn);
|
|
1705
1755
|
const builtin = gradientXml(id, paint);
|
|
1706
1756
|
if (builtin) return builtin;
|
|
1707
|
-
const kind = getPaintKind(paint.
|
|
1757
|
+
const kind = getPaintKind(paint.kind);
|
|
1708
1758
|
const custom = kind?.toSvg?.(id, paint);
|
|
1709
1759
|
if (custom) return custom;
|
|
1710
|
-
onWarn?.(kind ? `${paint.
|
|
1760
|
+
onWarn?.(kind ? `${paint.kind} fill has no vector form \u2014 omitted from <defs>` : `${paint.kind} fill is not registered \u2014 omitted from <defs>; a kind loaded on demand needs \`await warmSvg(nodes)\` before a synchronous export`);
|
|
1711
1761
|
return "";
|
|
1712
1762
|
}
|
|
1713
1763
|
function gradientUnitsAttr(units) {
|
|
1714
1764
|
return units === "bounds" ? "objectBoundingBox" : "userSpaceOnUse";
|
|
1715
1765
|
}
|
|
1716
1766
|
function gradientXml(id, paint) {
|
|
1717
|
-
if (paint.
|
|
1767
|
+
if (paint.kind === "linear-gradient") {
|
|
1718
1768
|
const stops = paint.stops.map(stopXml).join("");
|
|
1719
1769
|
return `<linearGradient id="${id}" gradientUnits="${gradientUnitsAttr(paint.units)}" x1="${trimNumber(paint.from.x)}" y1="${trimNumber(paint.from.y)}" x2="${trimNumber(paint.to.x)}" y2="${trimNumber(paint.to.y)}"${interpolateAttr(paint.interpolate)}>${stops}</linearGradient>`;
|
|
1720
1770
|
}
|
|
1721
|
-
if (paint.
|
|
1771
|
+
if (paint.kind === "conic-gradient") {
|
|
1722
1772
|
const stops = paint.stops.map(stopXml).join("");
|
|
1723
1773
|
const P = WEASEL_NS_PREFIX;
|
|
1724
1774
|
return `<${P}:conicGradient id="${id}" gradientUnits="${gradientUnitsAttr(paint.units)}" cx="${trimNumber(paint.center.x)}" cy="${trimNumber(paint.center.y)}" angle="${trimNumber(paint.angle)}"${interpolateAttr(paint.interpolate)}>${stops}</${P}:conicGradient>`;
|
|
1725
1775
|
}
|
|
1726
|
-
if (paint.
|
|
1776
|
+
if (paint.kind === "radial-gradient") {
|
|
1727
1777
|
const stops = paint.stops.map(stopXml).join("");
|
|
1728
1778
|
return `<radialGradient id="${id}" gradientUnits="${gradientUnitsAttr(paint.units)}" cx="${trimNumber(paint.center.x)}" cy="${trimNumber(paint.center.y)}" r="${trimNumber(paint.radius)}"${interpolateAttr(paint.interpolate)}>${stops}</radialGradient>`;
|
|
1729
1779
|
}
|
|
1730
1780
|
return "";
|
|
1731
1781
|
}
|
|
1732
1782
|
function paintFallback(paint) {
|
|
1733
|
-
const color = getPaintKind(paint.
|
|
1783
|
+
const color = getPaintKind(paint.kind)?.colorOf(paint);
|
|
1734
1784
|
if (!color) return "none";
|
|
1735
1785
|
const packed = /^#([0-9a-f]{6})[0-9a-f]{2}$/i.exec(color);
|
|
1736
1786
|
return packed ? `#${packed[1].toLowerCase()}` : color;
|
|
@@ -1816,8 +1866,6 @@ function polygonToPath(points, m = IDENTITY_MATRIX) {
|
|
|
1816
1866
|
b.close();
|
|
1817
1867
|
return transformPath(b.build(), m);
|
|
1818
1868
|
}
|
|
1819
|
-
|
|
1820
|
-
// src/lowerLeaf.ts
|
|
1821
1869
|
var SUPPORTED_LEAF_TAGS = /* @__PURE__ */ new Set([
|
|
1822
1870
|
"rect",
|
|
1823
1871
|
"circle",
|
|
@@ -1849,7 +1897,7 @@ function lowerLeaf(el, tag, m, onWarn) {
|
|
|
1849
1897
|
if (tag === "circle") return circleToPath(x("cx"), y("cy"), r("r"), m);
|
|
1850
1898
|
if (tag === "ellipse") return ellipseToPath(x("cx"), y("cy"), x("rx"), y("ry"), m);
|
|
1851
1899
|
if (tag === "line") return lineToPath(x("x1"), y("y1"), x("x2"), y("y2"), m);
|
|
1852
|
-
if (tag === "polyline") return polylineToPath(parsePoints(el.getAttribute("points") ?? ""), m);
|
|
1900
|
+
if (tag === "polyline") return svgFilled(polylineToPath(parsePoints(el.getAttribute("points") ?? ""), m));
|
|
1853
1901
|
if (tag === "polygon") return polygonToPath(parsePoints(el.getAttribute("points") ?? ""), m);
|
|
1854
1902
|
if (tag === "path") {
|
|
1855
1903
|
const d = el.getAttribute("d") ?? "";
|
|
@@ -1863,10 +1911,13 @@ function lowerLeaf(el, tag, m, onWarn) {
|
|
|
1863
1911
|
const rect = polygonToRectIfAxisAligned(transformed);
|
|
1864
1912
|
if (rect) return rect;
|
|
1865
1913
|
}
|
|
1866
|
-
return transformed;
|
|
1914
|
+
return svgFilled(transformed);
|
|
1867
1915
|
}
|
|
1868
1916
|
return null;
|
|
1869
1917
|
}
|
|
1918
|
+
function svgFilled(path) {
|
|
1919
|
+
return hasOpenSubpath(path) ? { ...path, openFill: "close" } : path;
|
|
1920
|
+
}
|
|
1870
1921
|
function polygonToRectIfAxisAligned(p) {
|
|
1871
1922
|
const cmds = p.commands;
|
|
1872
1923
|
if (cmds.length !== 5) return null;
|
|
@@ -2031,7 +2082,7 @@ function coreStroke(stroke) {
|
|
|
2031
2082
|
if (stroke.paint.kind === "gradient") {
|
|
2032
2083
|
paint = stroke.paint.paint;
|
|
2033
2084
|
} else if (stroke.paint.kind === "solid") {
|
|
2034
|
-
paint = stroke.paint.opacity != null ? {
|
|
2085
|
+
paint = stroke.paint.opacity != null ? { kind: "solid", color: stroke.paint.color, opacity: stroke.paint.opacity } : { kind: "solid", color: stroke.paint.color };
|
|
2035
2086
|
} else {
|
|
2036
2087
|
return void 0;
|
|
2037
2088
|
}
|
|
@@ -2187,19 +2238,74 @@ function anchorOffset(style, width2, text) {
|
|
|
2187
2238
|
}
|
|
2188
2239
|
|
|
2189
2240
|
// src/tspanLines.ts
|
|
2241
|
+
var LINES_ATTR = "data-weasel-lines";
|
|
2242
|
+
var statesLines = (tspan) => tspan.hasAttribute(LINES_ATTR);
|
|
2243
|
+
function linesXml(pieces, x, pitch) {
|
|
2244
|
+
let out = "";
|
|
2245
|
+
let pending = 0;
|
|
2246
|
+
let wrote = false;
|
|
2247
|
+
const tspan = (attrs, text) => `<tspan${attrs.map((a) => ` ${a}`).join("")}>${escapeText(text)}</tspan>`;
|
|
2248
|
+
const lineAttrs = () => {
|
|
2249
|
+
if (pending === 0) return [];
|
|
2250
|
+
const attrs = [`x="${trimNumber(x)}"`, `dy="${trimNumber(pending * pitch)}"`];
|
|
2251
|
+
if (pending > 1 || !wrote) attrs.push(`${LINES_ATTR}="${pending}"`);
|
|
2252
|
+
pending = 0;
|
|
2253
|
+
return attrs;
|
|
2254
|
+
};
|
|
2255
|
+
for (const piece of pieces) {
|
|
2256
|
+
piece.text.split(/[\n\u000B\u000C]/).forEach((line, i) => {
|
|
2257
|
+
if (i > 0) pending++;
|
|
2258
|
+
if (line === "") return;
|
|
2259
|
+
const attrs = [...piece.attrs ?? [], ...lineAttrs()];
|
|
2260
|
+
out += piece.attrs === null && attrs.length === 0 ? escapeText(line) : tspan(attrs, line);
|
|
2261
|
+
wrote = true;
|
|
2262
|
+
});
|
|
2263
|
+
}
|
|
2264
|
+
return pending > 0 ? out + tspan(lineAttrs(), "") : out;
|
|
2265
|
+
}
|
|
2266
|
+
var DEFAULT_LINE_HEIGHT = 1.2;
|
|
2190
2267
|
var LineTracker = class {
|
|
2191
|
-
|
|
2192
|
-
|
|
2268
|
+
constructor(text, onWarn) {
|
|
2269
|
+
this.onWarn = onWarn;
|
|
2193
2270
|
this.y = text.getAttribute("y");
|
|
2271
|
+
this.at = readLength(text, "y", "y", 0, onWarn);
|
|
2194
2272
|
}
|
|
2195
|
-
|
|
2196
|
-
|
|
2273
|
+
onWarn;
|
|
2274
|
+
y;
|
|
2275
|
+
/** Where the current line sits, in user units. */
|
|
2276
|
+
at;
|
|
2277
|
+
pitches = [];
|
|
2278
|
+
/** How many lines down `tspan` opens a new line, or 0 when it stays on the
|
|
2279
|
+
* current one. Call once per tspan, in order. `afterText` is whether any
|
|
2280
|
+
* text precedes it: the first line's own placement is not a pitch. */
|
|
2281
|
+
startsLine(tspan, afterText) {
|
|
2197
2282
|
const y = tspan.getAttribute("y");
|
|
2198
2283
|
const movedY = y != null && y !== this.y;
|
|
2199
2284
|
if (y != null) this.y = y;
|
|
2200
|
-
|
|
2201
|
-
const dy =
|
|
2202
|
-
|
|
2285
|
+
const from = this.at;
|
|
2286
|
+
const dy = readLength(tspan, "dy", "y", 0, this.onWarn);
|
|
2287
|
+
this.at = (y != null ? readLength(tspan, "y", "y", from, this.onWarn) : from) + dy;
|
|
2288
|
+
if (!tspan.hasAttribute("x") || !(movedY || dy !== 0)) return 0;
|
|
2289
|
+
const stated = Number(tspan.getAttribute(LINES_ATTR));
|
|
2290
|
+
const lines = Number.isInteger(stated) && stated > 0 ? stated : 1;
|
|
2291
|
+
if (afterText && this.at > from) this.pitches.push((this.at - from) / lines);
|
|
2292
|
+
return lines;
|
|
2293
|
+
}
|
|
2294
|
+
/**
|
|
2295
|
+
* The line height the lines were pitched at, as a multiple of `fontSize`:
|
|
2296
|
+
* `undefined` for a single line, or for lines at the default pitch. The
|
|
2297
|
+
* model holds one line height, so uneven pitches give their mean, which
|
|
2298
|
+
* keeps the block's height.
|
|
2299
|
+
*/
|
|
2300
|
+
lineHeight(fontSize) {
|
|
2301
|
+
const { pitches } = this;
|
|
2302
|
+
if (pitches.length === 0 || !(fontSize > 0)) return void 0;
|
|
2303
|
+
const mean = pitches.reduce((sum, p) => sum + p, 0) / pitches.length;
|
|
2304
|
+
if (pitches.some((p) => Math.abs(p - mean) > 1e-3 * fontSize)) {
|
|
2305
|
+
this.onWarn("<text> lines are pitched unevenly; they take the mean pitch as one line height");
|
|
2306
|
+
}
|
|
2307
|
+
const lineHeight = Math.round(mean / fontSize * 1e6) / 1e6;
|
|
2308
|
+
return Math.abs(lineHeight - DEFAULT_LINE_HEIGHT) < 1e-6 ? void 0 : lineHeight;
|
|
2203
2309
|
}
|
|
2204
2310
|
};
|
|
2205
2311
|
function warnUnreadPositions(el, onWarn) {
|
|
@@ -2208,28 +2314,88 @@ function warnUnreadPositions(el, onWarn) {
|
|
|
2208
2314
|
const raw = el.getAttribute(name)?.trim() ?? "";
|
|
2209
2315
|
if (/[\s,]/.test(raw)) onWarn(`<${tag} ${name}="${raw}"> positions each glyph; only the first value is read`);
|
|
2210
2316
|
}
|
|
2211
|
-
for (const name of ["dx", "rotate"]) {
|
|
2317
|
+
for (const name of tag === "tspan" ? ["rotate"] : ["dx", "rotate"]) {
|
|
2212
2318
|
const raw = el.getAttribute(name)?.trim() ?? "";
|
|
2213
2319
|
if (raw.split(/[\s,]+/).some((v) => parseFloat(v) !== 0 && v !== "")) {
|
|
2214
2320
|
onWarn(`<${tag} ${name}="${raw}"> is not read; the glyphs keep their own advance and angle`);
|
|
2215
2321
|
}
|
|
2216
2322
|
}
|
|
2217
2323
|
}
|
|
2218
|
-
function
|
|
2324
|
+
function sameStyle(a, b) {
|
|
2325
|
+
const style = (run) => JSON.stringify(
|
|
2326
|
+
Object.entries(run).filter(([k, v]) => k !== "text" && v !== void 0).sort(([j], [k]) => j < k ? -1 : 1)
|
|
2327
|
+
);
|
|
2328
|
+
return style(a) === style(b);
|
|
2329
|
+
}
|
|
2330
|
+
function joinLines(lines, leading = 0) {
|
|
2219
2331
|
const out = [];
|
|
2220
|
-
let breaks =
|
|
2332
|
+
let breaks = leading;
|
|
2221
2333
|
for (const line of lines) {
|
|
2222
2334
|
if (line.length > 0) {
|
|
2223
|
-
|
|
2224
|
-
|
|
2335
|
+
const [first, ...rest] = line;
|
|
2336
|
+
const head = breaks > 0 ? { ...first, text: "\n".repeat(breaks) + first.text } : first;
|
|
2337
|
+
const last = out[out.length - 1];
|
|
2338
|
+
if (breaks > 0 && last && sameStyle(last, head)) {
|
|
2339
|
+
out[out.length - 1] = { ...last, text: last.text + head.text };
|
|
2340
|
+
} else {
|
|
2341
|
+
out.push(head);
|
|
2342
|
+
}
|
|
2343
|
+
out.push(...rest);
|
|
2225
2344
|
breaks = 0;
|
|
2226
2345
|
}
|
|
2227
2346
|
if (out.length > 0) breaks++;
|
|
2228
2347
|
}
|
|
2229
2348
|
return out;
|
|
2230
2349
|
}
|
|
2350
|
+
var COLLAPSIBLE = /^\s|\s$|\s\s|[\n\t\r]/;
|
|
2351
|
+
function readDx(el, onWarn) {
|
|
2352
|
+
const raw = el.getAttribute("dx")?.trim();
|
|
2353
|
+
if (!raw) return null;
|
|
2354
|
+
const values = raw.split(/[\s,]+/).map((v) => resolveLength(v, el, "dx", "x", 0, onWarn));
|
|
2355
|
+
return values.some((v) => v !== 0) ? values : null;
|
|
2356
|
+
}
|
|
2357
|
+
var spacingOf = (run, inherited2) => resolveScreenLength(run.letterSpacing ?? inherited2 ?? 0, 1);
|
|
2358
|
+
function spread(run, extra, inherited2) {
|
|
2359
|
+
const base = spacingOf(run, inherited2);
|
|
2360
|
+
const out = [];
|
|
2361
|
+
let last;
|
|
2362
|
+
Array.from(run.text).forEach((glyph, i) => {
|
|
2363
|
+
const more = extra[i] ?? 0;
|
|
2364
|
+
if (out.length > 0 && more === last) out[out.length - 1].text += glyph;
|
|
2365
|
+
else out.push(more === 0 ? { ...run, text: glyph } : { ...run, text: glyph, letterSpacing: base + more });
|
|
2366
|
+
last = more;
|
|
2367
|
+
});
|
|
2368
|
+
return out;
|
|
2369
|
+
}
|
|
2370
|
+
function placeTspanRun(tspan, run, lines, inherited2, onWarn) {
|
|
2371
|
+
const line = lines[lines.length - 1];
|
|
2372
|
+
const dx = readDx(tspan, onWarn);
|
|
2373
|
+
if (!dx) {
|
|
2374
|
+
line.push(run);
|
|
2375
|
+
return 0;
|
|
2376
|
+
}
|
|
2377
|
+
const raw = tspan.getAttribute("dx");
|
|
2378
|
+
if (COLLAPSIBLE.test(run.text)) {
|
|
2379
|
+
onWarn(`<tspan dx="${raw}"> is not read: its text has white space SVG collapses, which moves the glyphs the list names`);
|
|
2380
|
+
line.push(run);
|
|
2381
|
+
return 0;
|
|
2382
|
+
}
|
|
2383
|
+
let lead = 0;
|
|
2384
|
+
let before = line.length - 1;
|
|
2385
|
+
while (before >= 0 && line[before].text === "") before--;
|
|
2386
|
+
if (dx[0] !== 0 && before >= 0) {
|
|
2387
|
+
const glyphs = Array.from(line[before].text);
|
|
2388
|
+
line.splice(before, 1, ...spread(line[before], [...glyphs.slice(1).map(() => 0), dx[0]], inherited2));
|
|
2389
|
+
} else if (dx[0] !== 0 && lines.length === 1) {
|
|
2390
|
+
lead = dx[0];
|
|
2391
|
+
} else if (dx[0] !== 0) {
|
|
2392
|
+
onWarn(`<tspan dx="${raw}"> opens a line; the line starts at the box edge, and the rest of the list is read`);
|
|
2393
|
+
}
|
|
2394
|
+
line.push(...spread(run, dx.slice(1), inherited2));
|
|
2395
|
+
return lead;
|
|
2396
|
+
}
|
|
2231
2397
|
|
|
2232
|
-
// src/
|
|
2398
|
+
// src/textWhitespace.ts
|
|
2233
2399
|
var XML_NS = "http://www.w3.org/XML/1998/namespace";
|
|
2234
2400
|
function preservesSpace(el) {
|
|
2235
2401
|
for (let e = el; e; e = e.parentElement) {
|
|
@@ -2266,6 +2432,15 @@ function collapseRunWhitespace(runs) {
|
|
|
2266
2432
|
if (runs[i].text === "") runs.splice(i, 1);
|
|
2267
2433
|
}
|
|
2268
2434
|
}
|
|
2435
|
+
|
|
2436
|
+
// src/fillPaint.ts
|
|
2437
|
+
function mapFillPaint(fill, f) {
|
|
2438
|
+
if (!fill.paint) return fill;
|
|
2439
|
+
const paint = f(fill.paint);
|
|
2440
|
+
return paint === fill.paint ? fill : { ...fill, paint };
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
// src/readText.ts
|
|
2269
2444
|
var ESTIMATED_GLYPH_ADVANCE_EM = 0.6;
|
|
2270
2445
|
function laidOutWidth(runs, style, plain) {
|
|
2271
2446
|
const resolved = resolveTextStyle(style);
|
|
@@ -2298,12 +2473,12 @@ function placeTextNode(node, m, onWarn) {
|
|
|
2298
2473
|
if (node.runs) node.runs = scaleRuns(node.runs, scale);
|
|
2299
2474
|
if (node.stroke) node.stroke = scaleStroke(node.stroke, scale);
|
|
2300
2475
|
}
|
|
2301
|
-
if (node.fill) node.fill =
|
|
2476
|
+
if (node.fill) node.fill = mapFillPaint(node.fill, (p) => fillThroughMatrix(p, inBox));
|
|
2302
2477
|
if (node.stroke?.paint) {
|
|
2303
2478
|
node.stroke = { ...node.stroke, paint: fillThroughMatrix(node.stroke.paint, inBox) };
|
|
2304
2479
|
}
|
|
2305
2480
|
if (node.runs) {
|
|
2306
|
-
node.runs = node.runs.map((r) => r.fill ? { ...r, fill:
|
|
2481
|
+
node.runs = node.runs.map((r) => r.fill ? { ...r, fill: mapFillPaint(r.fill, (p) => fillThroughMatrix(p, inBox)) } : r);
|
|
2307
2482
|
}
|
|
2308
2483
|
}
|
|
2309
2484
|
function parseTextElement(el, ctm, style, gradients, onWarn) {
|
|
@@ -2320,38 +2495,16 @@ function parseTextElement(el, ctm, style, gradients, onWarn) {
|
|
|
2320
2495
|
const textStyle = readTextStyle(leafStyle, onWarn);
|
|
2321
2496
|
const textPaint = readTextPaint(leafStyle, gradients, onWarn, isNonScalingStroke(el));
|
|
2322
2497
|
const fontSize = resolveScreenLength(textStyle.fontSize ?? 16, 1);
|
|
2323
|
-
const lineHeight = textStyle.lineHeight ?? 1.2;
|
|
2324
2498
|
const dominantBaseline = el.getAttribute("dominant-baseline");
|
|
2325
2499
|
const explicitTopAnchor = dominantBaseline === "text-before-edge" || dominantBaseline === "hanging";
|
|
2326
2500
|
const topY = explicitTopAnchor ? ay : ay - fontSize;
|
|
2327
2501
|
const inheritedShift = ownValue(el, "baseline-shift", onWarn) ?? {};
|
|
2328
|
-
const
|
|
2329
|
-
const
|
|
2330
|
-
const push = (run) => {
|
|
2331
|
-
runLines[runLines.length - 1].push(run);
|
|
2332
|
-
};
|
|
2333
|
-
for (let i = 0; i < el.childNodes.length; i++) {
|
|
2334
|
-
const child = el.childNodes[i];
|
|
2335
|
-
if (child.nodeType === 3) {
|
|
2336
|
-
const t = child.textContent ?? "";
|
|
2337
|
-
if (!t) continue;
|
|
2338
|
-
push({ text: t, ...inheritedShift });
|
|
2339
|
-
} else if (child.nodeType === 1) {
|
|
2340
|
-
const sp = child;
|
|
2341
|
-
if (sp.tagName.toLowerCase() !== "tspan") {
|
|
2342
|
-
onWarn(`<text> child <${sp.tagName}> not supported; flattening text content`);
|
|
2343
|
-
const t = sp.textContent ?? "";
|
|
2344
|
-
if (t) push({ text: t, ...inheritedShift });
|
|
2345
|
-
continue;
|
|
2346
|
-
}
|
|
2347
|
-
warnUnreadPositions(sp, onWarn);
|
|
2348
|
-
if (tracker.startsLine(sp)) runLines.push([]);
|
|
2349
|
-
push(readTspanRun(sp, gradients, leafStyle, onWarn));
|
|
2350
|
-
}
|
|
2351
|
-
}
|
|
2352
|
-
if (!preservesSpace(el)) for (const line of runLines) collapseRunWhitespace(line);
|
|
2353
|
-
const runs = joinLines(runLines);
|
|
2502
|
+
const tracker = new LineTracker(el, onWarn);
|
|
2503
|
+
const { runs, lead } = readRuns(el, tracker, inheritedShift, textStyle.letterSpacing, gradients, leafStyle, onWarn);
|
|
2354
2504
|
const plain = runs.map((r) => r.text).join("");
|
|
2505
|
+
const pitched = tracker.lineHeight(fontSize);
|
|
2506
|
+
if (pitched !== void 0) textStyle.lineHeight = pitched;
|
|
2507
|
+
const lineHeight = textStyle.lineHeight ?? DEFAULT_LINE_HEIGHT;
|
|
2355
2508
|
const dataW = num2(el.getAttribute("data-weasel-width"), NaN);
|
|
2356
2509
|
const dataH = num2(el.getAttribute("data-weasel-height"), NaN);
|
|
2357
2510
|
const width2 = Number.isFinite(dataW) ? dataW : laidOutWidth(runs, textStyle, plain);
|
|
@@ -2360,7 +2513,7 @@ function parseTextElement(el, ctm, style, gradients, onWarn) {
|
|
|
2360
2513
|
const opacity = ownValue(el, "opacity");
|
|
2361
2514
|
const node = {
|
|
2362
2515
|
kind: "text",
|
|
2363
|
-
x: ax - anchorOffset(textStyle, width2, plain),
|
|
2516
|
+
x: ax + lead - anchorOffset(textStyle, width2, plain),
|
|
2364
2517
|
y: topY,
|
|
2365
2518
|
width: width2,
|
|
2366
2519
|
height: height2,
|
|
@@ -2386,6 +2539,38 @@ function parseTextElement(el, ctm, style, gradients, onWarn) {
|
|
|
2386
2539
|
placeTextNode(node, multiply(ctm, localTransform), onWarn);
|
|
2387
2540
|
return node;
|
|
2388
2541
|
}
|
|
2542
|
+
function readRuns(el, tracker, inheritedShift, letterSpacing, gradients, leafStyle, onWarn) {
|
|
2543
|
+
const runLines = [[]];
|
|
2544
|
+
let leading = 0;
|
|
2545
|
+
let lead = 0;
|
|
2546
|
+
const push = (run) => {
|
|
2547
|
+
runLines[runLines.length - 1].push(run);
|
|
2548
|
+
};
|
|
2549
|
+
for (let i = 0; i < el.childNodes.length; i++) {
|
|
2550
|
+
const child = el.childNodes[i];
|
|
2551
|
+
if (child.nodeType === 3) {
|
|
2552
|
+
const t = child.textContent ?? "";
|
|
2553
|
+
if (!t) continue;
|
|
2554
|
+
push({ text: t, ...inheritedShift });
|
|
2555
|
+
} else if (child.nodeType === 1) {
|
|
2556
|
+
const sp = child;
|
|
2557
|
+
if (sp.tagName.toLowerCase() !== "tspan") {
|
|
2558
|
+
onWarn(`<text> child <${sp.tagName}> not supported; flattening text content`);
|
|
2559
|
+
const t = sp.textContent ?? "";
|
|
2560
|
+
if (t) push({ text: t, ...inheritedShift });
|
|
2561
|
+
continue;
|
|
2562
|
+
}
|
|
2563
|
+
warnUnreadPositions(sp, onWarn);
|
|
2564
|
+
const afterText = runLines.some((line) => line.some((r) => r.text.trim() !== ""));
|
|
2565
|
+
const lines = tracker.startsLine(sp, afterText);
|
|
2566
|
+
if (runLines.length === 1 && runLines[0].length === 0 && statesLines(sp)) leading = lines;
|
|
2567
|
+
else for (let n = 0; n < lines; n++) runLines.push([]);
|
|
2568
|
+
lead += placeTspanRun(sp, readTspanRun(sp, gradients, leafStyle, onWarn), runLines, letterSpacing, onWarn);
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
if (!preservesSpace(el)) for (const line of runLines) collapseRunWhitespace(line);
|
|
2572
|
+
return { runs: joinLines(runLines, leading), lead };
|
|
2573
|
+
}
|
|
2389
2574
|
function readTspanRun(el, gradients, style, onWarn) {
|
|
2390
2575
|
const text = el.textContent ?? "";
|
|
2391
2576
|
const run = { text };
|
|
@@ -2419,11 +2604,11 @@ function readTspanRun(el, gradients, style, onWarn) {
|
|
|
2419
2604
|
if (parsed?.kind === "none") {
|
|
2420
2605
|
run.fill = null;
|
|
2421
2606
|
} else if (parsed?.kind === "solid") {
|
|
2422
|
-
run.fill = {
|
|
2607
|
+
run.fill = { paint: { kind: "solid", color: parsed.color } };
|
|
2423
2608
|
} else if (parsed?.kind === "ref") {
|
|
2424
2609
|
const paint = gradients.get(parsed.id);
|
|
2425
|
-
if (paint) run.fill = paint;
|
|
2426
|
-
else if (parsed.fallback) run.fill = {
|
|
2610
|
+
if (paint) run.fill = { paint };
|
|
2611
|
+
else if (parsed.fallback) run.fill = { paint: { kind: "solid", color: parsed.fallback.color } };
|
|
2427
2612
|
}
|
|
2428
2613
|
}
|
|
2429
2614
|
const stroke = coreStroke(readStroke(ownStrokeStyle(el, tspanStyle), gradients, onWarn, isNonScalingStroke(el)));
|
|
@@ -2465,12 +2650,12 @@ function readTextPaint(style, gradients, onWarn, nonScalingStroke = false) {
|
|
|
2465
2650
|
if (fillRaw) {
|
|
2466
2651
|
const parsed = readProperty("fill", fillRaw);
|
|
2467
2652
|
if (parsed?.kind === "solid") {
|
|
2468
|
-
out.fill = {
|
|
2653
|
+
out.fill = { paint: { kind: "solid", color: parsed.color } };
|
|
2469
2654
|
} else if (parsed?.kind === "ref") {
|
|
2470
2655
|
const paint = gradients.get(parsed.id);
|
|
2471
|
-
if (paint) out.fill = paint;
|
|
2656
|
+
if (paint) out.fill = { paint };
|
|
2472
2657
|
else if (parsed.fallback) {
|
|
2473
|
-
out.fill = {
|
|
2658
|
+
out.fill = { paint: { kind: "solid", color: parsed.fallback.color } };
|
|
2474
2659
|
}
|
|
2475
2660
|
} else if (parsed?.kind === "none") {
|
|
2476
2661
|
out.fill = null;
|
|
@@ -2726,7 +2911,7 @@ function parseSvg(svg, opts = {}) {
|
|
|
2726
2911
|
for (const [id, paint] of collectPatterns(root, onWarn)) gradients.set(id, paint);
|
|
2727
2912
|
const rootStyle = deriveStyle(EMPTY_STYLE, root);
|
|
2728
2913
|
const clips = collectClipPaths(root, onWarn);
|
|
2729
|
-
const nodes = parseChildren(root, IDENTITY_MATRIX, rootStyle, gradients, clips, onWarn, uriToPrefix);
|
|
2914
|
+
const nodes = parseChildren(root, IDENTITY_MATRIX, rootStyle, gradients, clips, onWarn, uriToPrefix, opts.keepHidden === true);
|
|
2730
2915
|
const markers = ingestMarkers(root, nodes, gradients, onWarn);
|
|
2731
2916
|
const result = { nodes, warnings };
|
|
2732
2917
|
if (markers.length > 0) result.markers = markers;
|
|
@@ -2827,26 +3012,25 @@ function collectElementMeta(el, uriToPrefix) {
|
|
|
2827
3012
|
}
|
|
2828
3013
|
return Object.keys(meta).length > 0 ? meta : void 0;
|
|
2829
3014
|
}
|
|
2830
|
-
function parseChildren(parent, ctm, style, gradients, clips, onWarn, uriToPrefix) {
|
|
3015
|
+
function parseChildren(parent, ctm, style, gradients, clips, onWarn, uriToPrefix, keepHidden) {
|
|
2831
3016
|
const out = [];
|
|
2832
3017
|
for (let i = 0; i < parent.children.length; i++) {
|
|
2833
3018
|
const el = parent.children[i];
|
|
2834
3019
|
const ns = el.namespaceURI;
|
|
2835
3020
|
if (ns && ns !== "http://www.w3.org/2000/svg") continue;
|
|
2836
|
-
const
|
|
2837
|
-
if (
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
3021
|
+
const hidden = isHiddenElement(el, style);
|
|
3022
|
+
if (hidden && !keepHidden) continue;
|
|
3023
|
+
const node = parseElement(el, ctm, style, gradients, clips, onWarn, uriToPrefix, keepHidden);
|
|
3024
|
+
if (!node) continue;
|
|
3025
|
+
const parsed = Array.isArray(node) ? node : [node];
|
|
3026
|
+
if (hidden) for (const n of parsed) n.hidden = true;
|
|
3027
|
+
out.push(...parsed);
|
|
2841
3028
|
}
|
|
2842
3029
|
return out;
|
|
2843
3030
|
}
|
|
2844
|
-
function parseElement(el, ctm, style, gradients, clips, onWarn, uriToPrefix) {
|
|
3031
|
+
function parseElement(el, ctm, style, gradients, clips, onWarn, uriToPrefix, keepHidden) {
|
|
2845
3032
|
const tag = el.tagName.toLowerCase();
|
|
2846
3033
|
if (IGNORED_TAGS.has(tag)) return null;
|
|
2847
|
-
if (ownValue(el, "display") === "none") return null;
|
|
2848
|
-
const containers = tag === "g" || SUPPORTED_GROUP_TAGS.has(tag);
|
|
2849
|
-
if (!containers && isHidden(deriveStyle(style, el))) return null;
|
|
2850
3034
|
if (tag === "g" && el.hasAttribute("data-weasel-image")) {
|
|
2851
3035
|
const image = parseCroppedImage(el, ctm, onWarn);
|
|
2852
3036
|
if (image) {
|
|
@@ -2863,7 +3047,7 @@ function parseElement(el, ctm, style, gradients, clips, onWarn, uriToPrefix) {
|
|
|
2863
3047
|
const local = parseTransform(el.getAttribute("transform"), onWarn);
|
|
2864
3048
|
const childCtm = multiply(ctm, local);
|
|
2865
3049
|
const childStyle = deriveStyle(style, el);
|
|
2866
|
-
const children = parseChildren(el, childCtm, childStyle, gradients, clips, onWarn, uriToPrefix);
|
|
3050
|
+
const children = parseChildren(el, childCtm, childStyle, gradients, clips, onWarn, uriToPrefix, keepHidden);
|
|
2867
3051
|
const opacity2 = ownValue(el, "opacity");
|
|
2868
3052
|
const group = { kind: "group", children };
|
|
2869
3053
|
const clip = clipFor(el, childCtm, clips, onWarn);
|
|
@@ -2881,7 +3065,7 @@ function parseElement(el, ctm, style, gradients, clips, onWarn, uriToPrefix) {
|
|
|
2881
3065
|
if (el.hasAttribute("viewBox")) {
|
|
2882
3066
|
onWarn("nested <svg> viewBox is not modeled; its children are not rescaled");
|
|
2883
3067
|
}
|
|
2884
|
-
return parseChildren(el, multiply(ctm, offset), childStyle, gradients, clips, onWarn, uriToPrefix);
|
|
3068
|
+
return parseChildren(el, multiply(ctm, offset), childStyle, gradients, clips, onWarn, uriToPrefix, keepHidden);
|
|
2885
3069
|
}
|
|
2886
3070
|
if (tag === "text") {
|
|
2887
3071
|
const textNode = parseTextElement(el, ctm, style, gradients, onWarn);
|
|
@@ -2949,69 +3133,29 @@ function parseElement(el, ctm, style, gradients, clips, onWarn, uriToPrefix) {
|
|
|
2949
3133
|
if (meta) node.meta = meta;
|
|
2950
3134
|
return node;
|
|
2951
3135
|
}
|
|
2952
|
-
function
|
|
2953
|
-
const
|
|
3136
|
+
function isHiddenElement(el, style) {
|
|
3137
|
+
const tag = el.tagName.toLowerCase();
|
|
3138
|
+
if (IGNORED_TAGS.has(tag)) return false;
|
|
3139
|
+
if (ownValue(el, "display") === "none") return true;
|
|
3140
|
+
if (SUPPORTED_GROUP_TAGS.has(tag)) return false;
|
|
3141
|
+
const v = styleValue(deriveStyle(style, el), "visibility");
|
|
2954
3142
|
return v === "hidden" || v === "collapse";
|
|
2955
3143
|
}
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
const
|
|
2960
|
-
const
|
|
2961
|
-
|
|
2962
|
-
const rootAttrs = [`xmlns="http://www.w3.org/2000/svg"`];
|
|
2963
|
-
if (registry.usesPrivateNamespace() && namespaces[WEASEL_NS_PREFIX] == null) {
|
|
2964
|
-
rootAttrs.push(`xmlns:${WEASEL_NS_PREFIX}="${WEASEL_NS}"`);
|
|
2965
|
-
}
|
|
2966
|
-
for (const [prefix, uri] of Object.entries(namespaces)) {
|
|
2967
|
-
rootAttrs.push(`xmlns:${prefix}="${escapeAttr(uri)}"`);
|
|
3144
|
+
|
|
3145
|
+
// src/metaXml.ts
|
|
3146
|
+
function namespacedElementXml(prefix, localName2, el) {
|
|
3147
|
+
const attrs = [];
|
|
3148
|
+
for (const [name, value] of Object.entries(el.attrs)) {
|
|
3149
|
+
attrs.push(`${name}="${escapeAttr(value)}"`);
|
|
2968
3150
|
}
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
if (
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
const bucket = opts.documentMeta[prefix];
|
|
2975
|
-
if (!bucket?.attrs) continue;
|
|
2976
|
-
for (const [name, value] of Object.entries(bucket.attrs)) {
|
|
2977
|
-
rootAttrs.push(`${prefix}:${name}="${escapeAttr(value)}"`);
|
|
2978
|
-
}
|
|
3151
|
+
const head = attrs.length > 0 ? `<${prefix}:${localName2} ${attrs.join(" ")}>` : `<${prefix}:${localName2}>`;
|
|
3152
|
+
let body = "";
|
|
3153
|
+
if (el.children) {
|
|
3154
|
+
for (const [childName, list] of Object.entries(el.children)) {
|
|
3155
|
+
for (const child of list) body += namespacedElementXml(prefix, childName, child);
|
|
2979
3156
|
}
|
|
2980
|
-
}
|
|
2981
|
-
|
|
2982
|
-
if (opts.documentMeta) {
|
|
2983
|
-
for (const prefix of Object.keys(namespaces)) {
|
|
2984
|
-
const bucket = opts.documentMeta[prefix];
|
|
2985
|
-
if (!bucket?.elements) continue;
|
|
2986
|
-
for (const [localName2, list] of Object.entries(bucket.elements)) {
|
|
2987
|
-
for (const el of list) docMetaXml += namespacedElementXml(prefix, localName2, el);
|
|
2988
|
-
}
|
|
2989
|
-
}
|
|
2990
|
-
}
|
|
2991
|
-
const defsXml = registry.toDefsXml(opts.onWarn);
|
|
2992
|
-
const said = /* @__PURE__ */ new Set();
|
|
2993
|
-
const warn = (m) => {
|
|
2994
|
-
if (!opts.onWarn || said.has(m)) return;
|
|
2995
|
-
said.add(m);
|
|
2996
|
-
opts.onWarn(m);
|
|
2997
|
-
};
|
|
2998
|
-
const bodyXml = nodes.map((n) => nodeXml(n, registry, namespaces, warn)).join("");
|
|
2999
|
-
const titleXml = opts.title && opts.title.length > 0 ? `<title>${escapeText(opts.title)}</title>` : "";
|
|
3000
|
-
return `<svg ${rootAttrs.join(" ")}>${titleXml}${defsXml}${docMetaXml}${bodyXml}</svg>`;
|
|
3001
|
-
}
|
|
3002
|
-
function namespacedElementXml(prefix, localName2, el) {
|
|
3003
|
-
const attrs = [];
|
|
3004
|
-
for (const [name, value] of Object.entries(el.attrs)) {
|
|
3005
|
-
attrs.push(`${name}="${escapeAttr(value)}"`);
|
|
3006
|
-
}
|
|
3007
|
-
const head = attrs.length > 0 ? `<${prefix}:${localName2} ${attrs.join(" ")}>` : `<${prefix}:${localName2}>`;
|
|
3008
|
-
let body = "";
|
|
3009
|
-
if (el.children) {
|
|
3010
|
-
for (const [childName, list] of Object.entries(el.children)) {
|
|
3011
|
-
for (const child of list) body += namespacedElementXml(prefix, childName, child);
|
|
3012
|
-
}
|
|
3013
|
-
} else if (el.text != null) {
|
|
3014
|
-
body = escapeText(el.text);
|
|
3157
|
+
} else if (el.text != null) {
|
|
3158
|
+
body = escapeText(el.text);
|
|
3015
3159
|
}
|
|
3016
3160
|
return `${head}${body}</${prefix}:${localName2}>`;
|
|
3017
3161
|
}
|
|
@@ -3039,115 +3183,109 @@ function metaElementsXml(meta, namespaces) {
|
|
|
3039
3183
|
}
|
|
3040
3184
|
return out;
|
|
3041
3185
|
}
|
|
3042
|
-
function
|
|
3043
|
-
|
|
3186
|
+
function computeBounds(nodes) {
|
|
3187
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
3188
|
+
const visit = (n, ctm) => {
|
|
3189
|
+
if (n.hidden) return;
|
|
3044
3190
|
if (n.kind === "group") {
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
if (n.fill.kind === "gradient") registry.register(n.fill.paint);
|
|
3049
|
-
if (n.stroke && n.stroke.paint.kind === "gradient") registry.register(n.stroke.paint.paint);
|
|
3050
|
-
registerMarkers(n.stroke, registry);
|
|
3051
|
-
} else if (n.kind === "text") {
|
|
3052
|
-
registerTextPaint(n.fill, registry);
|
|
3053
|
-
registerTextPaint(n.stroke?.paint, registry);
|
|
3054
|
-
registerMarkers(n.stroke, registry);
|
|
3055
|
-
for (const run of n.runs ?? []) {
|
|
3056
|
-
registerTextPaint(run.fill, registry);
|
|
3057
|
-
registerTextPaint(run.stroke?.paint, registry);
|
|
3058
|
-
}
|
|
3191
|
+
const childCtm = n.transform ? multiply(ctm, n.transform) : ctm;
|
|
3192
|
+
n.children.forEach((c) => visit(c, childCtm));
|
|
3193
|
+
return;
|
|
3059
3194
|
}
|
|
3195
|
+
const b = n.kind === "text" || n.kind === "image" ? { minX: n.x, minY: n.y, maxX: n.x + n.width, maxY: n.y + n.height } : pathBounds(n.path);
|
|
3196
|
+
const frame = n.kind === "path" ? null : poseFrameOf(n);
|
|
3197
|
+
const local = frame ? multiply(ctm, frame) : n.rotation ? rotateAboutCenter(ctm, b, n.rotation) : ctm;
|
|
3198
|
+
for (const [x, y] of corners(b)) {
|
|
3199
|
+
const px = local[0] * x + local[2] * y + local[4];
|
|
3200
|
+
const py = local[1] * x + local[3] * y + local[5];
|
|
3201
|
+
if (px < minX) minX = px;
|
|
3202
|
+
if (py < minY) minY = py;
|
|
3203
|
+
if (px > maxX) maxX = px;
|
|
3204
|
+
if (py > maxY) maxY = py;
|
|
3205
|
+
}
|
|
3206
|
+
};
|
|
3207
|
+
nodes.forEach((n) => visit(n, IDENTITY_MATRIX));
|
|
3208
|
+
if (!Number.isFinite(minX)) {
|
|
3209
|
+
return { x: 0, y: 0, width: 0, height: 0 };
|
|
3060
3210
|
}
|
|
3211
|
+
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
|
3061
3212
|
}
|
|
3062
|
-
function
|
|
3063
|
-
|
|
3213
|
+
function corners(b) {
|
|
3214
|
+
return [[b.minX, b.minY], [b.maxX, b.minY], [b.maxX, b.maxY], [b.minX, b.maxY]];
|
|
3064
3215
|
}
|
|
3065
|
-
function
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3216
|
+
function rotateAboutCenter(ctm, b, angle) {
|
|
3217
|
+
const cx = (b.minX + b.maxX) / 2;
|
|
3218
|
+
const cy = (b.minY + b.maxY) / 2;
|
|
3219
|
+
const c = Math.cos(angle);
|
|
3220
|
+
const s = Math.sin(angle);
|
|
3221
|
+
return multiply(ctm, [c, s, -s, c, cx - cx * c + cy * s, cy - cx * s - cy * c]);
|
|
3222
|
+
}
|
|
3223
|
+
function pathBounds(path) {
|
|
3224
|
+
const b = boundsOfPath(path);
|
|
3225
|
+
return { minX: b.x, minY: b.y, maxX: b.x + b.width, maxY: b.y + b.height };
|
|
3226
|
+
}
|
|
3227
|
+
|
|
3228
|
+
// src/openSubpathFill.ts
|
|
3229
|
+
var capsEnds = (stroke) => stroke?.markerStart !== void 0 || stroke?.markerEnd !== void 0;
|
|
3230
|
+
var marked = (stroke) => capsEnds(stroke) || stroke?.markerMid !== void 0;
|
|
3231
|
+
function withoutEndMarkers(stroke) {
|
|
3232
|
+
const { markerStart: _start, markerEnd: _end, ...rest } = stroke;
|
|
3233
|
+
return rest;
|
|
3234
|
+
}
|
|
3235
|
+
var closedStroke = (stroke) => stroke && capsEnds(stroke) ? withoutEndMarkers(stroke) : stroke;
|
|
3236
|
+
var isClosed = (part) => splitByClosure(part).open === null;
|
|
3237
|
+
function samePath(a, b) {
|
|
3238
|
+
if (a === b) return true;
|
|
3239
|
+
return a.commands.length === b.commands.length && a.coords.length === b.coords.length && a.commands.every((c, i) => c === b.commands[i]) && a.coords.every((v, i) => v === b.coords[i]);
|
|
3240
|
+
}
|
|
3241
|
+
function strokeParts(path, stroke, filled, closed, open) {
|
|
3242
|
+
const none = { kind: "none" };
|
|
3243
|
+
const subs = marked(stroke) ? splitSubpaths(path) : [];
|
|
3244
|
+
if (subs.length > 1) {
|
|
3245
|
+
return subs.map((sub) => ({ path: sub, fill: none, stroke: isClosed(sub) ? closedStroke(stroke) : stroke }));
|
|
3246
|
+
}
|
|
3247
|
+
if (closed && open && filled === closed) {
|
|
3248
|
+
return [{ path: closed, fill: none, stroke: closedStroke(stroke) }, { path: open, fill: none, stroke }];
|
|
3249
|
+
}
|
|
3250
|
+
return [{ path, fill: none, stroke: open ? stroke : closedStroke(stroke) }];
|
|
3251
|
+
}
|
|
3252
|
+
function pathAsSvgDraws(node) {
|
|
3253
|
+
const { path, fill, stroke } = node;
|
|
3254
|
+
if (path.kind !== "polygon") return capsEnds(stroke) ? { ...node, stroke: closedStroke(stroke) } : node;
|
|
3255
|
+
if (path.commands.length === 0) return node;
|
|
3256
|
+
const { closed, open } = splitByClosure(path);
|
|
3257
|
+
const filled = fill.kind === "none" ? null : fillsOpenSubpaths(path) ? path : closed;
|
|
3258
|
+
const parts = strokeParts(path, stroke, filled, closed, open);
|
|
3259
|
+
const pieces = filled ? withFill(parts, filled, fill) : parts;
|
|
3260
|
+
if (pieces.length === 1) {
|
|
3261
|
+
const only = pieces[0];
|
|
3262
|
+
if (only.fill === fill && only.stroke === stroke) return node;
|
|
3263
|
+
const { stroke: _stroke, ...rest } = node;
|
|
3264
|
+
return { ...rest, fill: only.fill, ...only.stroke ? { stroke: only.stroke } : {} };
|
|
3265
|
+
}
|
|
3266
|
+
const group = {
|
|
3267
|
+
kind: "group",
|
|
3268
|
+
children: pieces.map((p) => ({ kind: "path", path: p.path, fill: p.fill, ...p.stroke ? { stroke: p.stroke } : {} }))
|
|
3269
|
+
};
|
|
3270
|
+
if (node.rotation != null && node.rotation !== 0) {
|
|
3271
|
+
const b = pathBounds(path);
|
|
3272
|
+
group.transform = rotateAboutPoint((b.minX + b.maxX) / 2, (b.minY + b.maxY) / 2, node.rotation);
|
|
3069
3273
|
}
|
|
3274
|
+
if (node.opacity != null) group.opacity = node.opacity;
|
|
3275
|
+
if (node.hidden) group.hidden = true;
|
|
3276
|
+
if (node.meta) group.meta = node.meta;
|
|
3277
|
+
return group;
|
|
3070
3278
|
}
|
|
3071
|
-
function
|
|
3072
|
-
|
|
3073
|
-
if (
|
|
3074
|
-
|
|
3075
|
-
return pathXml(node, registry, namespaces, warn);
|
|
3279
|
+
function withFill(parts, filled, fill) {
|
|
3280
|
+
const host = parts.findIndex((p) => samePath(p.path, filled));
|
|
3281
|
+
if (host < 0) return [{ path: filled, fill, stroke: void 0 }, ...parts];
|
|
3282
|
+
return [{ ...parts[host], fill }, ...parts.filter((_, i) => i !== host)];
|
|
3076
3283
|
}
|
|
3077
3284
|
function warnStrokeAlign(align, warn) {
|
|
3078
3285
|
if (align && align !== "center") {
|
|
3079
3286
|
warn(`a stroke aligned "${align}" of its edge is written centered: SVG has no stroke alignment`);
|
|
3080
3287
|
}
|
|
3081
3288
|
}
|
|
3082
|
-
function imageXml(node, namespaces) {
|
|
3083
|
-
const box = `x="${trimNumber(node.x)}" y="${trimNumber(node.y)}" width="${trimNumber(node.width)}" height="${trimNumber(node.height)}"`;
|
|
3084
|
-
const outer = [];
|
|
3085
|
-
if (node.opacity != null && node.opacity !== 1) {
|
|
3086
|
-
outer.push(`opacity="${trimNumber(node.opacity)}"`);
|
|
3087
|
-
}
|
|
3088
|
-
const frame = boxTransformAttr(node);
|
|
3089
|
-
if (frame) outer.push(frame);
|
|
3090
|
-
const metaAttrs = metaAttrsXml(node.meta, namespaces);
|
|
3091
|
-
const metaEls = metaElementsXml(node.meta, namespaces);
|
|
3092
|
-
const href = `href="${escapeAttr(node.href)}"`;
|
|
3093
|
-
if (!node.source && !node.flipX && !node.flipY) {
|
|
3094
|
-
const attrs = [href, box, 'preserveAspectRatio="none"', ...outer].join(" ");
|
|
3095
|
-
if (metaEls) return `<image ${attrs}${metaAttrs}>${metaEls}</image>`;
|
|
3096
|
-
return `<image ${attrs}${metaAttrs}/>`;
|
|
3097
|
-
}
|
|
3098
|
-
const src = node.source ?? { x: 0, y: 0, width: 1, height: 1 };
|
|
3099
|
-
const viewBox = [src.x, src.y, src.width, src.height].map(trimNumber).join(" ");
|
|
3100
|
-
const flip = node.flipX || node.flipY ? ` transform="matrix(${node.flipX ? -1 : 1} 0 0 ${node.flipY ? -1 : 1} ${trimNumber(node.flipX ? 2 * src.x + src.width : 0)} ${trimNumber(node.flipY ? 2 * src.y + src.height : 0)})"` : "";
|
|
3101
|
-
const head = ['<g data-weasel-image="true"', ...outer].join(" ");
|
|
3102
|
-
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>`;
|
|
3103
|
-
}
|
|
3104
|
-
function groupXml(node, registry, namespaces, warn) {
|
|
3105
|
-
const attrs = [];
|
|
3106
|
-
if (node.transform) {
|
|
3107
|
-
const m = formatMatrix(node.transform);
|
|
3108
|
-
if (m) attrs.push(`transform="${m}"`);
|
|
3109
|
-
}
|
|
3110
|
-
if (node.clip) {
|
|
3111
|
-
attrs.push(`clip-path="url(#${registry.clipId(node.clip)})"`);
|
|
3112
|
-
}
|
|
3113
|
-
if (node.opacity != null && node.opacity !== 1) {
|
|
3114
|
-
attrs.push(`opacity="${trimNumber(node.opacity)}"`);
|
|
3115
|
-
}
|
|
3116
|
-
const metaAttrs = metaAttrsXml(node.meta, namespaces);
|
|
3117
|
-
const head = attrs.length > 0 ? `<g ${attrs.join(" ")}${metaAttrs}>` : `<g${metaAttrs}>`;
|
|
3118
|
-
const body = node.children.map((c) => nodeXml(c, registry, namespaces, warn)).join("");
|
|
3119
|
-
return `${head}${body}${metaElementsXml(node.meta, namespaces)}</g>`;
|
|
3120
|
-
}
|
|
3121
|
-
function pathXml(node, registry, namespaces, warn) {
|
|
3122
|
-
const attrs = [`d="${serializePathD(node.path)}"`];
|
|
3123
|
-
const fillAttrs = paintAttrs(node.fill, "fill", registry);
|
|
3124
|
-
for (const a of fillAttrs) attrs.push(a);
|
|
3125
|
-
if (node.path.kind === "polygon" && node.path.fillRule === "evenodd") {
|
|
3126
|
-
attrs.push(`fill-rule="evenodd"`);
|
|
3127
|
-
}
|
|
3128
|
-
if (node.stroke) {
|
|
3129
|
-
const strokeAttrs = strokeAttrsFor(node.stroke, registry, warn);
|
|
3130
|
-
for (const a of strokeAttrs) attrs.push(a);
|
|
3131
|
-
} else {
|
|
3132
|
-
attrs.push('stroke="none"');
|
|
3133
|
-
}
|
|
3134
|
-
if (node.opacity != null && node.opacity !== 1) {
|
|
3135
|
-
attrs.push(`opacity="${trimNumber(node.opacity)}"`);
|
|
3136
|
-
}
|
|
3137
|
-
if (node.rotation != null && node.rotation !== 0) {
|
|
3138
|
-
const b = pathBounds(node.path);
|
|
3139
|
-
const cx = b.minX + (b.maxX - b.minX) / 2;
|
|
3140
|
-
const cy = b.minY + (b.maxY - b.minY) / 2;
|
|
3141
|
-
const deg = node.rotation * 180 / Math.PI;
|
|
3142
|
-
attrs.push(`transform="rotate(${trimNumber(deg)} ${trimNumber(cx)} ${trimNumber(cy)})"`);
|
|
3143
|
-
}
|
|
3144
|
-
const metaAttrs = metaAttrsXml(node.meta, namespaces);
|
|
3145
|
-
const metaEls = metaElementsXml(node.meta, namespaces);
|
|
3146
|
-
if (metaEls) {
|
|
3147
|
-
return `<path ${attrs.join(" ")}${metaAttrs}>${metaEls}</path>`;
|
|
3148
|
-
}
|
|
3149
|
-
return `<path ${attrs.join(" ")}${metaAttrs}/>`;
|
|
3150
|
-
}
|
|
3151
3289
|
function paintAttrs(paint, name, registry, includeOpacity = true) {
|
|
3152
3290
|
if (paint.kind === "none") return [`${name}="none"`];
|
|
3153
3291
|
const out = [`${name}="${paint.kind === "solid" ? escapeAttr(paint.color) : registry.ref(paint.paint)}"`];
|
|
@@ -3171,7 +3309,7 @@ function coreStrokeAttrs(stroke, registry, warn) {
|
|
|
3171
3309
|
const width2 = stroke.width ?? 1;
|
|
3172
3310
|
if (!((typeof width2 === "object" ? width2.px : width2) > 0)) return [];
|
|
3173
3311
|
warnStrokeAlign(stroke.align, warn);
|
|
3174
|
-
const attrs = [`stroke="${
|
|
3312
|
+
const attrs = [`stroke="${isSolidPaint(paint) ? escapeAttr(paint.color) : registry.ref(paint)}"`];
|
|
3175
3313
|
attrs.push(...opacityAttr("stroke-opacity", paint.opacity));
|
|
3176
3314
|
attrs.push(...strokeWidthAttrs(width2));
|
|
3177
3315
|
if (stroke.cap) attrs.push(`stroke-linecap="${stroke.cap}"`);
|
|
@@ -3223,49 +3361,14 @@ function strokeAttrsFor(stroke, registry, warn) {
|
|
|
3223
3361
|
}
|
|
3224
3362
|
return attrs;
|
|
3225
3363
|
}
|
|
3226
|
-
function computeBounds(nodes) {
|
|
3227
|
-
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
3228
|
-
const visit = (n, ctm) => {
|
|
3229
|
-
if (n.kind === "group") {
|
|
3230
|
-
const childCtm = n.transform ? multiply(ctm, n.transform) : ctm;
|
|
3231
|
-
n.children.forEach((c) => visit(c, childCtm));
|
|
3232
|
-
return;
|
|
3233
|
-
}
|
|
3234
|
-
const b = n.kind === "text" || n.kind === "image" ? { minX: n.x, minY: n.y, maxX: n.x + n.width, maxY: n.y + n.height } : pathBounds(n.path);
|
|
3235
|
-
const frame = n.kind === "path" ? null : poseFrameOf(n);
|
|
3236
|
-
const local = frame ? multiply(ctm, frame) : n.rotation ? rotateAboutCenter(ctm, b, n.rotation) : ctm;
|
|
3237
|
-
for (const [x, y] of corners(b)) {
|
|
3238
|
-
const px = local[0] * x + local[2] * y + local[4];
|
|
3239
|
-
const py = local[1] * x + local[3] * y + local[5];
|
|
3240
|
-
if (px < minX) minX = px;
|
|
3241
|
-
if (py < minY) minY = py;
|
|
3242
|
-
if (px > maxX) maxX = px;
|
|
3243
|
-
if (py > maxY) maxY = py;
|
|
3244
|
-
}
|
|
3245
|
-
};
|
|
3246
|
-
nodes.forEach((n) => visit(n, IDENTITY_MATRIX));
|
|
3247
|
-
if (!Number.isFinite(minX)) {
|
|
3248
|
-
return { x: 0, y: 0, width: 0, height: 0 };
|
|
3249
|
-
}
|
|
3250
|
-
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
|
3251
|
-
}
|
|
3252
|
-
function corners(b) {
|
|
3253
|
-
return [[b.minX, b.minY], [b.maxX, b.minY], [b.maxX, b.maxY], [b.minX, b.maxY]];
|
|
3254
|
-
}
|
|
3255
|
-
function rotateAboutCenter(ctm, b, angle) {
|
|
3256
|
-
const cx = (b.minX + b.maxX) / 2;
|
|
3257
|
-
const cy = (b.minY + b.maxY) / 2;
|
|
3258
|
-
const c = Math.cos(angle);
|
|
3259
|
-
const s = Math.sin(angle);
|
|
3260
|
-
return multiply(ctm, [c, s, -s, c, cx - cx * c + cy * s, cy - cx * s - cy * c]);
|
|
3261
|
-
}
|
|
3262
3364
|
function textXml(node, registry, namespaces, warn) {
|
|
3365
|
+
const x = node.x + anchorOffset(node.style, node.width, node.text);
|
|
3263
3366
|
const attrs = [
|
|
3264
|
-
`x="${trimNumber(
|
|
3367
|
+
`x="${trimNumber(x)}"`,
|
|
3265
3368
|
`y="${trimNumber(node.y)}"`,
|
|
3266
3369
|
`dominant-baseline="text-before-edge"`,
|
|
3267
|
-
//
|
|
3268
|
-
//
|
|
3370
|
+
// SVG's default whitespace handling would collapse indentation and runs
|
|
3371
|
+
// of spaces away on re-import.
|
|
3269
3372
|
`xml:space="preserve"`,
|
|
3270
3373
|
`data-weasel-width="${trimNumber(node.width)}"`,
|
|
3271
3374
|
`data-weasel-height="${trimNumber(node.height)}"`
|
|
@@ -3302,11 +3405,13 @@ function textXml(node, registry, namespaces, warn) {
|
|
|
3302
3405
|
attrs.push(`data-weasel-vertical-align="${node.verticalAlign}"`);
|
|
3303
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)`);
|
|
3304
3407
|
}
|
|
3305
|
-
if (node.fill
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3408
|
+
if (node.fill !== void 0) {
|
|
3409
|
+
const paint = node.fill?.paint;
|
|
3410
|
+
if (!paint) attrs.push('fill="none"');
|
|
3411
|
+
else {
|
|
3412
|
+
attrs.push(`fill="${isSolidPaint(paint) ? escapeAttr(paint.color) : registry.ref(paint)}"`);
|
|
3413
|
+
attrs.push(...opacityAttr("fill-opacity", paint.opacity));
|
|
3414
|
+
}
|
|
3310
3415
|
}
|
|
3311
3416
|
for (const a of coreStrokeAttrs(node.stroke, registry, warn)) attrs.push(a);
|
|
3312
3417
|
if (node.opacity != null && node.opacity !== 1) {
|
|
@@ -3314,7 +3419,9 @@ function textXml(node, registry, namespaces, warn) {
|
|
|
3314
3419
|
}
|
|
3315
3420
|
const frame = boxTransformAttr(node);
|
|
3316
3421
|
if (frame) attrs.push(frame);
|
|
3317
|
-
const
|
|
3422
|
+
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
|
+
const resolved = resolveTextStyle(style);
|
|
3424
|
+
const body = linesXml(pieces, x, resolved.fontSize * resolved.lineHeight);
|
|
3318
3425
|
const metaAttrs = metaAttrsXml(node.meta, namespaces);
|
|
3319
3426
|
const metaEls = metaElementsXml(node.meta, namespaces);
|
|
3320
3427
|
return `<text ${attrs.join(" ")}${metaAttrs}>${body}${metaEls}</text>`;
|
|
@@ -3324,7 +3431,7 @@ function scriptSizeFace(run, style) {
|
|
|
3324
3431
|
const face = resolveRunFace(run, resolveTextStyle(style));
|
|
3325
3432
|
return { family: face.fontFamily, weight: face.fontWeight, style: face.fontStyle };
|
|
3326
3433
|
}
|
|
3327
|
-
function
|
|
3434
|
+
function runAttrs(run, style, registry, warn) {
|
|
3328
3435
|
const attrs = [];
|
|
3329
3436
|
if (run.fontWeight != null) attrs.push(`font-weight="${run.fontWeight}"`);
|
|
3330
3437
|
else if (run.bold) attrs.push(`font-weight="700"`);
|
|
@@ -3346,18 +3453,15 @@ function runXml(run, style, registry, warn) {
|
|
|
3346
3453
|
}
|
|
3347
3454
|
const runDecoration = textDecorationValue(run.underline, run.strikethrough, run.overline);
|
|
3348
3455
|
if (runDecoration) attrs.push(`text-decoration="${runDecoration}"`);
|
|
3349
|
-
if (run.fill
|
|
3350
|
-
|
|
3351
|
-
if (
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
attrs.push(`fill="${registry.ref(run.fill)}"`);
|
|
3355
|
-
}
|
|
3456
|
+
if (run.fill !== void 0) {
|
|
3457
|
+
const paint = run.fill?.paint;
|
|
3458
|
+
if (!paint) attrs.push('fill="none"');
|
|
3459
|
+
else if (isSolidPaint(paint)) attrs.push(`fill="${escapeAttr(paint.color)}"`);
|
|
3460
|
+
else attrs.push(`fill="${registry.ref(paint)}"`);
|
|
3356
3461
|
}
|
|
3357
3462
|
for (const a of coreStrokeAttrs(run.stroke, registry, warn)) attrs.push(a);
|
|
3358
3463
|
if (run.textTransform) attrs.push(`style="text-transform:${run.textTransform}"`);
|
|
3359
|
-
|
|
3360
|
-
return `${head}${escapeText(run.text)}</tspan>`;
|
|
3464
|
+
return attrs;
|
|
3361
3465
|
}
|
|
3362
3466
|
function textDecorationValue(underline, strikethrough, overline) {
|
|
3363
3467
|
const tokens = [];
|
|
@@ -3366,9 +3470,193 @@ function textDecorationValue(underline, strikethrough, overline) {
|
|
|
3366
3470
|
if (overline) tokens.push("overline");
|
|
3367
3471
|
return tokens.length > 0 ? tokens.join(" ") : null;
|
|
3368
3472
|
}
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3473
|
+
|
|
3474
|
+
// src/prettyXml.ts
|
|
3475
|
+
var TOKEN2 = /<(?:[^>"]|"[^"]*")*>|[^<]+/g;
|
|
3476
|
+
var isTag = (t) => t !== void 0 && t.startsWith("<");
|
|
3477
|
+
var isClose = (t) => t.startsWith("</");
|
|
3478
|
+
var isSelfClosing = (t) => t.endsWith("/>");
|
|
3479
|
+
var tagName = (t) => /^<\/?([^\s/>]+)/.exec(t)[1];
|
|
3480
|
+
function matchingClose(tokens, open) {
|
|
3481
|
+
let depth = 0;
|
|
3482
|
+
for (let i = open; i < tokens.length; i++) {
|
|
3483
|
+
const t = tokens[i];
|
|
3484
|
+
if (!isTag(t) || isSelfClosing(t)) continue;
|
|
3485
|
+
depth += isClose(t) ? -1 : 1;
|
|
3486
|
+
if (depth === 0) return i;
|
|
3487
|
+
}
|
|
3488
|
+
return tokens.length - 1;
|
|
3489
|
+
}
|
|
3490
|
+
function prettyXml(xml) {
|
|
3491
|
+
const tokens = xml.match(TOKEN2) ?? [];
|
|
3492
|
+
const lines = [];
|
|
3493
|
+
let depth = 0;
|
|
3494
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
3495
|
+
const t = tokens[i];
|
|
3496
|
+
if (!isTag(t) || isSelfClosing(t)) {
|
|
3497
|
+
lines.push(" ".repeat(depth) + t);
|
|
3498
|
+
} else if (isClose(t)) {
|
|
3499
|
+
depth--;
|
|
3500
|
+
lines.push(" ".repeat(depth) + t);
|
|
3501
|
+
} else if (tagName(t) === "text" || !isTag(tokens[i + 1])) {
|
|
3502
|
+
const close2 = matchingClose(tokens, i);
|
|
3503
|
+
lines.push(" ".repeat(depth) + tokens.slice(i, close2 + 1).join(""));
|
|
3504
|
+
i = close2;
|
|
3505
|
+
} else {
|
|
3506
|
+
lines.push(" ".repeat(depth) + t);
|
|
3507
|
+
depth++;
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3510
|
+
return lines.join("\n");
|
|
3511
|
+
}
|
|
3512
|
+
|
|
3513
|
+
// src/serialize.ts
|
|
3514
|
+
function serializeSvg(nodes, opts = {}) {
|
|
3515
|
+
const registry = new PaintServerRegistry();
|
|
3516
|
+
registerPaintServers(nodes, registry);
|
|
3517
|
+
const bounds = opts.viewBox ?? computeBounds(nodes);
|
|
3518
|
+
const vb = `${trimNumber(bounds.x)} ${trimNumber(bounds.y)} ${trimNumber(bounds.width)} ${trimNumber(bounds.height)}`;
|
|
3519
|
+
const namespaces = opts.namespaces ?? {};
|
|
3520
|
+
const rootAttrs = [`xmlns="http://www.w3.org/2000/svg"`];
|
|
3521
|
+
if (registry.usesPrivateNamespace() && namespaces[WEASEL_NS_PREFIX] == null) {
|
|
3522
|
+
rootAttrs.push(`xmlns:${WEASEL_NS_PREFIX}="${WEASEL_NS}"`);
|
|
3523
|
+
}
|
|
3524
|
+
for (const [prefix, uri] of Object.entries(namespaces)) {
|
|
3525
|
+
rootAttrs.push(`xmlns:${prefix}="${escapeAttr(uri)}"`);
|
|
3526
|
+
}
|
|
3527
|
+
rootAttrs.push(`viewBox="${vb}"`);
|
|
3528
|
+
if (opts.width != null) rootAttrs.push(`width="${trimNumber(opts.width)}"`);
|
|
3529
|
+
if (opts.height != null) rootAttrs.push(`height="${trimNumber(opts.height)}"`);
|
|
3530
|
+
if (opts.documentMeta) {
|
|
3531
|
+
for (const prefix of Object.keys(namespaces)) {
|
|
3532
|
+
const bucket = opts.documentMeta[prefix];
|
|
3533
|
+
if (!bucket?.attrs) continue;
|
|
3534
|
+
for (const [name, value] of Object.entries(bucket.attrs)) {
|
|
3535
|
+
rootAttrs.push(`${prefix}:${name}="${escapeAttr(value)}"`);
|
|
3536
|
+
}
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
3539
|
+
const docMetaXml = metaElementsXml(opts.documentMeta, namespaces);
|
|
3540
|
+
const defsXml = registry.toDefsXml(opts.onWarn);
|
|
3541
|
+
const said = /* @__PURE__ */ new Set();
|
|
3542
|
+
const warn = (m) => {
|
|
3543
|
+
if (!opts.onWarn || said.has(m)) return;
|
|
3544
|
+
said.add(m);
|
|
3545
|
+
opts.onWarn(m);
|
|
3546
|
+
};
|
|
3547
|
+
const bodyXml = nodes.map((n) => nodeXml(n, registry, namespaces, warn)).join("");
|
|
3548
|
+
const titleXml = opts.title && opts.title.length > 0 ? `<title>${escapeText(opts.title)}</title>` : "";
|
|
3549
|
+
const xml = `<svg ${rootAttrs.join(" ")}>${titleXml}${defsXml}${docMetaXml}${bodyXml}</svg>`;
|
|
3550
|
+
return opts.pretty ? prettyXml(xml) : xml;
|
|
3551
|
+
}
|
|
3552
|
+
function registerPaintServers(nodes, registry) {
|
|
3553
|
+
for (const n of nodes) {
|
|
3554
|
+
if (n.kind === "group") {
|
|
3555
|
+
if (n.clip) registry.clipId(n.clip);
|
|
3556
|
+
registerPaintServers(n.children, registry);
|
|
3557
|
+
} else if (n.kind === "path") {
|
|
3558
|
+
if (n.fill.kind === "gradient") registry.register(n.fill.paint);
|
|
3559
|
+
if (n.stroke && n.stroke.paint.kind === "gradient") registry.register(n.stroke.paint.paint);
|
|
3560
|
+
registerMarkers(n.stroke, registry);
|
|
3561
|
+
} else if (n.kind === "text") {
|
|
3562
|
+
registerTextPaint(n.fill?.paint, registry);
|
|
3563
|
+
registerTextPaint(n.stroke?.paint, registry);
|
|
3564
|
+
registerMarkers(n.stroke, registry);
|
|
3565
|
+
for (const run of n.runs ?? []) {
|
|
3566
|
+
registerTextPaint(run.fill?.paint, registry);
|
|
3567
|
+
registerTextPaint(run.stroke?.paint, registry);
|
|
3568
|
+
}
|
|
3569
|
+
}
|
|
3570
|
+
}
|
|
3571
|
+
}
|
|
3572
|
+
function registerTextPaint(paint, registry) {
|
|
3573
|
+
if (paint && !isSolidPaint(paint)) registry.register(paint);
|
|
3574
|
+
}
|
|
3575
|
+
function registerMarkers(stroke, registry) {
|
|
3576
|
+
if (!stroke) return;
|
|
3577
|
+
for (const ref of [stroke.markerStart, stroke.markerMid, stroke.markerEnd]) {
|
|
3578
|
+
if (ref !== void 0) registry.markerId(ref);
|
|
3579
|
+
}
|
|
3580
|
+
}
|
|
3581
|
+
function nodeXml(node, registry, namespaces, warn) {
|
|
3582
|
+
const xml = kindXml(node, registry, namespaces, warn);
|
|
3583
|
+
return node.hidden ? xml.replace(/^<[\w:]+/, (open) => `${open} display="none"`) : xml;
|
|
3584
|
+
}
|
|
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);
|
|
3588
|
+
if (node.kind === "image") return imageXml(node, namespaces);
|
|
3589
|
+
const drawn = pathAsSvgDraws(node);
|
|
3590
|
+
return drawn.kind === "group" ? groupXml(drawn, registry, namespaces, warn) : pathXml(drawn, registry, namespaces, warn);
|
|
3591
|
+
}
|
|
3592
|
+
function imageXml(node, namespaces) {
|
|
3593
|
+
const box = `x="${trimNumber(node.x)}" y="${trimNumber(node.y)}" width="${trimNumber(node.width)}" height="${trimNumber(node.height)}"`;
|
|
3594
|
+
const outer = [];
|
|
3595
|
+
if (node.opacity != null && node.opacity !== 1) {
|
|
3596
|
+
outer.push(`opacity="${trimNumber(node.opacity)}"`);
|
|
3597
|
+
}
|
|
3598
|
+
const frame = boxTransformAttr(node);
|
|
3599
|
+
if (frame) outer.push(frame);
|
|
3600
|
+
const metaAttrs = metaAttrsXml(node.meta, namespaces);
|
|
3601
|
+
const metaEls = metaElementsXml(node.meta, namespaces);
|
|
3602
|
+
const href = `href="${escapeAttr(node.href)}"`;
|
|
3603
|
+
if (!node.source && !node.flipX && !node.flipY) {
|
|
3604
|
+
const attrs = [href, box, 'preserveAspectRatio="none"', ...outer].join(" ");
|
|
3605
|
+
if (metaEls) return `<image ${attrs}${metaAttrs}>${metaEls}</image>`;
|
|
3606
|
+
return `<image ${attrs}${metaAttrs}/>`;
|
|
3607
|
+
}
|
|
3608
|
+
const src = node.source ?? { x: 0, y: 0, width: 1, height: 1 };
|
|
3609
|
+
const viewBox = [src.x, src.y, src.width, src.height].map(trimNumber).join(" ");
|
|
3610
|
+
const flip = node.flipX || node.flipY ? ` transform="matrix(${node.flipX ? -1 : 1} 0 0 ${node.flipY ? -1 : 1} ${trimNumber(node.flipX ? 2 * src.x + src.width : 0)} ${trimNumber(node.flipY ? 2 * src.y + src.height : 0)})"` : "";
|
|
3611
|
+
const head = ['<g data-weasel-image="true"', ...outer].join(" ");
|
|
3612
|
+
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
|
+
}
|
|
3614
|
+
function groupXml(node, registry, namespaces, warn) {
|
|
3615
|
+
const attrs = [];
|
|
3616
|
+
if (node.transform) {
|
|
3617
|
+
const m = formatMatrix(node.transform);
|
|
3618
|
+
if (m) attrs.push(`transform="${m}"`);
|
|
3619
|
+
}
|
|
3620
|
+
if (node.clip) {
|
|
3621
|
+
attrs.push(`clip-path="url(#${registry.clipId(node.clip)})"`);
|
|
3622
|
+
}
|
|
3623
|
+
if (node.opacity != null && node.opacity !== 1) {
|
|
3624
|
+
attrs.push(`opacity="${trimNumber(node.opacity)}"`);
|
|
3625
|
+
}
|
|
3626
|
+
const metaAttrs = metaAttrsXml(node.meta, namespaces);
|
|
3627
|
+
const head = attrs.length > 0 ? `<g ${attrs.join(" ")}${metaAttrs}>` : `<g${metaAttrs}>`;
|
|
3628
|
+
const body = node.children.map((c) => nodeXml(c, registry, namespaces, warn)).join("");
|
|
3629
|
+
return `${head}${body}${metaElementsXml(node.meta, namespaces)}</g>`;
|
|
3630
|
+
}
|
|
3631
|
+
function pathXml(node, registry, namespaces, warn) {
|
|
3632
|
+
const attrs = [`d="${serializePathD(node.path)}"`];
|
|
3633
|
+
const fillAttrs = paintAttrs(node.fill, "fill", registry);
|
|
3634
|
+
for (const a of fillAttrs) attrs.push(a);
|
|
3635
|
+
if (node.path.kind === "polygon" && node.path.fillRule === "evenodd") {
|
|
3636
|
+
attrs.push(`fill-rule="evenodd"`);
|
|
3637
|
+
}
|
|
3638
|
+
if (node.stroke) {
|
|
3639
|
+
const strokeAttrs = strokeAttrsFor(node.stroke, registry, warn);
|
|
3640
|
+
for (const a of strokeAttrs) attrs.push(a);
|
|
3641
|
+
} else {
|
|
3642
|
+
attrs.push('stroke="none"');
|
|
3643
|
+
}
|
|
3644
|
+
if (node.opacity != null && node.opacity !== 1) {
|
|
3645
|
+
attrs.push(`opacity="${trimNumber(node.opacity)}"`);
|
|
3646
|
+
}
|
|
3647
|
+
if (node.rotation != null && node.rotation !== 0) {
|
|
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
|
+
}
|
|
3654
|
+
const metaAttrs = metaAttrsXml(node.meta, namespaces);
|
|
3655
|
+
const metaEls = metaElementsXml(node.meta, namespaces);
|
|
3656
|
+
if (metaEls) {
|
|
3657
|
+
return `<path ${attrs.join(" ")}${metaAttrs}>${metaEls}</path>`;
|
|
3658
|
+
}
|
|
3659
|
+
return `<path ${attrs.join(" ")}${metaAttrs}/>`;
|
|
3372
3660
|
}
|
|
3373
3661
|
function svgNeeds(nodes) {
|
|
3374
3662
|
const registry = new PaintServerRegistry();
|
|
@@ -3395,6 +3683,22 @@ function warmSvg(nodes) {
|
|
|
3395
3683
|
warmPaintKinds(needs.paintKinds.filter(isPaintKindKnown))
|
|
3396
3684
|
]).then(() => void 0);
|
|
3397
3685
|
}
|
|
3686
|
+
|
|
3687
|
+
// src/fadePaint.ts
|
|
3688
|
+
function fadeFill(fill, k) {
|
|
3689
|
+
return k === 1 ? fill : { ...fill, opacity: (fill.opacity ?? 1) * k };
|
|
3690
|
+
}
|
|
3691
|
+
function fadeStroke(stroke, k) {
|
|
3692
|
+
return k === 1 || !stroke.paint ? stroke : { ...stroke, paint: fadeFill(stroke.paint, k) };
|
|
3693
|
+
}
|
|
3694
|
+
function fadeRun(run, k) {
|
|
3695
|
+
if (k === 1 || !run.fill && !run.stroke) return run;
|
|
3696
|
+
return {
|
|
3697
|
+
...run,
|
|
3698
|
+
...run.fill ? { fill: mapFillPaint(run.fill, (p) => fadeFill(p, k)) } : {},
|
|
3699
|
+
...run.stroke ? { stroke: fadeStroke(run.stroke, k) } : {}
|
|
3700
|
+
};
|
|
3701
|
+
}
|
|
3398
3702
|
var CASCADE_OFFSET_PX = 24;
|
|
3399
3703
|
var VIEWPORT_FIT = 0.9;
|
|
3400
3704
|
function readFileText(file) {
|
|
@@ -3413,20 +3717,6 @@ function fillDataFromSvg(paint, box) {
|
|
|
3413
3717
|
}
|
|
3414
3718
|
return fillInBoxFrame(paint.paint, box);
|
|
3415
3719
|
}
|
|
3416
|
-
function fadeFill(fill, k) {
|
|
3417
|
-
return k === 1 ? fill : { ...fill, opacity: (fill.opacity ?? 1) * k };
|
|
3418
|
-
}
|
|
3419
|
-
function fadeStroke(stroke, k) {
|
|
3420
|
-
return k === 1 || !stroke.paint ? stroke : { ...stroke, paint: fadeFill(stroke.paint, k) };
|
|
3421
|
-
}
|
|
3422
|
-
function fadeRun(run, k) {
|
|
3423
|
-
if (k === 1 || !run.fill && !run.stroke) return run;
|
|
3424
|
-
return {
|
|
3425
|
-
...run,
|
|
3426
|
-
...run.fill ? { fill: fadeFill(run.fill, k) } : {},
|
|
3427
|
-
...run.stroke ? { stroke: fadeStroke(run.stroke, k) } : {}
|
|
3428
|
-
};
|
|
3429
|
-
}
|
|
3430
3720
|
function strokeDataFromSvg(stroke, box) {
|
|
3431
3721
|
if (!stroke || stroke.paint.kind === "none") return void 0;
|
|
3432
3722
|
const paint = fillDataFromSvg(stroke.paint, box);
|
|
@@ -3448,7 +3738,7 @@ function fillInBoxFrame(fill, box) {
|
|
|
3448
3738
|
return units === "world" ? fillToBoundsFrame(fill, box) : fill;
|
|
3449
3739
|
}
|
|
3450
3740
|
function runInBoxFrame(run, box) {
|
|
3451
|
-
const fill = run.fill ?
|
|
3741
|
+
const fill = run.fill ? mapFillPaint(run.fill, (p) => fillInBoxFrame(p, box)) : run.fill;
|
|
3452
3742
|
const stroke = run.stroke ? strokeInBoxFrame(run.stroke, box) : run.stroke;
|
|
3453
3743
|
if (fill === run.fill && stroke === run.stroke) return run;
|
|
3454
3744
|
return { ...run, ...fill ? { fill } : {}, ...stroke ? { stroke } : {} };
|
|
@@ -3468,7 +3758,7 @@ function svgNodesToKitDrafts(input, nextId, options = {}) {
|
|
|
3468
3758
|
const id = nextId(source);
|
|
3469
3759
|
const out = options.leaf ? options.leaf(id, { pose, data }, source) : data;
|
|
3470
3760
|
if (out === null) return null;
|
|
3471
|
-
drafts.push({ kind: "leaf", id, parentId, pose, data: out });
|
|
3761
|
+
drafts.push({ kind: "leaf", id, parentId, pose, data: out, ...source.hidden ? { hidden: true } : {} });
|
|
3472
3762
|
return pose;
|
|
3473
3763
|
};
|
|
3474
3764
|
const visit = (n, parentId, inherited2) => {
|
|
@@ -3478,7 +3768,9 @@ function svgNodesToKitDrafts(input, nextId, options = {}) {
|
|
|
3478
3768
|
kind: "container",
|
|
3479
3769
|
id: nextId(n),
|
|
3480
3770
|
parentId,
|
|
3481
|
-
pose: { x: 0, y: 0, width: 0, height: 0 }
|
|
3771
|
+
pose: { x: 0, y: 0, width: 0, height: 0 },
|
|
3772
|
+
data: n.clip ? { path: n.clip, fill: null } : {},
|
|
3773
|
+
...n.hidden ? { hidden: true } : {}
|
|
3482
3774
|
};
|
|
3483
3775
|
drafts.push(draft);
|
|
3484
3776
|
const acc = unionAABB(n.children.map((c) => visit(c, draft.id, k)));
|
|
@@ -3486,7 +3778,7 @@ function svgNodesToKitDrafts(input, nextId, options = {}) {
|
|
|
3486
3778
|
drafts.splice(drafts.indexOf(draft), 1);
|
|
3487
3779
|
return null;
|
|
3488
3780
|
}
|
|
3489
|
-
draft.pose = acc;
|
|
3781
|
+
draft.pose = n.clip ? boxOfPath(n.clip) : acc;
|
|
3490
3782
|
return draft.pose;
|
|
3491
3783
|
}
|
|
3492
3784
|
if (n.kind === "text") {
|
|
@@ -3512,7 +3804,7 @@ function svgNodesToKitDrafts(input, nextId, options = {}) {
|
|
|
3512
3804
|
...n.runs ? { runs: n.runs.map((r) => fadeRun(runInBoxFrame(r, box), k)) } : {},
|
|
3513
3805
|
// `!== undefined`, not a truthiness test: `null` is the document
|
|
3514
3806
|
// saying `fill="none"`, and absent takes the painter's default.
|
|
3515
|
-
...fill2 !== void 0 ? { fill: fill2
|
|
3807
|
+
...fill2 !== void 0 ? { fill: fill2 && mapFillPaint(fill2, (p) => fadeFill(fillInBoxFrame(p, box), k)) } : {},
|
|
3516
3808
|
...n.stroke !== void 0 ? { stroke: fadeStroke(strokeInBoxFrame(n.stroke, box), k) } : {}
|
|
3517
3809
|
});
|
|
3518
3810
|
}
|
|
@@ -3530,20 +3822,24 @@ function svgNodesToKitDrafts(input, nextId, options = {}) {
|
|
|
3530
3822
|
}
|
|
3531
3823
|
});
|
|
3532
3824
|
}
|
|
3533
|
-
const b =
|
|
3825
|
+
const b = boxOfPath(n.path);
|
|
3534
3826
|
const pose = { x: b.x, y: b.y, width: b.width, height: b.height };
|
|
3535
3827
|
if (n.rotation) pose.rotation = n.rotation;
|
|
3536
3828
|
const fill = fillDataFromSvg(n.fill, b);
|
|
3537
3829
|
const strokeFromSvg = strokeDataFromSvg(n.stroke, b);
|
|
3538
3830
|
return pushLeaf(n, parentId, pose, {
|
|
3539
3831
|
path: n.path,
|
|
3540
|
-
...fill !== void 0 ? { fill: fill && fadeFill(fill, k) } : {},
|
|
3832
|
+
...fill !== void 0 ? { fill: fill && { paint: fadeFill(fill, k) } } : {},
|
|
3541
3833
|
...strokeFromSvg !== void 0 ? { stroke: fadeStroke(strokeFromSvg, k) } : {}
|
|
3542
3834
|
});
|
|
3543
3835
|
};
|
|
3544
3836
|
for (const n of nodes) visit(n, null, 1);
|
|
3545
3837
|
return drafts;
|
|
3546
3838
|
}
|
|
3839
|
+
function boxOfPath(path) {
|
|
3840
|
+
const b = boundsOfPath(path);
|
|
3841
|
+
return { x: b.x, y: b.y, width: b.width, height: b.height };
|
|
3842
|
+
}
|
|
3547
3843
|
function isNodeList(input) {
|
|
3548
3844
|
return Array.isArray(input);
|
|
3549
3845
|
}
|
|
@@ -3575,7 +3871,7 @@ async function unpackSvgFiles(files, ctx) {
|
|
|
3575
3871
|
const wrapperId = ctx.scene.mintId();
|
|
3576
3872
|
const union2 = unionAABB(roots.map((d) => d.pose));
|
|
3577
3873
|
drafts = [
|
|
3578
|
-
{ kind: "container", id: wrapperId, parentId: null, pose: union2 },
|
|
3874
|
+
{ kind: "container", id: wrapperId, parentId: null, pose: union2, data: {} },
|
|
3579
3875
|
...drafts.map((d) => d.parentId === null ? { ...d, parentId: wrapperId } : d)
|
|
3580
3876
|
];
|
|
3581
3877
|
}
|
|
@@ -3607,7 +3903,7 @@ async function unpackSvgFiles(files, ctx) {
|
|
|
3607
3903
|
kind: d.kind,
|
|
3608
3904
|
layer,
|
|
3609
3905
|
pose: place(d.pose),
|
|
3610
|
-
data: d.kind === "leaf" ? scaleLeafData(d.data, scale) :
|
|
3906
|
+
data: d.kind === "leaf" ? scaleLeafData(d.data, scale) : d.data,
|
|
3611
3907
|
parent: d.parentId
|
|
3612
3908
|
},
|
|
3613
3909
|
label: "Insert SVG"
|
|
@@ -3622,7 +3918,7 @@ async function unpackSvgFiles(files, ctx) {
|
|
|
3622
3918
|
}
|
|
3623
3919
|
function svgPaintFromKit(fill, box) {
|
|
3624
3920
|
if (fill === null) return { kind: "none" };
|
|
3625
|
-
if (fill.
|
|
3921
|
+
if (fill.kind === void 0 || fill.kind === "solid") {
|
|
3626
3922
|
return { kind: "solid", color: fill.color, ...fill.opacity != null ? { opacity: fill.opacity } : {} };
|
|
3627
3923
|
}
|
|
3628
3924
|
return { kind: "gradient", paint: fillInPoseFrame(fill, box) };
|
|
@@ -3670,7 +3966,7 @@ function runInPoseFrame(run, box) {
|
|
|
3670
3966
|
if (!run.fill && !run.stroke) return run;
|
|
3671
3967
|
return {
|
|
3672
3968
|
...run,
|
|
3673
|
-
...run.fill ? { fill:
|
|
3969
|
+
...run.fill ? { fill: mapFillPaint(run.fill, (p) => fillInPoseFrame(p, box)) } : {},
|
|
3674
3970
|
...run.stroke ? { stroke: strokeInPoseFrame(run.stroke, box) } : {}
|
|
3675
3971
|
};
|
|
3676
3972
|
}
|
|
@@ -3688,7 +3984,7 @@ function svgLeafFromKit(data, pose) {
|
|
|
3688
3984
|
if (data.style) node2.style = data.style;
|
|
3689
3985
|
if (data.runs) node2.runs = data.runs.map((r) => runInPoseFrame(r, pose));
|
|
3690
3986
|
if (data.verticalAlign) node2.verticalAlign = data.verticalAlign;
|
|
3691
|
-
if (data.fill !== void 0) node2.fill = data.fill &&
|
|
3987
|
+
if (data.fill !== void 0) node2.fill = data.fill && mapFillPaint(data.fill, (p) => fillInPoseFrame(p, pose));
|
|
3692
3988
|
if (data.stroke) node2.stroke = strokeInPoseFrame(data.stroke, pose);
|
|
3693
3989
|
if (pose.rotation) node2.rotation = pose.rotation;
|
|
3694
3990
|
if (pose.transform) node2.transform = pose.transform;
|
|
@@ -3700,10 +3996,15 @@ function svgLeafFromKit(data, pose) {
|
|
|
3700
3996
|
const node = {
|
|
3701
3997
|
kind: "path",
|
|
3702
3998
|
path: pathInPoseFrame(data.path, pose),
|
|
3703
|
-
fill: svgPaintFromKit(fill, pose)
|
|
3999
|
+
fill: svgPaintFromKit(fill?.paint ?? null, pose)
|
|
3704
4000
|
};
|
|
3705
4001
|
if (stroke) node.stroke = stroke;
|
|
3706
|
-
|
|
4002
|
+
const frame = poseLinearOf(pose) && poseFrameOf(pose);
|
|
4003
|
+
if (frame) {
|
|
4004
|
+
node.path = pathThroughPoseFrame(node.path, pose);
|
|
4005
|
+
node.fill = paintThroughMatrix(node.fill, frame);
|
|
4006
|
+
if (stroke) node.stroke = strokeThroughMatrix(stroke, frame);
|
|
4007
|
+
} else if (pose.rotation) node.rotation = pose.rotation;
|
|
3707
4008
|
return node;
|
|
3708
4009
|
}
|
|
3709
4010
|
function svgNodesFromKit(tree, options = {}) {
|
|
@@ -3713,12 +4014,25 @@ function svgNodesFromKit(tree, options = {}) {
|
|
|
3713
4014
|
const node = tree.get(id);
|
|
3714
4015
|
if (!node) return null;
|
|
3715
4016
|
if (node.kind === "container") {
|
|
3716
|
-
const
|
|
3717
|
-
|
|
3718
|
-
const
|
|
3719
|
-
|
|
4017
|
+
const emitChildren = () => {
|
|
4018
|
+
const children = [];
|
|
4019
|
+
for (const c of tree.childrenOf(id)) {
|
|
4020
|
+
const child = emit(c);
|
|
4021
|
+
if (child) children.push(child);
|
|
4022
|
+
}
|
|
4023
|
+
return children;
|
|
4024
|
+
};
|
|
4025
|
+
const path = node.data?.path;
|
|
4026
|
+
const clip = path ? pathThroughPoseFrame(pathInPoseFrame(path, node.pose), node.pose) : void 0;
|
|
4027
|
+
const copies = node.copiesFromPose?.(node.pose, node) ?? [];
|
|
4028
|
+
const content = () => ({ kind: "group", children: emitChildren(), ...clip ? { clip } : {} });
|
|
4029
|
+
let g = content();
|
|
4030
|
+
if (copies.length > 0) {
|
|
4031
|
+
g = {
|
|
4032
|
+
kind: "group",
|
|
4033
|
+
children: [g, ...copies.map((transform) => ({ kind: "group", transform, children: [content()] }))]
|
|
4034
|
+
};
|
|
3720
4035
|
}
|
|
3721
|
-
const g = { kind: "group", children };
|
|
3722
4036
|
return group ? group(id, g) : g;
|
|
3723
4037
|
}
|
|
3724
4038
|
return leaf ? leaf(id, node) : svgLeafFromKit(node.data, node.pose);
|