@weasel-js/svg 1.1.0 → 1.3.0-pre.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 +23 -3
- package/dist/index.js +244 -80
- 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, TilePatternSpec, IngestCtx } from '@weasel-js/core';
|
|
1
|
+
import { Path, FillStyle, StyledRun, TextStyle, Stroke, TilePatternSpec, IngestCtx } from '@weasel-js/core';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Public types for `@weasel-js/svg`. The package exposes a flat,
|
|
@@ -86,6 +86,10 @@ interface SvgStroke {
|
|
|
86
86
|
* attribute may render with longer miters than the source SVG intended.
|
|
87
87
|
*/
|
|
88
88
|
miterLimit?: number;
|
|
89
|
+
/** `marker-start` / `marker-mid` / `marker-end`, as the bare `url(#id)` key. */
|
|
90
|
+
markerStart?: string;
|
|
91
|
+
markerMid?: string;
|
|
92
|
+
markerEnd?: string;
|
|
89
93
|
}
|
|
90
94
|
/**
|
|
91
95
|
* Leaf node: a path geometry plus fill/stroke. All other v1 shapes
|
|
@@ -155,8 +159,16 @@ interface SvgTextNode {
|
|
|
155
159
|
text: string;
|
|
156
160
|
/** Optional per-range styling — same shape as `TextPose.runs`. */
|
|
157
161
|
runs?: StyledRun[];
|
|
158
|
-
/** Node-wide
|
|
162
|
+
/** Node-wide typography. Defaults applied at render time via `resolveTextStyle`. */
|
|
159
163
|
style?: TextStyle;
|
|
164
|
+
/**
|
|
165
|
+
* Node-wide glyph paint, the `FillStyle` / `Stroke` a kit text node holds
|
|
166
|
+
* in `data.fill` / `data.stroke`. Not `SvgPaint`, which is the path
|
|
167
|
+
* nodes' shape — a text paint reaches the renderer through the runs, and
|
|
168
|
+
* `StyledRun.fill` / `.stroke` override these per range.
|
|
169
|
+
*/
|
|
170
|
+
fill?: FillStyle;
|
|
171
|
+
stroke?: Stroke;
|
|
160
172
|
/** Element-level opacity (`opacity="..."`), 0..1. */
|
|
161
173
|
opacity?: number;
|
|
162
174
|
/** Element-level rotation in **radians**, pivoting around the unrotated
|
|
@@ -358,6 +370,14 @@ type SvgSceneDraft = {
|
|
|
358
370
|
pose: DraftPose;
|
|
359
371
|
data: Record<string, unknown>;
|
|
360
372
|
};
|
|
373
|
+
/** Lower an `SvgStroke` onto the leaf's `data.stroke`.
|
|
374
|
+
*
|
|
375
|
+
* Everything the SVG carried — paint, width, cap, join, dash, miter limit,
|
|
376
|
+
* opacity — lands on the one `Stroke` `data.stroke` takes. The paint is
|
|
377
|
+
* normalized to the leaf's own box the same way a fill is, so a
|
|
378
|
+
* `userSpaceOnUse` gradient survives the fit-clamp and the drop-point
|
|
379
|
+
* placement. */
|
|
380
|
+
declare function strokeDataFromSvg(stroke: SvgStroke | undefined, box: SvgDraftBounds): Stroke | undefined;
|
|
361
381
|
/**
|
|
362
382
|
* Walk an `SvgNode[]` tree and emit a flat, parent-before-child list of
|
|
363
383
|
* {@link SvgSceneDraft}s. Each `<g>` becomes a container whose pose is the
|
|
@@ -373,4 +393,4 @@ declare function svgNodesToKitDrafts(nodes: readonly SvgNode[], nextId: () => st
|
|
|
373
393
|
*/
|
|
374
394
|
declare function unpackSvgFiles(files: File[], ctx: IngestCtx): Promise<void>;
|
|
375
395
|
|
|
376
|
-
export { IDENTITY_MATRIX, type Matrix, type NamespaceMeta, type NamespacedElement, type ParseOptions, type ParseResult, type SerializeOptions, type SvgDraftBounds, type SvgGroupNode, type SvgImageNode, type SvgNode, type SvgPaint, type SvgPathNode, type SvgSceneDraft, type SvgStroke, type SvgTextNode, UNBOUNDED_TEXT_WIDTH, parseSvg, serializeSvg, svgNodesToKitDrafts, tilePreviewCssUrl, tilePreviewSvg, unpackSvgFiles };
|
|
396
|
+
export { IDENTITY_MATRIX, type Matrix, type NamespaceMeta, type NamespacedElement, type ParseOptions, type ParseResult, type SerializeOptions, type SvgDraftBounds, type SvgGroupNode, type SvgImageNode, type SvgNode, type SvgPaint, type SvgPathNode, type SvgSceneDraft, type SvgStroke, type SvgTextNode, UNBOUNDED_TEXT_WIDTH, parseSvg, serializeSvg, strokeDataFromSvg, svgNodesToKitDrafts, tilePreviewCssUrl, tilePreviewSvg, unpackSvgFiles };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { pathFromD, PATH_M, PATH_L, PATH_Z, boundsOfPath, dwarn, createInsertOp, PathBuilder, parseColor, rgbaToHex, PATH_C, PATH_Q,
|
|
1
|
+
import { pathFromD, PATH_M, PATH_L, PATH_Z, boundsOfPath, getMarker, SCRIPT_METRICS, dwarn, createInsertOp, PathBuilder, parseColor, rgbaToHex, PATH_C, PATH_Q, resolveStrokeWidth, solid, fillToBoundsFrame, resolveTextStyle, getPaintKind } from '@weasel-js/core';
|
|
2
2
|
import { tileGeometry } from '@weasel-js/core/patterns-builtin';
|
|
3
3
|
|
|
4
4
|
// src/parse.ts
|
|
@@ -354,6 +354,39 @@ function readPattern(el, onWarn) {
|
|
|
354
354
|
function escapeAttr(s) {
|
|
355
355
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
356
356
|
}
|
|
357
|
+
function serializePathD(path) {
|
|
358
|
+
if (path.kind === "rect") {
|
|
359
|
+
const { x, y, width, height } = path;
|
|
360
|
+
return `M${trimNumber(x)} ${trimNumber(y)}h${trimNumber(width)}v${trimNumber(height)}h${trimNumber(-width)}Z`;
|
|
361
|
+
}
|
|
362
|
+
const cmds = path.commands;
|
|
363
|
+
const xs = path.coords;
|
|
364
|
+
const out = [];
|
|
365
|
+
let ci = 0;
|
|
366
|
+
for (let i = 0; i < cmds.length; i++) {
|
|
367
|
+
const c = cmds[i];
|
|
368
|
+
if (c === PATH_M) {
|
|
369
|
+
out.push(`M${trimNumber(xs[ci])} ${trimNumber(xs[ci + 1])}`);
|
|
370
|
+
ci += 2;
|
|
371
|
+
} else if (c === PATH_L) {
|
|
372
|
+
out.push(`L${trimNumber(xs[ci])} ${trimNumber(xs[ci + 1])}`);
|
|
373
|
+
ci += 2;
|
|
374
|
+
} else if (c === PATH_C) {
|
|
375
|
+
out.push(
|
|
376
|
+
`C${trimNumber(xs[ci])} ${trimNumber(xs[ci + 1])} ${trimNumber(xs[ci + 2])} ${trimNumber(xs[ci + 3])} ${trimNumber(xs[ci + 4])} ${trimNumber(xs[ci + 5])}`
|
|
377
|
+
);
|
|
378
|
+
ci += 6;
|
|
379
|
+
} else if (c === PATH_Q) {
|
|
380
|
+
out.push(
|
|
381
|
+
`Q${trimNumber(xs[ci])} ${trimNumber(xs[ci + 1])} ${trimNumber(xs[ci + 2])} ${trimNumber(xs[ci + 3])}`
|
|
382
|
+
);
|
|
383
|
+
ci += 4;
|
|
384
|
+
} else if (c === PATH_Z) {
|
|
385
|
+
out.push("Z");
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return out.join("");
|
|
389
|
+
}
|
|
357
390
|
|
|
358
391
|
// src/gradients.ts
|
|
359
392
|
var GRADIENT_TAGS = /* @__PURE__ */ new Set(["lineargradient", "radialgradient"]);
|
|
@@ -376,7 +409,7 @@ function warnUnsupportedDefsChildren(svg, onWarn) {
|
|
|
376
409
|
for (let i = 0; i < root.children.length; i++) {
|
|
377
410
|
const child = root.children[i];
|
|
378
411
|
const tag = child.tagName.toLowerCase();
|
|
379
|
-
if (GRADIENT_TAGS.has(tag) || tag === "pattern") continue;
|
|
412
|
+
if (GRADIENT_TAGS.has(tag) || tag === "pattern" || tag === "marker") continue;
|
|
380
413
|
onWarn(`unsupported <defs> child: <${child.tagName}>`);
|
|
381
414
|
}
|
|
382
415
|
}
|
|
@@ -478,6 +511,13 @@ var PaintServerRegistry = class {
|
|
|
478
511
|
byPaint = /* @__PURE__ */ new Map();
|
|
479
512
|
order = [];
|
|
480
513
|
counter = 0;
|
|
514
|
+
// Marker keys referenced by any stroke, in first-use order. The `<defs>`
|
|
515
|
+
// id is the marker key itself, not a minted counter id: `parseSvg` reads a
|
|
516
|
+
// `marker-end="url(#id)"` fragment back as the marker key directly (it
|
|
517
|
+
// never inspects the `<marker>` element), so a synthetic id would come
|
|
518
|
+
// back unresolvable and warn on round-trip.
|
|
519
|
+
markerKeys = [];
|
|
520
|
+
markerKeySet = /* @__PURE__ */ new Set();
|
|
481
521
|
register(paint) {
|
|
482
522
|
const existing = this.byPaint.get(paint);
|
|
483
523
|
if (existing) return existing;
|
|
@@ -486,20 +526,50 @@ var PaintServerRegistry = class {
|
|
|
486
526
|
this.order.push(paint);
|
|
487
527
|
return id;
|
|
488
528
|
}
|
|
489
|
-
/**
|
|
529
|
+
/** The `<defs>` id for a marker key — the key itself, minting nothing.
|
|
530
|
+
* `undefined` when nothing is registered under `key`, so a caller emits no
|
|
531
|
+
* attribute at all rather than a `url(#…)` pointing at a def that will
|
|
532
|
+
* never be written. */
|
|
533
|
+
markerId(key) {
|
|
534
|
+
if (getMarker(key) === void 0) return void 0;
|
|
535
|
+
if (!this.markerKeySet.has(key)) {
|
|
536
|
+
this.markerKeySet.add(key);
|
|
537
|
+
this.markerKeys.push(key);
|
|
538
|
+
}
|
|
539
|
+
return key;
|
|
540
|
+
}
|
|
541
|
+
/** Emit `<defs>...</defs>` XML for every registered paint server and marker. */
|
|
490
542
|
toDefsXml(onWarn) {
|
|
491
|
-
if (this.order.length === 0) return "";
|
|
543
|
+
if (this.order.length === 0 && this.markerKeys.length === 0) return "";
|
|
492
544
|
const parts = ["<defs>"];
|
|
493
545
|
for (const paint of this.order) {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
);
|
|
546
|
+
parts.push(paintServerXml(this.byPaint.get(paint), paint, onWarn));
|
|
547
|
+
}
|
|
548
|
+
for (const key of this.markerKeys) {
|
|
549
|
+
const entry = getMarker(key);
|
|
550
|
+
if (!entry) continue;
|
|
551
|
+
parts.push(entry.toSvg ? entry.toSvg(key, entry) : defaultMarkerXml(key, entry));
|
|
498
552
|
}
|
|
499
553
|
parts.push("</defs>");
|
|
500
554
|
return parts.join("");
|
|
501
555
|
}
|
|
502
556
|
};
|
|
557
|
+
function defaultMarkerXml(id, entry) {
|
|
558
|
+
const path = entry.path({ size: 1, stroke: { paint: { fill: "solid", color: "#000" } } });
|
|
559
|
+
const d = serializePathD(path);
|
|
560
|
+
const fill = entry.fill === "none" ? "none" : "context-stroke";
|
|
561
|
+
const outline = entry.outline ? ` stroke="context-stroke" stroke-width="${entry.outline.width}" stroke-linecap="round" stroke-linejoin="round"` : "";
|
|
562
|
+
return `<marker id="${id}" markerUnits="strokeWidth" markerWidth="8" markerHeight="8" refX="0" refY="0" orient="auto" overflow="visible"><path d="${d}" fill="${fill}"${outline}/></marker>`;
|
|
563
|
+
}
|
|
564
|
+
function paintServerXml(id, paint, onWarn) {
|
|
565
|
+
if (paint.fill === "pattern") return patternXml(id, paint, onWarn);
|
|
566
|
+
const builtin = gradientXml(id, paint);
|
|
567
|
+
if (builtin) return builtin;
|
|
568
|
+
const custom = getPaintKind(paint.fill)?.toSvg?.(id, paint);
|
|
569
|
+
if (custom) return custom;
|
|
570
|
+
onWarn?.(`${paint.fill} fill has no vector form \u2014 omitted from <defs>`);
|
|
571
|
+
return "";
|
|
572
|
+
}
|
|
503
573
|
function gradientUnitsAttr(units) {
|
|
504
574
|
return units === "bounds" ? "objectBoundingBox" : "userSpaceOnUse";
|
|
505
575
|
}
|
|
@@ -536,6 +606,9 @@ var INHERITABLE = [
|
|
|
536
606
|
"stroke-linejoin",
|
|
537
607
|
"stroke-dasharray",
|
|
538
608
|
"stroke-miterlimit",
|
|
609
|
+
"marker-start",
|
|
610
|
+
"marker-mid",
|
|
611
|
+
"marker-end",
|
|
539
612
|
"color",
|
|
540
613
|
"font-size",
|
|
541
614
|
"font-family",
|
|
@@ -543,7 +616,8 @@ var INHERITABLE = [
|
|
|
543
616
|
"font-style",
|
|
544
617
|
"text-anchor",
|
|
545
618
|
"letter-spacing",
|
|
546
|
-
"text-decoration"
|
|
619
|
+
"text-decoration",
|
|
620
|
+
"direction"
|
|
547
621
|
];
|
|
548
622
|
function readStyleProp(el, prop) {
|
|
549
623
|
const style = el.getAttribute("style");
|
|
@@ -588,6 +662,7 @@ var IGNORED_TAGS = /* @__PURE__ */ new Set([
|
|
|
588
662
|
"lineargradient",
|
|
589
663
|
"radialgradient",
|
|
590
664
|
"pattern",
|
|
665
|
+
"marker",
|
|
591
666
|
"title",
|
|
592
667
|
"desc",
|
|
593
668
|
"metadata"
|
|
@@ -953,8 +1028,28 @@ function readStroke(style, gradients, onWarn) {
|
|
|
953
1028
|
if (Number.isFinite(m) && m >= 1) stroke.miterLimit = m;
|
|
954
1029
|
else onWarn(`unrecognized stroke-miterlimit: ${miterAttr}`);
|
|
955
1030
|
}
|
|
1031
|
+
for (const [attr, field] of [
|
|
1032
|
+
["marker-start", "markerStart"],
|
|
1033
|
+
["marker-mid", "markerMid"],
|
|
1034
|
+
["marker-end", "markerEnd"]
|
|
1035
|
+
]) {
|
|
1036
|
+
const id = parseMarkerRef(style[attr] ?? null);
|
|
1037
|
+
if (id === void 0) continue;
|
|
1038
|
+
if (getMarker(id) === void 0) {
|
|
1039
|
+
onWarn(`${attr} references a marker this kit has no entry for: #${id}`);
|
|
1040
|
+
continue;
|
|
1041
|
+
}
|
|
1042
|
+
stroke[field] = id;
|
|
1043
|
+
}
|
|
956
1044
|
return stroke;
|
|
957
1045
|
}
|
|
1046
|
+
function parseMarkerRef(raw) {
|
|
1047
|
+
if (!raw) return void 0;
|
|
1048
|
+
const v = raw.trim();
|
|
1049
|
+
if (v === "" || v === "none") return void 0;
|
|
1050
|
+
const m = /^url\(\s*#([^)\s]+)\s*\)/.exec(v);
|
|
1051
|
+
return m ? m[1] : void 0;
|
|
1052
|
+
}
|
|
958
1053
|
var STROKE_KEYS = [
|
|
959
1054
|
"stroke",
|
|
960
1055
|
"stroke-width",
|
|
@@ -962,7 +1057,10 @@ var STROKE_KEYS = [
|
|
|
962
1057
|
"stroke-linecap",
|
|
963
1058
|
"stroke-linejoin",
|
|
964
1059
|
"stroke-dasharray",
|
|
965
|
-
"stroke-miterlimit"
|
|
1060
|
+
"stroke-miterlimit",
|
|
1061
|
+
"marker-start",
|
|
1062
|
+
"marker-mid",
|
|
1063
|
+
"marker-end"
|
|
966
1064
|
];
|
|
967
1065
|
function ownStrokeStyle(el) {
|
|
968
1066
|
const out = {};
|
|
@@ -987,6 +1085,9 @@ function coreStroke(stroke) {
|
|
|
987
1085
|
if (stroke.join) out.join = stroke.join;
|
|
988
1086
|
if (stroke.dash) out.dash = stroke.dash;
|
|
989
1087
|
if (stroke.miterLimit != null) out.miterLimit = stroke.miterLimit;
|
|
1088
|
+
if (stroke.markerStart) out.markerStart = stroke.markerStart;
|
|
1089
|
+
if (stroke.markerMid) out.markerMid = stroke.markerMid;
|
|
1090
|
+
if (stroke.markerEnd) out.markerEnd = stroke.markerEnd;
|
|
990
1091
|
return out;
|
|
991
1092
|
}
|
|
992
1093
|
function parseDashArray(s) {
|
|
@@ -1024,8 +1125,31 @@ function parseTextDecoration(raw) {
|
|
|
1024
1125
|
const out = {};
|
|
1025
1126
|
if (tokens.includes("underline")) out.underline = true;
|
|
1026
1127
|
if (tokens.includes("line-through")) out.strikethrough = true;
|
|
1128
|
+
if (tokens.includes("overline")) out.overline = true;
|
|
1027
1129
|
return out;
|
|
1028
1130
|
}
|
|
1131
|
+
function parseBaselineShift(raw, onWarn) {
|
|
1132
|
+
const keyword = raw.trim().toLowerCase();
|
|
1133
|
+
if (keyword === "super" || keyword === "sub") return { script: keyword };
|
|
1134
|
+
const n = parseFloat(raw);
|
|
1135
|
+
if (!Number.isFinite(n)) return {};
|
|
1136
|
+
const unit = /^-?[\d.]+([a-z%]+)$/i.exec(raw.trim())?.[1]?.toLowerCase();
|
|
1137
|
+
if (unit === "%") return { baselineShift: n / 100 };
|
|
1138
|
+
if (unit && unit !== "em") {
|
|
1139
|
+
onWarn(`baseline-shift "${raw}" uses unit "${unit}", which is not converted; treated as ${n} em`);
|
|
1140
|
+
}
|
|
1141
|
+
return n === 0 ? {} : { baselineShift: n };
|
|
1142
|
+
}
|
|
1143
|
+
function parseRunFontSize(raw, onWarn) {
|
|
1144
|
+
const n = parseFloat(raw);
|
|
1145
|
+
if (!Number.isFinite(n)) return {};
|
|
1146
|
+
const unit = /^-?[\d.]+([a-z%]+)$/i.exec(raw.trim())?.[1]?.toLowerCase();
|
|
1147
|
+
if (unit === "%") return { fontScale: n / 100 };
|
|
1148
|
+
if (unit && unit !== "px") {
|
|
1149
|
+
onWarn(`font-size "${raw}" uses unit "${unit}", which is not converted; treated as ${n} world units`);
|
|
1150
|
+
}
|
|
1151
|
+
return { fontSize: n };
|
|
1152
|
+
}
|
|
1029
1153
|
var XLINK_NS2 = "http://www.w3.org/1999/xlink";
|
|
1030
1154
|
function parseImageElement(el, ctm, onWarn) {
|
|
1031
1155
|
const href = el.getAttribute("href") ?? el.getAttributeNS(XLINK_NS2, "href") ?? el.getAttribute("xlink:href");
|
|
@@ -1124,7 +1248,8 @@ function parseTextElement(el, ctm, style, gradients, onWarn) {
|
|
|
1124
1248
|
const ax = m[0] * rawX + m[2] * rawY + m[4];
|
|
1125
1249
|
const ay = m[1] * rawX + m[3] * rawY + m[5];
|
|
1126
1250
|
const leafStyle = deriveStyle(style, el);
|
|
1127
|
-
const textStyle = readTextStyle(leafStyle,
|
|
1251
|
+
const textStyle = readTextStyle(leafStyle, onWarn);
|
|
1252
|
+
const textPaint = readTextPaint(leafStyle, gradients, onWarn);
|
|
1128
1253
|
const fontSize = textStyle.fontSize ?? 16;
|
|
1129
1254
|
const lineHeight = textStyle.lineHeight ?? 1.2;
|
|
1130
1255
|
const dominantBaseline = el.getAttribute("dominant-baseline");
|
|
@@ -1170,6 +1295,8 @@ function parseTextElement(el, ctm, style, gradients, onWarn) {
|
|
|
1170
1295
|
);
|
|
1171
1296
|
if (hasStyling) node.runs = runs;
|
|
1172
1297
|
if (Object.keys(textStyle).length > 0) node.style = textStyle;
|
|
1298
|
+
if (textPaint.fill != null) node.fill = textPaint.fill;
|
|
1299
|
+
if (textPaint.stroke != null) node.stroke = textPaint.stroke;
|
|
1173
1300
|
if (opacity != null) node.opacity = opacity;
|
|
1174
1301
|
if (!isIdentity(localTransform)) {
|
|
1175
1302
|
const worldLocal = rebaseTransform(ctm, localTransform);
|
|
@@ -1198,17 +1325,25 @@ function readTspanRun(el, gradients, style, onWarn) {
|
|
|
1198
1325
|
if (ff) run.fontFamily = ff;
|
|
1199
1326
|
const sz = ownProp(el, "font-size");
|
|
1200
1327
|
if (sz != null) {
|
|
1201
|
-
const
|
|
1202
|
-
if (
|
|
1328
|
+
const size = parseRunFontSize(sz, onWarn);
|
|
1329
|
+
if (size.fontSize != null) run.fontSize = size.fontSize;
|
|
1330
|
+
if (size.fontScale != null) run.fontScale = size.fontScale;
|
|
1203
1331
|
}
|
|
1204
1332
|
const ls = ownProp(el, "letter-spacing");
|
|
1205
1333
|
if (ls != null) {
|
|
1206
1334
|
const n = parseLetterSpacing(ls, onWarn);
|
|
1207
1335
|
if (n != null) run.letterSpacing = n;
|
|
1208
1336
|
}
|
|
1337
|
+
const bs = ownProp(el, "baseline-shift");
|
|
1338
|
+
if (bs != null) {
|
|
1339
|
+
const shift = parseBaselineShift(bs, onWarn);
|
|
1340
|
+
if (shift.script != null) run.script = shift.script;
|
|
1341
|
+
if (shift.baselineShift != null) run.baselineShift = shift.baselineShift;
|
|
1342
|
+
}
|
|
1209
1343
|
const decoration = parseTextDecoration(ownProp(el, "text-decoration"));
|
|
1210
1344
|
if (decoration.underline) run.underline = true;
|
|
1211
1345
|
if (decoration.strikethrough) run.strikethrough = true;
|
|
1346
|
+
if (decoration.overline) run.overline = true;
|
|
1212
1347
|
const tspanStyle = deriveStyle(style, el);
|
|
1213
1348
|
const fillAttr = resolveCurrentColor(ownProp(el, "fill"), tspanStyle);
|
|
1214
1349
|
if (fillAttr) {
|
|
@@ -1224,7 +1359,7 @@ function readTspanRun(el, gradients, style, onWarn) {
|
|
|
1224
1359
|
if (stroke) run.stroke = stroke;
|
|
1225
1360
|
return run;
|
|
1226
1361
|
}
|
|
1227
|
-
function readTextStyle(style,
|
|
1362
|
+
function readTextStyle(style, onWarn) {
|
|
1228
1363
|
const out = {};
|
|
1229
1364
|
const sz = style["font-size"];
|
|
1230
1365
|
if (sz != null) {
|
|
@@ -1240,10 +1375,12 @@ function readTextStyle(style, gradients, onWarn) {
|
|
|
1240
1375
|
}
|
|
1241
1376
|
const fs = style["font-style"];
|
|
1242
1377
|
if (fs === "italic" || fs === "normal") out.fontStyle = fs;
|
|
1243
|
-
|
|
1244
|
-
|
|
1378
|
+
if (style["direction"] === "rtl") out.direction = "rtl";
|
|
1379
|
+
const ltr = out.direction !== "rtl";
|
|
1380
|
+
const anchor = style["text-anchor"] ?? (ltr ? void 0 : "start");
|
|
1381
|
+
if (anchor === "start") out.align = ltr ? "left" : "right";
|
|
1245
1382
|
else if (anchor === "middle") out.align = "center";
|
|
1246
|
-
else if (anchor === "end") out.align = "right";
|
|
1383
|
+
else if (anchor === "end") out.align = ltr ? "right" : "left";
|
|
1247
1384
|
const ls = style["letter-spacing"];
|
|
1248
1385
|
if (ls != null) {
|
|
1249
1386
|
const n = parseLetterSpacing(ls, onWarn);
|
|
@@ -1252,6 +1389,11 @@ function readTextStyle(style, gradients, onWarn) {
|
|
|
1252
1389
|
const decoration = parseTextDecoration(style["text-decoration"] ?? null);
|
|
1253
1390
|
if (decoration.underline) out.underline = true;
|
|
1254
1391
|
if (decoration.strikethrough) out.strikethrough = true;
|
|
1392
|
+
if (decoration.overline) out.overline = true;
|
|
1393
|
+
return out;
|
|
1394
|
+
}
|
|
1395
|
+
function readTextPaint(style, gradients, onWarn) {
|
|
1396
|
+
const out = {};
|
|
1255
1397
|
const fillRaw = resolveCurrentColor(style["fill"] ?? null, style);
|
|
1256
1398
|
if (fillRaw) {
|
|
1257
1399
|
const parsed = parsePaintAttr(fillRaw);
|
|
@@ -1266,41 +1408,6 @@ function readTextStyle(style, gradients, onWarn) {
|
|
|
1266
1408
|
if (stroke) out.stroke = stroke;
|
|
1267
1409
|
return out;
|
|
1268
1410
|
}
|
|
1269
|
-
function serializePathD(path) {
|
|
1270
|
-
if (path.kind === "rect") {
|
|
1271
|
-
const { x, y, width, height } = path;
|
|
1272
|
-
return `M${trimNumber(x)} ${trimNumber(y)}h${trimNumber(width)}v${trimNumber(height)}h${trimNumber(-width)}Z`;
|
|
1273
|
-
}
|
|
1274
|
-
const cmds = path.commands;
|
|
1275
|
-
const xs = path.coords;
|
|
1276
|
-
const out = [];
|
|
1277
|
-
let ci = 0;
|
|
1278
|
-
for (let i = 0; i < cmds.length; i++) {
|
|
1279
|
-
const c = cmds[i];
|
|
1280
|
-
if (c === PATH_M) {
|
|
1281
|
-
out.push(`M${trimNumber(xs[ci])} ${trimNumber(xs[ci + 1])}`);
|
|
1282
|
-
ci += 2;
|
|
1283
|
-
} else if (c === PATH_L) {
|
|
1284
|
-
out.push(`L${trimNumber(xs[ci])} ${trimNumber(xs[ci + 1])}`);
|
|
1285
|
-
ci += 2;
|
|
1286
|
-
} else if (c === PATH_C) {
|
|
1287
|
-
out.push(
|
|
1288
|
-
`C${trimNumber(xs[ci])} ${trimNumber(xs[ci + 1])} ${trimNumber(xs[ci + 2])} ${trimNumber(xs[ci + 3])} ${trimNumber(xs[ci + 4])} ${trimNumber(xs[ci + 5])}`
|
|
1289
|
-
);
|
|
1290
|
-
ci += 6;
|
|
1291
|
-
} else if (c === PATH_Q) {
|
|
1292
|
-
out.push(
|
|
1293
|
-
`Q${trimNumber(xs[ci])} ${trimNumber(xs[ci + 1])} ${trimNumber(xs[ci + 2])} ${trimNumber(xs[ci + 3])}`
|
|
1294
|
-
);
|
|
1295
|
-
ci += 4;
|
|
1296
|
-
} else if (c === PATH_Z) {
|
|
1297
|
-
out.push("Z");
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
return out.join("");
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
// src/serialize.ts
|
|
1304
1411
|
function serializeSvg(nodes, opts = {}) {
|
|
1305
1412
|
const registry = new PaintServerRegistry();
|
|
1306
1413
|
registerPaintServers(nodes, registry);
|
|
@@ -1385,9 +1492,11 @@ function registerPaintServers(nodes, registry) {
|
|
|
1385
1492
|
} else if (n.kind === "path") {
|
|
1386
1493
|
if (n.fill.kind === "gradient") registry.register(n.fill.paint);
|
|
1387
1494
|
if (n.stroke && n.stroke.paint.kind === "gradient") registry.register(n.stroke.paint.paint);
|
|
1495
|
+
registerMarkers(n.stroke, registry);
|
|
1388
1496
|
} else if (n.kind === "text") {
|
|
1389
|
-
registerTextPaint(n.
|
|
1390
|
-
registerTextPaint(n.
|
|
1497
|
+
registerTextPaint(n.fill, registry);
|
|
1498
|
+
registerTextPaint(n.stroke?.paint, registry);
|
|
1499
|
+
registerMarkers(n.stroke, registry);
|
|
1391
1500
|
for (const run of n.runs ?? []) {
|
|
1392
1501
|
registerTextPaint(run.fill, registry);
|
|
1393
1502
|
registerTextPaint(run.stroke?.paint, registry);
|
|
@@ -1398,6 +1507,18 @@ function registerPaintServers(nodes, registry) {
|
|
|
1398
1507
|
function registerTextPaint(paint, registry) {
|
|
1399
1508
|
if (paint && !("color" in paint)) registry.register(paint);
|
|
1400
1509
|
}
|
|
1510
|
+
function markerKeyOfRef(ref) {
|
|
1511
|
+
if (typeof ref === "string") return ref;
|
|
1512
|
+
if (ref && typeof ref === "object" && "key" in ref) return String(ref.key);
|
|
1513
|
+
return void 0;
|
|
1514
|
+
}
|
|
1515
|
+
function registerMarkers(stroke, registry) {
|
|
1516
|
+
if (!stroke) return;
|
|
1517
|
+
for (const ref of [stroke.markerStart, stroke.markerMid, stroke.markerEnd]) {
|
|
1518
|
+
const key = markerKeyOfRef(ref);
|
|
1519
|
+
if (key) registry.markerId(key);
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1401
1522
|
function nodeXml(node, registry, namespaces) {
|
|
1402
1523
|
if (node.kind === "group") return groupXml(node, registry, namespaces);
|
|
1403
1524
|
if (node.kind === "text") return textXml(node, registry, namespaces);
|
|
@@ -1485,7 +1606,7 @@ function paintAttrs(paint, name, registry, includeOpacity = true) {
|
|
|
1485
1606
|
}
|
|
1486
1607
|
function coreStrokeAttrs(stroke, registry) {
|
|
1487
1608
|
if (!stroke) return [];
|
|
1488
|
-
const width = stroke.width ?? 1;
|
|
1609
|
+
const width = resolveStrokeWidth(stroke.width ?? 1, 1);
|
|
1489
1610
|
if (!(width > 0)) return [];
|
|
1490
1611
|
const attrs = [];
|
|
1491
1612
|
if ("color" in stroke.paint) {
|
|
@@ -1503,6 +1624,18 @@ function coreStrokeAttrs(stroke, registry) {
|
|
|
1503
1624
|
attrs.push(`stroke-dasharray="${stroke.dash.map(trimNumber).join(" ")}"`);
|
|
1504
1625
|
}
|
|
1505
1626
|
if (stroke.miterLimit != null) attrs.push(`stroke-miterlimit="${trimNumber(stroke.miterLimit)}"`);
|
|
1627
|
+
for (const [field, attr] of [
|
|
1628
|
+
["markerStart", "marker-start"],
|
|
1629
|
+
["markerMid", "marker-mid"],
|
|
1630
|
+
["markerEnd", "marker-end"]
|
|
1631
|
+
]) {
|
|
1632
|
+
const ref = stroke[field];
|
|
1633
|
+
if (ref === void 0) continue;
|
|
1634
|
+
const key = typeof ref === "string" ? ref : ref.key;
|
|
1635
|
+
const id = registry.markerId(key);
|
|
1636
|
+
if (id === void 0) continue;
|
|
1637
|
+
attrs.push(`${attr}="url(#${id})"`);
|
|
1638
|
+
}
|
|
1506
1639
|
return attrs;
|
|
1507
1640
|
}
|
|
1508
1641
|
function strokeAttrsFor(stroke, registry) {
|
|
@@ -1524,6 +1657,17 @@ function strokeAttrsFor(stroke, registry) {
|
|
|
1524
1657
|
if (stroke.miterLimit != null) {
|
|
1525
1658
|
attrs.push(`stroke-miterlimit="${trimNumber(stroke.miterLimit)}"`);
|
|
1526
1659
|
}
|
|
1660
|
+
for (const [field, attr] of [
|
|
1661
|
+
["markerStart", "marker-start"],
|
|
1662
|
+
["markerMid", "marker-mid"],
|
|
1663
|
+
["markerEnd", "marker-end"]
|
|
1664
|
+
]) {
|
|
1665
|
+
const ref = stroke[field];
|
|
1666
|
+
if (ref === void 0) continue;
|
|
1667
|
+
const id = registry.markerId(ref);
|
|
1668
|
+
if (id === void 0) continue;
|
|
1669
|
+
attrs.push(`${attr}="url(#${id})"`);
|
|
1670
|
+
}
|
|
1527
1671
|
return attrs;
|
|
1528
1672
|
}
|
|
1529
1673
|
function computeBounds(nodes) {
|
|
@@ -1577,27 +1721,30 @@ function textXml(node, registry, namespaces) {
|
|
|
1577
1721
|
if (style?.fontFamily) attrs.push(`font-family="${escapeAttr2(style.fontFamily)}"`);
|
|
1578
1722
|
if (style?.fontWeight != null) attrs.push(`font-weight="${String(style.fontWeight)}"`);
|
|
1579
1723
|
if (style?.fontStyle && style.fontStyle !== "normal") attrs.push(`font-style="${style.fontStyle}"`);
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1724
|
+
const direction = style?.direction ?? "ltr";
|
|
1725
|
+
if (direction === "rtl") attrs.push('direction="rtl"');
|
|
1726
|
+
if (style?.align != null || direction === "rtl") {
|
|
1727
|
+
const align = style?.align ?? "left";
|
|
1728
|
+
const anchor = align === "center" ? "middle" : align === "start" ? "start" : align === "end" ? "end" : align === "left" === (direction === "ltr") ? "start" : "end";
|
|
1729
|
+
if (anchor !== "start") attrs.push(`text-anchor="${anchor}"`);
|
|
1583
1730
|
}
|
|
1584
1731
|
if (style?.letterSpacing != null && style.letterSpacing !== 0) {
|
|
1585
1732
|
attrs.push(`letter-spacing="${trimNumber(style.letterSpacing)}"`);
|
|
1586
1733
|
}
|
|
1587
|
-
const decoration = textDecorationValue(style?.underline, style?.strikethrough);
|
|
1734
|
+
const decoration = textDecorationValue(style?.underline, style?.strikethrough, style?.overline);
|
|
1588
1735
|
if (decoration) attrs.push(`text-decoration="${decoration}"`);
|
|
1589
|
-
if (
|
|
1590
|
-
if ("color" in
|
|
1591
|
-
attrs.push(`fill="${
|
|
1592
|
-
if (
|
|
1593
|
-
attrs.push(`fill-opacity="${trimNumber(
|
|
1736
|
+
if (node.fill) {
|
|
1737
|
+
if ("color" in node.fill) {
|
|
1738
|
+
attrs.push(`fill="${node.fill.color}"`);
|
|
1739
|
+
if (node.fill.opacity != null && node.fill.opacity !== 1) {
|
|
1740
|
+
attrs.push(`fill-opacity="${trimNumber(node.fill.opacity)}"`);
|
|
1594
1741
|
}
|
|
1595
1742
|
} else {
|
|
1596
|
-
const id = registry.register(
|
|
1743
|
+
const id = registry.register(node.fill);
|
|
1597
1744
|
attrs.push(`fill="url(#${id})"`);
|
|
1598
1745
|
}
|
|
1599
1746
|
}
|
|
1600
|
-
for (const a of coreStrokeAttrs(
|
|
1747
|
+
for (const a of coreStrokeAttrs(node.stroke, registry)) attrs.push(a);
|
|
1601
1748
|
if (node.opacity != null && node.opacity !== 1) {
|
|
1602
1749
|
attrs.push(`opacity="${trimNumber(node.opacity)}"`);
|
|
1603
1750
|
}
|
|
@@ -1617,11 +1764,19 @@ function runXml(run, registry) {
|
|
|
1617
1764
|
if (run.bold) attrs.push(`font-weight="700"`);
|
|
1618
1765
|
if (run.italic) attrs.push(`font-style="italic"`);
|
|
1619
1766
|
if (run.fontFamily) attrs.push(`font-family="${escapeAttr2(run.fontFamily)}"`);
|
|
1767
|
+
const presetScale = run.script != null && run.baselineShift != null ? SCRIPT_METRICS[run.script].size : void 0;
|
|
1768
|
+
const scale = run.fontScale ?? presetScale;
|
|
1620
1769
|
if (run.fontSize != null) attrs.push(`font-size="${trimNumber(run.fontSize)}"`);
|
|
1770
|
+
else if (scale != null) attrs.push(`font-size="${trimNumber(scale * 100)}%"`);
|
|
1771
|
+
if (run.baselineShift != null) {
|
|
1772
|
+
attrs.push(`baseline-shift="${trimNumber(run.baselineShift * 100)}%"`);
|
|
1773
|
+
} else if (run.script) {
|
|
1774
|
+
attrs.push(`baseline-shift="${run.script}"`);
|
|
1775
|
+
}
|
|
1621
1776
|
if (run.letterSpacing != null) {
|
|
1622
1777
|
attrs.push(`letter-spacing="${trimNumber(run.letterSpacing)}"`);
|
|
1623
1778
|
}
|
|
1624
|
-
const runDecoration = textDecorationValue(run.underline, run.strikethrough);
|
|
1779
|
+
const runDecoration = textDecorationValue(run.underline, run.strikethrough, run.overline);
|
|
1625
1780
|
if (runDecoration) attrs.push(`text-decoration="${runDecoration}"`);
|
|
1626
1781
|
if (run.fill) {
|
|
1627
1782
|
if ("color" in run.fill) {
|
|
@@ -1635,10 +1790,11 @@ function runXml(run, registry) {
|
|
|
1635
1790
|
const head = attrs.length > 0 ? `<tspan ${attrs.join(" ")}>` : "<tspan>";
|
|
1636
1791
|
return `${head}${escapeText(run.text)}</tspan>`;
|
|
1637
1792
|
}
|
|
1638
|
-
function textDecorationValue(underline, strikethrough) {
|
|
1793
|
+
function textDecorationValue(underline, strikethrough, overline) {
|
|
1639
1794
|
const tokens = [];
|
|
1640
1795
|
if (underline) tokens.push("underline");
|
|
1641
1796
|
if (strikethrough) tokens.push("line-through");
|
|
1797
|
+
if (overline) tokens.push("overline");
|
|
1642
1798
|
return tokens.length > 0 ? tokens.join(" ") : null;
|
|
1643
1799
|
}
|
|
1644
1800
|
function escapeAttr2(s) {
|
|
@@ -1661,7 +1817,6 @@ function pathBounds(path) {
|
|
|
1661
1817
|
}
|
|
1662
1818
|
var CASCADE_OFFSET_PX = 24;
|
|
1663
1819
|
var VIEWPORT_FIT = 0.9;
|
|
1664
|
-
var GRADIENT_FALLBACK = "#888888";
|
|
1665
1820
|
function readFileText(file) {
|
|
1666
1821
|
return new Promise((resolve, reject) => {
|
|
1667
1822
|
const reader = new FileReader();
|
|
@@ -1676,18 +1831,27 @@ function freshSvgNodeId() {
|
|
|
1676
1831
|
}
|
|
1677
1832
|
function fillFromPaint(paint, box) {
|
|
1678
1833
|
if (!paint) return void 0;
|
|
1679
|
-
if (paint.kind === "none") return
|
|
1680
|
-
if (paint.kind === "solid") return paint.color;
|
|
1834
|
+
if (paint.kind === "none") return null;
|
|
1835
|
+
if (paint.kind === "solid") return solid(paint.color);
|
|
1681
1836
|
const g = paint.paint;
|
|
1682
1837
|
const units = "units" in g ? g.units : void 0;
|
|
1683
1838
|
return units === "world" ? fillToBoundsFrame(g, box) : g;
|
|
1684
1839
|
}
|
|
1685
|
-
function
|
|
1686
|
-
if (!paint) return void 0;
|
|
1687
|
-
|
|
1688
|
-
if (paint
|
|
1689
|
-
|
|
1690
|
-
|
|
1840
|
+
function strokeDataFromSvg(stroke, box) {
|
|
1841
|
+
if (!stroke || stroke.paint.kind === "none") return void 0;
|
|
1842
|
+
const paint = fillFromPaint(stroke.paint, box);
|
|
1843
|
+
if (paint === void 0 || paint === null) return void 0;
|
|
1844
|
+
return {
|
|
1845
|
+
paint: stroke.opacity !== void 0 ? { ...paint, opacity: stroke.opacity } : paint,
|
|
1846
|
+
width: stroke.width,
|
|
1847
|
+
...stroke.cap !== void 0 ? { cap: stroke.cap } : {},
|
|
1848
|
+
...stroke.join !== void 0 ? { join: stroke.join } : {},
|
|
1849
|
+
...stroke.dash !== void 0 ? { dash: stroke.dash } : {},
|
|
1850
|
+
...stroke.miterLimit !== void 0 ? { miterLimit: stroke.miterLimit } : {},
|
|
1851
|
+
...stroke.markerStart !== void 0 ? { markerStart: stroke.markerStart } : {},
|
|
1852
|
+
...stroke.markerMid !== void 0 ? { markerMid: stroke.markerMid } : {},
|
|
1853
|
+
...stroke.markerEnd !== void 0 ? { markerEnd: stroke.markerEnd } : {}
|
|
1854
|
+
};
|
|
1691
1855
|
}
|
|
1692
1856
|
function svgNodesToKitDrafts(nodes, nextId) {
|
|
1693
1857
|
const drafts = [];
|
|
@@ -1750,7 +1914,7 @@ function svgNodesToKitDrafts(nodes, nextId) {
|
|
|
1750
1914
|
const pose = { x: b.x, y: b.y, width: b.width, height: b.height };
|
|
1751
1915
|
if (n.rotation) pose.rotation = n.rotation;
|
|
1752
1916
|
const fill = fillFromPaint(n.fill, b);
|
|
1753
|
-
const
|
|
1917
|
+
const strokeFromSvg = strokeDataFromSvg(n.stroke, b);
|
|
1754
1918
|
drafts.push({
|
|
1755
1919
|
kind: "leaf",
|
|
1756
1920
|
id: nextId(),
|
|
@@ -1759,7 +1923,7 @@ function svgNodesToKitDrafts(nodes, nextId) {
|
|
|
1759
1923
|
data: {
|
|
1760
1924
|
path: n.path,
|
|
1761
1925
|
...fill !== void 0 ? { fill } : {},
|
|
1762
|
-
...
|
|
1926
|
+
...strokeFromSvg !== void 0 ? { stroke: strokeFromSvg } : {}
|
|
1763
1927
|
}
|
|
1764
1928
|
});
|
|
1765
1929
|
return pose;
|
|
@@ -1853,6 +2017,6 @@ async function unpackSvgFiles(files, ctx) {
|
|
|
1853
2017
|
}
|
|
1854
2018
|
}
|
|
1855
2019
|
|
|
1856
|
-
export { IDENTITY_MATRIX, UNBOUNDED_TEXT_WIDTH, parseSvg, serializeSvg, svgNodesToKitDrafts, tilePreviewCssUrl, tilePreviewSvg, unpackSvgFiles };
|
|
2020
|
+
export { IDENTITY_MATRIX, UNBOUNDED_TEXT_WIDTH, parseSvg, serializeSvg, strokeDataFromSvg, svgNodesToKitDrafts, tilePreviewCssUrl, tilePreviewSvg, unpackSvgFiles };
|
|
1857
2021
|
//# sourceMappingURL=index.js.map
|
|
1858
2022
|
//# sourceMappingURL=index.js.map
|