@weasel-js/svg 1.6.0 → 1.7.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 +47 -0
- package/dist/index.d.ts +138 -12
- package/dist/index.js +273 -158
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { pathFromD, PATH_M, PATH_L, PATH_Z, boundsOfPath, resolveScreenLength, SCRIPT_METRICS,
|
|
1
|
+
import { pathFromD, PATH_M, PATH_L, PATH_Z, boundsOfPath, resolveScreenLength, SCRIPT_METRICS, solid, fillToBoundsFrame, getMarker, registerMarker, dwarn, unionAABB, createInsertOp, fillInPoseFrame, DEFAULT_SHAPE_FILL, pathInPoseFrame, DEFAULT_TEXT_STYLE, PathBuilder, parseColor, rgbaToHex, resolveTextStyle, layoutRuns, resolveRuns, resolveAlign, PATH_C, PATH_Q, resolveMarkerSize, getPaintKind } from '@weasel-js/core';
|
|
2
2
|
import { identity, multiply, invert } from '@weasel-js/geom';
|
|
3
3
|
import { tileGeometry } from '@weasel-js/core/patterns-builtin';
|
|
4
4
|
|
|
@@ -682,13 +682,18 @@ function serializePathD(path) {
|
|
|
682
682
|
// src/gradients.ts
|
|
683
683
|
var WEASEL_NS = "urn:weasel-js:svg";
|
|
684
684
|
var WEASEL_NS_PREFIX = "wzl";
|
|
685
|
-
function
|
|
686
|
-
const kind = paint.fill ?? "solid";
|
|
685
|
+
function nativeSvgKind(kind) {
|
|
687
686
|
return kind === "solid" || kind === "linear-gradient" || kind === "radial-gradient" || kind === "pattern";
|
|
688
687
|
}
|
|
688
|
+
function hasNativeSvgForm(paint) {
|
|
689
|
+
return nativeSvgKind(paint.fill ?? "solid");
|
|
690
|
+
}
|
|
689
691
|
var INTERPOLATE_SPACES = /* @__PURE__ */ new Set(["rgb", "oklab", "oklch"]);
|
|
692
|
+
function nativeSvgSpace(space) {
|
|
693
|
+
return !space || space === "rgb";
|
|
694
|
+
}
|
|
690
695
|
function interpolateAttr(space) {
|
|
691
|
-
if (
|
|
696
|
+
if (nativeSvgSpace(space)) return "";
|
|
692
697
|
return ` ${WEASEL_NS_PREFIX}:interpolate="${space}"`;
|
|
693
698
|
}
|
|
694
699
|
function readInterpolate(el, elements) {
|
|
@@ -698,7 +703,7 @@ function readInterpolate(el, elements) {
|
|
|
698
703
|
}
|
|
699
704
|
function usesInterpolateAttr(paint) {
|
|
700
705
|
const space = paint.interpolate;
|
|
701
|
-
return space
|
|
706
|
+
return !nativeSvgSpace(space) && INTERPOLATE_SPACES.has(space);
|
|
702
707
|
}
|
|
703
708
|
function localName(tag) {
|
|
704
709
|
const i = tag.indexOf(":");
|
|
@@ -773,8 +778,8 @@ function readStops(el, onWarn) {
|
|
|
773
778
|
const colorAttr = ownProp(c, "stop-color") ?? "#000000";
|
|
774
779
|
const parsed = parsePaintAttr(colorAttr);
|
|
775
780
|
if (parsed && parsed.kind === "solid") {
|
|
776
|
-
const
|
|
777
|
-
const own =
|
|
781
|
+
const opacityAttr2 = ownProp(c, "stop-opacity");
|
|
782
|
+
const own = opacityAttr2 != null ? parseRatio(opacityAttr2) : NaN;
|
|
778
783
|
const alpha = Number.isFinite(own) ? own : parsed.alpha;
|
|
779
784
|
stops.push({ offset, color: alpha < 1 ? applyAlpha(parsed.color, alpha) : parsed.color });
|
|
780
785
|
} else {
|
|
@@ -877,12 +882,13 @@ var PaintServerRegistry = class {
|
|
|
877
882
|
byPaint = /* @__PURE__ */ new Map();
|
|
878
883
|
order = [];
|
|
879
884
|
counter = 0;
|
|
880
|
-
// Marker
|
|
881
|
-
// id is the marker key itself, not a minted counter
|
|
882
|
-
// `marker-end="url(#id)"` fragment back as the marker
|
|
883
|
-
// registry knows it, so a synthetic id would come
|
|
884
|
-
|
|
885
|
-
|
|
885
|
+
// Marker defs referenced by any stroke, by `<defs>` id in first-use order.
|
|
886
|
+
// An unsized reference's id is the marker key itself, not a minted counter
|
|
887
|
+
// id: `parseSvg` reads a `marker-end="url(#id)"` fragment back as the marker
|
|
888
|
+
// key directly when the registry knows it, so a synthetic id would come
|
|
889
|
+
// back as a stranger. A sized one gets a def of its own, stamped with its
|
|
890
|
+
// key and size so the parser can read the reference back whole.
|
|
891
|
+
markers = /* @__PURE__ */ new Map();
|
|
886
892
|
// Clip paths, in first-use order. Identity, not value: two groups clipped by
|
|
887
893
|
// equal outlines get a def each, which costs bytes and misleads nobody.
|
|
888
894
|
clips = [];
|
|
@@ -910,19 +916,19 @@ var PaintServerRegistry = class {
|
|
|
910
916
|
/** Whether any registered paint serializes outside SVG's own vocabulary, so
|
|
911
917
|
* the root has to declare {@link WEASEL_NS}. */
|
|
912
918
|
usesPrivateNamespace() {
|
|
913
|
-
return this.order.some((paint) => !hasNativeSvgForm(paint) || usesInterpolateAttr(paint));
|
|
919
|
+
return this.order.some((paint) => !hasNativeSvgForm(paint) || usesInterpolateAttr(paint)) || [...this.markers.values()].some((m) => m.size !== void 0 || writesInset(getMarker(m.key)));
|
|
914
920
|
}
|
|
915
|
-
/** The `<defs>` id for a marker
|
|
916
|
-
*
|
|
917
|
-
*
|
|
918
|
-
* never be written. */
|
|
919
|
-
markerId(
|
|
921
|
+
/** The `<defs>` id for a marker reference — the key itself when it has no
|
|
922
|
+
* size, else the key suffixed with the size. `undefined` when nothing is
|
|
923
|
+
* registered under the key, so a caller emits no attribute at all rather
|
|
924
|
+
* than a `url(#…)` pointing at a def that will never be written. */
|
|
925
|
+
markerId(ref) {
|
|
926
|
+
const key = typeof ref === "string" ? ref : ref.key;
|
|
927
|
+
const size = typeof ref === "string" ? void 0 : ref.size;
|
|
920
928
|
if (getMarker(key) === void 0) return void 0;
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
}
|
|
925
|
-
return key;
|
|
929
|
+
const id = size === void 0 ? key : `${key}-s${typeof size === "number" ? trimNumber(size) : `${trimNumber(size.px)}px`}`;
|
|
930
|
+
if (!this.markers.has(id)) this.markers.set(id, size === void 0 ? { key } : { key, size });
|
|
931
|
+
return id;
|
|
926
932
|
}
|
|
927
933
|
/** Mint (or recall) the `<defs>` id for a clip outline. */
|
|
928
934
|
clipId(path) {
|
|
@@ -936,17 +942,22 @@ var PaintServerRegistry = class {
|
|
|
936
942
|
/** Emit `<defs>...</defs>` XML for every registered paint server, marker
|
|
937
943
|
* and clip path. */
|
|
938
944
|
toDefsXml(onWarn) {
|
|
939
|
-
if (this.order.length === 0 && this.
|
|
945
|
+
if (this.order.length === 0 && this.markers.size === 0 && this.clips.length === 0) {
|
|
940
946
|
return "";
|
|
941
947
|
}
|
|
942
948
|
const parts = ["<defs>"];
|
|
943
949
|
for (const paint of this.order) {
|
|
944
950
|
parts.push(paintServerXml(this.byPaint.get(paint), paint, onWarn));
|
|
945
951
|
}
|
|
946
|
-
for (const key of this.
|
|
952
|
+
for (const [id, { key, size }] of this.markers) {
|
|
947
953
|
const entry = getMarker(key);
|
|
948
954
|
if (!entry) continue;
|
|
949
|
-
|
|
955
|
+
if (entry.toSvg) {
|
|
956
|
+
if (size !== void 0) onWarn?.(`marker "${key}" writes its own <marker> def, which cannot carry a reference's size; written at its own size`);
|
|
957
|
+
parts.push(entry.toSvg(id, entry));
|
|
958
|
+
} else {
|
|
959
|
+
parts.push(defaultMarkerXml(id, entry, onWarn, key, size));
|
|
960
|
+
}
|
|
950
961
|
}
|
|
951
962
|
for (const path of this.clips) {
|
|
952
963
|
parts.push(
|
|
@@ -957,13 +968,19 @@ var PaintServerRegistry = class {
|
|
|
957
968
|
return parts.join("");
|
|
958
969
|
}
|
|
959
970
|
};
|
|
960
|
-
function
|
|
961
|
-
|
|
971
|
+
function writesInset(entry) {
|
|
972
|
+
return entry !== void 0 && !entry.toSvg && (entry.inset ?? 0) !== 0;
|
|
973
|
+
}
|
|
974
|
+
function defaultMarkerXml(id, entry, onWarn, key, size) {
|
|
975
|
+
const unit = size === void 0 ? 1 : resolveMarkerSize({ key: key ?? id, size }, 1);
|
|
976
|
+
const path = entry.path({ size: unit, stroke: { paint: { fill: "solid", color: "#000" } } });
|
|
962
977
|
const d = serializePathD(path);
|
|
963
978
|
const fill = markerPaintAttrs("fill", entry.fill, id, onWarn);
|
|
964
|
-
const outline = entry.outline ? ` ${markerPaintAttrs("stroke", entry.outline.paint, id, onWarn)} stroke-width="${trimNumber(entry.outline.width)}" stroke-linecap="round" stroke-linejoin="round"` : "";
|
|
979
|
+
const outline = entry.outline ? ` ${markerPaintAttrs("stroke", entry.outline.paint, id, onWarn)} stroke-width="${trimNumber(entry.outline.width * unit)}" stroke-linecap="round" stroke-linejoin="round"` : "";
|
|
965
980
|
const orient = typeof entry.orient === "number" ? trimNumber(entry.orient * 180 / Math.PI) : "auto-start-reverse";
|
|
966
|
-
|
|
981
|
+
const units = size === void 0 ? 'markerUnits="strokeWidth" markerWidth="8" markerHeight="8"' : `markerUnits="userSpaceOnUse" markerWidth="${trimNumber(8 * unit)}" markerHeight="${trimNumber(8 * unit)}" ${WEASEL_NS_PREFIX}:key="${key}" ${WEASEL_NS_PREFIX}:size="${typeof size === "number" ? trimNumber(size) : `${trimNumber(size.px)}px`}"`;
|
|
982
|
+
const inset = writesInset(entry) ? ` ${WEASEL_NS_PREFIX}:inset="${trimNumber(entry.inset)}"` : "";
|
|
983
|
+
return `<marker id="${id}" ${units}${inset} refX="0" refY="0" orient="${orient}" overflow="visible"><path d="${d}" ${fill}${outline}/></marker>`;
|
|
967
984
|
}
|
|
968
985
|
function markerPaintAttrs(attr, paint, id, onWarn) {
|
|
969
986
|
if (paint === "none") return `${attr}="none"`;
|
|
@@ -1415,7 +1432,7 @@ function readPaint(style, attr, defaultColor, gradients, onWarn) {
|
|
|
1415
1432
|
onWarn(`${attr} references unknown gradient #${parsed.id}`);
|
|
1416
1433
|
return { kind: "solid", color: defaultColor };
|
|
1417
1434
|
}
|
|
1418
|
-
return { kind: "gradient", paint };
|
|
1435
|
+
return { kind: "gradient", paint: opacity != null ? { ...paint, opacity } : paint };
|
|
1419
1436
|
}
|
|
1420
1437
|
const out = { kind: "solid", color: parsed.color };
|
|
1421
1438
|
const a = opacity ?? (parsed.alpha < 1 ? parsed.alpha : void 0);
|
|
@@ -1923,6 +1940,16 @@ function ingestMarkers(root, nodes, gradients, onWarn) {
|
|
|
1923
1940
|
delete stroke[field];
|
|
1924
1941
|
continue;
|
|
1925
1942
|
}
|
|
1943
|
+
const sized = sizedMarkerRef(el);
|
|
1944
|
+
if (sized) {
|
|
1945
|
+
if (getMarker(sized.key) === void 0 && !minted.has(sized.key)) {
|
|
1946
|
+
const entry2 = markerEntryFrom(el, id, role, resolveMarkerSize(sized, 1), gradients, onWarn);
|
|
1947
|
+
minted.set(sized.key, entry2 && { ...entry2, id: sized.key });
|
|
1948
|
+
}
|
|
1949
|
+
if (minted.get(sized.key) === null) delete stroke[field];
|
|
1950
|
+
else stroke[field] = sized;
|
|
1951
|
+
continue;
|
|
1952
|
+
}
|
|
1926
1953
|
const cacheKey = `${id}\0${role}\0${w}`;
|
|
1927
1954
|
if (!minted.has(cacheKey)) minted.set(cacheKey, markerEntryFrom(el, id, role, w, gradients, onWarn));
|
|
1928
1955
|
const entry = minted.get(cacheKey);
|
|
@@ -1945,6 +1972,22 @@ function ingestMarkers(root, nodes, gradients, onWarn) {
|
|
|
1945
1972
|
for (const entry of minted.values()) if (entry) out.set(entry.id, entry);
|
|
1946
1973
|
return [...out.values()];
|
|
1947
1974
|
}
|
|
1975
|
+
function weaselAttr(el, name) {
|
|
1976
|
+
return el.getAttributeNS(WEASEL_NS, name) ?? el.getAttribute(`${WEASEL_NS_PREFIX}:${name}`);
|
|
1977
|
+
}
|
|
1978
|
+
function sizedMarkerRef(el) {
|
|
1979
|
+
const key = weaselAttr(el, "key");
|
|
1980
|
+
const raw = weaselAttr(el, "size")?.trim();
|
|
1981
|
+
if (!key || !raw) return void 0;
|
|
1982
|
+
const px = raw.endsWith("px");
|
|
1983
|
+
const n = parseFloat(px ? raw.slice(0, -2) : raw);
|
|
1984
|
+
if (!Number.isFinite(n) || n <= 0) return void 0;
|
|
1985
|
+
return { key, size: px ? { px: n } : n };
|
|
1986
|
+
}
|
|
1987
|
+
function markerInsetAttr(el) {
|
|
1988
|
+
const n = parseFloat(weaselAttr(el, "inset") ?? "");
|
|
1989
|
+
return Number.isFinite(n) && n > 0 ? n : 0;
|
|
1990
|
+
}
|
|
1948
1991
|
function styleAt(el) {
|
|
1949
1992
|
const chain = [];
|
|
1950
1993
|
for (let e = el; e; e = e.parentElement) chain.unshift(e);
|
|
@@ -2066,6 +2109,7 @@ function markerEntryFrom(el, id, role, strokeWidth, gradients, onWarn) {
|
|
|
2066
2109
|
pi += p.path.coords.length;
|
|
2067
2110
|
}
|
|
2068
2111
|
const fillRule = parts[0].path.fillRule ?? "nonzero";
|
|
2112
|
+
const inset = markerInsetAttr(el);
|
|
2069
2113
|
const round = (v) => Math.round(v * 1e4) / 1e4 + 0;
|
|
2070
2114
|
const hash = shortHash(JSON.stringify([
|
|
2071
2115
|
Array.from(commands),
|
|
@@ -2073,11 +2117,13 @@ function markerEntryFrom(el, id, role, strokeWidth, gradients, onWarn) {
|
|
|
2073
2117
|
fillRule,
|
|
2074
2118
|
fill,
|
|
2075
2119
|
outline,
|
|
2076
|
-
typeof orient === "number" ? round(orient) : orient
|
|
2120
|
+
typeof orient === "number" ? round(orient) : orient,
|
|
2121
|
+
...inset !== 0 ? [round(inset)] : []
|
|
2077
2122
|
]));
|
|
2078
2123
|
const key = id.endsWith(`-${hash}`) ? id : `${id}-${hash}`;
|
|
2079
2124
|
return {
|
|
2080
2125
|
id: key,
|
|
2126
|
+
reads: [],
|
|
2081
2127
|
path: ({ size }) => {
|
|
2082
2128
|
const scaled = new Float32Array(coords.length);
|
|
2083
2129
|
for (let i = 0; i < coords.length; i++) scaled[i] = coords[i] * size;
|
|
@@ -2086,7 +2132,7 @@ function markerEntryFrom(el, id, role, strokeWidth, gradients, onWarn) {
|
|
|
2086
2132
|
fill,
|
|
2087
2133
|
outline,
|
|
2088
2134
|
orient,
|
|
2089
|
-
inset
|
|
2135
|
+
inset
|
|
2090
2136
|
};
|
|
2091
2137
|
}
|
|
2092
2138
|
function serializeSvg(nodes, opts = {}) {
|
|
@@ -2198,16 +2244,10 @@ function registerPaintServers(nodes, registry) {
|
|
|
2198
2244
|
function registerTextPaint(paint, registry) {
|
|
2199
2245
|
if (paint && !("color" in paint)) registry.register(paint);
|
|
2200
2246
|
}
|
|
2201
|
-
function markerKeyOfRef(ref) {
|
|
2202
|
-
if (typeof ref === "string") return ref;
|
|
2203
|
-
if (ref && typeof ref === "object" && "key" in ref) return String(ref.key);
|
|
2204
|
-
return void 0;
|
|
2205
|
-
}
|
|
2206
2247
|
function registerMarkers(stroke, registry) {
|
|
2207
2248
|
if (!stroke) return;
|
|
2208
2249
|
for (const ref of [stroke.markerStart, stroke.markerMid, stroke.markerEnd]) {
|
|
2209
|
-
|
|
2210
|
-
if (key) registry.markerId(key);
|
|
2250
|
+
if (ref !== void 0) registry.markerId(ref);
|
|
2211
2251
|
}
|
|
2212
2252
|
}
|
|
2213
2253
|
function nodeXml(node, registry, namespaces, warn) {
|
|
@@ -2296,14 +2336,16 @@ function pathXml(node, registry, namespaces, warn) {
|
|
|
2296
2336
|
}
|
|
2297
2337
|
function paintAttrs(paint, name, registry, includeOpacity = true) {
|
|
2298
2338
|
if (paint.kind === "none") return [`${name}="none"`];
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2339
|
+
const out = [`${name}="${paint.kind === "solid" ? paint.color : registry.ref(paint.paint)}"`];
|
|
2340
|
+
if (includeOpacity) out.push(...opacityAttr(`${name}-opacity`, svgPaintOpacity(paint)));
|
|
2341
|
+
return out;
|
|
2342
|
+
}
|
|
2343
|
+
function svgPaintOpacity(paint) {
|
|
2344
|
+
if (paint.kind === "solid") return paint.opacity;
|
|
2345
|
+
return paint.kind === "gradient" ? paint.paint.opacity : void 0;
|
|
2346
|
+
}
|
|
2347
|
+
function opacityAttr(name, v) {
|
|
2348
|
+
return v != null && v !== 1 ? [`${name}="${trimNumber(v)}"`] : [];
|
|
2307
2349
|
}
|
|
2308
2350
|
function strokeWidthAttrs(width) {
|
|
2309
2351
|
if (typeof width === "number") return [`stroke-width="${trimNumber(width)}"`];
|
|
@@ -2315,15 +2357,8 @@ function coreStrokeAttrs(stroke, registry, warn) {
|
|
|
2315
2357
|
const width = stroke.width ?? 1;
|
|
2316
2358
|
if (!((typeof width === "object" ? width.px : width) > 0)) return [];
|
|
2317
2359
|
warnStrokeAlign(stroke.align, warn);
|
|
2318
|
-
const attrs = [];
|
|
2319
|
-
|
|
2320
|
-
attrs.push(`stroke="${paint.color}"`);
|
|
2321
|
-
if (paint.opacity != null && paint.opacity !== 1) {
|
|
2322
|
-
attrs.push(`stroke-opacity="${trimNumber(paint.opacity)}"`);
|
|
2323
|
-
}
|
|
2324
|
-
} else {
|
|
2325
|
-
attrs.push(`stroke="${registry.ref(paint)}"`);
|
|
2326
|
-
}
|
|
2360
|
+
const attrs = [`stroke="${"color" in paint ? paint.color : registry.ref(paint)}"`];
|
|
2361
|
+
attrs.push(...opacityAttr("stroke-opacity", paint.opacity));
|
|
2327
2362
|
attrs.push(...strokeWidthAttrs(width));
|
|
2328
2363
|
if (stroke.cap) attrs.push(`stroke-linecap="${stroke.cap}"`);
|
|
2329
2364
|
if (stroke.join) attrs.push(`stroke-linejoin="${stroke.join}"`);
|
|
@@ -2338,8 +2373,7 @@ function coreStrokeAttrs(stroke, registry, warn) {
|
|
|
2338
2373
|
]) {
|
|
2339
2374
|
const ref = stroke[field];
|
|
2340
2375
|
if (ref === void 0) continue;
|
|
2341
|
-
const
|
|
2342
|
-
const id = registry.markerId(key);
|
|
2376
|
+
const id = registry.markerId(ref);
|
|
2343
2377
|
if (id === void 0) continue;
|
|
2344
2378
|
attrs.push(`${attr}="url(#${id})"`);
|
|
2345
2379
|
}
|
|
@@ -2349,10 +2383,7 @@ function strokeAttrsFor(stroke, registry, warn) {
|
|
|
2349
2383
|
warnStrokeAlign(stroke.align, warn);
|
|
2350
2384
|
const attrs = paintAttrs(stroke.paint, "stroke", registry, false);
|
|
2351
2385
|
attrs.push(...strokeWidthAttrs(stroke.width));
|
|
2352
|
-
|
|
2353
|
-
if (opacity != null && opacity !== 1) {
|
|
2354
|
-
attrs.push(`stroke-opacity="${trimNumber(opacity)}"`);
|
|
2355
|
-
}
|
|
2386
|
+
attrs.push(...opacityAttr("stroke-opacity", stroke.opacity ?? svgPaintOpacity(stroke.paint)));
|
|
2356
2387
|
if (stroke.cap) {
|
|
2357
2388
|
attrs.push(`stroke-linecap="${stroke.cap}"`);
|
|
2358
2389
|
}
|
|
@@ -2441,6 +2472,7 @@ function textXml(node, registry, namespaces, warn) {
|
|
|
2441
2472
|
}
|
|
2442
2473
|
const decoration = textDecorationValue(style?.underline, style?.strikethrough, style?.overline);
|
|
2443
2474
|
if (decoration) attrs.push(`text-decoration="${decoration}"`);
|
|
2475
|
+
if (style?.script) attrs.push(`baseline-shift="${style.script}"`);
|
|
2444
2476
|
if (style?.textTransform && style.textTransform !== "none") {
|
|
2445
2477
|
attrs.push(`style="text-transform:${style.textTransform}"`);
|
|
2446
2478
|
}
|
|
@@ -2455,14 +2487,8 @@ function textXml(node, registry, namespaces, warn) {
|
|
|
2455
2487
|
if (node.fill === null) {
|
|
2456
2488
|
attrs.push('fill="none"');
|
|
2457
2489
|
} else if (node.fill) {
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
if (node.fill.opacity != null && node.fill.opacity !== 1) {
|
|
2461
|
-
attrs.push(`fill-opacity="${trimNumber(node.fill.opacity)}"`);
|
|
2462
|
-
}
|
|
2463
|
-
} else {
|
|
2464
|
-
attrs.push(`fill="${registry.ref(node.fill)}"`);
|
|
2465
|
-
}
|
|
2490
|
+
attrs.push(`fill="${"color" in node.fill ? node.fill.color : registry.ref(node.fill)}"`);
|
|
2491
|
+
attrs.push(...opacityAttr("fill-opacity", node.fill.opacity));
|
|
2466
2492
|
}
|
|
2467
2493
|
for (const a of coreStrokeAttrs(node.stroke, registry, warn)) attrs.push(a);
|
|
2468
2494
|
if (node.opacity != null && node.opacity !== 1) {
|
|
@@ -2549,17 +2575,31 @@ var svgIdCounter = 0;
|
|
|
2549
2575
|
function freshSvgNodeId() {
|
|
2550
2576
|
return `n${(svgIdCounter++).toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
2551
2577
|
}
|
|
2552
|
-
function
|
|
2578
|
+
function fillDataFromSvg(paint, box) {
|
|
2553
2579
|
if (!paint) return void 0;
|
|
2554
2580
|
if (paint.kind === "none") return null;
|
|
2555
|
-
if (paint.kind === "solid")
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
return
|
|
2581
|
+
if (paint.kind === "solid") {
|
|
2582
|
+
return paint.opacity != null ? { ...solid(paint.color), opacity: paint.opacity } : solid(paint.color);
|
|
2583
|
+
}
|
|
2584
|
+
return fillInBoxFrame(paint.paint, box);
|
|
2585
|
+
}
|
|
2586
|
+
function fadeFill(fill, k) {
|
|
2587
|
+
return k === 1 ? fill : { ...fill, opacity: (fill.opacity ?? 1) * k };
|
|
2588
|
+
}
|
|
2589
|
+
function fadeStroke(stroke, k) {
|
|
2590
|
+
return k === 1 || !stroke.paint ? stroke : { ...stroke, paint: fadeFill(stroke.paint, k) };
|
|
2591
|
+
}
|
|
2592
|
+
function fadeRun(run, k) {
|
|
2593
|
+
if (k === 1 || !run.fill && !run.stroke) return run;
|
|
2594
|
+
return {
|
|
2595
|
+
...run,
|
|
2596
|
+
...run.fill ? { fill: fadeFill(run.fill, k) } : {},
|
|
2597
|
+
...run.stroke ? { stroke: fadeStroke(run.stroke, k) } : {}
|
|
2598
|
+
};
|
|
2559
2599
|
}
|
|
2560
2600
|
function strokeDataFromSvg(stroke, box) {
|
|
2561
2601
|
if (!stroke || stroke.paint.kind === "none") return void 0;
|
|
2562
|
-
const paint =
|
|
2602
|
+
const paint = fillDataFromSvg(stroke.paint, box);
|
|
2563
2603
|
if (paint === void 0 || paint === null) return void 0;
|
|
2564
2604
|
return {
|
|
2565
2605
|
paint: stroke.opacity !== void 0 ? { ...paint, opacity: stroke.opacity } : paint,
|
|
@@ -2573,22 +2613,6 @@ function strokeDataFromSvg(stroke, box) {
|
|
|
2573
2613
|
...stroke.markerEnd !== void 0 ? { markerEnd: stroke.markerEnd } : {}
|
|
2574
2614
|
};
|
|
2575
2615
|
}
|
|
2576
|
-
function svgImageFromKit(image, pose) {
|
|
2577
|
-
const node = {
|
|
2578
|
-
kind: "image",
|
|
2579
|
-
href: image.src,
|
|
2580
|
-
x: pose.x,
|
|
2581
|
-
y: pose.y,
|
|
2582
|
-
width: pose.width,
|
|
2583
|
-
height: pose.height
|
|
2584
|
-
};
|
|
2585
|
-
if (image.source) node.source = { ...image.source };
|
|
2586
|
-
if (image.flipX) node.flipX = true;
|
|
2587
|
-
if (image.flipY) node.flipY = true;
|
|
2588
|
-
if (image.opacity != null) node.opacity = image.opacity;
|
|
2589
|
-
if (pose.rotation) node.rotation = pose.rotation;
|
|
2590
|
-
return node;
|
|
2591
|
-
}
|
|
2592
2616
|
function fillInBoxFrame(fill, box) {
|
|
2593
2617
|
const units = "units" in fill ? fill.units : void 0;
|
|
2594
2618
|
return units === "world" ? fillToBoundsFrame(fill, box) : fill;
|
|
@@ -2604,28 +2628,36 @@ function strokeInBoxFrame(stroke, box) {
|
|
|
2604
2628
|
const paint = fillInBoxFrame(stroke.paint, box);
|
|
2605
2629
|
return paint === stroke.paint ? stroke : { ...stroke, paint };
|
|
2606
2630
|
}
|
|
2607
|
-
function svgNodesToKitDrafts(
|
|
2631
|
+
function svgNodesToKitDrafts(input, nextId, options = {}) {
|
|
2632
|
+
const nodes = isNodeList(input) ? input : input.nodes;
|
|
2633
|
+
if (!isNodeList(input)) {
|
|
2634
|
+
for (const m of input.markers ?? []) if (getMarker(m.id) === void 0) registerMarker(m);
|
|
2635
|
+
}
|
|
2608
2636
|
const drafts = [];
|
|
2609
|
-
const
|
|
2637
|
+
const pushLeaf = (source, parentId, pose, data) => {
|
|
2638
|
+
const id = nextId(source);
|
|
2639
|
+
const out = options.leaf ? options.leaf(id, { pose, data }, source) : data;
|
|
2640
|
+
if (out === null) return null;
|
|
2641
|
+
drafts.push({ kind: "leaf", id, parentId, pose, data: out });
|
|
2642
|
+
return pose;
|
|
2643
|
+
};
|
|
2644
|
+
const visit = (n, parentId, inherited) => {
|
|
2645
|
+
const k = inherited * (n.opacity ?? 1);
|
|
2610
2646
|
if (n.kind === "group") {
|
|
2611
2647
|
const draft = {
|
|
2612
2648
|
kind: "container",
|
|
2613
|
-
id: nextId(),
|
|
2649
|
+
id: nextId(n),
|
|
2614
2650
|
parentId,
|
|
2615
2651
|
pose: { x: 0, y: 0, width: 0, height: 0 }
|
|
2616
2652
|
};
|
|
2617
2653
|
drafts.push(draft);
|
|
2618
|
-
|
|
2619
|
-
for (const c of n.children) {
|
|
2620
|
-
const b2 = visit(c, draft.id);
|
|
2621
|
-
if (b2) acc = acc ? unionRect(acc, b2) : b2;
|
|
2622
|
-
}
|
|
2654
|
+
const acc = unionAABB(n.children.map((c) => visit(c, draft.id, k)));
|
|
2623
2655
|
if (!acc) {
|
|
2624
2656
|
drafts.splice(drafts.indexOf(draft), 1);
|
|
2625
2657
|
return null;
|
|
2626
2658
|
}
|
|
2627
2659
|
draft.pose = acc;
|
|
2628
|
-
return
|
|
2660
|
+
return draft.pose;
|
|
2629
2661
|
}
|
|
2630
2662
|
if (n.kind === "text") {
|
|
2631
2663
|
const pose2 = {
|
|
@@ -2641,65 +2673,48 @@ function svgNodesToKitDrafts(nodes, nextId) {
|
|
|
2641
2673
|
width: pose2.width,
|
|
2642
2674
|
height: pose2.height
|
|
2643
2675
|
};
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
// `!== undefined`, not a truthiness test: `null` is the document
|
|
2655
|
-
// saying `fill="none"`, and absent takes the painter's default.
|
|
2656
|
-
...n.fill !== void 0 ? { fill: n.fill === null ? null : fillInBoxFrame(n.fill, box) } : {},
|
|
2657
|
-
...n.stroke !== void 0 ? { stroke: strokeInBoxFrame(n.stroke, box) } : {}
|
|
2658
|
-
}
|
|
2676
|
+
const fill2 = n.fill === void 0 && k !== 1 ? DEFAULT_TEXT_STYLE.fill : n.fill;
|
|
2677
|
+
return pushLeaf(n, parentId, pose2, {
|
|
2678
|
+
text: n.text,
|
|
2679
|
+
...n.style ? { style: n.style } : {},
|
|
2680
|
+
...n.verticalAlign ? { verticalAlign: n.verticalAlign } : {},
|
|
2681
|
+
...n.runs ? { runs: n.runs.map((r) => fadeRun(runInBoxFrame(r, box), k)) } : {},
|
|
2682
|
+
// `!== undefined`, not a truthiness test: `null` is the document
|
|
2683
|
+
// saying `fill="none"`, and absent takes the painter's default.
|
|
2684
|
+
...fill2 !== void 0 ? { fill: fill2 === null ? null : fadeFill(fillInBoxFrame(fill2, box), k) } : {},
|
|
2685
|
+
...n.stroke !== void 0 ? { stroke: fadeStroke(strokeInBoxFrame(n.stroke, box), k) } : {}
|
|
2659
2686
|
});
|
|
2660
|
-
return pose2;
|
|
2661
2687
|
}
|
|
2662
2688
|
if (n.kind === "image") {
|
|
2663
2689
|
const pose2 = { x: n.x, y: n.y, width: n.width, height: n.height };
|
|
2664
2690
|
if (n.rotation) pose2.rotation = n.rotation;
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
src: n.href,
|
|
2673
|
-
...n.opacity != null ? { opacity: n.opacity } : {},
|
|
2674
|
-
...n.source ? { source: { ...n.source } } : {},
|
|
2675
|
-
...n.flipX ? { flipX: true } : {},
|
|
2676
|
-
...n.flipY ? { flipY: true } : {}
|
|
2677
|
-
}
|
|
2691
|
+
return pushLeaf(n, parentId, pose2, {
|
|
2692
|
+
image: {
|
|
2693
|
+
src: n.href,
|
|
2694
|
+
...k !== 1 ? { opacity: k } : {},
|
|
2695
|
+
...n.source ? { source: { ...n.source } } : {},
|
|
2696
|
+
...n.flipX ? { flipX: true } : {},
|
|
2697
|
+
...n.flipY ? { flipY: true } : {}
|
|
2678
2698
|
}
|
|
2679
2699
|
});
|
|
2680
|
-
return pose2;
|
|
2681
2700
|
}
|
|
2682
2701
|
const b = n.path.kind === "rect" ? { x: n.path.x, y: n.path.y, width: n.path.width, height: n.path.height } : boundsOfPath(n.path);
|
|
2683
2702
|
const pose = { x: b.x, y: b.y, width: b.width, height: b.height };
|
|
2684
2703
|
if (n.rotation) pose.rotation = n.rotation;
|
|
2685
|
-
const fill =
|
|
2704
|
+
const fill = fillDataFromSvg(n.fill, b);
|
|
2686
2705
|
const strokeFromSvg = strokeDataFromSvg(n.stroke, b);
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
pose,
|
|
2692
|
-
data: {
|
|
2693
|
-
path: n.path,
|
|
2694
|
-
...fill !== void 0 ? { fill } : {},
|
|
2695
|
-
...strokeFromSvg !== void 0 ? { stroke: strokeFromSvg } : {}
|
|
2696
|
-
}
|
|
2706
|
+
return pushLeaf(n, parentId, pose, {
|
|
2707
|
+
path: n.path,
|
|
2708
|
+
...fill !== void 0 ? { fill: fill && fadeFill(fill, k) } : {},
|
|
2709
|
+
...strokeFromSvg !== void 0 ? { stroke: fadeStroke(strokeFromSvg, k) } : {}
|
|
2697
2710
|
});
|
|
2698
|
-
return pose;
|
|
2699
2711
|
};
|
|
2700
|
-
for (const n of nodes) visit(n, null);
|
|
2712
|
+
for (const n of nodes) visit(n, null, 1);
|
|
2701
2713
|
return drafts;
|
|
2702
2714
|
}
|
|
2715
|
+
function isNodeList(input) {
|
|
2716
|
+
return Array.isArray(input);
|
|
2717
|
+
}
|
|
2703
2718
|
function scaleTextData(data, scale) {
|
|
2704
2719
|
if (scale === 1 || typeof data.text !== "string") return data;
|
|
2705
2720
|
const style = data.style ?? {};
|
|
@@ -2713,13 +2728,6 @@ function scaleTextData(data, scale) {
|
|
|
2713
2728
|
style: { ...style, fontSize: scaleSize(style.fontSize ?? resolveTextStyle(style).fontSize) }
|
|
2714
2729
|
};
|
|
2715
2730
|
}
|
|
2716
|
-
function unionRect(a, b) {
|
|
2717
|
-
const minX = Math.min(a.x, b.x);
|
|
2718
|
-
const minY = Math.min(a.y, b.y);
|
|
2719
|
-
const maxX = Math.max(a.x + a.width, b.x + b.width);
|
|
2720
|
-
const maxY = Math.max(a.y + a.height, b.y + b.height);
|
|
2721
|
-
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
|
2722
|
-
}
|
|
2723
2731
|
async function unpackSvgFiles(files, ctx) {
|
|
2724
2732
|
const layer = ctx.scene.layers[0]?.id ?? "default";
|
|
2725
2733
|
let index = 0;
|
|
@@ -2728,8 +2736,7 @@ async function unpackSvgFiles(files, ctx) {
|
|
|
2728
2736
|
const text = await readFileText(file);
|
|
2729
2737
|
const parsed = parseSvg(text);
|
|
2730
2738
|
for (const w of parsed.warnings) dwarn("ingest", `svg "${file.name}":`, w);
|
|
2731
|
-
|
|
2732
|
-
let drafts = svgNodesToKitDrafts(parsed.nodes, freshSvgNodeId);
|
|
2739
|
+
let drafts = svgNodesToKitDrafts(parsed, freshSvgNodeId);
|
|
2733
2740
|
if (drafts.length === 0) {
|
|
2734
2741
|
console.warn(`weasel ingest: svg "${file.name}" parsed to no drawable nodes`);
|
|
2735
2742
|
continue;
|
|
@@ -2737,13 +2744,13 @@ async function unpackSvgFiles(files, ctx) {
|
|
|
2737
2744
|
const roots = drafts.filter((d) => d.parentId === null);
|
|
2738
2745
|
if (roots.length > 1) {
|
|
2739
2746
|
const wrapperId = freshSvgNodeId();
|
|
2740
|
-
const union2 = roots.map((d) => d.pose)
|
|
2747
|
+
const union2 = unionAABB(roots.map((d) => d.pose));
|
|
2741
2748
|
drafts = [
|
|
2742
2749
|
{ kind: "container", id: wrapperId, parentId: null, pose: union2 },
|
|
2743
2750
|
...drafts.map((d) => d.parentId === null ? { ...d, parentId: wrapperId } : d)
|
|
2744
2751
|
];
|
|
2745
2752
|
}
|
|
2746
|
-
const union = drafts.filter((d) => d.parentId === null).map((d) => d.pose)
|
|
2753
|
+
const union = unionAABB(drafts.filter((d) => d.parentId === null).map((d) => d.pose));
|
|
2747
2754
|
const view = ctx.viewportWorldRect();
|
|
2748
2755
|
const scale = Math.min(
|
|
2749
2756
|
1,
|
|
@@ -2784,7 +2791,115 @@ async function unpackSvgFiles(files, ctx) {
|
|
|
2784
2791
|
}
|
|
2785
2792
|
}
|
|
2786
2793
|
}
|
|
2794
|
+
function svgPaintFromKit(fill, box) {
|
|
2795
|
+
if (fill === null) return { kind: "none" };
|
|
2796
|
+
if (fill.fill === void 0 || fill.fill === "solid") {
|
|
2797
|
+
return { kind: "solid", color: fill.color, ...fill.opacity != null ? { opacity: fill.opacity } : {} };
|
|
2798
|
+
}
|
|
2799
|
+
return { kind: "gradient", paint: fillInPoseFrame(fill, box) };
|
|
2800
|
+
}
|
|
2801
|
+
function svgStrokeFromKit(stroke, box) {
|
|
2802
|
+
if (!stroke?.paint) return void 0;
|
|
2803
|
+
const width = stroke.width ?? 1;
|
|
2804
|
+
if (!((typeof width === "object" ? width.px : width) > 0)) return void 0;
|
|
2805
|
+
const opacity = stroke.paint.opacity;
|
|
2806
|
+
return {
|
|
2807
|
+
paint: svgPaintFromKit(stroke.paint, box),
|
|
2808
|
+
width,
|
|
2809
|
+
...opacity != null ? { opacity } : {},
|
|
2810
|
+
...stroke.cap !== void 0 ? { cap: stroke.cap } : {},
|
|
2811
|
+
...stroke.join !== void 0 ? { join: stroke.join } : {},
|
|
2812
|
+
...stroke.dash !== void 0 ? { dash: stroke.dash } : {},
|
|
2813
|
+
...stroke.miterLimit !== void 0 ? { miterLimit: stroke.miterLimit } : {},
|
|
2814
|
+
...stroke.align !== void 0 ? { align: stroke.align } : {},
|
|
2815
|
+
...stroke.markerStart !== void 0 ? { markerStart: stroke.markerStart } : {},
|
|
2816
|
+
...stroke.markerMid !== void 0 ? { markerMid: stroke.markerMid } : {},
|
|
2817
|
+
...stroke.markerEnd !== void 0 ? { markerEnd: stroke.markerEnd } : {}
|
|
2818
|
+
};
|
|
2819
|
+
}
|
|
2820
|
+
function svgImageFromKit(image, pose) {
|
|
2821
|
+
const node = {
|
|
2822
|
+
kind: "image",
|
|
2823
|
+
href: image.src,
|
|
2824
|
+
x: pose.x,
|
|
2825
|
+
y: pose.y,
|
|
2826
|
+
width: pose.width,
|
|
2827
|
+
height: pose.height
|
|
2828
|
+
};
|
|
2829
|
+
if (image.source) node.source = { ...image.source };
|
|
2830
|
+
if (image.flipX) node.flipX = true;
|
|
2831
|
+
if (image.flipY) node.flipY = true;
|
|
2832
|
+
if (image.opacity != null) node.opacity = image.opacity;
|
|
2833
|
+
if (pose.rotation) node.rotation = pose.rotation;
|
|
2834
|
+
return node;
|
|
2835
|
+
}
|
|
2836
|
+
function strokeInPoseFrame(stroke, box) {
|
|
2837
|
+
return stroke.paint ? { ...stroke, paint: fillInPoseFrame(stroke.paint, box) } : stroke;
|
|
2838
|
+
}
|
|
2839
|
+
function runInPoseFrame(run, box) {
|
|
2840
|
+
if (!run.fill && !run.stroke) return run;
|
|
2841
|
+
return {
|
|
2842
|
+
...run,
|
|
2843
|
+
...run.fill ? { fill: fillInPoseFrame(run.fill, box) } : {},
|
|
2844
|
+
...run.stroke ? { stroke: strokeInPoseFrame(run.stroke, box) } : {}
|
|
2845
|
+
};
|
|
2846
|
+
}
|
|
2847
|
+
function svgLeafFromKit(data, pose) {
|
|
2848
|
+
if (data.image) return svgImageFromKit(data.image, pose);
|
|
2849
|
+
if (data.text != null) {
|
|
2850
|
+
const node2 = {
|
|
2851
|
+
kind: "text",
|
|
2852
|
+
x: pose.x,
|
|
2853
|
+
y: pose.y,
|
|
2854
|
+
width: pose.width,
|
|
2855
|
+
height: pose.height,
|
|
2856
|
+
text: data.text
|
|
2857
|
+
};
|
|
2858
|
+
if (data.style) node2.style = data.style;
|
|
2859
|
+
if (data.runs) node2.runs = data.runs.map((r) => runInPoseFrame(r, pose));
|
|
2860
|
+
if (data.verticalAlign) node2.verticalAlign = data.verticalAlign;
|
|
2861
|
+
if (data.fill !== void 0) node2.fill = data.fill && fillInPoseFrame(data.fill, pose);
|
|
2862
|
+
if (data.stroke) node2.stroke = strokeInPoseFrame(data.stroke, pose);
|
|
2863
|
+
if (pose.rotation) node2.rotation = pose.rotation;
|
|
2864
|
+
return node2;
|
|
2865
|
+
}
|
|
2866
|
+
if (!data.path) return null;
|
|
2867
|
+
const stroke = svgStrokeFromKit(data.stroke, pose);
|
|
2868
|
+
const fill = data.fill !== void 0 ? data.fill : stroke ? null : DEFAULT_SHAPE_FILL;
|
|
2869
|
+
const node = {
|
|
2870
|
+
kind: "path",
|
|
2871
|
+
path: pathInPoseFrame(data.path, pose),
|
|
2872
|
+
fill: svgPaintFromKit(fill, pose)
|
|
2873
|
+
};
|
|
2874
|
+
if (stroke) node.stroke = stroke;
|
|
2875
|
+
if (pose.rotation) node.rotation = pose.rotation;
|
|
2876
|
+
return node;
|
|
2877
|
+
}
|
|
2878
|
+
function svgNodesFromKit(tree, options = {}) {
|
|
2879
|
+
const { include, leaf, group } = options;
|
|
2880
|
+
const emit = (id) => {
|
|
2881
|
+
if (include && !include(id)) return null;
|
|
2882
|
+
const node = tree.get(id);
|
|
2883
|
+
if (!node) return null;
|
|
2884
|
+
if (node.kind === "container") {
|
|
2885
|
+
const children = [];
|
|
2886
|
+
for (const c of tree.childrenOf(id)) {
|
|
2887
|
+
const child = emit(c);
|
|
2888
|
+
if (child) children.push(child);
|
|
2889
|
+
}
|
|
2890
|
+
const g = { kind: "group", children };
|
|
2891
|
+
return group ? group(id, g) : g;
|
|
2892
|
+
}
|
|
2893
|
+
return leaf ? leaf(id, node) : svgLeafFromKit(node.data, node.pose);
|
|
2894
|
+
};
|
|
2895
|
+
const out = [];
|
|
2896
|
+
for (const id of options.roots ?? tree.roots) {
|
|
2897
|
+
const n = emit(id);
|
|
2898
|
+
if (n) out.push(n);
|
|
2899
|
+
}
|
|
2900
|
+
return out;
|
|
2901
|
+
}
|
|
2787
2902
|
|
|
2788
|
-
export { IDENTITY_MATRIX, parseSvg, serializeSvg, strokeDataFromSvg, svgImageFromKit, svgNodesToKitDrafts, tilePreviewCssUrl, tilePreviewSvg, unpackSvgFiles };
|
|
2903
|
+
export { IDENTITY_MATRIX, fillDataFromSvg, nativeSvgKind, nativeSvgSpace, parseSvg, serializeSvg, strokeDataFromSvg, svgImageFromKit, svgLeafFromKit, svgNodesFromKit, svgNodesToKitDrafts, svgPaintFromKit, svgStrokeFromKit, tilePreviewCssUrl, tilePreviewSvg, unpackSvgFiles };
|
|
2789
2904
|
//# sourceMappingURL=index.js.map
|
|
2790
2905
|
//# sourceMappingURL=index.js.map
|