@pixodesk/svg-animator-web 1.0.17 → 1.0.18

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.cjs CHANGED
@@ -7,6 +7,7 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
9
  var __propIsEnum = Object.prototype.propertyIsEnumerable;
10
+ var __pow = Math.pow;
10
11
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
11
12
  var __spreadValues = (a, b) => {
12
13
  for (var prop in b || (b = {}))
@@ -83,6 +84,7 @@ __export(index_exports, {
83
84
  PxStrokeGradientEffectSchema: () => PxStrokeGradientEffectSchema,
84
85
  PxSvgNodeExtra: () => PxSvgNodeExtra,
85
86
  PxTextAlongPathEffectSchema: () => PxTextAlongPathEffectSchema,
87
+ PxTextEffectSchema: () => PxTextEffectSchema,
86
88
  PxTransformPartsSchema: () => PxTransformPartsSchema,
87
89
  PxTransformValueSchema: () => PxTransformValueSchema,
88
90
  PxTransformationEffectSchema: () => PxTransformationEffectSchema,
@@ -110,9 +112,13 @@ __export(index_exports, {
110
112
  getNormalizedProps: () => getNormalizedProps,
111
113
  isPxElementFileFormat: () => isPxElementFileFormat,
112
114
  isPxElementFileFormatDeep: () => isPxElementFileFormatDeep,
115
+ jsonElementFactory: () => jsonElementFactory,
113
116
  loadTagAnimators: () => loadTagAnimators,
114
117
  materialiseAllInTree: () => materialiseAllInTree,
115
118
  materialiseAnimatedUseInstances: () => materialiseAnimatedUseInstances,
119
+ materialiseGlyphText: () => materialiseGlyphText,
120
+ materialiseGlyphTextAlongPath: () => materialiseGlyphTextAlongPath,
121
+ materialiseGlyphTextHorizontal: () => materialiseGlyphTextHorizontal,
116
122
  materialiseInternalLoopsInPropAnim: () => materialiseInternalLoopsInPropAnim,
117
123
  materialiseInternalLoopsInTree: () => materialiseInternalLoopsInTree,
118
124
  materialiseMotionPathInPropAnim: () => materialiseMotionPathInPropAnim,
@@ -1005,10 +1011,22 @@ var PxTriggerSchema = implementsInterface()(px.object({
1005
1011
  outAction: px.enum(["continue", "pause", "reset", "reverse"]).optional(),
1006
1012
  scrollIntoViewThreshold: px.number().optional()
1007
1013
  }));
1014
+ var PxGlyphSchema = implementsInterface()(px.object({
1015
+ width: px.number(),
1016
+ d: px.string()
1017
+ }));
1018
+ var PxGlyphFontSchema = implementsInterface()(px.object({
1019
+ fFamily: px.string(),
1020
+ style: px.string(),
1021
+ ascent: px.number(),
1022
+ unitsPerEm: px.number(),
1023
+ glyphs: px.record(PxGlyphSchema)
1024
+ }));
1008
1025
  var PxDefsSchema = implementsInterface()(px.object({
1009
1026
  easings: px.record(px.tuple([px.number(), px.number(), px.number(), px.number()])).optional(),
1010
1027
  animations: px.record(PxAnimationDefinitionSchema).optional(),
1011
- styles: px.record(px.any()).optional()
1028
+ styles: px.record(px.any()).optional(),
1029
+ glyphs: px.record(PxGlyphFontSchema).optional()
1012
1030
  }));
1013
1031
  var PxAnimatorConfigSchema = implementsInterface()(px.object({
1014
1032
  mode: px.enum([PxAnimatorMode.auto, PxAnimatorMode.webapi, PxAnimatorMode.frames]).optional(),
@@ -1129,6 +1147,9 @@ var PxTextAlongPathEffectSchema = implementsInterface()(px.object({
1129
1147
  startOffset: PxAnimatableNumberSchema.optional(),
1130
1148
  textLength: PxAnimatableNumberSchema.optional()
1131
1149
  }));
1150
+ var PxTextEffectSchema = implementsInterface()(px.object({
1151
+ useGlyphs: px.boolean().optional()
1152
+ }));
1132
1153
  var PxEffectsSchema = implementsInterface()(px.object({
1133
1154
  transformation: PxTransformationEffectSchema.optional(),
1134
1155
  repeater: PxRepeaterEffectSchema.optional(),
@@ -1138,7 +1159,8 @@ var PxEffectsSchema = implementsInterface()(px.object({
1138
1159
  isCombinedShape: px.boolean().optional(),
1139
1160
  fillGradient: PxFillGradientEffectSchema.optional(),
1140
1161
  strokeGradient: PxStrokeGradientEffectSchema.optional(),
1141
- textAlongPath: PxTextAlongPathEffectSchema.optional()
1162
+ textAlongPath: PxTextAlongPathEffectSchema.optional(),
1163
+ text: PxTextEffectSchema.optional()
1142
1164
  }));
1143
1165
  function validateNodeEffects(root, opts) {
1144
1166
  const warnings = [];
@@ -2052,6 +2074,61 @@ function applyAnimatableNumber(node, attrName, raw) {
2052
2074
  }
2053
2075
  }
2054
2076
 
2077
+ // src/effects/elementFactory.ts
2078
+ var jsonElementFactory = (type, props, children) => {
2079
+ const node = { type };
2080
+ for (const k in props) if (props[k] !== void 0) node[k] = props[k];
2081
+ const arr = Array.isArray(children) ? children.filter((c) => c != null) : children != null ? [children] : [];
2082
+ if (arr.length) node.children = arr;
2083
+ return node;
2084
+ };
2085
+
2086
+ // src/effects/glyphPathBake.ts
2087
+ function fmt(v, decimals) {
2088
+ return Math.round(v) === v ? "" + Math.round(v) : v.toFixed(decimals);
2089
+ }
2090
+ function pack(nums, decimals) {
2091
+ let s = "";
2092
+ for (let i = 0; i < nums.length; i++) {
2093
+ const str2 = fmt(nums[i], decimals);
2094
+ if (i > 0 && str2.charCodeAt(0) !== 45) s += " ";
2095
+ s += str2;
2096
+ }
2097
+ return s;
2098
+ }
2099
+ function apply(m, x, y) {
2100
+ return [m[0] * x + m[2] * y + m[4], m[1] * x + m[3] * y + m[5]];
2101
+ }
2102
+ var TOKEN_RE = /([MLCQZ])|(-?\d*\.?\d+(?:e[-+]?\d+)?)/gi;
2103
+ function transformPathData(d, m, decimals = 2) {
2104
+ const tokens = [];
2105
+ let match;
2106
+ TOKEN_RE.lastIndex = 0;
2107
+ while ((match = TOKEN_RE.exec(d)) !== null) tokens.push(match[0]);
2108
+ let out = "";
2109
+ let i = 0;
2110
+ const num2 = () => parseFloat(tokens[i++]);
2111
+ while (i < tokens.length) {
2112
+ const cmd = tokens[i++];
2113
+ if (cmd === "M" || cmd === "L") {
2114
+ const [x, y] = apply(m, num2(), num2());
2115
+ out += cmd + pack([x, y], decimals);
2116
+ } else if (cmd === "C") {
2117
+ const [x1, y1] = apply(m, num2(), num2());
2118
+ const [x2, y2] = apply(m, num2(), num2());
2119
+ const [x, y] = apply(m, num2(), num2());
2120
+ out += "C" + pack([x1, y1, x2, y2, x, y], decimals);
2121
+ } else if (cmd === "Q") {
2122
+ const [x1, y1] = apply(m, num2(), num2());
2123
+ const [x, y] = apply(m, num2(), num2());
2124
+ out += "Q" + pack([x1, y1, x, y], decimals);
2125
+ } else if (cmd === "Z" || cmd === "z") {
2126
+ out += "Z";
2127
+ }
2128
+ }
2129
+ return out;
2130
+ }
2131
+
2055
2132
  // src/PxAnimatorUtil.ts
2056
2133
  function bezierToSvgPath(path) {
2057
2134
  var _a, _b, _c, _d;
@@ -2424,6 +2501,24 @@ function bezier2D_arcLengthLUT(P0, P1, P2, P3, steps = 100) {
2424
2501
  }
2425
2502
  return { ts, ds };
2426
2503
  }
2504
+ function bezier2D_tForDistance(lut, distance) {
2505
+ const { ts, ds } = lut;
2506
+ const last = ds.length - 1;
2507
+ if (distance <= 0) return ts[0];
2508
+ if (distance >= ds[last]) return ts[last];
2509
+ let lo = 1;
2510
+ let hi = last;
2511
+ while (lo < hi) {
2512
+ const mid = lo + hi >>> 1;
2513
+ if (ds[mid] < distance) lo = mid + 1;
2514
+ else hi = mid;
2515
+ }
2516
+ const dPrev = ds[hi - 1];
2517
+ const dCur = ds[hi];
2518
+ const span = dCur - dPrev;
2519
+ const frac = span > 0 ? (distance - dPrev) / span : 0;
2520
+ return ts[hi - 1] + frac * (ts[hi] - ts[hi - 1]);
2521
+ }
2427
2522
  function bezier2D_arcAtT(lut, t) {
2428
2523
  const { ts, ds } = lut;
2429
2524
  const last = ts.length - 1;
@@ -2446,6 +2541,437 @@ function invertEasing(easing) {
2446
2541
  return cubicBezier(flipped);
2447
2542
  }
2448
2543
 
2544
+ // src/effects/pathSampler.ts
2545
+ var LUT_STEPS = 48;
2546
+ var CMD_RE = /[MmLlHhVvCcSsQqTtAaZz]/;
2547
+ function tokenize(d) {
2548
+ const tokens = [];
2549
+ const re = /([MmLlHhVvCcSsQqTtAaZz])|(-?\d*\.?\d+(?:[eE][-+]?\d+)?)/g;
2550
+ let m;
2551
+ while ((m = re.exec(d)) !== null) tokens.push(m[0]);
2552
+ return tokens;
2553
+ }
2554
+ function quadToCubic(P0, Qc, P3) {
2555
+ return {
2556
+ P1: [P0[0] + 2 / 3 * (Qc[0] - P0[0]), P0[1] + 2 / 3 * (Qc[1] - P0[1])],
2557
+ P2: [P3[0] + 2 / 3 * (Qc[0] - P3[0]), P3[1] + 2 / 3 * (Qc[1] - P3[1])]
2558
+ };
2559
+ }
2560
+ function parseCubics(d) {
2561
+ const tokens = tokenize(d);
2562
+ const segs = [];
2563
+ let i = 0;
2564
+ let cx = 0, cy = 0;
2565
+ let sx = 0, sy = 0;
2566
+ let pcx = 0, pcy = 0;
2567
+ let pqx = 0, pqy = 0;
2568
+ let prevCmd = "";
2569
+ const num2 = () => parseFloat(tokens[i++]);
2570
+ const push = (P1, P2, P3) => {
2571
+ const P0 = [cx, cy];
2572
+ const lut = bezier2D_arcLengthLUT(P0, P1, P2, P3, LUT_STEPS);
2573
+ segs.push({ P0, P1, P2, P3, lut, len: lut.ds[lut.ds.length - 1] });
2574
+ cx = P3[0];
2575
+ cy = P3[1];
2576
+ };
2577
+ const pushLine = (x, y) => {
2578
+ push(
2579
+ [cx + (x - cx) / 3, cy + (y - cy) / 3],
2580
+ [cx + 2 * (x - cx) / 3, cy + 2 * (y - cy) / 3],
2581
+ [x, y]
2582
+ );
2583
+ };
2584
+ while (i < tokens.length) {
2585
+ let cmd = tokens[i];
2586
+ if (CMD_RE.test(cmd)) i++;
2587
+ else cmd = prevCmd === "M" ? "L" : prevCmd === "m" ? "l" : prevCmd;
2588
+ const rel = cmd >= "a";
2589
+ const U = cmd.toUpperCase();
2590
+ if (U === "Z") {
2591
+ pushLine(sx, sy);
2592
+ cx = sx;
2593
+ cy = sy;
2594
+ prevCmd = cmd;
2595
+ continue;
2596
+ }
2597
+ if (U === "M") {
2598
+ const x = num2() + (rel ? cx : 0), y = num2() + (rel ? cy : 0);
2599
+ cx = x;
2600
+ cy = y;
2601
+ sx = x;
2602
+ sy = y;
2603
+ prevCmd = cmd;
2604
+ continue;
2605
+ }
2606
+ if (U === "L") {
2607
+ pushLine(num2() + (rel ? cx : 0), num2() + (rel ? cy : 0));
2608
+ } else if (U === "H") {
2609
+ pushLine(num2() + (rel ? cx : 0), cy);
2610
+ } else if (U === "V") {
2611
+ pushLine(cx, num2() + (rel ? cy : 0));
2612
+ } else if (U === "C") {
2613
+ const p1 = [num2() + (rel ? cx : 0), num2() + (rel ? cy : 0)];
2614
+ const p2 = [num2() + (rel ? cx : 0), num2() + (rel ? cy : 0)];
2615
+ const p3 = [num2() + (rel ? cx : 0), num2() + (rel ? cy : 0)];
2616
+ pcx = p2[0];
2617
+ pcy = p2[1];
2618
+ push(p1, p2, p3);
2619
+ } else if (U === "S") {
2620
+ const smooth = prevCmd.toUpperCase() === "C" || prevCmd.toUpperCase() === "S";
2621
+ const p1 = smooth ? [2 * cx - pcx, 2 * cy - pcy] : [cx, cy];
2622
+ const p2 = [num2() + (rel ? cx : 0), num2() + (rel ? cy : 0)];
2623
+ const p3 = [num2() + (rel ? cx : 0), num2() + (rel ? cy : 0)];
2624
+ pcx = p2[0];
2625
+ pcy = p2[1];
2626
+ push(p1, p2, p3);
2627
+ } else if (U === "Q") {
2628
+ const qc = [num2() + (rel ? cx : 0), num2() + (rel ? cy : 0)];
2629
+ const p3 = [num2() + (rel ? cx : 0), num2() + (rel ? cy : 0)];
2630
+ pqx = qc[0];
2631
+ pqy = qc[1];
2632
+ const { P1, P2 } = quadToCubic([cx, cy], qc, p3);
2633
+ push(P1, P2, p3);
2634
+ } else if (U === "T") {
2635
+ const smooth = prevCmd.toUpperCase() === "Q" || prevCmd.toUpperCase() === "T";
2636
+ const qc = smooth ? [2 * cx - pqx, 2 * cy - pqy] : [cx, cy];
2637
+ const p3 = [num2() + (rel ? cx : 0), num2() + (rel ? cy : 0)];
2638
+ pqx = qc[0];
2639
+ pqy = qc[1];
2640
+ const { P1, P2 } = quadToCubic([cx, cy], qc, p3);
2641
+ push(P1, P2, p3);
2642
+ } else if (U === "A") {
2643
+ i += 5;
2644
+ pushLine(num2() + (rel ? cx : 0), num2() + (rel ? cy : 0));
2645
+ } else {
2646
+ i++;
2647
+ continue;
2648
+ }
2649
+ prevCmd = cmd;
2650
+ }
2651
+ return segs.length ? segs : null;
2652
+ }
2653
+ function clamp2(v, lo, hi) {
2654
+ return v < lo ? lo : v > hi ? hi : v;
2655
+ }
2656
+ function createPathSampler(d) {
2657
+ const segs = parseCubics(d);
2658
+ if (!segs) return null;
2659
+ const cum = new Float64Array(segs.length + 1);
2660
+ for (let k = 0; k < segs.length; k++) cum[k + 1] = cum[k] + segs[k].len;
2661
+ const totalLength = cum[segs.length];
2662
+ const start = segs[0].P0, end = segs[segs.length - 1].P3;
2663
+ const closed = Math.hypot(end[0] - start[0], end[1] - start[1]) < 1e-3;
2664
+ const sampleOn = (dist) => {
2665
+ let k = 0;
2666
+ while (k < segs.length - 1 && dist > cum[k + 1]) k++;
2667
+ const seg = segs[k];
2668
+ const local = dist - cum[k];
2669
+ const t = seg.len > 0 ? bezier2D_tForDistance(seg.lut, local) : 0;
2670
+ const [x, y] = bezier2D_pointAt(seg.P0, seg.P1, seg.P2, seg.P3, t);
2671
+ const [dx, dy] = bezier2D_derivativeAt(seg.P0, seg.P1, seg.P2, seg.P3, t);
2672
+ return { x, y, angle: Math.atan2(dy, dx) };
2673
+ };
2674
+ return {
2675
+ totalLength,
2676
+ sampleAtDistance(dist) {
2677
+ if (!closed && (dist < 0 || dist > totalLength)) {
2678
+ const edge = dist < 0 ? 0 : totalLength;
2679
+ const p = sampleOn(edge);
2680
+ const over = dist - edge;
2681
+ return { x: p.x + Math.cos(p.angle) * over, y: p.y + Math.sin(p.angle) * over, angle: p.angle };
2682
+ }
2683
+ return sampleOn(clamp2(dist, 0, totalLength));
2684
+ }
2685
+ };
2686
+ }
2687
+
2688
+ // src/effects/textGlyphsEffect.ts
2689
+ var DEFAULT_FONT_SIZE = 16;
2690
+ var TEXT_ATTR_KEYS = [
2691
+ "fontFamily",
2692
+ "fontSize",
2693
+ "fontWeight",
2694
+ "fontStyle",
2695
+ "textAnchor",
2696
+ "letterSpacing",
2697
+ "wordSpacing",
2698
+ "textDecoration",
2699
+ "textTransform",
2700
+ "whiteSpace",
2701
+ "x",
2702
+ "y",
2703
+ "dx",
2704
+ "dy",
2705
+ "lengthAdjust",
2706
+ "fill",
2707
+ "stroke",
2708
+ "strokeWidth",
2709
+ "effects",
2710
+ TEXT_ATTR,
2711
+ TEXT_CONTENT_ATTR,
2712
+ "xml:space"
2713
+ ];
2714
+ function parseLen(v) {
2715
+ if (typeof v === "number") return v;
2716
+ if (typeof v === "string") {
2717
+ const n = parseFloat(v);
2718
+ return isNaN(n) ? void 0 : n;
2719
+ }
2720
+ return void 0;
2721
+ }
2722
+ function str(v) {
2723
+ return typeof v === "string" ? v : void 0;
2724
+ }
2725
+ function resolveStyle(node, parent) {
2726
+ var _a, _b, _c, _d, _e, _f, _g;
2727
+ return {
2728
+ fontFamily: (_a = str(node.fontFamily)) != null ? _a : parent.fontFamily,
2729
+ fontSize: (_b = parseLen(node.fontSize)) != null ? _b : parent.fontSize,
2730
+ fill: (_c = node.fill) != null ? _c : parent.fill,
2731
+ stroke: (_d = node.stroke) != null ? _d : parent.stroke,
2732
+ strokeWidth: (_e = node.strokeWidth) != null ? _e : parent.strokeWidth,
2733
+ letterSpacing: (_f = parseLen(node.letterSpacing)) != null ? _f : parent.letterSpacing,
2734
+ wordSpacing: (_g = parseLen(node.wordSpacing)) != null ? _g : parent.wordSpacing
2735
+ };
2736
+ }
2737
+ function rootStyleOf(node) {
2738
+ var _a, _b, _c;
2739
+ return {
2740
+ fontFamily: str(node.fontFamily),
2741
+ fontSize: (_a = parseLen(node.fontSize)) != null ? _a : DEFAULT_FONT_SIZE,
2742
+ fill: node.fill,
2743
+ stroke: node.stroke,
2744
+ strokeWidth: node.strokeWidth,
2745
+ letterSpacing: (_b = parseLen(node.letterSpacing)) != null ? _b : 0,
2746
+ wordSpacing: (_c = parseLen(node.wordSpacing)) != null ? _c : 0
2747
+ };
2748
+ }
2749
+ function paintOf(s) {
2750
+ const p = {};
2751
+ if (s.fill !== void 0) p.fill = s.fill;
2752
+ if (s.stroke !== void 0) p.stroke = s.stroke;
2753
+ if (s.strokeWidth !== void 0) p.strokeWidth = s.strokeWidth;
2754
+ return p;
2755
+ }
2756
+ function glyphFontFor(s, glyphs, soleFont, warnings) {
2757
+ var _a;
2758
+ const gf = s.fontFamily ? glyphs[s.fontFamily] : soleFont;
2759
+ if (!gf) warnings == null ? void 0 : warnings.push('textGlyphs: no glyphs for font "' + ((_a = s.fontFamily) != null ? _a : "") + '"');
2760
+ return gf;
2761
+ }
2762
+ function soleFontOf(glyphs) {
2763
+ const names = Object.keys(glyphs);
2764
+ return names.length === 1 ? glyphs[names[0]] : void 0;
2765
+ }
2766
+ function materialiseGlyphTextHorizontal(node, opts) {
2767
+ var _a, _b, _c, _d;
2768
+ const { glyphs, create = jsonElementFactory, warnings } = opts;
2769
+ const soleFont = soleFontOf(glyphs);
2770
+ const pen = { x: (_a = parseLen(node.x)) != null ? _a : 0, y: (_b = parseLen(node.y)) != null ? _b : 0 };
2771
+ const placements = [];
2772
+ const lines = [{ start: pen.x, end: pen.x }];
2773
+ let line = 0;
2774
+ const renderChars = (content, s) => {
2775
+ const gf = glyphFontFor(s, glyphs, soleFont, warnings);
2776
+ if (!gf) return;
2777
+ const scale = s.fontSize / gf.unitsPerEm;
2778
+ const paint = paintOf(s);
2779
+ for (let i = 0; i < content.length; i++) {
2780
+ const ch = content.charAt(i);
2781
+ const g = gf.glyphs[ch];
2782
+ if (g && g.d) placements.push({ glyphD: g.d, m: [scale, 0, 0, scale, pen.x, pen.y], paint, line, x: pen.x, y: pen.y, scale });
2783
+ pen.x += (g ? g.width : 0) * scale + s.letterSpacing + (ch === " " ? s.wordSpacing : 0);
2784
+ lines[line].end = pen.x;
2785
+ }
2786
+ };
2787
+ const walk = (el, parentStyle) => {
2788
+ var _a2, _b2, _c2, _d2;
2789
+ const s = resolveStyle(el, parentStyle);
2790
+ const x = parseLen(el.x);
2791
+ const y = parseLen(el.y);
2792
+ if (x !== void 0) {
2793
+ pen.x = x;
2794
+ line = lines.length;
2795
+ lines.push({ start: pen.x, end: pen.x });
2796
+ }
2797
+ if (y !== void 0) pen.y = y;
2798
+ pen.x += (_a2 = parseLen(el.dx)) != null ? _a2 : 0;
2799
+ pen.y += (_b2 = parseLen(el.dy)) != null ? _b2 : 0;
2800
+ const content = (_c2 = str(el[TEXT_ATTR])) != null ? _c2 : str(el[TEXT_CONTENT_ATTR]);
2801
+ if (content && !((_d2 = el.children) == null ? void 0 : _d2.length)) renderChars(content, s);
2802
+ if (el.children) for (const ch of el.children) walk(ch, s);
2803
+ };
2804
+ const rootStyle = rootStyleOf(node);
2805
+ if (node.children) for (const ch of node.children) walk(ch, rootStyle);
2806
+ const rootContent = (_c = str(node[TEXT_ATTR])) != null ? _c : str(node[TEXT_CONTENT_ATTR]);
2807
+ if (rootContent && !((_d = node.children) == null ? void 0 : _d.length)) renderChars(rootContent, rootStyle);
2808
+ const anchor = str(node.textAnchor);
2809
+ if (anchor === "middle" || anchor === "end") {
2810
+ for (const p of placements) {
2811
+ const w = lines[p.line].end - lines[p.line].start;
2812
+ const shift = anchor === "middle" ? -w / 2 : -w;
2813
+ p.m = [p.scale, 0, 0, p.scale, p.x + shift, p.y];
2814
+ }
2815
+ }
2816
+ return toGroup(node, buildPaths(placements, create, warnings), create);
2817
+ }
2818
+ function collectAlongPathCells(node, glyphs, soleFont, warnings) {
2819
+ const cells = [];
2820
+ let adv = 0;
2821
+ const walk = (el, parentStyle) => {
2822
+ var _a, _b;
2823
+ const s = resolveStyle(el, parentStyle);
2824
+ const content = (_a = str(el[TEXT_ATTR])) != null ? _a : str(el[TEXT_CONTENT_ATTR]);
2825
+ if (content && !((_b = el.children) == null ? void 0 : _b.length)) {
2826
+ const gf = glyphFontFor(s, glyphs, soleFont, warnings);
2827
+ if (gf) {
2828
+ const scale = s.fontSize / gf.unitsPerEm;
2829
+ const paint = paintOf(s);
2830
+ for (let i = 0; i < content.length; i++) {
2831
+ const ch = content.charAt(i);
2832
+ const g = gf.glyphs[ch];
2833
+ const glyphAdv = (g ? g.width : 0) * scale;
2834
+ if (g && g.d) cells.push({ glyphD: g.d, widthEm: g.width, scale, paint, midBase: adv + glyphAdv / 2 });
2835
+ adv += glyphAdv + s.letterSpacing + (ch === " " ? s.wordSpacing : 0);
2836
+ }
2837
+ }
2838
+ }
2839
+ if (el.children) for (const ch of el.children) walk(ch, s);
2840
+ };
2841
+ walk(node, rootStyleOf(node));
2842
+ return { cells, width: adv };
2843
+ }
2844
+ function alongAffine(sampler, dist, scale, widthEm) {
2845
+ const { x, y, angle } = sampler.sampleAtDistance(dist);
2846
+ const cos = Math.cos(angle), sin = Math.sin(angle), hw = widthEm / 2;
2847
+ return [scale * cos, scale * sin, -scale * sin, scale * cos, x - scale * cos * hw, y - scale * sin * hw];
2848
+ }
2849
+ function materialiseGlyphTextAlongPath(node, pathD, startOffset, opts, textLength) {
2850
+ var _a;
2851
+ const { glyphs, create = jsonElementFactory, warnings } = opts;
2852
+ const sampler = pathD ? createPathSampler(pathD) : null;
2853
+ if (!sampler) {
2854
+ warnings == null ? void 0 : warnings.push("textGlyphs: unparsable along-path geometry");
2855
+ return null;
2856
+ }
2857
+ const soleFont = soleFontOf(glyphs);
2858
+ const { cells, width } = collectAlongPathCells(node, glyphs, soleFont, warnings);
2859
+ if (!cells.length) return toGroup(node, [], create);
2860
+ if (textLength && textLength > 0 && width > 0) {
2861
+ const k = textLength / width;
2862
+ for (const c of cells) c.midBase *= k;
2863
+ }
2864
+ const so = readAnimatable(startOffset);
2865
+ if (so.kind === "animated" /* Animated */ && so.keyframes.length >= 2) {
2866
+ return toGroup(node, buildAnimatedAlongPath(cells, sampler, so.keyframes, so.loop, create), create);
2867
+ }
2868
+ const base = so.kind === "animated" /* Animated */ ? Number((_a = so.keyframes[0]) == null ? void 0 : _a.value) || 0 : so.kind === "static" /* Static */ ? Number(so.value) || 0 : 0;
2869
+ const placements = cells.map((c) => ({
2870
+ glyphD: c.glyphD,
2871
+ paint: c.paint,
2872
+ m: alongAffine(sampler, base + c.midBase, c.scale, c.widthEm)
2873
+ }));
2874
+ return toGroup(node, buildPaths(placements, create, warnings), create);
2875
+ }
2876
+ var ALONG_PATH_MAX_STEPS = 48;
2877
+ var ALONG_PATH_MAX_STEPS_PER_SEGMENT = 64;
2878
+ function roundN(v, n) {
2879
+ const f = __pow(10, n);
2880
+ return Math.round(v * f) / f;
2881
+ }
2882
+ function buildAnimatedAlongPath(cells, sampler, sokfs, loop, create) {
2883
+ const step = Math.max(sampler.totalLength / ALONG_PATH_MAX_STEPS, 0.5);
2884
+ const timeOf = (kf) => Number(kf.time) || 0;
2885
+ const offOf = (kf) => Number(kf.value) || 0;
2886
+ const out = [];
2887
+ for (const c of cells) {
2888
+ const centred = [c.scale, 0, 0, c.scale, -c.scale * (c.widthEm / 2), 0];
2889
+ const d = transformPathData(c.glyphD, centred);
2890
+ const sampleKf = (dist, time) => {
2891
+ const { x, y, angle } = sampler.sampleAtDistance(dist);
2892
+ return {
2893
+ time,
2894
+ value: {
2895
+ ["translate" /* Translate */]: [roundN(x, 3), roundN(y, 3)],
2896
+ ["rotate" /* Rotate */]: roundN(angle * 180 / Math.PI, 3)
2897
+ }
2898
+ };
2899
+ };
2900
+ const kfs = [sampleKf(offOf(sokfs[0]) + c.midBase, timeOf(sokfs[0]))];
2901
+ for (let k = 1; k < sokfs.length; k++) {
2902
+ const t0 = timeOf(sokfs[k - 1]), t1 = timeOf(sokfs[k]);
2903
+ const o0 = offOf(sokfs[k - 1]), o1 = offOf(sokfs[k]);
2904
+ const n = Math.min(ALONG_PATH_MAX_STEPS_PER_SEGMENT, Math.max(1, Math.ceil(Math.abs(o1 - o0) / step)));
2905
+ for (let s = 1; s <= n; s++) {
2906
+ const f = s / n;
2907
+ kfs.push(sampleKf(o0 + f * (o1 - o0) + c.midBase, t0 + f * (t1 - t0)));
2908
+ }
2909
+ }
2910
+ const transform = { keyframes: kfs };
2911
+ if (loop !== void 0) transform.loop = loop;
2912
+ out.push(create("path", __spreadProps(__spreadValues({ d }, paintProps(c.paint)), { animate: { transform } }), []));
2913
+ }
2914
+ return out;
2915
+ }
2916
+ function paintProps(paint) {
2917
+ const p = {};
2918
+ if (paint.fill !== void 0) p.fill = paint.fill;
2919
+ if (paint.stroke !== void 0) p.stroke = paint.stroke;
2920
+ if (paint.strokeWidth !== void 0) p.strokeWidth = paint.strokeWidth;
2921
+ return p;
2922
+ }
2923
+ function buildPaths(placements, create, warnings) {
2924
+ var _a, _b, _c;
2925
+ if (!placements.length) {
2926
+ warnings == null ? void 0 : warnings.push("textGlyphs: nothing to render");
2927
+ return [];
2928
+ }
2929
+ const byPaint = /* @__PURE__ */ new Map();
2930
+ for (const p of placements) {
2931
+ const key = JSON.stringify([(_a = p.paint.fill) != null ? _a : null, (_b = p.paint.stroke) != null ? _b : null, (_c = p.paint.strokeWidth) != null ? _c : null]);
2932
+ const baked = transformPathData(p.glyphD, p.m);
2933
+ const entry = byPaint.get(key);
2934
+ if (entry) entry.d += baked;
2935
+ else byPaint.set(key, { paint: p.paint, d: baked });
2936
+ }
2937
+ const out = [];
2938
+ for (const { paint, d } of byPaint.values()) out.push(create("path", __spreadValues({ d }, paintProps(paint)), []));
2939
+ return out;
2940
+ }
2941
+ function toGroup(node, children, create) {
2942
+ const gProps = {};
2943
+ for (const k of Object.keys(node)) {
2944
+ if (k === "type" || k === "children" || TEXT_ATTR_KEYS.indexOf(k) !== -1) continue;
2945
+ gProps[k] = node[k];
2946
+ }
2947
+ if (gProps.style && typeof gProps.style === "object") {
2948
+ const style = __spreadValues({}, gProps.style);
2949
+ delete style["white-space"];
2950
+ if (Object.keys(style).length) gProps.style = style;
2951
+ else delete gProps.style;
2952
+ }
2953
+ return create("g", gProps, children);
2954
+ }
2955
+ function materialiseGlyphText(node, opts) {
2956
+ if (opts.alongPath) return materialiseGlyphTextAlongPath(node, opts.alongPath.pathD, opts.alongPath.startOffset, opts, opts.alongPath.textLength);
2957
+ return materialiseGlyphTextHorizontal(node, opts);
2958
+ }
2959
+ function applyTextGlyphsEffect(node, fx, ctx) {
2960
+ if (!(fx == null ? void 0 : fx.useGlyphs)) return node;
2961
+ if (!ctx.glyphs) {
2962
+ ctx.warnings.push("textGlyphs: no definitions.glyphs \u2014 left as native <text>");
2963
+ return node;
2964
+ }
2965
+ return materialiseGlyphTextHorizontal(node, { glyphs: ctx.glyphs, warnings: ctx.warnings });
2966
+ }
2967
+ function applyTextGlyphsAlongPath(node, ctx, pathD, startOffset, textLength) {
2968
+ if (!ctx.glyphs) {
2969
+ ctx.warnings.push("textGlyphs: no definitions.glyphs");
2970
+ return null;
2971
+ }
2972
+ return materialiseGlyphTextAlongPath(node, pathD, startOffset, { glyphs: ctx.glyphs, warnings: ctx.warnings }, textLength);
2973
+ }
2974
+
2449
2975
  // src/PxMotionPath.ts
2450
2976
  function getKfTranslate(kf) {
2451
2977
  var _a;
@@ -2889,12 +3415,12 @@ function parseSvgPathToBezier(d) {
2889
3415
  }
2890
3416
  return res;
2891
3417
  }
2892
- function extractPathData(str) {
2893
- if (str.startsWith("path(") && str.endsWith(")")) {
2894
- return str.slice(5, -1);
3418
+ function extractPathData(str2) {
3419
+ if (str2.startsWith("path(") && str2.endsWith(")")) {
3420
+ return str2.slice(5, -1);
2895
3421
  }
2896
- if (/^[MmZzLlHhVvCcSsQqTtAa]/.test(str)) {
2897
- return str;
3422
+ if (/^[MmZzLlHhVvCcSsQqTtAa]/.test(str2)) {
3423
+ return str2;
2898
3424
  }
2899
3425
  return void 0;
2900
3426
  }
@@ -3819,7 +4345,7 @@ function rectToPathD(node) {
3819
4345
 
3820
4346
  // src/effects/PlayerEffectsUtil.ts
3821
4347
  function applyPlayerEffects(root) {
3822
- var _a;
4348
+ var _a, _b;
3823
4349
  const ctx = {
3824
4350
  defs: [],
3825
4351
  warnings: [],
@@ -3830,7 +4356,8 @@ function applyPlayerEffects(root) {
3830
4356
  maskAncestorChains: /* @__PURE__ */ new Map(),
3831
4357
  // Resolved engine: `frames` ONLY when explicitly set; auto/webapi/unset →
3832
4358
  // webapi (we're not 100% sure it's frames, and CSS/WAAPI need the inline form).
3833
- engine: ((_a = getAnimatorConfig(root)) == null ? void 0 : _a.mode) === PxAnimatorMode.frames ? PxAnimatorEngine.frames : PxAnimatorEngine.webapi
4359
+ engine: ((_a = getAnimatorConfig(root)) == null ? void 0 : _a.mode) === PxAnimatorMode.frames ? PxAnimatorEngine.frames : PxAnimatorEngine.webapi,
4360
+ glyphs: (_b = getDefs(root)) == null ? void 0 : _b.glyphs
3834
4361
  };
3835
4362
  const working = clone(root);
3836
4363
  indexById(working, ctx.idMap);
@@ -3842,16 +4369,34 @@ function applyPlayerEffects(root) {
3842
4369
  return { root: out, defs: ctx.defs, warnings: ctx.warnings, errors: ctx.errors };
3843
4370
  }
3844
4371
  function applyPlayerEffects_exceptRetime(node, ctx) {
4372
+ var _a;
3845
4373
  if (node.children) node.children = node.children.map((child) => applyPlayerEffects_exceptRetime(child, ctx));
3846
4374
  const fx = node.effects;
3847
4375
  const originalId = typeof node.id === "string" ? node.id : void 0;
3848
4376
  const innerIdForContentRef = originalId ? ctx.contentRefInnerIds.get(originalId) : void 0;
3849
4377
  if (!fx && !innerIdForContentRef) return node;
3850
- const { transformation, repeater, maskedBy, trimPath, clone: cloneFx, fillGradient, strokeGradient, textAlongPath } = fx != null ? fx : {};
4378
+ const { transformation, repeater, maskedBy, trimPath, clone: cloneFx, fillGradient, strokeGradient, textAlongPath, text } = fx != null ? fx : {};
3851
4379
  const isCombinedShape = fx == null ? void 0 : fx.isCombinedShape;
3852
4380
  if (fx) delete node.effects;
3853
4381
  let n = node;
3854
- n = applyTextAlongPathEffect(n, textAlongPath, ctx);
4382
+ let consumedByGlyphs = false;
4383
+ if (text == null ? void 0 : text.useGlyphs) {
4384
+ if (textAlongPath) {
4385
+ const pathNode = typeof textAlongPath.href === "string" ? ctx.idMap.get(textAlongPath.href) : void 0;
4386
+ const pathD = pathNode && typeof pathNode.d === "string" ? pathNode.d : void 0;
4387
+ const rawTL = textAlongPath.textLength;
4388
+ const textLength = typeof rawTL === "number" ? rawTL : rawTL && typeof rawTL === "object" ? typeof rawTL.value === "number" ? rawTL.value : Array.isArray(rawTL.keyframes) && rawTL.keyframes.length ? Number((_a = rawTL.keyframes[0]) == null ? void 0 : _a.value) : void 0 : void 0;
4389
+ const glyphed = applyTextGlyphsAlongPath(n, ctx, pathD, textAlongPath.startOffset, textLength);
4390
+ if (glyphed) {
4391
+ n = glyphed;
4392
+ consumedByGlyphs = true;
4393
+ }
4394
+ } else {
4395
+ n = applyTextGlyphsEffect(n, text, ctx);
4396
+ consumedByGlyphs = true;
4397
+ }
4398
+ }
4399
+ if (!consumedByGlyphs) n = applyTextAlongPathEffect(n, textAlongPath, ctx);
3855
4400
  n = applyFillGradientEffect(n, fillGradient, ctx);
3856
4401
  n = applyStrokeGradientEffect(n, strokeGradient, ctx);
3857
4402
  n = applyTrimPathEffect(n, trimPath, isCombinedShape, ctx);
@@ -4094,8 +4639,8 @@ var DATA_RASTER_IMAGE_RE = /^data:image\/(?:png|jpe?g|gif|webp|bmp);base64,/i;
4094
4639
  var DATA_SVG_IMAGE_RE = /^data:image\/svg\+xml(?:;[^,]*)?,/i;
4095
4640
  var DATA_IMAGE_BASE64_PAYLOAD_RE = /^data:image\/[^;,]*;base64,([A-Za-z0-9+/]{8})/i;
4096
4641
  var BASE64_RASTER_MAGICS = ["iVBORw0K", "/9j/", "R0lGOD", "UklGR", "Qk"];
4097
- function isContentSniffedRasterImage(str) {
4098
- const m = DATA_IMAGE_BASE64_PAYLOAD_RE.exec(str);
4642
+ function isContentSniffedRasterImage(str2) {
4643
+ const m = DATA_IMAGE_BASE64_PAYLOAD_RE.exec(str2);
4099
4644
  return !!m && BASE64_RASTER_MAGICS.some((magic) => m[1].startsWith(magic));
4100
4645
  }
4101
4646
  function isDangerousAttrName(nameLower) {
@@ -4109,18 +4654,18 @@ function sanitiseAttributeValue(name, value) {
4109
4654
  return void 0;
4110
4655
  }
4111
4656
  if (nameLower === "fill" || nameLower === "stroke" || nameLower === "stopcolor") {
4112
- const str = String(value);
4113
- if (str.includes("url(") && !/^url\(#[^)]+\)$/.test(str)) {
4657
+ const str2 = String(value);
4658
+ if (str2.includes("url(") && !/^url\(#[^)]+\)$/.test(str2)) {
4114
4659
  console.warn('Attribute "' + nameLower + '" blocked: url() must be internal url(#id), got:', value);
4115
4660
  return void 0;
4116
4661
  }
4117
4662
  return value;
4118
4663
  }
4119
4664
  if (URL_VALUE_ATTRS_LOWER.has(nameLower)) {
4120
- const str = String(value);
4121
- if (str.startsWith("#")) return value;
4122
- if (/^url\(#[^)]+\)$/.test(str)) return value;
4123
- if (IMAGE_REF_ATTRS_LOWER.has(nameLower) && (DATA_RASTER_IMAGE_RE.test(str) || DATA_SVG_IMAGE_RE.test(str) || isContentSniffedRasterImage(str))) return value;
4665
+ const str2 = String(value);
4666
+ if (str2.startsWith("#")) return value;
4667
+ if (/^url\(#[^)]+\)$/.test(str2)) return value;
4668
+ if (IMAGE_REF_ATTRS_LOWER.has(nameLower) && (DATA_RASTER_IMAGE_RE.test(str2) || DATA_SVG_IMAGE_RE.test(str2) || isContentSniffedRasterImage(str2))) return value;
4124
4669
  console.warn('Attribute "' + nameLower + '" blocked: must be #id, url(#id), or data:image/\u2026 URI, got:', value);
4125
4670
  return void 0;
4126
4671
  }
@@ -4150,7 +4695,7 @@ function createElement(tagName, normalisedProps, style, children, textContent) {
4150
4695
  if (textContent) element.textContent = textContent;
4151
4696
  return element;
4152
4697
  }
4153
- function resolveStyle(style, defs) {
4698
+ function resolveStyle2(style, defs) {
4154
4699
  var _a;
4155
4700
  if (!style) return void 0;
4156
4701
  if (typeof style === "string") {
@@ -4186,7 +4731,7 @@ function renderNode(node, defs) {
4186
4731
  if (!node) return null;
4187
4732
  const _a = node, { type, children, style } = _a, props = __objRest(_a, ["type", "children", "style"]);
4188
4733
  const nodeDefs = getDefs(node) || defs;
4189
- const resolvedStyle = resolveStyle(style, nodeDefs);
4734
+ const resolvedStyle = resolveStyle2(style, nodeDefs);
4190
4735
  let childElements;
4191
4736
  if (children) {
4192
4737
  for (const ch of children) {
@@ -5240,6 +5785,7 @@ function subtractMultiset(a, b) {
5240
5785
  PxStrokeGradientEffectSchema,
5241
5786
  PxSvgNodeExtra,
5242
5787
  PxTextAlongPathEffectSchema,
5788
+ PxTextEffectSchema,
5243
5789
  PxTransformPartsSchema,
5244
5790
  PxTransformValueSchema,
5245
5791
  PxTransformationEffectSchema,
@@ -5267,9 +5813,13 @@ function subtractMultiset(a, b) {
5267
5813
  getNormalizedProps,
5268
5814
  isPxElementFileFormat,
5269
5815
  isPxElementFileFormatDeep,
5816
+ jsonElementFactory,
5270
5817
  loadTagAnimators,
5271
5818
  materialiseAllInTree,
5272
5819
  materialiseAnimatedUseInstances,
5820
+ materialiseGlyphText,
5821
+ materialiseGlyphTextAlongPath,
5822
+ materialiseGlyphTextHorizontal,
5273
5823
  materialiseInternalLoopsInPropAnim,
5274
5824
  materialiseInternalLoopsInTree,
5275
5825
  materialiseMotionPathInPropAnim,