@weasel-js/svg 1.9.4 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -8
- package/dist/index.d.ts +129 -145
- package/dist/index.js +456 -275
- 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, resolveScreenLength, scriptMetricsFor, warmFonts, warmPaintKinds, isPaintKindKnown, solid, fillToBoundsFrame, getMarker, registerMarker, dwarn, unionAABB, createInsertOp, fillInPoseFrame, DEFAULT_SHAPE_FILL, pathInPoseFrame, DEFAULT_TEXT_STYLE, pathFromD,
|
|
1
|
+
import { identity, multiply, invert, fillsOpenSubpaths, splitByClosure, splitSubpaths, rotateAboutPoint, hasOpenSubpath } from '@weasel-js/geom';
|
|
2
|
+
import { isTileKind, boundsOfPath, transformPath, isSolidPaint, resolveScreenLength, scriptMetricsFor, warmFonts, warmPaintKinds, isPaintKindKnown, solid, fillToBoundsFrame, getMarker, registerMarker, dwarn, unionAABB, createInsertOp, fillInPoseFrame, DEFAULT_SHAPE_FILL, pathInPoseFrame, poseLinearOf, poseFrameOf, pathThroughPoseFrame, DEFAULT_TEXT_STYLE, pathFromD, isPatternPaint, PATH_M, PATH_L, PATH_C, PATH_Q, PATH_Z, resolveAlign, paragraphDirection, resolveRunFace, resolveTextStyle, parseColor, rgbaToHex, layoutRuns, resolveRuns, PathBuilder, getPaintKind, resolveMarkerSize, 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) {
|
|
@@ -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,48 @@ function anchorOffset(style, width2, text) {
|
|
|
2187
2238
|
}
|
|
2188
2239
|
|
|
2189
2240
|
// src/tspanLines.ts
|
|
2241
|
+
var DEFAULT_LINE_HEIGHT = 1.2;
|
|
2190
2242
|
var LineTracker = class {
|
|
2191
|
-
|
|
2192
|
-
|
|
2243
|
+
constructor(text, onWarn) {
|
|
2244
|
+
this.onWarn = onWarn;
|
|
2193
2245
|
this.y = text.getAttribute("y");
|
|
2246
|
+
this.at = readLength(text, "y", "y", 0, onWarn);
|
|
2194
2247
|
}
|
|
2195
|
-
|
|
2196
|
-
|
|
2248
|
+
onWarn;
|
|
2249
|
+
y;
|
|
2250
|
+
/** Where the current line sits, in user units. */
|
|
2251
|
+
at;
|
|
2252
|
+
pitches = [];
|
|
2253
|
+
/** Whether `tspan` opens a new line. Call once per tspan, in order.
|
|
2254
|
+
* `afterText` is whether any text precedes it: the first line's own
|
|
2255
|
+
* placement is not a pitch. */
|
|
2256
|
+
startsLine(tspan, afterText) {
|
|
2197
2257
|
const y = tspan.getAttribute("y");
|
|
2198
2258
|
const movedY = y != null && y !== this.y;
|
|
2199
2259
|
if (y != null) this.y = y;
|
|
2260
|
+
const from = this.at;
|
|
2261
|
+
const dy = readLength(tspan, "dy", "y", 0, this.onWarn);
|
|
2262
|
+
this.at = (y != null ? readLength(tspan, "y", "y", from, this.onWarn) : from) + dy;
|
|
2200
2263
|
if (!tspan.hasAttribute("x")) return false;
|
|
2201
|
-
const
|
|
2202
|
-
|
|
2264
|
+
const starts = movedY || dy !== 0;
|
|
2265
|
+
if (starts && afterText && this.at > from) this.pitches.push(this.at - from);
|
|
2266
|
+
return starts;
|
|
2267
|
+
}
|
|
2268
|
+
/**
|
|
2269
|
+
* The line height the lines were pitched at, as a multiple of `fontSize`:
|
|
2270
|
+
* `undefined` for a single line, or for lines at the default pitch. The
|
|
2271
|
+
* model holds one line height, so uneven pitches give their mean, which
|
|
2272
|
+
* keeps the block's height.
|
|
2273
|
+
*/
|
|
2274
|
+
lineHeight(fontSize) {
|
|
2275
|
+
const { pitches } = this;
|
|
2276
|
+
if (pitches.length === 0 || !(fontSize > 0)) return void 0;
|
|
2277
|
+
const mean = pitches.reduce((sum, p) => sum + p, 0) / pitches.length;
|
|
2278
|
+
if (pitches.some((p) => Math.abs(p - mean) > 1e-3 * fontSize)) {
|
|
2279
|
+
this.onWarn("<text> lines are pitched unevenly; they take the mean pitch as one line height");
|
|
2280
|
+
}
|
|
2281
|
+
const lineHeight = mean / fontSize;
|
|
2282
|
+
return Math.abs(lineHeight - DEFAULT_LINE_HEIGHT) < 1e-6 ? void 0 : lineHeight;
|
|
2203
2283
|
}
|
|
2204
2284
|
};
|
|
2205
2285
|
function warnUnreadPositions(el, onWarn) {
|
|
@@ -2229,6 +2309,13 @@ function joinLines(lines) {
|
|
|
2229
2309
|
return out;
|
|
2230
2310
|
}
|
|
2231
2311
|
|
|
2312
|
+
// src/fillPaint.ts
|
|
2313
|
+
function mapFillPaint(fill, f) {
|
|
2314
|
+
if (!fill.paint) return fill;
|
|
2315
|
+
const paint = f(fill.paint);
|
|
2316
|
+
return paint === fill.paint ? fill : { ...fill, paint };
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2232
2319
|
// src/readText.ts
|
|
2233
2320
|
var XML_NS = "http://www.w3.org/XML/1998/namespace";
|
|
2234
2321
|
function preservesSpace(el) {
|
|
@@ -2298,12 +2385,12 @@ function placeTextNode(node, m, onWarn) {
|
|
|
2298
2385
|
if (node.runs) node.runs = scaleRuns(node.runs, scale);
|
|
2299
2386
|
if (node.stroke) node.stroke = scaleStroke(node.stroke, scale);
|
|
2300
2387
|
}
|
|
2301
|
-
if (node.fill) node.fill =
|
|
2388
|
+
if (node.fill) node.fill = mapFillPaint(node.fill, (p) => fillThroughMatrix(p, inBox));
|
|
2302
2389
|
if (node.stroke?.paint) {
|
|
2303
2390
|
node.stroke = { ...node.stroke, paint: fillThroughMatrix(node.stroke.paint, inBox) };
|
|
2304
2391
|
}
|
|
2305
2392
|
if (node.runs) {
|
|
2306
|
-
node.runs = node.runs.map((r) => r.fill ? { ...r, fill:
|
|
2393
|
+
node.runs = node.runs.map((r) => r.fill ? { ...r, fill: mapFillPaint(r.fill, (p) => fillThroughMatrix(p, inBox)) } : r);
|
|
2307
2394
|
}
|
|
2308
2395
|
}
|
|
2309
2396
|
function parseTextElement(el, ctm, style, gradients, onWarn) {
|
|
@@ -2320,13 +2407,12 @@ function parseTextElement(el, ctm, style, gradients, onWarn) {
|
|
|
2320
2407
|
const textStyle = readTextStyle(leafStyle, onWarn);
|
|
2321
2408
|
const textPaint = readTextPaint(leafStyle, gradients, onWarn, isNonScalingStroke(el));
|
|
2322
2409
|
const fontSize = resolveScreenLength(textStyle.fontSize ?? 16, 1);
|
|
2323
|
-
const lineHeight = textStyle.lineHeight ?? 1.2;
|
|
2324
2410
|
const dominantBaseline = el.getAttribute("dominant-baseline");
|
|
2325
2411
|
const explicitTopAnchor = dominantBaseline === "text-before-edge" || dominantBaseline === "hanging";
|
|
2326
2412
|
const topY = explicitTopAnchor ? ay : ay - fontSize;
|
|
2327
2413
|
const inheritedShift = ownValue(el, "baseline-shift", onWarn) ?? {};
|
|
2328
2414
|
const runLines = [[]];
|
|
2329
|
-
const tracker = new LineTracker(el);
|
|
2415
|
+
const tracker = new LineTracker(el, onWarn);
|
|
2330
2416
|
const push = (run) => {
|
|
2331
2417
|
runLines[runLines.length - 1].push(run);
|
|
2332
2418
|
};
|
|
@@ -2345,13 +2431,17 @@ function parseTextElement(el, ctm, style, gradients, onWarn) {
|
|
|
2345
2431
|
continue;
|
|
2346
2432
|
}
|
|
2347
2433
|
warnUnreadPositions(sp, onWarn);
|
|
2348
|
-
|
|
2434
|
+
const afterText = runLines.some((line) => line.some((r) => r.text.trim() !== ""));
|
|
2435
|
+
if (tracker.startsLine(sp, afterText)) runLines.push([]);
|
|
2349
2436
|
push(readTspanRun(sp, gradients, leafStyle, onWarn));
|
|
2350
2437
|
}
|
|
2351
2438
|
}
|
|
2352
2439
|
if (!preservesSpace(el)) for (const line of runLines) collapseRunWhitespace(line);
|
|
2353
2440
|
const runs = joinLines(runLines);
|
|
2354
2441
|
const plain = runs.map((r) => r.text).join("");
|
|
2442
|
+
const pitched = tracker.lineHeight(fontSize);
|
|
2443
|
+
if (pitched !== void 0) textStyle.lineHeight = pitched;
|
|
2444
|
+
const lineHeight = textStyle.lineHeight ?? DEFAULT_LINE_HEIGHT;
|
|
2355
2445
|
const dataW = num2(el.getAttribute("data-weasel-width"), NaN);
|
|
2356
2446
|
const dataH = num2(el.getAttribute("data-weasel-height"), NaN);
|
|
2357
2447
|
const width2 = Number.isFinite(dataW) ? dataW : laidOutWidth(runs, textStyle, plain);
|
|
@@ -2419,11 +2509,11 @@ function readTspanRun(el, gradients, style, onWarn) {
|
|
|
2419
2509
|
if (parsed?.kind === "none") {
|
|
2420
2510
|
run.fill = null;
|
|
2421
2511
|
} else if (parsed?.kind === "solid") {
|
|
2422
|
-
run.fill = {
|
|
2512
|
+
run.fill = { paint: { kind: "solid", color: parsed.color } };
|
|
2423
2513
|
} else if (parsed?.kind === "ref") {
|
|
2424
2514
|
const paint = gradients.get(parsed.id);
|
|
2425
|
-
if (paint) run.fill = paint;
|
|
2426
|
-
else if (parsed.fallback) run.fill = {
|
|
2515
|
+
if (paint) run.fill = { paint };
|
|
2516
|
+
else if (parsed.fallback) run.fill = { paint: { kind: "solid", color: parsed.fallback.color } };
|
|
2427
2517
|
}
|
|
2428
2518
|
}
|
|
2429
2519
|
const stroke = coreStroke(readStroke(ownStrokeStyle(el, tspanStyle), gradients, onWarn, isNonScalingStroke(el)));
|
|
@@ -2465,12 +2555,12 @@ function readTextPaint(style, gradients, onWarn, nonScalingStroke = false) {
|
|
|
2465
2555
|
if (fillRaw) {
|
|
2466
2556
|
const parsed = readProperty("fill", fillRaw);
|
|
2467
2557
|
if (parsed?.kind === "solid") {
|
|
2468
|
-
out.fill = {
|
|
2558
|
+
out.fill = { paint: { kind: "solid", color: parsed.color } };
|
|
2469
2559
|
} else if (parsed?.kind === "ref") {
|
|
2470
2560
|
const paint = gradients.get(parsed.id);
|
|
2471
|
-
if (paint) out.fill = paint;
|
|
2561
|
+
if (paint) out.fill = { paint };
|
|
2472
2562
|
else if (parsed.fallback) {
|
|
2473
|
-
out.fill = {
|
|
2563
|
+
out.fill = { paint: { kind: "solid", color: parsed.fallback.color } };
|
|
2474
2564
|
}
|
|
2475
2565
|
} else if (parsed?.kind === "none") {
|
|
2476
2566
|
out.fill = null;
|
|
@@ -2726,7 +2816,7 @@ function parseSvg(svg, opts = {}) {
|
|
|
2726
2816
|
for (const [id, paint] of collectPatterns(root, onWarn)) gradients.set(id, paint);
|
|
2727
2817
|
const rootStyle = deriveStyle(EMPTY_STYLE, root);
|
|
2728
2818
|
const clips = collectClipPaths(root, onWarn);
|
|
2729
|
-
const nodes = parseChildren(root, IDENTITY_MATRIX, rootStyle, gradients, clips, onWarn, uriToPrefix);
|
|
2819
|
+
const nodes = parseChildren(root, IDENTITY_MATRIX, rootStyle, gradients, clips, onWarn, uriToPrefix, opts.keepHidden === true);
|
|
2730
2820
|
const markers = ingestMarkers(root, nodes, gradients, onWarn);
|
|
2731
2821
|
const result = { nodes, warnings };
|
|
2732
2822
|
if (markers.length > 0) result.markers = markers;
|
|
@@ -2827,26 +2917,25 @@ function collectElementMeta(el, uriToPrefix) {
|
|
|
2827
2917
|
}
|
|
2828
2918
|
return Object.keys(meta).length > 0 ? meta : void 0;
|
|
2829
2919
|
}
|
|
2830
|
-
function parseChildren(parent, ctm, style, gradients, clips, onWarn, uriToPrefix) {
|
|
2920
|
+
function parseChildren(parent, ctm, style, gradients, clips, onWarn, uriToPrefix, keepHidden) {
|
|
2831
2921
|
const out = [];
|
|
2832
2922
|
for (let i = 0; i < parent.children.length; i++) {
|
|
2833
2923
|
const el = parent.children[i];
|
|
2834
2924
|
const ns = el.namespaceURI;
|
|
2835
2925
|
if (ns && ns !== "http://www.w3.org/2000/svg") continue;
|
|
2836
|
-
const
|
|
2837
|
-
if (
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2926
|
+
const hidden = isHiddenElement(el, style);
|
|
2927
|
+
if (hidden && !keepHidden) continue;
|
|
2928
|
+
const node = parseElement(el, ctm, style, gradients, clips, onWarn, uriToPrefix, keepHidden);
|
|
2929
|
+
if (!node) continue;
|
|
2930
|
+
const parsed = Array.isArray(node) ? node : [node];
|
|
2931
|
+
if (hidden) for (const n of parsed) n.hidden = true;
|
|
2932
|
+
out.push(...parsed);
|
|
2841
2933
|
}
|
|
2842
2934
|
return out;
|
|
2843
2935
|
}
|
|
2844
|
-
function parseElement(el, ctm, style, gradients, clips, onWarn, uriToPrefix) {
|
|
2936
|
+
function parseElement(el, ctm, style, gradients, clips, onWarn, uriToPrefix, keepHidden) {
|
|
2845
2937
|
const tag = el.tagName.toLowerCase();
|
|
2846
2938
|
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
2939
|
if (tag === "g" && el.hasAttribute("data-weasel-image")) {
|
|
2851
2940
|
const image = parseCroppedImage(el, ctm, onWarn);
|
|
2852
2941
|
if (image) {
|
|
@@ -2863,7 +2952,7 @@ function parseElement(el, ctm, style, gradients, clips, onWarn, uriToPrefix) {
|
|
|
2863
2952
|
const local = parseTransform(el.getAttribute("transform"), onWarn);
|
|
2864
2953
|
const childCtm = multiply(ctm, local);
|
|
2865
2954
|
const childStyle = deriveStyle(style, el);
|
|
2866
|
-
const children = parseChildren(el, childCtm, childStyle, gradients, clips, onWarn, uriToPrefix);
|
|
2955
|
+
const children = parseChildren(el, childCtm, childStyle, gradients, clips, onWarn, uriToPrefix, keepHidden);
|
|
2867
2956
|
const opacity2 = ownValue(el, "opacity");
|
|
2868
2957
|
const group = { kind: "group", children };
|
|
2869
2958
|
const clip = clipFor(el, childCtm, clips, onWarn);
|
|
@@ -2881,7 +2970,7 @@ function parseElement(el, ctm, style, gradients, clips, onWarn, uriToPrefix) {
|
|
|
2881
2970
|
if (el.hasAttribute("viewBox")) {
|
|
2882
2971
|
onWarn("nested <svg> viewBox is not modeled; its children are not rescaled");
|
|
2883
2972
|
}
|
|
2884
|
-
return parseChildren(el, multiply(ctm, offset), childStyle, gradients, clips, onWarn, uriToPrefix);
|
|
2973
|
+
return parseChildren(el, multiply(ctm, offset), childStyle, gradients, clips, onWarn, uriToPrefix, keepHidden);
|
|
2885
2974
|
}
|
|
2886
2975
|
if (tag === "text") {
|
|
2887
2976
|
const textNode = parseTextElement(el, ctm, style, gradients, onWarn);
|
|
@@ -2949,56 +3038,16 @@ function parseElement(el, ctm, style, gradients, clips, onWarn, uriToPrefix) {
|
|
|
2949
3038
|
if (meta) node.meta = meta;
|
|
2950
3039
|
return node;
|
|
2951
3040
|
}
|
|
2952
|
-
function
|
|
2953
|
-
const
|
|
3041
|
+
function isHiddenElement(el, style) {
|
|
3042
|
+
const tag = el.tagName.toLowerCase();
|
|
3043
|
+
if (IGNORED_TAGS.has(tag)) return false;
|
|
3044
|
+
if (ownValue(el, "display") === "none") return true;
|
|
3045
|
+
if (SUPPORTED_GROUP_TAGS.has(tag)) return false;
|
|
3046
|
+
const v = styleValue(deriveStyle(style, el), "visibility");
|
|
2954
3047
|
return v === "hidden" || v === "collapse";
|
|
2955
3048
|
}
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
registerPaintServers(nodes, registry);
|
|
2959
|
-
const bounds = opts.viewBox ?? computeBounds(nodes);
|
|
2960
|
-
const vb = `${trimNumber(bounds.x)} ${trimNumber(bounds.y)} ${trimNumber(bounds.width)} ${trimNumber(bounds.height)}`;
|
|
2961
|
-
const namespaces = opts.namespaces ?? {};
|
|
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)}"`);
|
|
2968
|
-
}
|
|
2969
|
-
rootAttrs.push(`viewBox="${vb}"`);
|
|
2970
|
-
if (opts.width != null) rootAttrs.push(`width="${trimNumber(opts.width)}"`);
|
|
2971
|
-
if (opts.height != null) rootAttrs.push(`height="${trimNumber(opts.height)}"`);
|
|
2972
|
-
if (opts.documentMeta) {
|
|
2973
|
-
for (const prefix of Object.keys(namespaces)) {
|
|
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
|
-
}
|
|
2979
|
-
}
|
|
2980
|
-
}
|
|
2981
|
-
let docMetaXml = "";
|
|
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
|
-
}
|
|
3049
|
+
|
|
3050
|
+
// src/metaXml.ts
|
|
3002
3051
|
function namespacedElementXml(prefix, localName2, el) {
|
|
3003
3052
|
const attrs = [];
|
|
3004
3053
|
for (const [name, value] of Object.entries(el.attrs)) {
|
|
@@ -3039,6 +3088,158 @@ function metaElementsXml(meta, namespaces) {
|
|
|
3039
3088
|
}
|
|
3040
3089
|
return out;
|
|
3041
3090
|
}
|
|
3091
|
+
function computeBounds(nodes) {
|
|
3092
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
3093
|
+
const visit = (n, ctm) => {
|
|
3094
|
+
if (n.hidden) return;
|
|
3095
|
+
if (n.kind === "group") {
|
|
3096
|
+
const childCtm = n.transform ? multiply(ctm, n.transform) : ctm;
|
|
3097
|
+
n.children.forEach((c) => visit(c, childCtm));
|
|
3098
|
+
return;
|
|
3099
|
+
}
|
|
3100
|
+
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);
|
|
3101
|
+
const frame = n.kind === "path" ? null : poseFrameOf(n);
|
|
3102
|
+
const local = frame ? multiply(ctm, frame) : n.rotation ? rotateAboutCenter(ctm, b, n.rotation) : ctm;
|
|
3103
|
+
for (const [x, y] of corners(b)) {
|
|
3104
|
+
const px = local[0] * x + local[2] * y + local[4];
|
|
3105
|
+
const py = local[1] * x + local[3] * y + local[5];
|
|
3106
|
+
if (px < minX) minX = px;
|
|
3107
|
+
if (py < minY) minY = py;
|
|
3108
|
+
if (px > maxX) maxX = px;
|
|
3109
|
+
if (py > maxY) maxY = py;
|
|
3110
|
+
}
|
|
3111
|
+
};
|
|
3112
|
+
nodes.forEach((n) => visit(n, IDENTITY_MATRIX));
|
|
3113
|
+
if (!Number.isFinite(minX)) {
|
|
3114
|
+
return { x: 0, y: 0, width: 0, height: 0 };
|
|
3115
|
+
}
|
|
3116
|
+
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
|
3117
|
+
}
|
|
3118
|
+
function corners(b) {
|
|
3119
|
+
return [[b.minX, b.minY], [b.maxX, b.minY], [b.maxX, b.maxY], [b.minX, b.maxY]];
|
|
3120
|
+
}
|
|
3121
|
+
function rotateAboutCenter(ctm, b, angle) {
|
|
3122
|
+
const cx = (b.minX + b.maxX) / 2;
|
|
3123
|
+
const cy = (b.minY + b.maxY) / 2;
|
|
3124
|
+
const c = Math.cos(angle);
|
|
3125
|
+
const s = Math.sin(angle);
|
|
3126
|
+
return multiply(ctm, [c, s, -s, c, cx - cx * c + cy * s, cy - cx * s - cy * c]);
|
|
3127
|
+
}
|
|
3128
|
+
function pathBounds(path) {
|
|
3129
|
+
const b = boundsOfPath(path);
|
|
3130
|
+
return { minX: b.x, minY: b.y, maxX: b.x + b.width, maxY: b.y + b.height };
|
|
3131
|
+
}
|
|
3132
|
+
|
|
3133
|
+
// src/openSubpathFill.ts
|
|
3134
|
+
var capsEnds = (stroke) => stroke?.markerStart !== void 0 || stroke?.markerEnd !== void 0;
|
|
3135
|
+
function withoutEndMarkers(stroke) {
|
|
3136
|
+
const { markerStart: _start, markerEnd: _end, ...rest } = stroke;
|
|
3137
|
+
return rest;
|
|
3138
|
+
}
|
|
3139
|
+
function withOpenSubpathsUnfilled(node) {
|
|
3140
|
+
const path = node.path;
|
|
3141
|
+
if (path.kind !== "polygon" || node.fill.kind === "none" || fillsOpenSubpaths(path)) return node;
|
|
3142
|
+
const { closed, open } = splitByClosure(path);
|
|
3143
|
+
if (!open) return node;
|
|
3144
|
+
const { fill, stroke } = node;
|
|
3145
|
+
const opens = capsEnds(stroke) ? splitSubpaths(open) : [open];
|
|
3146
|
+
if (!closed && opens.length === 1) return { ...node, fill: { kind: "none" } };
|
|
3147
|
+
const piece = (part, paint, line = stroke) => ({ kind: "path", path: part, fill: paint, ...line ? { stroke: line } : {} });
|
|
3148
|
+
const group = {
|
|
3149
|
+
kind: "group",
|
|
3150
|
+
children: [
|
|
3151
|
+
...closed ? [piece(closed, fill, stroke && withoutEndMarkers(stroke))] : [],
|
|
3152
|
+
...opens.map((part) => piece(part, { kind: "none" }))
|
|
3153
|
+
]
|
|
3154
|
+
};
|
|
3155
|
+
if (node.rotation != null && node.rotation !== 0) {
|
|
3156
|
+
const b = pathBounds(path);
|
|
3157
|
+
group.transform = rotateAboutPoint((b.minX + b.maxX) / 2, (b.minY + b.maxY) / 2, node.rotation);
|
|
3158
|
+
}
|
|
3159
|
+
if (node.opacity != null) group.opacity = node.opacity;
|
|
3160
|
+
if (node.hidden) group.hidden = true;
|
|
3161
|
+
if (node.meta) group.meta = node.meta;
|
|
3162
|
+
return group;
|
|
3163
|
+
}
|
|
3164
|
+
|
|
3165
|
+
// src/prettyXml.ts
|
|
3166
|
+
var TOKEN2 = /<(?:[^>"]|"[^"]*")*>|[^<]+/g;
|
|
3167
|
+
var isTag = (t) => t !== void 0 && t.startsWith("<");
|
|
3168
|
+
var isClose = (t) => t.startsWith("</");
|
|
3169
|
+
var isSelfClosing = (t) => t.endsWith("/>");
|
|
3170
|
+
var tagName = (t) => /^<\/?([^\s/>]+)/.exec(t)[1];
|
|
3171
|
+
function matchingClose(tokens, open) {
|
|
3172
|
+
let depth = 0;
|
|
3173
|
+
for (let i = open; i < tokens.length; i++) {
|
|
3174
|
+
const t = tokens[i];
|
|
3175
|
+
if (!isTag(t) || isSelfClosing(t)) continue;
|
|
3176
|
+
depth += isClose(t) ? -1 : 1;
|
|
3177
|
+
if (depth === 0) return i;
|
|
3178
|
+
}
|
|
3179
|
+
return tokens.length - 1;
|
|
3180
|
+
}
|
|
3181
|
+
function prettyXml(xml) {
|
|
3182
|
+
const tokens = xml.match(TOKEN2) ?? [];
|
|
3183
|
+
const lines = [];
|
|
3184
|
+
let depth = 0;
|
|
3185
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
3186
|
+
const t = tokens[i];
|
|
3187
|
+
if (!isTag(t) || isSelfClosing(t)) {
|
|
3188
|
+
lines.push(" ".repeat(depth) + t);
|
|
3189
|
+
} else if (isClose(t)) {
|
|
3190
|
+
depth--;
|
|
3191
|
+
lines.push(" ".repeat(depth) + t);
|
|
3192
|
+
} else if (tagName(t) === "text" || !isTag(tokens[i + 1])) {
|
|
3193
|
+
const close2 = matchingClose(tokens, i);
|
|
3194
|
+
lines.push(" ".repeat(depth) + tokens.slice(i, close2 + 1).join(""));
|
|
3195
|
+
i = close2;
|
|
3196
|
+
} else {
|
|
3197
|
+
lines.push(" ".repeat(depth) + t);
|
|
3198
|
+
depth++;
|
|
3199
|
+
}
|
|
3200
|
+
}
|
|
3201
|
+
return lines.join("\n");
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
// src/serialize.ts
|
|
3205
|
+
function serializeSvg(nodes, opts = {}) {
|
|
3206
|
+
const registry = new PaintServerRegistry();
|
|
3207
|
+
registerPaintServers(nodes, registry);
|
|
3208
|
+
const bounds = opts.viewBox ?? computeBounds(nodes);
|
|
3209
|
+
const vb = `${trimNumber(bounds.x)} ${trimNumber(bounds.y)} ${trimNumber(bounds.width)} ${trimNumber(bounds.height)}`;
|
|
3210
|
+
const namespaces = opts.namespaces ?? {};
|
|
3211
|
+
const rootAttrs = [`xmlns="http://www.w3.org/2000/svg"`];
|
|
3212
|
+
if (registry.usesPrivateNamespace() && namespaces[WEASEL_NS_PREFIX] == null) {
|
|
3213
|
+
rootAttrs.push(`xmlns:${WEASEL_NS_PREFIX}="${WEASEL_NS}"`);
|
|
3214
|
+
}
|
|
3215
|
+
for (const [prefix, uri] of Object.entries(namespaces)) {
|
|
3216
|
+
rootAttrs.push(`xmlns:${prefix}="${escapeAttr(uri)}"`);
|
|
3217
|
+
}
|
|
3218
|
+
rootAttrs.push(`viewBox="${vb}"`);
|
|
3219
|
+
if (opts.width != null) rootAttrs.push(`width="${trimNumber(opts.width)}"`);
|
|
3220
|
+
if (opts.height != null) rootAttrs.push(`height="${trimNumber(opts.height)}"`);
|
|
3221
|
+
if (opts.documentMeta) {
|
|
3222
|
+
for (const prefix of Object.keys(namespaces)) {
|
|
3223
|
+
const bucket = opts.documentMeta[prefix];
|
|
3224
|
+
if (!bucket?.attrs) continue;
|
|
3225
|
+
for (const [name, value] of Object.entries(bucket.attrs)) {
|
|
3226
|
+
rootAttrs.push(`${prefix}:${name}="${escapeAttr(value)}"`);
|
|
3227
|
+
}
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
const docMetaXml = metaElementsXml(opts.documentMeta, namespaces);
|
|
3231
|
+
const defsXml = registry.toDefsXml(opts.onWarn);
|
|
3232
|
+
const said = /* @__PURE__ */ new Set();
|
|
3233
|
+
const warn = (m) => {
|
|
3234
|
+
if (!opts.onWarn || said.has(m)) return;
|
|
3235
|
+
said.add(m);
|
|
3236
|
+
opts.onWarn(m);
|
|
3237
|
+
};
|
|
3238
|
+
const bodyXml = nodes.map((n) => nodeXml(n, registry, namespaces, warn)).join("");
|
|
3239
|
+
const titleXml = opts.title && opts.title.length > 0 ? `<title>${escapeText(opts.title)}</title>` : "";
|
|
3240
|
+
const xml = `<svg ${rootAttrs.join(" ")}>${titleXml}${defsXml}${docMetaXml}${bodyXml}</svg>`;
|
|
3241
|
+
return opts.pretty ? prettyXml(xml) : xml;
|
|
3242
|
+
}
|
|
3042
3243
|
function registerPaintServers(nodes, registry) {
|
|
3043
3244
|
for (const n of nodes) {
|
|
3044
3245
|
if (n.kind === "group") {
|
|
@@ -3049,18 +3250,18 @@ function registerPaintServers(nodes, registry) {
|
|
|
3049
3250
|
if (n.stroke && n.stroke.paint.kind === "gradient") registry.register(n.stroke.paint.paint);
|
|
3050
3251
|
registerMarkers(n.stroke, registry);
|
|
3051
3252
|
} else if (n.kind === "text") {
|
|
3052
|
-
registerTextPaint(n.fill, registry);
|
|
3253
|
+
registerTextPaint(n.fill?.paint, registry);
|
|
3053
3254
|
registerTextPaint(n.stroke?.paint, registry);
|
|
3054
3255
|
registerMarkers(n.stroke, registry);
|
|
3055
3256
|
for (const run of n.runs ?? []) {
|
|
3056
|
-
registerTextPaint(run.fill, registry);
|
|
3257
|
+
registerTextPaint(run.fill?.paint, registry);
|
|
3057
3258
|
registerTextPaint(run.stroke?.paint, registry);
|
|
3058
3259
|
}
|
|
3059
3260
|
}
|
|
3060
3261
|
}
|
|
3061
3262
|
}
|
|
3062
3263
|
function registerTextPaint(paint, registry) {
|
|
3063
|
-
if (paint && !(
|
|
3264
|
+
if (paint && !isSolidPaint(paint)) registry.register(paint);
|
|
3064
3265
|
}
|
|
3065
3266
|
function registerMarkers(stroke, registry) {
|
|
3066
3267
|
if (!stroke) return;
|
|
@@ -3069,10 +3270,15 @@ function registerMarkers(stroke, registry) {
|
|
|
3069
3270
|
}
|
|
3070
3271
|
}
|
|
3071
3272
|
function nodeXml(node, registry, namespaces, warn) {
|
|
3273
|
+
const xml = kindXml(node, registry, namespaces, warn);
|
|
3274
|
+
return node.hidden ? xml.replace(/^<[\w:]+/, (open) => `${open} display="none"`) : xml;
|
|
3275
|
+
}
|
|
3276
|
+
function kindXml(node, registry, namespaces, warn) {
|
|
3072
3277
|
if (node.kind === "group") return groupXml(node, registry, namespaces, warn);
|
|
3073
3278
|
if (node.kind === "text") return textXml(node, registry, namespaces, warn);
|
|
3074
3279
|
if (node.kind === "image") return imageXml(node, namespaces);
|
|
3075
|
-
|
|
3280
|
+
const drawn = withOpenSubpathsUnfilled(node);
|
|
3281
|
+
return drawn.kind === "group" ? groupXml(drawn, registry, namespaces, warn) : pathXml(drawn, registry, namespaces, warn);
|
|
3076
3282
|
}
|
|
3077
3283
|
function warnStrokeAlign(align, warn) {
|
|
3078
3284
|
if (align && align !== "center") {
|
|
@@ -3171,7 +3377,7 @@ function coreStrokeAttrs(stroke, registry, warn) {
|
|
|
3171
3377
|
const width2 = stroke.width ?? 1;
|
|
3172
3378
|
if (!((typeof width2 === "object" ? width2.px : width2) > 0)) return [];
|
|
3173
3379
|
warnStrokeAlign(stroke.align, warn);
|
|
3174
|
-
const attrs = [`stroke="${
|
|
3380
|
+
const attrs = [`stroke="${isSolidPaint(paint) ? escapeAttr(paint.color) : registry.ref(paint)}"`];
|
|
3175
3381
|
attrs.push(...opacityAttr("stroke-opacity", paint.opacity));
|
|
3176
3382
|
attrs.push(...strokeWidthAttrs(width2));
|
|
3177
3383
|
if (stroke.cap) attrs.push(`stroke-linecap="${stroke.cap}"`);
|
|
@@ -3223,42 +3429,6 @@ function strokeAttrsFor(stroke, registry, warn) {
|
|
|
3223
3429
|
}
|
|
3224
3430
|
return attrs;
|
|
3225
3431
|
}
|
|
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
3432
|
function textXml(node, registry, namespaces, warn) {
|
|
3263
3433
|
const attrs = [
|
|
3264
3434
|
`x="${trimNumber(node.x + anchorOffset(node.style, node.width, node.text))}"`,
|
|
@@ -3302,11 +3472,13 @@ function textXml(node, registry, namespaces, warn) {
|
|
|
3302
3472
|
attrs.push(`data-weasel-vertical-align="${node.verticalAlign}"`);
|
|
3303
3473
|
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
3474
|
}
|
|
3305
|
-
if (node.fill
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3475
|
+
if (node.fill !== void 0) {
|
|
3476
|
+
const paint = node.fill?.paint;
|
|
3477
|
+
if (!paint) attrs.push('fill="none"');
|
|
3478
|
+
else {
|
|
3479
|
+
attrs.push(`fill="${isSolidPaint(paint) ? escapeAttr(paint.color) : registry.ref(paint)}"`);
|
|
3480
|
+
attrs.push(...opacityAttr("fill-opacity", paint.opacity));
|
|
3481
|
+
}
|
|
3310
3482
|
}
|
|
3311
3483
|
for (const a of coreStrokeAttrs(node.stroke, registry, warn)) attrs.push(a);
|
|
3312
3484
|
if (node.opacity != null && node.opacity !== 1) {
|
|
@@ -3346,13 +3518,11 @@ function runXml(run, style, registry, warn) {
|
|
|
3346
3518
|
}
|
|
3347
3519
|
const runDecoration = textDecorationValue(run.underline, run.strikethrough, run.overline);
|
|
3348
3520
|
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
|
-
}
|
|
3521
|
+
if (run.fill !== void 0) {
|
|
3522
|
+
const paint = run.fill?.paint;
|
|
3523
|
+
if (!paint) attrs.push('fill="none"');
|
|
3524
|
+
else if (isSolidPaint(paint)) attrs.push(`fill="${escapeAttr(paint.color)}"`);
|
|
3525
|
+
else attrs.push(`fill="${registry.ref(paint)}"`);
|
|
3356
3526
|
}
|
|
3357
3527
|
for (const a of coreStrokeAttrs(run.stroke, registry, warn)) attrs.push(a);
|
|
3358
3528
|
if (run.textTransform) attrs.push(`style="text-transform:${run.textTransform}"`);
|
|
@@ -3366,10 +3536,6 @@ function textDecorationValue(underline, strikethrough, overline) {
|
|
|
3366
3536
|
if (overline) tokens.push("overline");
|
|
3367
3537
|
return tokens.length > 0 ? tokens.join(" ") : null;
|
|
3368
3538
|
}
|
|
3369
|
-
function pathBounds(path) {
|
|
3370
|
-
const b = boundsOfPath(path);
|
|
3371
|
-
return { minX: b.x, minY: b.y, maxX: b.x + b.width, maxY: b.y + b.height };
|
|
3372
|
-
}
|
|
3373
3539
|
function svgNeeds(nodes) {
|
|
3374
3540
|
const registry = new PaintServerRegistry();
|
|
3375
3541
|
registerPaintServers(nodes, registry);
|
|
@@ -3395,6 +3561,22 @@ function warmSvg(nodes) {
|
|
|
3395
3561
|
warmPaintKinds(needs.paintKinds.filter(isPaintKindKnown))
|
|
3396
3562
|
]).then(() => void 0);
|
|
3397
3563
|
}
|
|
3564
|
+
|
|
3565
|
+
// src/fadePaint.ts
|
|
3566
|
+
function fadeFill(fill, k) {
|
|
3567
|
+
return k === 1 ? fill : { ...fill, opacity: (fill.opacity ?? 1) * k };
|
|
3568
|
+
}
|
|
3569
|
+
function fadeStroke(stroke, k) {
|
|
3570
|
+
return k === 1 || !stroke.paint ? stroke : { ...stroke, paint: fadeFill(stroke.paint, k) };
|
|
3571
|
+
}
|
|
3572
|
+
function fadeRun(run, k) {
|
|
3573
|
+
if (k === 1 || !run.fill && !run.stroke) return run;
|
|
3574
|
+
return {
|
|
3575
|
+
...run,
|
|
3576
|
+
...run.fill ? { fill: mapFillPaint(run.fill, (p) => fadeFill(p, k)) } : {},
|
|
3577
|
+
...run.stroke ? { stroke: fadeStroke(run.stroke, k) } : {}
|
|
3578
|
+
};
|
|
3579
|
+
}
|
|
3398
3580
|
var CASCADE_OFFSET_PX = 24;
|
|
3399
3581
|
var VIEWPORT_FIT = 0.9;
|
|
3400
3582
|
function readFileText(file) {
|
|
@@ -3413,20 +3595,6 @@ function fillDataFromSvg(paint, box) {
|
|
|
3413
3595
|
}
|
|
3414
3596
|
return fillInBoxFrame(paint.paint, box);
|
|
3415
3597
|
}
|
|
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
3598
|
function strokeDataFromSvg(stroke, box) {
|
|
3431
3599
|
if (!stroke || stroke.paint.kind === "none") return void 0;
|
|
3432
3600
|
const paint = fillDataFromSvg(stroke.paint, box);
|
|
@@ -3448,7 +3616,7 @@ function fillInBoxFrame(fill, box) {
|
|
|
3448
3616
|
return units === "world" ? fillToBoundsFrame(fill, box) : fill;
|
|
3449
3617
|
}
|
|
3450
3618
|
function runInBoxFrame(run, box) {
|
|
3451
|
-
const fill = run.fill ?
|
|
3619
|
+
const fill = run.fill ? mapFillPaint(run.fill, (p) => fillInBoxFrame(p, box)) : run.fill;
|
|
3452
3620
|
const stroke = run.stroke ? strokeInBoxFrame(run.stroke, box) : run.stroke;
|
|
3453
3621
|
if (fill === run.fill && stroke === run.stroke) return run;
|
|
3454
3622
|
return { ...run, ...fill ? { fill } : {}, ...stroke ? { stroke } : {} };
|
|
@@ -3468,7 +3636,7 @@ function svgNodesToKitDrafts(input, nextId, options = {}) {
|
|
|
3468
3636
|
const id = nextId(source);
|
|
3469
3637
|
const out = options.leaf ? options.leaf(id, { pose, data }, source) : data;
|
|
3470
3638
|
if (out === null) return null;
|
|
3471
|
-
drafts.push({ kind: "leaf", id, parentId, pose, data: out });
|
|
3639
|
+
drafts.push({ kind: "leaf", id, parentId, pose, data: out, ...source.hidden ? { hidden: true } : {} });
|
|
3472
3640
|
return pose;
|
|
3473
3641
|
};
|
|
3474
3642
|
const visit = (n, parentId, inherited2) => {
|
|
@@ -3478,7 +3646,9 @@ function svgNodesToKitDrafts(input, nextId, options = {}) {
|
|
|
3478
3646
|
kind: "container",
|
|
3479
3647
|
id: nextId(n),
|
|
3480
3648
|
parentId,
|
|
3481
|
-
pose: { x: 0, y: 0, width: 0, height: 0 }
|
|
3649
|
+
pose: { x: 0, y: 0, width: 0, height: 0 },
|
|
3650
|
+
data: n.clip ? { path: n.clip, fill: null } : {},
|
|
3651
|
+
...n.hidden ? { hidden: true } : {}
|
|
3482
3652
|
};
|
|
3483
3653
|
drafts.push(draft);
|
|
3484
3654
|
const acc = unionAABB(n.children.map((c) => visit(c, draft.id, k)));
|
|
@@ -3486,7 +3656,7 @@ function svgNodesToKitDrafts(input, nextId, options = {}) {
|
|
|
3486
3656
|
drafts.splice(drafts.indexOf(draft), 1);
|
|
3487
3657
|
return null;
|
|
3488
3658
|
}
|
|
3489
|
-
draft.pose = acc;
|
|
3659
|
+
draft.pose = n.clip ? boxOfPath(n.clip) : acc;
|
|
3490
3660
|
return draft.pose;
|
|
3491
3661
|
}
|
|
3492
3662
|
if (n.kind === "text") {
|
|
@@ -3512,7 +3682,7 @@ function svgNodesToKitDrafts(input, nextId, options = {}) {
|
|
|
3512
3682
|
...n.runs ? { runs: n.runs.map((r) => fadeRun(runInBoxFrame(r, box), k)) } : {},
|
|
3513
3683
|
// `!== undefined`, not a truthiness test: `null` is the document
|
|
3514
3684
|
// saying `fill="none"`, and absent takes the painter's default.
|
|
3515
|
-
...fill2 !== void 0 ? { fill: fill2
|
|
3685
|
+
...fill2 !== void 0 ? { fill: fill2 && mapFillPaint(fill2, (p) => fadeFill(fillInBoxFrame(p, box), k)) } : {},
|
|
3516
3686
|
...n.stroke !== void 0 ? { stroke: fadeStroke(strokeInBoxFrame(n.stroke, box), k) } : {}
|
|
3517
3687
|
});
|
|
3518
3688
|
}
|
|
@@ -3530,20 +3700,24 @@ function svgNodesToKitDrafts(input, nextId, options = {}) {
|
|
|
3530
3700
|
}
|
|
3531
3701
|
});
|
|
3532
3702
|
}
|
|
3533
|
-
const b =
|
|
3703
|
+
const b = boxOfPath(n.path);
|
|
3534
3704
|
const pose = { x: b.x, y: b.y, width: b.width, height: b.height };
|
|
3535
3705
|
if (n.rotation) pose.rotation = n.rotation;
|
|
3536
3706
|
const fill = fillDataFromSvg(n.fill, b);
|
|
3537
3707
|
const strokeFromSvg = strokeDataFromSvg(n.stroke, b);
|
|
3538
3708
|
return pushLeaf(n, parentId, pose, {
|
|
3539
3709
|
path: n.path,
|
|
3540
|
-
...fill !== void 0 ? { fill: fill && fadeFill(fill, k) } : {},
|
|
3710
|
+
...fill !== void 0 ? { fill: fill && { paint: fadeFill(fill, k) } } : {},
|
|
3541
3711
|
...strokeFromSvg !== void 0 ? { stroke: fadeStroke(strokeFromSvg, k) } : {}
|
|
3542
3712
|
});
|
|
3543
3713
|
};
|
|
3544
3714
|
for (const n of nodes) visit(n, null, 1);
|
|
3545
3715
|
return drafts;
|
|
3546
3716
|
}
|
|
3717
|
+
function boxOfPath(path) {
|
|
3718
|
+
const b = boundsOfPath(path);
|
|
3719
|
+
return { x: b.x, y: b.y, width: b.width, height: b.height };
|
|
3720
|
+
}
|
|
3547
3721
|
function isNodeList(input) {
|
|
3548
3722
|
return Array.isArray(input);
|
|
3549
3723
|
}
|
|
@@ -3575,7 +3749,7 @@ async function unpackSvgFiles(files, ctx) {
|
|
|
3575
3749
|
const wrapperId = ctx.scene.mintId();
|
|
3576
3750
|
const union2 = unionAABB(roots.map((d) => d.pose));
|
|
3577
3751
|
drafts = [
|
|
3578
|
-
{ kind: "container", id: wrapperId, parentId: null, pose: union2 },
|
|
3752
|
+
{ kind: "container", id: wrapperId, parentId: null, pose: union2, data: {} },
|
|
3579
3753
|
...drafts.map((d) => d.parentId === null ? { ...d, parentId: wrapperId } : d)
|
|
3580
3754
|
];
|
|
3581
3755
|
}
|
|
@@ -3607,7 +3781,7 @@ async function unpackSvgFiles(files, ctx) {
|
|
|
3607
3781
|
kind: d.kind,
|
|
3608
3782
|
layer,
|
|
3609
3783
|
pose: place(d.pose),
|
|
3610
|
-
data: d.kind === "leaf" ? scaleLeafData(d.data, scale) :
|
|
3784
|
+
data: d.kind === "leaf" ? scaleLeafData(d.data, scale) : d.data,
|
|
3611
3785
|
parent: d.parentId
|
|
3612
3786
|
},
|
|
3613
3787
|
label: "Insert SVG"
|
|
@@ -3622,7 +3796,7 @@ async function unpackSvgFiles(files, ctx) {
|
|
|
3622
3796
|
}
|
|
3623
3797
|
function svgPaintFromKit(fill, box) {
|
|
3624
3798
|
if (fill === null) return { kind: "none" };
|
|
3625
|
-
if (fill.
|
|
3799
|
+
if (fill.kind === void 0 || fill.kind === "solid") {
|
|
3626
3800
|
return { kind: "solid", color: fill.color, ...fill.opacity != null ? { opacity: fill.opacity } : {} };
|
|
3627
3801
|
}
|
|
3628
3802
|
return { kind: "gradient", paint: fillInPoseFrame(fill, box) };
|
|
@@ -3670,7 +3844,7 @@ function runInPoseFrame(run, box) {
|
|
|
3670
3844
|
if (!run.fill && !run.stroke) return run;
|
|
3671
3845
|
return {
|
|
3672
3846
|
...run,
|
|
3673
|
-
...run.fill ? { fill:
|
|
3847
|
+
...run.fill ? { fill: mapFillPaint(run.fill, (p) => fillInPoseFrame(p, box)) } : {},
|
|
3674
3848
|
...run.stroke ? { stroke: strokeInPoseFrame(run.stroke, box) } : {}
|
|
3675
3849
|
};
|
|
3676
3850
|
}
|
|
@@ -3688,7 +3862,7 @@ function svgLeafFromKit(data, pose) {
|
|
|
3688
3862
|
if (data.style) node2.style = data.style;
|
|
3689
3863
|
if (data.runs) node2.runs = data.runs.map((r) => runInPoseFrame(r, pose));
|
|
3690
3864
|
if (data.verticalAlign) node2.verticalAlign = data.verticalAlign;
|
|
3691
|
-
if (data.fill !== void 0) node2.fill = data.fill &&
|
|
3865
|
+
if (data.fill !== void 0) node2.fill = data.fill && mapFillPaint(data.fill, (p) => fillInPoseFrame(p, pose));
|
|
3692
3866
|
if (data.stroke) node2.stroke = strokeInPoseFrame(data.stroke, pose);
|
|
3693
3867
|
if (pose.rotation) node2.rotation = pose.rotation;
|
|
3694
3868
|
if (pose.transform) node2.transform = pose.transform;
|
|
@@ -3700,10 +3874,15 @@ function svgLeafFromKit(data, pose) {
|
|
|
3700
3874
|
const node = {
|
|
3701
3875
|
kind: "path",
|
|
3702
3876
|
path: pathInPoseFrame(data.path, pose),
|
|
3703
|
-
fill: svgPaintFromKit(fill, pose)
|
|
3877
|
+
fill: svgPaintFromKit(fill?.paint ?? null, pose)
|
|
3704
3878
|
};
|
|
3705
3879
|
if (stroke) node.stroke = stroke;
|
|
3706
|
-
|
|
3880
|
+
const frame = poseLinearOf(pose) && poseFrameOf(pose);
|
|
3881
|
+
if (frame) {
|
|
3882
|
+
node.path = pathThroughPoseFrame(node.path, pose);
|
|
3883
|
+
node.fill = paintThroughMatrix(node.fill, frame);
|
|
3884
|
+
if (stroke) node.stroke = strokeThroughMatrix(stroke, frame);
|
|
3885
|
+
} else if (pose.rotation) node.rotation = pose.rotation;
|
|
3707
3886
|
return node;
|
|
3708
3887
|
}
|
|
3709
3888
|
function svgNodesFromKit(tree, options = {}) {
|
|
@@ -3719,6 +3898,8 @@ function svgNodesFromKit(tree, options = {}) {
|
|
|
3719
3898
|
if (child) children.push(child);
|
|
3720
3899
|
}
|
|
3721
3900
|
const g = { kind: "group", children };
|
|
3901
|
+
const clip = node.data?.path;
|
|
3902
|
+
if (clip) g.clip = pathThroughPoseFrame(pathInPoseFrame(clip, node.pose), node.pose);
|
|
3722
3903
|
return group ? group(id, g) : g;
|
|
3723
3904
|
}
|
|
3724
3905
|
return leaf ? leaf(id, node) : svgLeafFromKit(node.data, node.pose);
|