@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.js CHANGED
@@ -4,6 +4,7 @@ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
4
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
6
  var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
+ var __pow = Math.pow;
7
8
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
9
  var __spreadValues = (a, b) => {
9
10
  for (var prop in b || (b = {}))
@@ -906,10 +907,22 @@ var PxTriggerSchema = implementsInterface()(px.object({
906
907
  outAction: px.enum(["continue", "pause", "reset", "reverse"]).optional(),
907
908
  scrollIntoViewThreshold: px.number().optional()
908
909
  }));
910
+ var PxGlyphSchema = implementsInterface()(px.object({
911
+ width: px.number(),
912
+ d: px.string()
913
+ }));
914
+ var PxGlyphFontSchema = implementsInterface()(px.object({
915
+ fFamily: px.string(),
916
+ style: px.string(),
917
+ ascent: px.number(),
918
+ unitsPerEm: px.number(),
919
+ glyphs: px.record(PxGlyphSchema)
920
+ }));
909
921
  var PxDefsSchema = implementsInterface()(px.object({
910
922
  easings: px.record(px.tuple([px.number(), px.number(), px.number(), px.number()])).optional(),
911
923
  animations: px.record(PxAnimationDefinitionSchema).optional(),
912
- styles: px.record(px.any()).optional()
924
+ styles: px.record(px.any()).optional(),
925
+ glyphs: px.record(PxGlyphFontSchema).optional()
913
926
  }));
914
927
  var PxAnimatorConfigSchema = implementsInterface()(px.object({
915
928
  mode: px.enum([PxAnimatorMode.auto, PxAnimatorMode.webapi, PxAnimatorMode.frames]).optional(),
@@ -1030,6 +1043,9 @@ var PxTextAlongPathEffectSchema = implementsInterface()(px.object({
1030
1043
  startOffset: PxAnimatableNumberSchema.optional(),
1031
1044
  textLength: PxAnimatableNumberSchema.optional()
1032
1045
  }));
1046
+ var PxTextEffectSchema = implementsInterface()(px.object({
1047
+ useGlyphs: px.boolean().optional()
1048
+ }));
1033
1049
  var PxEffectsSchema = implementsInterface()(px.object({
1034
1050
  transformation: PxTransformationEffectSchema.optional(),
1035
1051
  repeater: PxRepeaterEffectSchema.optional(),
@@ -1039,7 +1055,8 @@ var PxEffectsSchema = implementsInterface()(px.object({
1039
1055
  isCombinedShape: px.boolean().optional(),
1040
1056
  fillGradient: PxFillGradientEffectSchema.optional(),
1041
1057
  strokeGradient: PxStrokeGradientEffectSchema.optional(),
1042
- textAlongPath: PxTextAlongPathEffectSchema.optional()
1058
+ textAlongPath: PxTextAlongPathEffectSchema.optional(),
1059
+ text: PxTextEffectSchema.optional()
1043
1060
  }));
1044
1061
  function validateNodeEffects(root, opts) {
1045
1062
  const warnings = [];
@@ -1953,6 +1970,61 @@ function applyAnimatableNumber(node, attrName, raw) {
1953
1970
  }
1954
1971
  }
1955
1972
 
1973
+ // src/effects/elementFactory.ts
1974
+ var jsonElementFactory = (type, props, children) => {
1975
+ const node = { type };
1976
+ for (const k in props) if (props[k] !== void 0) node[k] = props[k];
1977
+ const arr = Array.isArray(children) ? children.filter((c) => c != null) : children != null ? [children] : [];
1978
+ if (arr.length) node.children = arr;
1979
+ return node;
1980
+ };
1981
+
1982
+ // src/effects/glyphPathBake.ts
1983
+ function fmt(v, decimals) {
1984
+ return Math.round(v) === v ? "" + Math.round(v) : v.toFixed(decimals);
1985
+ }
1986
+ function pack(nums, decimals) {
1987
+ let s = "";
1988
+ for (let i = 0; i < nums.length; i++) {
1989
+ const str2 = fmt(nums[i], decimals);
1990
+ if (i > 0 && str2.charCodeAt(0) !== 45) s += " ";
1991
+ s += str2;
1992
+ }
1993
+ return s;
1994
+ }
1995
+ function apply(m, x, y) {
1996
+ return [m[0] * x + m[2] * y + m[4], m[1] * x + m[3] * y + m[5]];
1997
+ }
1998
+ var TOKEN_RE = /([MLCQZ])|(-?\d*\.?\d+(?:e[-+]?\d+)?)/gi;
1999
+ function transformPathData(d, m, decimals = 2) {
2000
+ const tokens = [];
2001
+ let match;
2002
+ TOKEN_RE.lastIndex = 0;
2003
+ while ((match = TOKEN_RE.exec(d)) !== null) tokens.push(match[0]);
2004
+ let out = "";
2005
+ let i = 0;
2006
+ const num2 = () => parseFloat(tokens[i++]);
2007
+ while (i < tokens.length) {
2008
+ const cmd = tokens[i++];
2009
+ if (cmd === "M" || cmd === "L") {
2010
+ const [x, y] = apply(m, num2(), num2());
2011
+ out += cmd + pack([x, y], decimals);
2012
+ } else if (cmd === "C") {
2013
+ const [x1, y1] = apply(m, num2(), num2());
2014
+ const [x2, y2] = apply(m, num2(), num2());
2015
+ const [x, y] = apply(m, num2(), num2());
2016
+ out += "C" + pack([x1, y1, x2, y2, x, y], decimals);
2017
+ } else if (cmd === "Q") {
2018
+ const [x1, y1] = apply(m, num2(), num2());
2019
+ const [x, y] = apply(m, num2(), num2());
2020
+ out += "Q" + pack([x1, y1, x, y], decimals);
2021
+ } else if (cmd === "Z" || cmd === "z") {
2022
+ out += "Z";
2023
+ }
2024
+ }
2025
+ return out;
2026
+ }
2027
+
1956
2028
  // src/PxAnimatorUtil.ts
1957
2029
  function bezierToSvgPath(path) {
1958
2030
  var _a, _b, _c, _d;
@@ -2325,6 +2397,24 @@ function bezier2D_arcLengthLUT(P0, P1, P2, P3, steps = 100) {
2325
2397
  }
2326
2398
  return { ts, ds };
2327
2399
  }
2400
+ function bezier2D_tForDistance(lut, distance) {
2401
+ const { ts, ds } = lut;
2402
+ const last = ds.length - 1;
2403
+ if (distance <= 0) return ts[0];
2404
+ if (distance >= ds[last]) return ts[last];
2405
+ let lo = 1;
2406
+ let hi = last;
2407
+ while (lo < hi) {
2408
+ const mid = lo + hi >>> 1;
2409
+ if (ds[mid] < distance) lo = mid + 1;
2410
+ else hi = mid;
2411
+ }
2412
+ const dPrev = ds[hi - 1];
2413
+ const dCur = ds[hi];
2414
+ const span = dCur - dPrev;
2415
+ const frac = span > 0 ? (distance - dPrev) / span : 0;
2416
+ return ts[hi - 1] + frac * (ts[hi] - ts[hi - 1]);
2417
+ }
2328
2418
  function bezier2D_arcAtT(lut, t) {
2329
2419
  const { ts, ds } = lut;
2330
2420
  const last = ts.length - 1;
@@ -2347,6 +2437,437 @@ function invertEasing(easing) {
2347
2437
  return cubicBezier(flipped);
2348
2438
  }
2349
2439
 
2440
+ // src/effects/pathSampler.ts
2441
+ var LUT_STEPS = 48;
2442
+ var CMD_RE = /[MmLlHhVvCcSsQqTtAaZz]/;
2443
+ function tokenize(d) {
2444
+ const tokens = [];
2445
+ const re = /([MmLlHhVvCcSsQqTtAaZz])|(-?\d*\.?\d+(?:[eE][-+]?\d+)?)/g;
2446
+ let m;
2447
+ while ((m = re.exec(d)) !== null) tokens.push(m[0]);
2448
+ return tokens;
2449
+ }
2450
+ function quadToCubic(P0, Qc, P3) {
2451
+ return {
2452
+ P1: [P0[0] + 2 / 3 * (Qc[0] - P0[0]), P0[1] + 2 / 3 * (Qc[1] - P0[1])],
2453
+ P2: [P3[0] + 2 / 3 * (Qc[0] - P3[0]), P3[1] + 2 / 3 * (Qc[1] - P3[1])]
2454
+ };
2455
+ }
2456
+ function parseCubics(d) {
2457
+ const tokens = tokenize(d);
2458
+ const segs = [];
2459
+ let i = 0;
2460
+ let cx = 0, cy = 0;
2461
+ let sx = 0, sy = 0;
2462
+ let pcx = 0, pcy = 0;
2463
+ let pqx = 0, pqy = 0;
2464
+ let prevCmd = "";
2465
+ const num2 = () => parseFloat(tokens[i++]);
2466
+ const push = (P1, P2, P3) => {
2467
+ const P0 = [cx, cy];
2468
+ const lut = bezier2D_arcLengthLUT(P0, P1, P2, P3, LUT_STEPS);
2469
+ segs.push({ P0, P1, P2, P3, lut, len: lut.ds[lut.ds.length - 1] });
2470
+ cx = P3[0];
2471
+ cy = P3[1];
2472
+ };
2473
+ const pushLine = (x, y) => {
2474
+ push(
2475
+ [cx + (x - cx) / 3, cy + (y - cy) / 3],
2476
+ [cx + 2 * (x - cx) / 3, cy + 2 * (y - cy) / 3],
2477
+ [x, y]
2478
+ );
2479
+ };
2480
+ while (i < tokens.length) {
2481
+ let cmd = tokens[i];
2482
+ if (CMD_RE.test(cmd)) i++;
2483
+ else cmd = prevCmd === "M" ? "L" : prevCmd === "m" ? "l" : prevCmd;
2484
+ const rel = cmd >= "a";
2485
+ const U = cmd.toUpperCase();
2486
+ if (U === "Z") {
2487
+ pushLine(sx, sy);
2488
+ cx = sx;
2489
+ cy = sy;
2490
+ prevCmd = cmd;
2491
+ continue;
2492
+ }
2493
+ if (U === "M") {
2494
+ const x = num2() + (rel ? cx : 0), y = num2() + (rel ? cy : 0);
2495
+ cx = x;
2496
+ cy = y;
2497
+ sx = x;
2498
+ sy = y;
2499
+ prevCmd = cmd;
2500
+ continue;
2501
+ }
2502
+ if (U === "L") {
2503
+ pushLine(num2() + (rel ? cx : 0), num2() + (rel ? cy : 0));
2504
+ } else if (U === "H") {
2505
+ pushLine(num2() + (rel ? cx : 0), cy);
2506
+ } else if (U === "V") {
2507
+ pushLine(cx, num2() + (rel ? cy : 0));
2508
+ } else if (U === "C") {
2509
+ const p1 = [num2() + (rel ? cx : 0), num2() + (rel ? cy : 0)];
2510
+ const p2 = [num2() + (rel ? cx : 0), num2() + (rel ? cy : 0)];
2511
+ const p3 = [num2() + (rel ? cx : 0), num2() + (rel ? cy : 0)];
2512
+ pcx = p2[0];
2513
+ pcy = p2[1];
2514
+ push(p1, p2, p3);
2515
+ } else if (U === "S") {
2516
+ const smooth = prevCmd.toUpperCase() === "C" || prevCmd.toUpperCase() === "S";
2517
+ const p1 = smooth ? [2 * cx - pcx, 2 * cy - pcy] : [cx, cy];
2518
+ const p2 = [num2() + (rel ? cx : 0), num2() + (rel ? cy : 0)];
2519
+ const p3 = [num2() + (rel ? cx : 0), num2() + (rel ? cy : 0)];
2520
+ pcx = p2[0];
2521
+ pcy = p2[1];
2522
+ push(p1, p2, p3);
2523
+ } else if (U === "Q") {
2524
+ const qc = [num2() + (rel ? cx : 0), num2() + (rel ? cy : 0)];
2525
+ const p3 = [num2() + (rel ? cx : 0), num2() + (rel ? cy : 0)];
2526
+ pqx = qc[0];
2527
+ pqy = qc[1];
2528
+ const { P1, P2 } = quadToCubic([cx, cy], qc, p3);
2529
+ push(P1, P2, p3);
2530
+ } else if (U === "T") {
2531
+ const smooth = prevCmd.toUpperCase() === "Q" || prevCmd.toUpperCase() === "T";
2532
+ const qc = smooth ? [2 * cx - pqx, 2 * cy - pqy] : [cx, cy];
2533
+ const p3 = [num2() + (rel ? cx : 0), num2() + (rel ? cy : 0)];
2534
+ pqx = qc[0];
2535
+ pqy = qc[1];
2536
+ const { P1, P2 } = quadToCubic([cx, cy], qc, p3);
2537
+ push(P1, P2, p3);
2538
+ } else if (U === "A") {
2539
+ i += 5;
2540
+ pushLine(num2() + (rel ? cx : 0), num2() + (rel ? cy : 0));
2541
+ } else {
2542
+ i++;
2543
+ continue;
2544
+ }
2545
+ prevCmd = cmd;
2546
+ }
2547
+ return segs.length ? segs : null;
2548
+ }
2549
+ function clamp2(v, lo, hi) {
2550
+ return v < lo ? lo : v > hi ? hi : v;
2551
+ }
2552
+ function createPathSampler(d) {
2553
+ const segs = parseCubics(d);
2554
+ if (!segs) return null;
2555
+ const cum = new Float64Array(segs.length + 1);
2556
+ for (let k = 0; k < segs.length; k++) cum[k + 1] = cum[k] + segs[k].len;
2557
+ const totalLength = cum[segs.length];
2558
+ const start = segs[0].P0, end = segs[segs.length - 1].P3;
2559
+ const closed = Math.hypot(end[0] - start[0], end[1] - start[1]) < 1e-3;
2560
+ const sampleOn = (dist) => {
2561
+ let k = 0;
2562
+ while (k < segs.length - 1 && dist > cum[k + 1]) k++;
2563
+ const seg = segs[k];
2564
+ const local = dist - cum[k];
2565
+ const t = seg.len > 0 ? bezier2D_tForDistance(seg.lut, local) : 0;
2566
+ const [x, y] = bezier2D_pointAt(seg.P0, seg.P1, seg.P2, seg.P3, t);
2567
+ const [dx, dy] = bezier2D_derivativeAt(seg.P0, seg.P1, seg.P2, seg.P3, t);
2568
+ return { x, y, angle: Math.atan2(dy, dx) };
2569
+ };
2570
+ return {
2571
+ totalLength,
2572
+ sampleAtDistance(dist) {
2573
+ if (!closed && (dist < 0 || dist > totalLength)) {
2574
+ const edge = dist < 0 ? 0 : totalLength;
2575
+ const p = sampleOn(edge);
2576
+ const over = dist - edge;
2577
+ return { x: p.x + Math.cos(p.angle) * over, y: p.y + Math.sin(p.angle) * over, angle: p.angle };
2578
+ }
2579
+ return sampleOn(clamp2(dist, 0, totalLength));
2580
+ }
2581
+ };
2582
+ }
2583
+
2584
+ // src/effects/textGlyphsEffect.ts
2585
+ var DEFAULT_FONT_SIZE = 16;
2586
+ var TEXT_ATTR_KEYS = [
2587
+ "fontFamily",
2588
+ "fontSize",
2589
+ "fontWeight",
2590
+ "fontStyle",
2591
+ "textAnchor",
2592
+ "letterSpacing",
2593
+ "wordSpacing",
2594
+ "textDecoration",
2595
+ "textTransform",
2596
+ "whiteSpace",
2597
+ "x",
2598
+ "y",
2599
+ "dx",
2600
+ "dy",
2601
+ "lengthAdjust",
2602
+ "fill",
2603
+ "stroke",
2604
+ "strokeWidth",
2605
+ "effects",
2606
+ TEXT_ATTR,
2607
+ TEXT_CONTENT_ATTR,
2608
+ "xml:space"
2609
+ ];
2610
+ function parseLen(v) {
2611
+ if (typeof v === "number") return v;
2612
+ if (typeof v === "string") {
2613
+ const n = parseFloat(v);
2614
+ return isNaN(n) ? void 0 : n;
2615
+ }
2616
+ return void 0;
2617
+ }
2618
+ function str(v) {
2619
+ return typeof v === "string" ? v : void 0;
2620
+ }
2621
+ function resolveStyle(node, parent) {
2622
+ var _a, _b, _c, _d, _e, _f, _g;
2623
+ return {
2624
+ fontFamily: (_a = str(node.fontFamily)) != null ? _a : parent.fontFamily,
2625
+ fontSize: (_b = parseLen(node.fontSize)) != null ? _b : parent.fontSize,
2626
+ fill: (_c = node.fill) != null ? _c : parent.fill,
2627
+ stroke: (_d = node.stroke) != null ? _d : parent.stroke,
2628
+ strokeWidth: (_e = node.strokeWidth) != null ? _e : parent.strokeWidth,
2629
+ letterSpacing: (_f = parseLen(node.letterSpacing)) != null ? _f : parent.letterSpacing,
2630
+ wordSpacing: (_g = parseLen(node.wordSpacing)) != null ? _g : parent.wordSpacing
2631
+ };
2632
+ }
2633
+ function rootStyleOf(node) {
2634
+ var _a, _b, _c;
2635
+ return {
2636
+ fontFamily: str(node.fontFamily),
2637
+ fontSize: (_a = parseLen(node.fontSize)) != null ? _a : DEFAULT_FONT_SIZE,
2638
+ fill: node.fill,
2639
+ stroke: node.stroke,
2640
+ strokeWidth: node.strokeWidth,
2641
+ letterSpacing: (_b = parseLen(node.letterSpacing)) != null ? _b : 0,
2642
+ wordSpacing: (_c = parseLen(node.wordSpacing)) != null ? _c : 0
2643
+ };
2644
+ }
2645
+ function paintOf(s) {
2646
+ const p = {};
2647
+ if (s.fill !== void 0) p.fill = s.fill;
2648
+ if (s.stroke !== void 0) p.stroke = s.stroke;
2649
+ if (s.strokeWidth !== void 0) p.strokeWidth = s.strokeWidth;
2650
+ return p;
2651
+ }
2652
+ function glyphFontFor(s, glyphs, soleFont, warnings) {
2653
+ var _a;
2654
+ const gf = s.fontFamily ? glyphs[s.fontFamily] : soleFont;
2655
+ if (!gf) warnings == null ? void 0 : warnings.push('textGlyphs: no glyphs for font "' + ((_a = s.fontFamily) != null ? _a : "") + '"');
2656
+ return gf;
2657
+ }
2658
+ function soleFontOf(glyphs) {
2659
+ const names = Object.keys(glyphs);
2660
+ return names.length === 1 ? glyphs[names[0]] : void 0;
2661
+ }
2662
+ function materialiseGlyphTextHorizontal(node, opts) {
2663
+ var _a, _b, _c, _d;
2664
+ const { glyphs, create = jsonElementFactory, warnings } = opts;
2665
+ const soleFont = soleFontOf(glyphs);
2666
+ const pen = { x: (_a = parseLen(node.x)) != null ? _a : 0, y: (_b = parseLen(node.y)) != null ? _b : 0 };
2667
+ const placements = [];
2668
+ const lines = [{ start: pen.x, end: pen.x }];
2669
+ let line = 0;
2670
+ const renderChars = (content, s) => {
2671
+ const gf = glyphFontFor(s, glyphs, soleFont, warnings);
2672
+ if (!gf) return;
2673
+ const scale = s.fontSize / gf.unitsPerEm;
2674
+ const paint = paintOf(s);
2675
+ for (let i = 0; i < content.length; i++) {
2676
+ const ch = content.charAt(i);
2677
+ const g = gf.glyphs[ch];
2678
+ 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 });
2679
+ pen.x += (g ? g.width : 0) * scale + s.letterSpacing + (ch === " " ? s.wordSpacing : 0);
2680
+ lines[line].end = pen.x;
2681
+ }
2682
+ };
2683
+ const walk = (el, parentStyle) => {
2684
+ var _a2, _b2, _c2, _d2;
2685
+ const s = resolveStyle(el, parentStyle);
2686
+ const x = parseLen(el.x);
2687
+ const y = parseLen(el.y);
2688
+ if (x !== void 0) {
2689
+ pen.x = x;
2690
+ line = lines.length;
2691
+ lines.push({ start: pen.x, end: pen.x });
2692
+ }
2693
+ if (y !== void 0) pen.y = y;
2694
+ pen.x += (_a2 = parseLen(el.dx)) != null ? _a2 : 0;
2695
+ pen.y += (_b2 = parseLen(el.dy)) != null ? _b2 : 0;
2696
+ const content = (_c2 = str(el[TEXT_ATTR])) != null ? _c2 : str(el[TEXT_CONTENT_ATTR]);
2697
+ if (content && !((_d2 = el.children) == null ? void 0 : _d2.length)) renderChars(content, s);
2698
+ if (el.children) for (const ch of el.children) walk(ch, s);
2699
+ };
2700
+ const rootStyle = rootStyleOf(node);
2701
+ if (node.children) for (const ch of node.children) walk(ch, rootStyle);
2702
+ const rootContent = (_c = str(node[TEXT_ATTR])) != null ? _c : str(node[TEXT_CONTENT_ATTR]);
2703
+ if (rootContent && !((_d = node.children) == null ? void 0 : _d.length)) renderChars(rootContent, rootStyle);
2704
+ const anchor = str(node.textAnchor);
2705
+ if (anchor === "middle" || anchor === "end") {
2706
+ for (const p of placements) {
2707
+ const w = lines[p.line].end - lines[p.line].start;
2708
+ const shift = anchor === "middle" ? -w / 2 : -w;
2709
+ p.m = [p.scale, 0, 0, p.scale, p.x + shift, p.y];
2710
+ }
2711
+ }
2712
+ return toGroup(node, buildPaths(placements, create, warnings), create);
2713
+ }
2714
+ function collectAlongPathCells(node, glyphs, soleFont, warnings) {
2715
+ const cells = [];
2716
+ let adv = 0;
2717
+ const walk = (el, parentStyle) => {
2718
+ var _a, _b;
2719
+ const s = resolveStyle(el, parentStyle);
2720
+ const content = (_a = str(el[TEXT_ATTR])) != null ? _a : str(el[TEXT_CONTENT_ATTR]);
2721
+ if (content && !((_b = el.children) == null ? void 0 : _b.length)) {
2722
+ const gf = glyphFontFor(s, glyphs, soleFont, warnings);
2723
+ if (gf) {
2724
+ const scale = s.fontSize / gf.unitsPerEm;
2725
+ const paint = paintOf(s);
2726
+ for (let i = 0; i < content.length; i++) {
2727
+ const ch = content.charAt(i);
2728
+ const g = gf.glyphs[ch];
2729
+ const glyphAdv = (g ? g.width : 0) * scale;
2730
+ if (g && g.d) cells.push({ glyphD: g.d, widthEm: g.width, scale, paint, midBase: adv + glyphAdv / 2 });
2731
+ adv += glyphAdv + s.letterSpacing + (ch === " " ? s.wordSpacing : 0);
2732
+ }
2733
+ }
2734
+ }
2735
+ if (el.children) for (const ch of el.children) walk(ch, s);
2736
+ };
2737
+ walk(node, rootStyleOf(node));
2738
+ return { cells, width: adv };
2739
+ }
2740
+ function alongAffine(sampler, dist, scale, widthEm) {
2741
+ const { x, y, angle } = sampler.sampleAtDistance(dist);
2742
+ const cos = Math.cos(angle), sin = Math.sin(angle), hw = widthEm / 2;
2743
+ return [scale * cos, scale * sin, -scale * sin, scale * cos, x - scale * cos * hw, y - scale * sin * hw];
2744
+ }
2745
+ function materialiseGlyphTextAlongPath(node, pathD, startOffset, opts, textLength) {
2746
+ var _a;
2747
+ const { glyphs, create = jsonElementFactory, warnings } = opts;
2748
+ const sampler = pathD ? createPathSampler(pathD) : null;
2749
+ if (!sampler) {
2750
+ warnings == null ? void 0 : warnings.push("textGlyphs: unparsable along-path geometry");
2751
+ return null;
2752
+ }
2753
+ const soleFont = soleFontOf(glyphs);
2754
+ const { cells, width } = collectAlongPathCells(node, glyphs, soleFont, warnings);
2755
+ if (!cells.length) return toGroup(node, [], create);
2756
+ if (textLength && textLength > 0 && width > 0) {
2757
+ const k = textLength / width;
2758
+ for (const c of cells) c.midBase *= k;
2759
+ }
2760
+ const so = readAnimatable(startOffset);
2761
+ if (so.kind === "animated" /* Animated */ && so.keyframes.length >= 2) {
2762
+ return toGroup(node, buildAnimatedAlongPath(cells, sampler, so.keyframes, so.loop, create), create);
2763
+ }
2764
+ 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;
2765
+ const placements = cells.map((c) => ({
2766
+ glyphD: c.glyphD,
2767
+ paint: c.paint,
2768
+ m: alongAffine(sampler, base + c.midBase, c.scale, c.widthEm)
2769
+ }));
2770
+ return toGroup(node, buildPaths(placements, create, warnings), create);
2771
+ }
2772
+ var ALONG_PATH_MAX_STEPS = 48;
2773
+ var ALONG_PATH_MAX_STEPS_PER_SEGMENT = 64;
2774
+ function roundN(v, n) {
2775
+ const f = __pow(10, n);
2776
+ return Math.round(v * f) / f;
2777
+ }
2778
+ function buildAnimatedAlongPath(cells, sampler, sokfs, loop, create) {
2779
+ const step = Math.max(sampler.totalLength / ALONG_PATH_MAX_STEPS, 0.5);
2780
+ const timeOf = (kf) => Number(kf.time) || 0;
2781
+ const offOf = (kf) => Number(kf.value) || 0;
2782
+ const out = [];
2783
+ for (const c of cells) {
2784
+ const centred = [c.scale, 0, 0, c.scale, -c.scale * (c.widthEm / 2), 0];
2785
+ const d = transformPathData(c.glyphD, centred);
2786
+ const sampleKf = (dist, time) => {
2787
+ const { x, y, angle } = sampler.sampleAtDistance(dist);
2788
+ return {
2789
+ time,
2790
+ value: {
2791
+ ["translate" /* Translate */]: [roundN(x, 3), roundN(y, 3)],
2792
+ ["rotate" /* Rotate */]: roundN(angle * 180 / Math.PI, 3)
2793
+ }
2794
+ };
2795
+ };
2796
+ const kfs = [sampleKf(offOf(sokfs[0]) + c.midBase, timeOf(sokfs[0]))];
2797
+ for (let k = 1; k < sokfs.length; k++) {
2798
+ const t0 = timeOf(sokfs[k - 1]), t1 = timeOf(sokfs[k]);
2799
+ const o0 = offOf(sokfs[k - 1]), o1 = offOf(sokfs[k]);
2800
+ const n = Math.min(ALONG_PATH_MAX_STEPS_PER_SEGMENT, Math.max(1, Math.ceil(Math.abs(o1 - o0) / step)));
2801
+ for (let s = 1; s <= n; s++) {
2802
+ const f = s / n;
2803
+ kfs.push(sampleKf(o0 + f * (o1 - o0) + c.midBase, t0 + f * (t1 - t0)));
2804
+ }
2805
+ }
2806
+ const transform = { keyframes: kfs };
2807
+ if (loop !== void 0) transform.loop = loop;
2808
+ out.push(create("path", __spreadProps(__spreadValues({ d }, paintProps(c.paint)), { animate: { transform } }), []));
2809
+ }
2810
+ return out;
2811
+ }
2812
+ function paintProps(paint) {
2813
+ const p = {};
2814
+ if (paint.fill !== void 0) p.fill = paint.fill;
2815
+ if (paint.stroke !== void 0) p.stroke = paint.stroke;
2816
+ if (paint.strokeWidth !== void 0) p.strokeWidth = paint.strokeWidth;
2817
+ return p;
2818
+ }
2819
+ function buildPaths(placements, create, warnings) {
2820
+ var _a, _b, _c;
2821
+ if (!placements.length) {
2822
+ warnings == null ? void 0 : warnings.push("textGlyphs: nothing to render");
2823
+ return [];
2824
+ }
2825
+ const byPaint = /* @__PURE__ */ new Map();
2826
+ for (const p of placements) {
2827
+ 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]);
2828
+ const baked = transformPathData(p.glyphD, p.m);
2829
+ const entry = byPaint.get(key);
2830
+ if (entry) entry.d += baked;
2831
+ else byPaint.set(key, { paint: p.paint, d: baked });
2832
+ }
2833
+ const out = [];
2834
+ for (const { paint, d } of byPaint.values()) out.push(create("path", __spreadValues({ d }, paintProps(paint)), []));
2835
+ return out;
2836
+ }
2837
+ function toGroup(node, children, create) {
2838
+ const gProps = {};
2839
+ for (const k of Object.keys(node)) {
2840
+ if (k === "type" || k === "children" || TEXT_ATTR_KEYS.indexOf(k) !== -1) continue;
2841
+ gProps[k] = node[k];
2842
+ }
2843
+ if (gProps.style && typeof gProps.style === "object") {
2844
+ const style = __spreadValues({}, gProps.style);
2845
+ delete style["white-space"];
2846
+ if (Object.keys(style).length) gProps.style = style;
2847
+ else delete gProps.style;
2848
+ }
2849
+ return create("g", gProps, children);
2850
+ }
2851
+ function materialiseGlyphText(node, opts) {
2852
+ if (opts.alongPath) return materialiseGlyphTextAlongPath(node, opts.alongPath.pathD, opts.alongPath.startOffset, opts, opts.alongPath.textLength);
2853
+ return materialiseGlyphTextHorizontal(node, opts);
2854
+ }
2855
+ function applyTextGlyphsEffect(node, fx, ctx) {
2856
+ if (!(fx == null ? void 0 : fx.useGlyphs)) return node;
2857
+ if (!ctx.glyphs) {
2858
+ ctx.warnings.push("textGlyphs: no definitions.glyphs \u2014 left as native <text>");
2859
+ return node;
2860
+ }
2861
+ return materialiseGlyphTextHorizontal(node, { glyphs: ctx.glyphs, warnings: ctx.warnings });
2862
+ }
2863
+ function applyTextGlyphsAlongPath(node, ctx, pathD, startOffset, textLength) {
2864
+ if (!ctx.glyphs) {
2865
+ ctx.warnings.push("textGlyphs: no definitions.glyphs");
2866
+ return null;
2867
+ }
2868
+ return materialiseGlyphTextAlongPath(node, pathD, startOffset, { glyphs: ctx.glyphs, warnings: ctx.warnings }, textLength);
2869
+ }
2870
+
2350
2871
  // src/PxMotionPath.ts
2351
2872
  function getKfTranslate(kf) {
2352
2873
  var _a;
@@ -2790,12 +3311,12 @@ function parseSvgPathToBezier(d) {
2790
3311
  }
2791
3312
  return res;
2792
3313
  }
2793
- function extractPathData(str) {
2794
- if (str.startsWith("path(") && str.endsWith(")")) {
2795
- return str.slice(5, -1);
3314
+ function extractPathData(str2) {
3315
+ if (str2.startsWith("path(") && str2.endsWith(")")) {
3316
+ return str2.slice(5, -1);
2796
3317
  }
2797
- if (/^[MmZzLlHhVvCcSsQqTtAa]/.test(str)) {
2798
- return str;
3318
+ if (/^[MmZzLlHhVvCcSsQqTtAa]/.test(str2)) {
3319
+ return str2;
2799
3320
  }
2800
3321
  return void 0;
2801
3322
  }
@@ -3720,7 +4241,7 @@ function rectToPathD(node) {
3720
4241
 
3721
4242
  // src/effects/PlayerEffectsUtil.ts
3722
4243
  function applyPlayerEffects(root) {
3723
- var _a;
4244
+ var _a, _b;
3724
4245
  const ctx = {
3725
4246
  defs: [],
3726
4247
  warnings: [],
@@ -3731,7 +4252,8 @@ function applyPlayerEffects(root) {
3731
4252
  maskAncestorChains: /* @__PURE__ */ new Map(),
3732
4253
  // Resolved engine: `frames` ONLY when explicitly set; auto/webapi/unset →
3733
4254
  // webapi (we're not 100% sure it's frames, and CSS/WAAPI need the inline form).
3734
- engine: ((_a = getAnimatorConfig(root)) == null ? void 0 : _a.mode) === PxAnimatorMode.frames ? PxAnimatorEngine.frames : PxAnimatorEngine.webapi
4255
+ engine: ((_a = getAnimatorConfig(root)) == null ? void 0 : _a.mode) === PxAnimatorMode.frames ? PxAnimatorEngine.frames : PxAnimatorEngine.webapi,
4256
+ glyphs: (_b = getDefs(root)) == null ? void 0 : _b.glyphs
3735
4257
  };
3736
4258
  const working = clone(root);
3737
4259
  indexById(working, ctx.idMap);
@@ -3743,16 +4265,34 @@ function applyPlayerEffects(root) {
3743
4265
  return { root: out, defs: ctx.defs, warnings: ctx.warnings, errors: ctx.errors };
3744
4266
  }
3745
4267
  function applyPlayerEffects_exceptRetime(node, ctx) {
4268
+ var _a;
3746
4269
  if (node.children) node.children = node.children.map((child) => applyPlayerEffects_exceptRetime(child, ctx));
3747
4270
  const fx = node.effects;
3748
4271
  const originalId = typeof node.id === "string" ? node.id : void 0;
3749
4272
  const innerIdForContentRef = originalId ? ctx.contentRefInnerIds.get(originalId) : void 0;
3750
4273
  if (!fx && !innerIdForContentRef) return node;
3751
- const { transformation, repeater, maskedBy, trimPath, clone: cloneFx, fillGradient, strokeGradient, textAlongPath } = fx != null ? fx : {};
4274
+ const { transformation, repeater, maskedBy, trimPath, clone: cloneFx, fillGradient, strokeGradient, textAlongPath, text } = fx != null ? fx : {};
3752
4275
  const isCombinedShape = fx == null ? void 0 : fx.isCombinedShape;
3753
4276
  if (fx) delete node.effects;
3754
4277
  let n = node;
3755
- n = applyTextAlongPathEffect(n, textAlongPath, ctx);
4278
+ let consumedByGlyphs = false;
4279
+ if (text == null ? void 0 : text.useGlyphs) {
4280
+ if (textAlongPath) {
4281
+ const pathNode = typeof textAlongPath.href === "string" ? ctx.idMap.get(textAlongPath.href) : void 0;
4282
+ const pathD = pathNode && typeof pathNode.d === "string" ? pathNode.d : void 0;
4283
+ const rawTL = textAlongPath.textLength;
4284
+ 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;
4285
+ const glyphed = applyTextGlyphsAlongPath(n, ctx, pathD, textAlongPath.startOffset, textLength);
4286
+ if (glyphed) {
4287
+ n = glyphed;
4288
+ consumedByGlyphs = true;
4289
+ }
4290
+ } else {
4291
+ n = applyTextGlyphsEffect(n, text, ctx);
4292
+ consumedByGlyphs = true;
4293
+ }
4294
+ }
4295
+ if (!consumedByGlyphs) n = applyTextAlongPathEffect(n, textAlongPath, ctx);
3756
4296
  n = applyFillGradientEffect(n, fillGradient, ctx);
3757
4297
  n = applyStrokeGradientEffect(n, strokeGradient, ctx);
3758
4298
  n = applyTrimPathEffect(n, trimPath, isCombinedShape, ctx);
@@ -3995,8 +4535,8 @@ var DATA_RASTER_IMAGE_RE = /^data:image\/(?:png|jpe?g|gif|webp|bmp);base64,/i;
3995
4535
  var DATA_SVG_IMAGE_RE = /^data:image\/svg\+xml(?:;[^,]*)?,/i;
3996
4536
  var DATA_IMAGE_BASE64_PAYLOAD_RE = /^data:image\/[^;,]*;base64,([A-Za-z0-9+/]{8})/i;
3997
4537
  var BASE64_RASTER_MAGICS = ["iVBORw0K", "/9j/", "R0lGOD", "UklGR", "Qk"];
3998
- function isContentSniffedRasterImage(str) {
3999
- const m = DATA_IMAGE_BASE64_PAYLOAD_RE.exec(str);
4538
+ function isContentSniffedRasterImage(str2) {
4539
+ const m = DATA_IMAGE_BASE64_PAYLOAD_RE.exec(str2);
4000
4540
  return !!m && BASE64_RASTER_MAGICS.some((magic) => m[1].startsWith(magic));
4001
4541
  }
4002
4542
  function isDangerousAttrName(nameLower) {
@@ -4010,18 +4550,18 @@ function sanitiseAttributeValue(name, value) {
4010
4550
  return void 0;
4011
4551
  }
4012
4552
  if (nameLower === "fill" || nameLower === "stroke" || nameLower === "stopcolor") {
4013
- const str = String(value);
4014
- if (str.includes("url(") && !/^url\(#[^)]+\)$/.test(str)) {
4553
+ const str2 = String(value);
4554
+ if (str2.includes("url(") && !/^url\(#[^)]+\)$/.test(str2)) {
4015
4555
  console.warn('Attribute "' + nameLower + '" blocked: url() must be internal url(#id), got:', value);
4016
4556
  return void 0;
4017
4557
  }
4018
4558
  return value;
4019
4559
  }
4020
4560
  if (URL_VALUE_ATTRS_LOWER.has(nameLower)) {
4021
- const str = String(value);
4022
- if (str.startsWith("#")) return value;
4023
- if (/^url\(#[^)]+\)$/.test(str)) return value;
4024
- if (IMAGE_REF_ATTRS_LOWER.has(nameLower) && (DATA_RASTER_IMAGE_RE.test(str) || DATA_SVG_IMAGE_RE.test(str) || isContentSniffedRasterImage(str))) return value;
4561
+ const str2 = String(value);
4562
+ if (str2.startsWith("#")) return value;
4563
+ if (/^url\(#[^)]+\)$/.test(str2)) return value;
4564
+ if (IMAGE_REF_ATTRS_LOWER.has(nameLower) && (DATA_RASTER_IMAGE_RE.test(str2) || DATA_SVG_IMAGE_RE.test(str2) || isContentSniffedRasterImage(str2))) return value;
4025
4565
  console.warn('Attribute "' + nameLower + '" blocked: must be #id, url(#id), or data:image/\u2026 URI, got:', value);
4026
4566
  return void 0;
4027
4567
  }
@@ -4051,7 +4591,7 @@ function createElement(tagName, normalisedProps, style, children, textContent) {
4051
4591
  if (textContent) element.textContent = textContent;
4052
4592
  return element;
4053
4593
  }
4054
- function resolveStyle(style, defs) {
4594
+ function resolveStyle2(style, defs) {
4055
4595
  var _a;
4056
4596
  if (!style) return void 0;
4057
4597
  if (typeof style === "string") {
@@ -4087,7 +4627,7 @@ function renderNode(node, defs) {
4087
4627
  if (!node) return null;
4088
4628
  const _a = node, { type, children, style } = _a, props = __objRest(_a, ["type", "children", "style"]);
4089
4629
  const nodeDefs = getDefs(node) || defs;
4090
- const resolvedStyle = resolveStyle(style, nodeDefs);
4630
+ const resolvedStyle = resolveStyle2(style, nodeDefs);
4091
4631
  let childElements;
4092
4632
  if (children) {
4093
4633
  for (const ch of children) {
@@ -5140,6 +5680,7 @@ export {
5140
5680
  PxStrokeGradientEffectSchema,
5141
5681
  PxSvgNodeExtra,
5142
5682
  PxTextAlongPathEffectSchema,
5683
+ PxTextEffectSchema,
5143
5684
  PxTransformPartsSchema,
5144
5685
  PxTransformValueSchema,
5145
5686
  PxTransformationEffectSchema,
@@ -5167,9 +5708,13 @@ export {
5167
5708
  getNormalizedProps,
5168
5709
  isPxElementFileFormat,
5169
5710
  isPxElementFileFormatDeep,
5711
+ jsonElementFactory,
5170
5712
  loadTagAnimators,
5171
5713
  materialiseAllInTree,
5172
5714
  materialiseAnimatedUseInstances,
5715
+ materialiseGlyphText,
5716
+ materialiseGlyphTextAlongPath,
5717
+ materialiseGlyphTextHorizontal,
5173
5718
  materialiseInternalLoopsInPropAnim,
5174
5719
  materialiseInternalLoopsInTree,
5175
5720
  materialiseMotionPathInPropAnim,