@weasel-js/svg 0.7.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +35 -2
- package/dist/index.js +188 -28
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Path, FillStyle, StyledRun, TextStyle, IngestCtx } from '@weasel-js/core';
|
|
1
|
+
import { Path, FillStyle, StyledRun, TextStyle, TilePatternSpec, IngestCtx } from '@weasel-js/core';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Public types for `@weasel-js/svg`. The package exposes a flat,
|
|
@@ -206,6 +206,12 @@ interface SerializeOptions {
|
|
|
206
206
|
width: number;
|
|
207
207
|
height: number;
|
|
208
208
|
};
|
|
209
|
+
/**
|
|
210
|
+
* Called for paint that can't be expressed in SVG and is dropped — a
|
|
211
|
+
* conic gradient, or a pattern carrying a `TextureHandle` instead of a
|
|
212
|
+
* `TilePatternSpec`. Without this the loss is silent.
|
|
213
|
+
*/
|
|
214
|
+
onWarn?: (message: string) => void;
|
|
209
215
|
/** Emit `width="..."` on the root `<svg>`. */
|
|
210
216
|
width?: number;
|
|
211
217
|
/** Emit `height="..."` on the root `<svg>`. */
|
|
@@ -263,6 +269,33 @@ declare function parseSvg(svg: string, opts?: ParseOptions): ParseResult;
|
|
|
263
269
|
*/
|
|
264
270
|
declare function serializeSvg(nodes: SvgNode[], opts?: SerializeOptions): string;
|
|
265
271
|
|
|
272
|
+
/**
|
|
273
|
+
* Map between SVG `<pattern>` elements and weasel's `pattern` `FillStyle`.
|
|
274
|
+
* The counterpart to `gradients.ts` for the other paint-server kind.
|
|
275
|
+
*
|
|
276
|
+
* The tile's shapes come from `tileGeometry` in core, so the vector form a
|
|
277
|
+
* viewer sees is generated from the same description that rasterizes the GL
|
|
278
|
+
* texture. Alongside them we write the spec itself onto a `data-weasel-tile`
|
|
279
|
+
* attribute: any SVG consumer renders the shapes, and weasel reads the
|
|
280
|
+
* attribute back to recover the exact spec rather than reverse-engineering
|
|
281
|
+
* it from geometry.
|
|
282
|
+
*
|
|
283
|
+
* A pattern whose payload is a `TextureHandle` has no vector form — the
|
|
284
|
+
* handle is a session-scoped registry key with no tile description behind
|
|
285
|
+
* it — and exports as nothing, with a warning. Same treatment conic
|
|
286
|
+
* gradients get.
|
|
287
|
+
*/
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* A single tile as a standalone `<svg>` document, sized to its own extent.
|
|
291
|
+
* For UI that needs to show a tile outside a document — a picker swatch,
|
|
292
|
+
* set as a repeating CSS `background-image` via a data URI. Shares the shape
|
|
293
|
+
* mapper with `patternXml`, so a preview can't drift from what paints.
|
|
294
|
+
*/
|
|
295
|
+
declare function tilePreviewSvg(spec: TilePatternSpec, background?: string): string;
|
|
296
|
+
/** `tilePreviewSvg` packed as a `url(...)` value for CSS `background-image`. */
|
|
297
|
+
declare function tilePreviewCssUrl(spec: TilePatternSpec, background?: string): string;
|
|
298
|
+
|
|
266
299
|
interface SvgDraftBounds {
|
|
267
300
|
x: number;
|
|
268
301
|
y: number;
|
|
@@ -304,4 +337,4 @@ declare function svgNodesToKitDrafts(nodes: readonly SvgNode[], nextId: () => st
|
|
|
304
337
|
*/
|
|
305
338
|
declare function unpackSvgFiles(files: File[], ctx: IngestCtx): Promise<void>;
|
|
306
339
|
|
|
307
|
-
export { IDENTITY_MATRIX, type Matrix, type NamespaceMeta, type NamespacedElement, type ParseOptions, type ParseResult, type SerializeOptions, type SvgDraftBounds, type SvgGroupNode, type SvgNode, type SvgPaint, type SvgPathNode, type SvgSceneDraft, type SvgStroke, type SvgTextNode, parseSvg, serializeSvg, svgNodesToKitDrafts, unpackSvgFiles };
|
|
340
|
+
export { IDENTITY_MATRIX, type Matrix, type NamespaceMeta, type NamespacedElement, type ParseOptions, type ParseResult, type SerializeOptions, type SvgDraftBounds, type SvgGroupNode, type SvgNode, type SvgPaint, type SvgPathNode, type SvgSceneDraft, type SvgStroke, type SvgTextNode, parseSvg, serializeSvg, svgNodesToKitDrafts, tilePreviewCssUrl, tilePreviewSvg, unpackSvgFiles };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { pathFromD, PATH_M, PATH_L, PATH_Z, boundsOfPath, dwarn, createInsertOp, PathBuilder, parseColor, rgbaToHex, PATH_C, PATH_Q } from '@weasel-js/core';
|
|
2
|
+
import { tileGeometry } from '@weasel-js/core/patterns-builtin';
|
|
2
3
|
|
|
3
4
|
// src/parse.ts
|
|
4
5
|
|
|
@@ -241,6 +242,98 @@ function parsePaintAttr(raw) {
|
|
|
241
242
|
return null;
|
|
242
243
|
}
|
|
243
244
|
}
|
|
245
|
+
function patternSpecOf(paint) {
|
|
246
|
+
return "tile" in paint.pattern ? paint.pattern : null;
|
|
247
|
+
}
|
|
248
|
+
function patternXml(id, paint, onWarn) {
|
|
249
|
+
const spec = patternSpecOf(paint);
|
|
250
|
+
if (!spec) {
|
|
251
|
+
onWarn?.("pattern fill carries a TextureHandle, which has no vector form \u2014 omitted from <defs>");
|
|
252
|
+
return "";
|
|
253
|
+
}
|
|
254
|
+
const geometry = tileGeometry(spec);
|
|
255
|
+
const body = geometry.shapes.map(shapeXml).join("");
|
|
256
|
+
const origin = paint.origin ?? { x: 0, y: 0 };
|
|
257
|
+
const attrs = [
|
|
258
|
+
`id="${id}"`,
|
|
259
|
+
'patternUnits="userSpaceOnUse"',
|
|
260
|
+
`width="${trimNumber(geometry.size)}"`,
|
|
261
|
+
`height="${trimNumber(geometry.size)}"`
|
|
262
|
+
];
|
|
263
|
+
if (origin.x !== 0 || origin.y !== 0) {
|
|
264
|
+
attrs.push(`x="${trimNumber(origin.x)}"`, `y="${trimNumber(origin.y)}"`);
|
|
265
|
+
}
|
|
266
|
+
attrs.push(`data-weasel-tile="${escapeAttr(JSON.stringify(spec))}"`);
|
|
267
|
+
return `<pattern ${attrs.join(" ")}>${body}</pattern>`;
|
|
268
|
+
}
|
|
269
|
+
function tilePreviewSvg(spec, background) {
|
|
270
|
+
const geometry = tileGeometry(spec);
|
|
271
|
+
const s = trimNumber(geometry.size);
|
|
272
|
+
const bg = background ? `<rect width="${s}" height="${s}" fill="${background}"/>` : "";
|
|
273
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${s}" height="${s}" viewBox="0 0 ${s} ${s}">${bg}${geometry.shapes.map(shapeXml).join("")}</svg>`;
|
|
274
|
+
}
|
|
275
|
+
function tilePreviewCssUrl(spec, background) {
|
|
276
|
+
return `url("data:image/svg+xml,${encodeURIComponent(tilePreviewSvg(spec, background))}")`;
|
|
277
|
+
}
|
|
278
|
+
function shapeXml(shape) {
|
|
279
|
+
switch (shape.kind) {
|
|
280
|
+
case "line":
|
|
281
|
+
return `<line x1="${trimNumber(shape.x1)}" y1="${trimNumber(shape.y1)}" x2="${trimNumber(shape.x2)}" y2="${trimNumber(shape.y2)}" stroke="${shape.color}" stroke-width="${trimNumber(shape.width)}"/>`;
|
|
282
|
+
case "circle":
|
|
283
|
+
return `<circle cx="${trimNumber(shape.cx)}" cy="${trimNumber(shape.cy)}" r="${trimNumber(shape.r)}" fill="${shape.color}"/>`;
|
|
284
|
+
case "ellipse": {
|
|
285
|
+
const deg = shape.rotation * 180 / Math.PI;
|
|
286
|
+
const transform = deg === 0 ? "" : ` transform="rotate(${trimNumber(deg)} ${trimNumber(shape.cx)} ${trimNumber(shape.cy)})"`;
|
|
287
|
+
return `<ellipse cx="${trimNumber(shape.cx)}" cy="${trimNumber(shape.cy)}" rx="${trimNumber(shape.rx)}" ry="${trimNumber(shape.ry)}" fill="${shape.color}"${transform}/>`;
|
|
288
|
+
}
|
|
289
|
+
case "rect":
|
|
290
|
+
return `<rect x="${trimNumber(shape.x)}" y="${trimNumber(shape.y)}" width="${trimNumber(shape.width)}" height="${trimNumber(shape.height)}" fill="${shape.color}"/>`;
|
|
291
|
+
default:
|
|
292
|
+
return "";
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function collectPatterns(svg, onWarn) {
|
|
296
|
+
const out = /* @__PURE__ */ new Map();
|
|
297
|
+
const defs = svg.getElementsByTagName("defs");
|
|
298
|
+
for (let d = 0; d < defs.length; d++) {
|
|
299
|
+
const root = defs[d];
|
|
300
|
+
for (let i = 0; i < root.children.length; i++) {
|
|
301
|
+
const child = root.children[i];
|
|
302
|
+
if (child.tagName.toLowerCase() !== "pattern") continue;
|
|
303
|
+
const id = child.getAttribute("id");
|
|
304
|
+
if (!id) continue;
|
|
305
|
+
const paint = readPattern(child, onWarn);
|
|
306
|
+
if (paint) out.set(id, paint);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return out;
|
|
310
|
+
}
|
|
311
|
+
function readPattern(el, onWarn) {
|
|
312
|
+
const raw = el.getAttribute("data-weasel-tile");
|
|
313
|
+
if (!raw) {
|
|
314
|
+
onWarn?.(`<pattern id="${el.getAttribute("id")}"> has no data-weasel-tile \u2014 unsupported pattern, dropped`);
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
let spec;
|
|
318
|
+
try {
|
|
319
|
+
spec = JSON.parse(raw);
|
|
320
|
+
} catch {
|
|
321
|
+
onWarn?.(`<pattern id="${el.getAttribute("id")}"> has malformed data-weasel-tile \u2014 dropped`);
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
if (typeof spec?.tile !== "string" || typeof spec?.color !== "string") {
|
|
325
|
+
onWarn?.(`<pattern id="${el.getAttribute("id")}"> data-weasel-tile is not a tile spec \u2014 dropped`);
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
const x = parseFloat(el.getAttribute("x") ?? "0");
|
|
329
|
+
const y = parseFloat(el.getAttribute("y") ?? "0");
|
|
330
|
+
const paint = { fill: "pattern", pattern: spec };
|
|
331
|
+
if (x !== 0 || y !== 0) paint.origin = { x, y };
|
|
332
|
+
return paint;
|
|
333
|
+
}
|
|
334
|
+
function escapeAttr(s) {
|
|
335
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
336
|
+
}
|
|
244
337
|
|
|
245
338
|
// src/gradients.ts
|
|
246
339
|
function collectGradients(svg, onWarn) {
|
|
@@ -259,7 +352,7 @@ function collectGradients(svg, onWarn) {
|
|
|
259
352
|
} else if (tag === "radialgradient") {
|
|
260
353
|
const paint = readRadialGradient(child, onWarn);
|
|
261
354
|
if (paint) out.set(id, paint);
|
|
262
|
-
} else if (tag !== "
|
|
355
|
+
} else if (tag !== "pattern") {
|
|
263
356
|
onWarn?.(`unsupported <defs> child: <${child.tagName}>`);
|
|
264
357
|
}
|
|
265
358
|
}
|
|
@@ -315,25 +408,27 @@ function readRadialGradient(el, onWarn) {
|
|
|
315
408
|
stops
|
|
316
409
|
};
|
|
317
410
|
}
|
|
318
|
-
var
|
|
411
|
+
var PaintServerRegistry = class {
|
|
319
412
|
byPaint = /* @__PURE__ */ new Map();
|
|
320
413
|
order = [];
|
|
321
414
|
counter = 0;
|
|
322
415
|
register(paint) {
|
|
323
416
|
const existing = this.byPaint.get(paint);
|
|
324
417
|
if (existing) return existing;
|
|
325
|
-
const id = `grad${this.counter++}`;
|
|
418
|
+
const id = paint.fill === "pattern" ? `pat${this.counter++}` : `grad${this.counter++}`;
|
|
326
419
|
this.byPaint.set(paint, id);
|
|
327
420
|
this.order.push(paint);
|
|
328
421
|
return id;
|
|
329
422
|
}
|
|
330
|
-
/** Emit `<defs>...</defs>` XML for
|
|
331
|
-
toDefsXml() {
|
|
423
|
+
/** Emit `<defs>...</defs>` XML for every registered paint server. */
|
|
424
|
+
toDefsXml(onWarn) {
|
|
332
425
|
if (this.order.length === 0) return "";
|
|
333
426
|
const parts = ["<defs>"];
|
|
334
427
|
for (const paint of this.order) {
|
|
335
428
|
const id = this.byPaint.get(paint);
|
|
336
|
-
parts.push(
|
|
429
|
+
parts.push(
|
|
430
|
+
paint.fill === "pattern" ? patternXml(id, paint, onWarn) : gradientXml(id, paint)
|
|
431
|
+
);
|
|
337
432
|
}
|
|
338
433
|
parts.push("</defs>");
|
|
339
434
|
return parts.join("");
|
|
@@ -446,6 +541,7 @@ function parseSvg(svg, opts = {}) {
|
|
|
446
541
|
}
|
|
447
542
|
const documentMeta = collectDocumentMeta(root, uriToPrefix);
|
|
448
543
|
const gradients = collectGradients(root, onWarn);
|
|
544
|
+
for (const [id, paint] of collectPatterns(root, onWarn)) gradients.set(id, paint);
|
|
449
545
|
const rootStyle = deriveStyle(EMPTY_STYLE, root);
|
|
450
546
|
const nodes = parseChildren(root, IDENTITY_MATRIX, rootStyle, gradients, onWarn, uriToPrefix);
|
|
451
547
|
const result = { nodes, warnings };
|
|
@@ -766,6 +862,40 @@ function readStroke(style, gradients, onWarn) {
|
|
|
766
862
|
}
|
|
767
863
|
return stroke;
|
|
768
864
|
}
|
|
865
|
+
var STROKE_KEYS = [
|
|
866
|
+
"stroke",
|
|
867
|
+
"stroke-width",
|
|
868
|
+
"stroke-opacity",
|
|
869
|
+
"stroke-linecap",
|
|
870
|
+
"stroke-linejoin",
|
|
871
|
+
"stroke-dasharray",
|
|
872
|
+
"stroke-miterlimit"
|
|
873
|
+
];
|
|
874
|
+
function ownStrokeStyle(el) {
|
|
875
|
+
const out = {};
|
|
876
|
+
for (const k of STROKE_KEYS) {
|
|
877
|
+
const v = ownProp(el, k);
|
|
878
|
+
if (v != null) out[k] = v;
|
|
879
|
+
}
|
|
880
|
+
return out;
|
|
881
|
+
}
|
|
882
|
+
function coreStroke(stroke) {
|
|
883
|
+
if (!stroke) return void 0;
|
|
884
|
+
let paint;
|
|
885
|
+
if (stroke.paint.kind === "gradient") {
|
|
886
|
+
paint = stroke.paint.paint;
|
|
887
|
+
} else if (stroke.paint.kind === "solid") {
|
|
888
|
+
paint = stroke.paint.opacity != null ? { fill: "solid", color: stroke.paint.color, opacity: stroke.paint.opacity } : { fill: "solid", color: stroke.paint.color };
|
|
889
|
+
} else {
|
|
890
|
+
return void 0;
|
|
891
|
+
}
|
|
892
|
+
const out = { paint, width: stroke.width };
|
|
893
|
+
if (stroke.cap) out.cap = stroke.cap;
|
|
894
|
+
if (stroke.join) out.join = stroke.join;
|
|
895
|
+
if (stroke.dash) out.dash = stroke.dash;
|
|
896
|
+
if (stroke.miterLimit != null) out.miterLimit = stroke.miterLimit;
|
|
897
|
+
return out;
|
|
898
|
+
}
|
|
769
899
|
function parseDashArray(s) {
|
|
770
900
|
const tokens = s.trim().split(/[\s,]+/).filter(Boolean);
|
|
771
901
|
if (tokens.length === 0) return null;
|
|
@@ -816,7 +946,7 @@ function parseTextElement(el, ctm, style, gradients, onWarn) {
|
|
|
816
946
|
const ax = m[0] * rawX + m[2] * rawY + m[4];
|
|
817
947
|
const ay = m[1] * rawX + m[3] * rawY + m[5];
|
|
818
948
|
const leafStyle = deriveStyle(style, el);
|
|
819
|
-
const textStyle = readTextStyle(leafStyle,
|
|
949
|
+
const textStyle = readTextStyle(leafStyle, gradients, onWarn);
|
|
820
950
|
const fontSize = textStyle.fontSize ?? 16;
|
|
821
951
|
const lineHeight = textStyle.lineHeight ?? 1.2;
|
|
822
952
|
const dominantBaseline = el.getAttribute("dominant-baseline");
|
|
@@ -863,7 +993,7 @@ function parseTextElement(el, ctm, style, gradients, onWarn) {
|
|
|
863
993
|
text: plain
|
|
864
994
|
};
|
|
865
995
|
const hasStyling = runs.some(
|
|
866
|
-
(r) =>
|
|
996
|
+
(r) => Object.entries(r).some(([k, v]) => k !== "text" && v !== void 0)
|
|
867
997
|
);
|
|
868
998
|
if (hasStyling) node.runs = runs;
|
|
869
999
|
if (Object.keys(textStyle).length > 0) node.style = textStyle;
|
|
@@ -916,9 +1046,11 @@ function readTspanRun(el, gradients, style, onWarn) {
|
|
|
916
1046
|
if (paint) run.fill = paint;
|
|
917
1047
|
}
|
|
918
1048
|
}
|
|
1049
|
+
const stroke = coreStroke(readStroke(ownStrokeStyle(el), gradients, onWarn));
|
|
1050
|
+
if (stroke) run.stroke = stroke;
|
|
919
1051
|
return run;
|
|
920
1052
|
}
|
|
921
|
-
function readTextStyle(style,
|
|
1053
|
+
function readTextStyle(style, gradients, onWarn) {
|
|
922
1054
|
const out = {};
|
|
923
1055
|
const sz = style["font-size"];
|
|
924
1056
|
if (sz != null) {
|
|
@@ -956,9 +1088,8 @@ function readTextStyle(style, el, gradients, onWarn) {
|
|
|
956
1088
|
if (paint) out.fill = paint;
|
|
957
1089
|
}
|
|
958
1090
|
}
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
}
|
|
1091
|
+
const stroke = coreStroke(readStroke(style, gradients, onWarn));
|
|
1092
|
+
if (stroke) out.stroke = stroke;
|
|
962
1093
|
return out;
|
|
963
1094
|
}
|
|
964
1095
|
function serializePathD(path) {
|
|
@@ -997,14 +1128,14 @@ function serializePathD(path) {
|
|
|
997
1128
|
|
|
998
1129
|
// src/serialize.ts
|
|
999
1130
|
function serializeSvg(nodes, opts = {}) {
|
|
1000
|
-
const registry = new
|
|
1001
|
-
|
|
1131
|
+
const registry = new PaintServerRegistry();
|
|
1132
|
+
registerPaintServers(nodes, registry);
|
|
1002
1133
|
const bounds = opts.viewBox ?? computeBounds(nodes);
|
|
1003
1134
|
const vb = `${trimNumber(bounds.x)} ${trimNumber(bounds.y)} ${trimNumber(bounds.width)} ${trimNumber(bounds.height)}`;
|
|
1004
1135
|
const namespaces = opts.namespaces ?? {};
|
|
1005
1136
|
const rootAttrs = [`xmlns="http://www.w3.org/2000/svg"`];
|
|
1006
1137
|
for (const [prefix, uri] of Object.entries(namespaces)) {
|
|
1007
|
-
rootAttrs.push(`xmlns:${prefix}="${
|
|
1138
|
+
rootAttrs.push(`xmlns:${prefix}="${escapeAttr2(uri)}"`);
|
|
1008
1139
|
}
|
|
1009
1140
|
rootAttrs.push(`viewBox="${vb}"`);
|
|
1010
1141
|
if (opts.width != null) rootAttrs.push(`width="${trimNumber(opts.width)}"`);
|
|
@@ -1014,7 +1145,7 @@ function serializeSvg(nodes, opts = {}) {
|
|
|
1014
1145
|
const bucket = opts.documentMeta[prefix];
|
|
1015
1146
|
if (!bucket?.attrs) continue;
|
|
1016
1147
|
for (const [name, value] of Object.entries(bucket.attrs)) {
|
|
1017
|
-
rootAttrs.push(`${prefix}:${name}="${
|
|
1148
|
+
rootAttrs.push(`${prefix}:${name}="${escapeAttr2(value)}"`);
|
|
1018
1149
|
}
|
|
1019
1150
|
}
|
|
1020
1151
|
}
|
|
@@ -1028,7 +1159,7 @@ function serializeSvg(nodes, opts = {}) {
|
|
|
1028
1159
|
}
|
|
1029
1160
|
}
|
|
1030
1161
|
}
|
|
1031
|
-
const defsXml = registry.toDefsXml();
|
|
1162
|
+
const defsXml = registry.toDefsXml(opts.onWarn);
|
|
1032
1163
|
const bodyXml = nodes.map((n) => nodeXml(n, registry, namespaces)).join("");
|
|
1033
1164
|
const titleXml = opts.title && opts.title.length > 0 ? `<title>${escapeText(opts.title)}</title>` : "";
|
|
1034
1165
|
return `<svg ${rootAttrs.join(" ")}>${titleXml}${defsXml}${docMetaXml}${bodyXml}</svg>`;
|
|
@@ -1036,7 +1167,7 @@ function serializeSvg(nodes, opts = {}) {
|
|
|
1036
1167
|
function namespacedElementXml(prefix, localName, el) {
|
|
1037
1168
|
const attrs = [];
|
|
1038
1169
|
for (const [name, value] of Object.entries(el.attrs)) {
|
|
1039
|
-
attrs.push(`${name}="${
|
|
1170
|
+
attrs.push(`${name}="${escapeAttr2(value)}"`);
|
|
1040
1171
|
}
|
|
1041
1172
|
const head = attrs.length > 0 ? `<${prefix}:${localName} ${attrs.join(" ")}>` : `<${prefix}:${localName}>`;
|
|
1042
1173
|
let body = "";
|
|
@@ -1056,7 +1187,7 @@ function metaAttrsXml(meta, namespaces) {
|
|
|
1056
1187
|
const bucket = meta[prefix];
|
|
1057
1188
|
if (!bucket?.attrs) continue;
|
|
1058
1189
|
for (const [name, value] of Object.entries(bucket.attrs)) {
|
|
1059
|
-
parts.push(`${prefix}:${name}="${
|
|
1190
|
+
parts.push(`${prefix}:${name}="${escapeAttr2(value)}"`);
|
|
1060
1191
|
}
|
|
1061
1192
|
}
|
|
1062
1193
|
return parts.length > 0 ? ` ${parts.join(" ")}` : "";
|
|
@@ -1073,21 +1204,26 @@ function metaElementsXml(meta, namespaces) {
|
|
|
1073
1204
|
}
|
|
1074
1205
|
return out;
|
|
1075
1206
|
}
|
|
1076
|
-
function
|
|
1207
|
+
function registerPaintServers(nodes, registry) {
|
|
1077
1208
|
for (const n of nodes) {
|
|
1078
1209
|
if (n.kind === "group") {
|
|
1079
|
-
|
|
1210
|
+
registerPaintServers(n.children, registry);
|
|
1080
1211
|
} else if (n.kind === "path") {
|
|
1081
1212
|
if (n.fill.kind === "gradient") registry.register(n.fill.paint);
|
|
1082
1213
|
if (n.stroke && n.stroke.paint.kind === "gradient") registry.register(n.stroke.paint.paint);
|
|
1083
1214
|
} else if (n.kind === "text") {
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1215
|
+
registerTextPaint(n.style?.fill, registry);
|
|
1216
|
+
registerTextPaint(n.style?.stroke?.paint, registry);
|
|
1217
|
+
for (const run of n.runs ?? []) {
|
|
1218
|
+
registerTextPaint(run.fill, registry);
|
|
1219
|
+
registerTextPaint(run.stroke?.paint, registry);
|
|
1087
1220
|
}
|
|
1088
1221
|
}
|
|
1089
1222
|
}
|
|
1090
1223
|
}
|
|
1224
|
+
function registerTextPaint(paint, registry) {
|
|
1225
|
+
if (paint && !("color" in paint)) registry.register(paint);
|
|
1226
|
+
}
|
|
1091
1227
|
function nodeXml(node, registry, namespaces) {
|
|
1092
1228
|
if (node.kind === "group") return groupXml(node, registry, namespaces);
|
|
1093
1229
|
if (node.kind === "text") return textXml(node, registry, namespaces);
|
|
@@ -1149,6 +1285,28 @@ function paintAttrs(paint, name, registry) {
|
|
|
1149
1285
|
const id = registry.register(paint.paint);
|
|
1150
1286
|
return [`${name}="url(#${id})"`];
|
|
1151
1287
|
}
|
|
1288
|
+
function coreStrokeAttrs(stroke, registry) {
|
|
1289
|
+
if (!stroke) return [];
|
|
1290
|
+
const width = stroke.width ?? 1;
|
|
1291
|
+
if (!(width > 0)) return [];
|
|
1292
|
+
const attrs = [];
|
|
1293
|
+
if ("color" in stroke.paint) {
|
|
1294
|
+
attrs.push(`stroke="${stroke.paint.color}"`);
|
|
1295
|
+
if (stroke.paint.opacity != null && stroke.paint.opacity !== 1) {
|
|
1296
|
+
attrs.push(`stroke-opacity="${trimNumber(stroke.paint.opacity)}"`);
|
|
1297
|
+
}
|
|
1298
|
+
} else {
|
|
1299
|
+
attrs.push(`stroke="url(#${registry.register(stroke.paint)})"`);
|
|
1300
|
+
}
|
|
1301
|
+
attrs.push(`stroke-width="${trimNumber(width)}"`);
|
|
1302
|
+
if (stroke.cap) attrs.push(`stroke-linecap="${stroke.cap}"`);
|
|
1303
|
+
if (stroke.join) attrs.push(`stroke-linejoin="${stroke.join}"`);
|
|
1304
|
+
if (stroke.dash && stroke.dash.length > 0) {
|
|
1305
|
+
attrs.push(`stroke-dasharray="${stroke.dash.map(trimNumber).join(" ")}"`);
|
|
1306
|
+
}
|
|
1307
|
+
if (stroke.miterLimit != null) attrs.push(`stroke-miterlimit="${trimNumber(stroke.miterLimit)}"`);
|
|
1308
|
+
return attrs;
|
|
1309
|
+
}
|
|
1152
1310
|
function strokeAttrsFor(stroke, registry) {
|
|
1153
1311
|
const attrs = paintAttrs(stroke.paint, "stroke", registry);
|
|
1154
1312
|
attrs.push(`stroke-width="${trimNumber(stroke.width)}"`);
|
|
@@ -1198,7 +1356,7 @@ function textXml(node, registry, namespaces) {
|
|
|
1198
1356
|
];
|
|
1199
1357
|
const style = node.style;
|
|
1200
1358
|
if (style?.fontSize != null) attrs.push(`font-size="${trimNumber(style.fontSize)}"`);
|
|
1201
|
-
if (style?.fontFamily) attrs.push(`font-family="${
|
|
1359
|
+
if (style?.fontFamily) attrs.push(`font-family="${escapeAttr2(style.fontFamily)}"`);
|
|
1202
1360
|
if (style?.fontWeight != null) attrs.push(`font-weight="${String(style.fontWeight)}"`);
|
|
1203
1361
|
if (style?.fontStyle && style.fontStyle !== "normal") attrs.push(`font-style="${style.fontStyle}"`);
|
|
1204
1362
|
if (style?.align && style.align !== "left") {
|
|
@@ -1221,6 +1379,7 @@ function textXml(node, registry, namespaces) {
|
|
|
1221
1379
|
attrs.push(`fill="url(#${id})"`);
|
|
1222
1380
|
}
|
|
1223
1381
|
}
|
|
1382
|
+
for (const a of coreStrokeAttrs(style?.stroke, registry)) attrs.push(a);
|
|
1224
1383
|
if (node.opacity != null && node.opacity !== 1) {
|
|
1225
1384
|
attrs.push(`opacity="${trimNumber(node.opacity)}"`);
|
|
1226
1385
|
}
|
|
@@ -1239,7 +1398,7 @@ function runXml(run, registry) {
|
|
|
1239
1398
|
const attrs = [];
|
|
1240
1399
|
if (run.bold) attrs.push(`font-weight="700"`);
|
|
1241
1400
|
if (run.italic) attrs.push(`font-style="italic"`);
|
|
1242
|
-
if (run.fontFamily) attrs.push(`font-family="${
|
|
1401
|
+
if (run.fontFamily) attrs.push(`font-family="${escapeAttr2(run.fontFamily)}"`);
|
|
1243
1402
|
if (run.fontSize != null) attrs.push(`font-size="${trimNumber(run.fontSize)}"`);
|
|
1244
1403
|
if (run.letterSpacing != null) {
|
|
1245
1404
|
attrs.push(`letter-spacing="${trimNumber(run.letterSpacing)}"`);
|
|
@@ -1254,6 +1413,7 @@ function runXml(run, registry) {
|
|
|
1254
1413
|
attrs.push(`fill="url(#${id})"`);
|
|
1255
1414
|
}
|
|
1256
1415
|
}
|
|
1416
|
+
for (const a of coreStrokeAttrs(run.stroke, registry)) attrs.push(a);
|
|
1257
1417
|
const head = attrs.length > 0 ? `<tspan ${attrs.join(" ")}>` : "<tspan>";
|
|
1258
1418
|
return `${head}${escapeText(run.text)}</tspan>`;
|
|
1259
1419
|
}
|
|
@@ -1263,7 +1423,7 @@ function textDecorationValue(underline, strikethrough) {
|
|
|
1263
1423
|
if (strikethrough) tokens.push("line-through");
|
|
1264
1424
|
return tokens.length > 0 ? tokens.join(" ") : null;
|
|
1265
1425
|
}
|
|
1266
|
-
function
|
|
1426
|
+
function escapeAttr2(s) {
|
|
1267
1427
|
return s.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
1268
1428
|
}
|
|
1269
1429
|
function escapeText(s) {
|
|
@@ -1430,6 +1590,6 @@ async function unpackSvgFiles(files, ctx) {
|
|
|
1430
1590
|
}
|
|
1431
1591
|
}
|
|
1432
1592
|
|
|
1433
|
-
export { IDENTITY_MATRIX, parseSvg, serializeSvg, svgNodesToKitDrafts, unpackSvgFiles };
|
|
1593
|
+
export { IDENTITY_MATRIX, parseSvg, serializeSvg, svgNodesToKitDrafts, tilePreviewCssUrl, tilePreviewSvg, unpackSvgFiles };
|
|
1434
1594
|
//# sourceMappingURL=index.js.map
|
|
1435
1595
|
//# sourceMappingURL=index.js.map
|