@pixodesk/svg-animator-web 1.0.18 → 1.0.20

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
@@ -984,6 +984,10 @@ var PxMaskedByEffectSchema = implementsInterface()(px.object({
984
984
  maskUnits: px.string().optional(),
985
985
  maskContentUnits: px.string().optional()
986
986
  }));
987
+ var PxClipPathEffectSchema = implementsInterface()(px.object({
988
+ d: px.string().optional(),
989
+ animate: PxPropertyAnimationSchema.optional()
990
+ }));
987
991
  var PxTrimPathEffectSchema = implementsInterface()(px.object({
988
992
  offset: PxAnimatableNumberSchema.optional(),
989
993
  range: PxAnimatableVec2Schema.optional(),
@@ -1035,8 +1039,9 @@ var PxFillGradientEffectSchema = implementsInterface()(px.object({
1035
1039
  gradientTransform: px.string().optional()
1036
1040
  }));
1037
1041
  var PxStrokeGradientEffectSchema = PxFillGradientEffectSchema;
1038
- var PxTextAlongPathEffectSchema = implementsInterface()(px.object({
1039
- href: px.string(),
1042
+ var PxTextPathEffectSchema = implementsInterface()(px.object({
1043
+ path: px.string(),
1044
+ pathOverflow: px.string().optional(),
1040
1045
  lengthAdjust: px.string().optional(),
1041
1046
  method: px.string().optional(),
1042
1047
  spacing: px.string().optional(),
@@ -1050,12 +1055,13 @@ var PxEffectsSchema = implementsInterface()(px.object({
1050
1055
  transformation: PxTransformationEffectSchema.optional(),
1051
1056
  repeater: PxRepeaterEffectSchema.optional(),
1052
1057
  maskedBy: PxMaskedByEffectSchema.optional(),
1058
+ clipPath: PxClipPathEffectSchema.optional(),
1053
1059
  trimPath: PxTrimPathEffectSchema.optional(),
1054
1060
  clone: PxCloneEffectSchema.optional(),
1055
1061
  isCombinedShape: px.boolean().optional(),
1056
1062
  fillGradient: PxFillGradientEffectSchema.optional(),
1057
1063
  strokeGradient: PxStrokeGradientEffectSchema.optional(),
1058
- textAlongPath: PxTextAlongPathEffectSchema.optional(),
1064
+ textPath: PxTextPathEffectSchema.optional(),
1059
1065
  text: PxTextEffectSchema.optional()
1060
1066
  }));
1061
1067
  function validateNodeEffects(root, opts) {
@@ -1356,6 +1362,17 @@ function formatOffset(o) {
1356
1362
  return pct + "%";
1357
1363
  }
1358
1364
 
1365
+ // src/effects/clipPathEffect.ts
1366
+ function applyClipPathEffect(node, fx, ctx) {
1367
+ if (!fx || !fx.d && !fx.animate) return node;
1368
+ const clipId = genId(ctx, "clip");
1369
+ const pathChild = { type: "path", d: fx.d };
1370
+ if (fx.animate) pathChild.animate = { d: fx.animate };
1371
+ ctx.defs.push({ type: "clipPath", id: clipId, children: [pathChild] });
1372
+ node.clipPath = "url(#" + clipId + ")";
1373
+ return node;
1374
+ }
1375
+
1359
1376
  // src/effects/maskedByEffect.ts
1360
1377
  function applyMaskedByEffect(node, fx, transformation, ctx) {
1361
1378
  if (!fx) return node;
@@ -1798,6 +1815,9 @@ function synthesiseScale(raw, i) {
1798
1815
  var RETIME_MATERIALISATION_MODE_INLINE_G = false;
1799
1816
  function asRetime(r) {
1800
1817
  var _a, _b;
1818
+ if (r.timeCrop) {
1819
+ console.warn("retime: `timeCrop` is not supported yet and will be ignored");
1820
+ }
1801
1821
  return { start: (_a = r.start) != null ? _a : 0, stretch: (_b = r.stretch) != null ? _b : 1 };
1802
1822
  }
1803
1823
  function concatRetime(child, parent) {
@@ -1928,103 +1948,6 @@ function remapKeyframeTimesOnly(node, start, stretch) {
1928
1948
  }
1929
1949
  }
1930
1950
 
1931
- // src/effects/textAlongPathEffect.ts
1932
- function applyTextAlongPathEffect(node, fx, _ctx) {
1933
- var _a;
1934
- if (!fx) return node;
1935
- const href = typeof fx.href === "string" && !fx.href.startsWith("#") ? "#" + fx.href : fx.href;
1936
- const textPath = {
1937
- type: "textPath",
1938
- href,
1939
- children: (_a = node.children) != null ? _a : []
1940
- };
1941
- if (fx.lengthAdjust !== void 0) textPath.lengthAdjust = fx.lengthAdjust;
1942
- if (fx.method !== void 0) textPath.method = fx.method;
1943
- if (fx.spacing !== void 0) textPath.spacing = fx.spacing;
1944
- applyAnimatableNumber(textPath, "startOffset", fx.startOffset);
1945
- applyAnimatableNumber(textPath, "textLength", fx.textLength);
1946
- node.children = [textPath];
1947
- return node;
1948
- }
1949
- function applyAnimatableNumber(node, attrName, raw) {
1950
- if (raw === void 0 || raw === null) return;
1951
- if (typeof raw === "number") {
1952
- node[attrName] = String(raw);
1953
- return;
1954
- }
1955
- if (typeof raw === "object" && raw !== null) {
1956
- const obj = raw;
1957
- if (Array.isArray(obj.keyframes)) {
1958
- const prevAnimate = node.animate && typeof node.animate === "object" && !Array.isArray(node.animate) ? node.animate : void 0;
1959
- const animate = __spreadValues({}, prevAnimate || {});
1960
- const block = { keyframes: obj.keyframes };
1961
- if (obj.loop !== void 0) block.loop = obj.loop;
1962
- animate[attrName] = block;
1963
- node.animate = animate;
1964
- return;
1965
- }
1966
- if (typeof obj.value === "number") {
1967
- node[attrName] = String(obj.value);
1968
- return;
1969
- }
1970
- }
1971
- }
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
-
2028
1951
  // src/PxAnimatorUtil.ts
2029
1952
  function bezierToSvgPath(path) {
2030
1953
  var _a, _b, _c, _d;
@@ -2313,6 +2236,8 @@ var SVG_CAMEL_CASE_ATTRS = /* @__PURE__ */ new Set([
2313
2236
  // Filter
2314
2237
  "filterUnits",
2315
2238
  "primitiveUnits",
2239
+ "tableValues",
2240
+ // feFuncR/G/B/A transfer table (type="table")
2316
2241
  "stdDeviation",
2317
2242
  "baseFrequency",
2318
2243
  "numOctaves",
@@ -2569,7 +2494,11 @@ function createPathSampler(d) {
2569
2494
  };
2570
2495
  return {
2571
2496
  totalLength,
2497
+ closed,
2572
2498
  sampleAtDistance(dist) {
2499
+ if (closed && totalLength > 0 && (dist < 0 || dist > totalLength)) {
2500
+ return sampleOn((dist % totalLength + totalLength) % totalLength);
2501
+ }
2573
2502
  if (!closed && (dist < 0 || dist > totalLength)) {
2574
2503
  const edge = dist < 0 ? 0 : totalLength;
2575
2504
  const p = sampleOn(edge);
@@ -2581,6 +2510,173 @@ function createPathSampler(d) {
2581
2510
  };
2582
2511
  }
2583
2512
 
2513
+ // src/effects/textPathEffect.ts
2514
+ var EXTEND_MARGIN_FRAC = 0.15;
2515
+ function numRange(v) {
2516
+ if (typeof v === "number") return { min: v, max: v };
2517
+ if (v && typeof v === "object") {
2518
+ const o = v;
2519
+ if (typeof o.value === "number") return { min: o.value, max: o.value };
2520
+ if (Array.isArray(o.keyframes) && o.keyframes.length) {
2521
+ const vals = o.keyframes.map((k) => Number(k.value) || 0);
2522
+ return { min: Math.min(...vals), max: Math.max(...vals) };
2523
+ }
2524
+ }
2525
+ return { min: 0, max: 0 };
2526
+ }
2527
+ function estimateTextAdvance(node) {
2528
+ let chars = 0, maxFont = 16;
2529
+ const walk = (el) => {
2530
+ var _a, _b;
2531
+ const fs = parseFloat(String((_a = el.fontSize) != null ? _a : "")) || 0;
2532
+ if (fs) maxFont = Math.max(maxFont, fs);
2533
+ const t = (_b = el.text) != null ? _b : el.textContent;
2534
+ if (typeof t === "string") chars += t.length;
2535
+ if (el.children) for (const c of el.children) walk(c);
2536
+ };
2537
+ walk(node);
2538
+ return chars * maxFont;
2539
+ }
2540
+ var r3 = (n) => Math.round(n * 1e3) / 1e3;
2541
+ function shiftAnimatable(v, by) {
2542
+ if (!by || v === void 0 || v === null) return v;
2543
+ if (typeof v === "number") return v + by;
2544
+ const o = v;
2545
+ if (typeof o.value === "number") return __spreadProps(__spreadValues({}, o), { value: o.value + by });
2546
+ if (Array.isArray(o.keyframes)) return __spreadProps(__spreadValues({}, o), { keyframes: o.keyframes.map((k) => __spreadProps(__spreadValues({}, k), { value: (Number(k.value) || 0) + by })) });
2547
+ return v;
2548
+ }
2549
+ function extendedPathForBrowser(pathD, opts) {
2550
+ var _a;
2551
+ if (opts.pathOverflow === "clip") return { d: pathD, startShift: 0 };
2552
+ const sampler = createPathSampler(pathD);
2553
+ if (!sampler || sampler.closed || sampler.totalLength <= 0) return { d: pathD, startShift: 0 };
2554
+ const L = sampler.totalLength;
2555
+ const margin = EXTEND_MARGIN_FRAC * L;
2556
+ const so = numRange(opts.startOffset);
2557
+ const runWidth = numRange(opts.textLength).max || ((_a = opts.advance) != null ? _a : 0);
2558
+ const startOverflow = Math.max(0, -so.min);
2559
+ const endOverflow = Math.max(0, so.max + runWidth - L);
2560
+ const startExt = startOverflow > 0 ? startOverflow + margin : 0;
2561
+ const endExt = endOverflow > 0 ? endOverflow + margin : 0;
2562
+ if (endExt <= 0 && startExt <= 0) return { d: pathD, startShift: 0 };
2563
+ const s = sampler.sampleAtDistance(0);
2564
+ const e = sampler.sampleAtDistance(L);
2565
+ let d = pathD;
2566
+ if (startExt > 0) {
2567
+ const sx = s.x - Math.cos(s.angle) * startExt, sy = s.y - Math.sin(s.angle) * startExt;
2568
+ const rest = pathD.replace(/^\s*[Mm]\s*-?[\d.]+[\s,]+-?[\d.]+/, "");
2569
+ d = "M" + r3(sx) + "," + r3(sy) + "L" + r3(s.x) + "," + r3(s.y) + rest;
2570
+ }
2571
+ if (endExt > 0) {
2572
+ const ex = e.x + Math.cos(e.angle) * endExt, ey = e.y + Math.sin(e.angle) * endExt;
2573
+ d += "L" + r3(ex) + "," + r3(ey);
2574
+ }
2575
+ return { d, startShift: startExt };
2576
+ }
2577
+ function applyTextPathEffect(node, fx, ctx) {
2578
+ var _a;
2579
+ if (!fx || typeof fx.path !== "string" || !fx.path) return node;
2580
+ const pathId = genId(ctx, "tpath");
2581
+ const { d, startShift } = extendedPathForBrowser(fx.path, {
2582
+ pathOverflow: fx.pathOverflow,
2583
+ startOffset: fx.startOffset,
2584
+ textLength: fx.textLength,
2585
+ advance: estimateTextAdvance(node)
2586
+ });
2587
+ ctx.defs.push({ type: "path", id: pathId, d });
2588
+ const textPath = {
2589
+ type: "textPath",
2590
+ href: "#" + pathId,
2591
+ children: (_a = node.children) != null ? _a : []
2592
+ };
2593
+ if (fx.lengthAdjust !== void 0) textPath.lengthAdjust = fx.lengthAdjust;
2594
+ if (fx.method !== void 0) textPath.method = fx.method;
2595
+ if (fx.spacing !== void 0) textPath.spacing = fx.spacing;
2596
+ applyAnimatableNumber(textPath, "startOffset", shiftAnimatable(fx.startOffset, startShift));
2597
+ applyAnimatableNumber(textPath, "textLength", fx.textLength);
2598
+ node.children = [textPath];
2599
+ return node;
2600
+ }
2601
+ function applyAnimatableNumber(node, attrName, raw) {
2602
+ if (raw === void 0 || raw === null) return;
2603
+ if (typeof raw === "number") {
2604
+ node[attrName] = String(raw);
2605
+ return;
2606
+ }
2607
+ if (typeof raw === "object" && raw !== null) {
2608
+ const obj = raw;
2609
+ if (Array.isArray(obj.keyframes)) {
2610
+ const prevAnimate = node.animate && typeof node.animate === "object" && !Array.isArray(node.animate) ? node.animate : void 0;
2611
+ const animate = __spreadValues({}, prevAnimate || {});
2612
+ const block = { keyframes: obj.keyframes };
2613
+ if (obj.loop !== void 0) block.loop = obj.loop;
2614
+ animate[attrName] = block;
2615
+ node.animate = animate;
2616
+ return;
2617
+ }
2618
+ if (typeof obj.value === "number") {
2619
+ node[attrName] = String(obj.value);
2620
+ return;
2621
+ }
2622
+ }
2623
+ }
2624
+
2625
+ // src/effects/elementFactory.ts
2626
+ var jsonElementFactory = (type, props, children) => {
2627
+ const node = { type };
2628
+ for (const k in props) if (props[k] !== void 0) node[k] = props[k];
2629
+ const arr = Array.isArray(children) ? children.filter((c) => c != null) : children != null ? [children] : [];
2630
+ if (arr.length) node.children = arr;
2631
+ return node;
2632
+ };
2633
+
2634
+ // src/effects/glyphPathBake.ts
2635
+ function fmt(v, decimals) {
2636
+ return Math.round(v) === v ? "" + Math.round(v) : v.toFixed(decimals);
2637
+ }
2638
+ function pack(nums, decimals) {
2639
+ let s = "";
2640
+ for (let i = 0; i < nums.length; i++) {
2641
+ const str2 = fmt(nums[i], decimals);
2642
+ if (i > 0 && str2.charCodeAt(0) !== 45) s += " ";
2643
+ s += str2;
2644
+ }
2645
+ return s;
2646
+ }
2647
+ function apply(m, x, y) {
2648
+ return [m[0] * x + m[2] * y + m[4], m[1] * x + m[3] * y + m[5]];
2649
+ }
2650
+ var TOKEN_RE = /([MLCQZ])|(-?\d*\.?\d+(?:e[-+]?\d+)?)/gi;
2651
+ function transformPathData(d, m, decimals = 2) {
2652
+ const tokens = [];
2653
+ let match;
2654
+ TOKEN_RE.lastIndex = 0;
2655
+ while ((match = TOKEN_RE.exec(d)) !== null) tokens.push(match[0]);
2656
+ let out = "";
2657
+ let i = 0;
2658
+ const num2 = () => parseFloat(tokens[i++]);
2659
+ while (i < tokens.length) {
2660
+ const cmd = tokens[i++];
2661
+ if (cmd === "M" || cmd === "L") {
2662
+ const [x, y] = apply(m, num2(), num2());
2663
+ out += cmd + pack([x, y], decimals);
2664
+ } else if (cmd === "C") {
2665
+ const [x1, y1] = apply(m, num2(), num2());
2666
+ const [x2, y2] = apply(m, num2(), num2());
2667
+ const [x, y] = apply(m, num2(), num2());
2668
+ out += "C" + pack([x1, y1, x2, y2, x, y], decimals);
2669
+ } else if (cmd === "Q") {
2670
+ const [x1, y1] = apply(m, num2(), num2());
2671
+ const [x, y] = apply(m, num2(), num2());
2672
+ out += "Q" + pack([x1, y1, x, y], decimals);
2673
+ } else if (cmd === "Z" || cmd === "z") {
2674
+ out += "Z";
2675
+ }
2676
+ }
2677
+ return out;
2678
+ }
2679
+
2584
2680
  // src/effects/textGlyphsEffect.ts
2585
2681
  var DEFAULT_FONT_SIZE = 16;
2586
2682
  var TEXT_ATTR_KEYS = [
@@ -2659,6 +2755,19 @@ function soleFontOf(glyphs) {
2659
2755
  const names = Object.keys(glyphs);
2660
2756
  return names.length === 1 ? glyphs[names[0]] : void 0;
2661
2757
  }
2758
+ var MISSING_GLYPH_ADVANCE_EM = 0.6;
2759
+ function missingGlyphBoxEm(advanceEm, ascentEm) {
2760
+ if (advanceEm <= 0 || ascentEm <= 0) return "";
2761
+ const inset = 0.08;
2762
+ const x0 = advanceEm * inset, x1 = advanceEm * (1 - inset);
2763
+ const y1 = -ascentEm * (1 - inset);
2764
+ const bw = x1 - x0, bh = -y1;
2765
+ const t = Math.max(1, Math.min(bw, bh) * 0.12);
2766
+ const outer = "M" + x0 + " 0L" + x1 + " 0L" + x1 + " " + y1 + "L" + x0 + " " + y1 + "Z";
2767
+ if (bw <= 2 * t || bh <= 2 * t) return outer;
2768
+ const ix0 = x0 + t, ix1 = x1 - t, iyb = -t, iyt = y1 + t;
2769
+ return outer + "M" + ix0 + " " + iyb + "L" + ix0 + " " + iyt + "L" + ix1 + " " + iyt + "L" + ix1 + " " + iyb + "Z";
2770
+ }
2662
2771
  function materialiseGlyphTextHorizontal(node, opts) {
2663
2772
  var _a, _b, _c, _d;
2664
2773
  const { glyphs, create = jsonElementFactory, warnings } = opts;
@@ -2668,15 +2777,26 @@ function materialiseGlyphTextHorizontal(node, opts) {
2668
2777
  const lines = [{ start: pen.x, end: pen.x }];
2669
2778
  let line = 0;
2670
2779
  const renderChars = (content, s) => {
2780
+ var _a2;
2671
2781
  const gf = glyphFontFor(s, glyphs, soleFont, warnings);
2672
- if (!gf) return;
2673
- const scale = s.fontSize / gf.unitsPerEm;
2782
+ const upm = (gf == null ? void 0 : gf.unitsPerEm) || 1e3;
2783
+ const scale = s.fontSize / upm;
2784
+ const ascentEm = (_a2 = gf == null ? void 0 : gf.ascent) != null ? _a2 : 0.9 * upm;
2674
2785
  const paint = paintOf(s);
2675
2786
  for (let i = 0; i < content.length; i++) {
2676
2787
  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);
2788
+ const g = gf == null ? void 0 : gf.glyphs[ch];
2789
+ if (g && g.d) {
2790
+ placements.push({ glyphD: g.d, m: [scale, 0, 0, scale, pen.x, pen.y], paint, line, x: pen.x, y: pen.y, scale });
2791
+ pen.x += g.width * scale;
2792
+ } else if (/\S/.test(ch)) {
2793
+ const advEm = g && g.width > 0 ? g.width : MISSING_GLYPH_ADVANCE_EM * upm;
2794
+ placements.push({ glyphD: missingGlyphBoxEm(advEm, ascentEm), m: [scale, 0, 0, scale, pen.x, pen.y], paint, line, x: pen.x, y: pen.y, scale });
2795
+ pen.x += advEm * scale;
2796
+ } else {
2797
+ pen.x += (g ? g.width : 0) * scale;
2798
+ }
2799
+ pen.x += s.letterSpacing + (ch === " " ? s.wordSpacing : 0);
2680
2800
  lines[line].end = pen.x;
2681
2801
  }
2682
2802
  };
@@ -2711,24 +2831,152 @@ function materialiseGlyphTextHorizontal(node, opts) {
2711
2831
  }
2712
2832
  return toGroup(node, buildPaths(placements, create, warnings), create);
2713
2833
  }
2834
+ function layoutGlyphTextChars(node, opts) {
2835
+ var _a, _b, _c, _d, _e;
2836
+ if ((_a = opts.alongPath) == null ? void 0 : _a.pathD) return layoutGlyphTextCharsAlongPath(node, opts.alongPath.pathD, opts);
2837
+ const { glyphs, warnings } = opts;
2838
+ const soleFont = soleFontOf(glyphs);
2839
+ const pen = { x: (_b = parseLen(node.x)) != null ? _b : 0, y: (_c = parseLen(node.y)) != null ? _c : 0 };
2840
+ const boxes = [];
2841
+ const lines = [{ start: pen.x, end: pen.x }];
2842
+ let line = 0;
2843
+ const renderChars = (content, s) => {
2844
+ var _a2;
2845
+ const gf = glyphFontFor(s, glyphs, soleFont, warnings);
2846
+ const upm = (gf == null ? void 0 : gf.unitsPerEm) || 1e3;
2847
+ const scale = s.fontSize / upm;
2848
+ const ascent = ((_a2 = gf == null ? void 0 : gf.ascent) != null ? _a2 : 0.9 * upm) * scale;
2849
+ for (let i = 0; i < content.length; i++) {
2850
+ const ch = content.charAt(i);
2851
+ const g = gf == null ? void 0 : gf.glyphs[ch];
2852
+ const advance = (g ? g.width : 0) * scale + s.letterSpacing + (ch === " " ? s.wordSpacing : 0);
2853
+ boxes.push({ x: pen.x, y: pen.y, width: advance, ascent, fontSize: s.fontSize, line });
2854
+ pen.x += advance;
2855
+ lines[line].end = pen.x;
2856
+ }
2857
+ };
2858
+ const walk = (el, parentStyle) => {
2859
+ var _a2, _b2, _c2, _d2;
2860
+ const s = resolveStyle(el, parentStyle);
2861
+ const x = parseLen(el.x);
2862
+ const y = parseLen(el.y);
2863
+ if (x !== void 0) {
2864
+ pen.x = x;
2865
+ line = lines.length;
2866
+ lines.push({ start: pen.x, end: pen.x });
2867
+ }
2868
+ if (y !== void 0) pen.y = y;
2869
+ pen.x += (_a2 = parseLen(el.dx)) != null ? _a2 : 0;
2870
+ pen.y += (_b2 = parseLen(el.dy)) != null ? _b2 : 0;
2871
+ const content = (_c2 = str(el[TEXT_ATTR])) != null ? _c2 : str(el[TEXT_CONTENT_ATTR]);
2872
+ if (content && !((_d2 = el.children) == null ? void 0 : _d2.length)) renderChars(content, s);
2873
+ if (el.children) for (const ch of el.children) walk(ch, s);
2874
+ };
2875
+ const rootStyle = rootStyleOf(node);
2876
+ if (node.children) for (const ch of node.children) walk(ch, rootStyle);
2877
+ const rootContent = (_d = str(node[TEXT_ATTR])) != null ? _d : str(node[TEXT_CONTENT_ATTR]);
2878
+ if (rootContent && !((_e = node.children) == null ? void 0 : _e.length)) renderChars(rootContent, rootStyle);
2879
+ const anchor = str(node.textAnchor);
2880
+ if (anchor === "middle" || anchor === "end") {
2881
+ for (const b of boxes) {
2882
+ const w = lines[b.line].end - lines[b.line].start;
2883
+ b.x += anchor === "middle" ? -w / 2 : -w;
2884
+ }
2885
+ }
2886
+ return boxes.map((_f) => {
2887
+ var _g = _f, { line: _l } = _g, b = __objRest(_g, ["line"]);
2888
+ return b;
2889
+ });
2890
+ }
2891
+ function layoutGlyphTextCharsAlongPath(node, pathD, opts) {
2892
+ var _a;
2893
+ const { glyphs, warnings, alongPath } = opts;
2894
+ const sampler = createPathSampler(pathD);
2895
+ if (!sampler) {
2896
+ warnings == null ? void 0 : warnings.push("textGlyphs: unparsable along-path geometry (caret)");
2897
+ return [];
2898
+ }
2899
+ const soleFont = soleFontOf(glyphs);
2900
+ const chars = [];
2901
+ let adv = 0;
2902
+ const walk = (el, parentStyle) => {
2903
+ var _a2, _b, _c;
2904
+ const s = resolveStyle(el, parentStyle);
2905
+ const content = (_a2 = str(el[TEXT_ATTR])) != null ? _a2 : str(el[TEXT_CONTENT_ATTR]);
2906
+ if (content && !((_b = el.children) == null ? void 0 : _b.length)) {
2907
+ const gf = glyphFontFor(s, glyphs, soleFont, warnings);
2908
+ const upm = (gf == null ? void 0 : gf.unitsPerEm) || 1e3;
2909
+ const scale = s.fontSize / upm;
2910
+ const ascent = ((_c = gf == null ? void 0 : gf.ascent) != null ? _c : 0.9 * upm) * scale;
2911
+ for (let i = 0; i < content.length; i++) {
2912
+ const ch = content.charAt(i);
2913
+ const g = gf == null ? void 0 : gf.glyphs[ch];
2914
+ const glyphW = (g ? g.width : 0) * scale;
2915
+ const advance = glyphW + s.letterSpacing + (ch === " " ? s.wordSpacing : 0);
2916
+ chars.push({ advStart: adv, advEnd: adv + advance, glyphW, ascent, fontSize: s.fontSize });
2917
+ adv += advance;
2918
+ }
2919
+ }
2920
+ if (el.children) for (const ch of el.children) walk(ch, s);
2921
+ };
2922
+ walk(node, rootStyleOf(node));
2923
+ const width = adv;
2924
+ const k = (alongPath == null ? void 0 : alongPath.textLength) && alongPath.textLength > 0 && width > 0 ? alongPath.textLength / width : 1;
2925
+ const so = readAnimatable(alongPath == null ? void 0 : alongPath.startOffset);
2926
+ const { along: alongOffset, perp } = alongPathNodeOffsets(node);
2927
+ const base = alongOffset + (so.kind === "animated" /* Animated */ ? Number((_a = so.keyframes[0]) == null ? void 0 : _a.value) || 0 : so.kind === "static" /* Static */ ? Number(so.value) || 0 : 0);
2928
+ const withPerp = (p) => ({
2929
+ x: p.x - perp * Math.sin(p.angle),
2930
+ y: p.y + perp * Math.cos(p.angle),
2931
+ angle: p.angle
2932
+ });
2933
+ return chars.map((c) => {
2934
+ const dStart = base + c.advStart * k;
2935
+ const dEnd = base + c.advEnd * k;
2936
+ const p0 = withPerp(sampler.sampleAtDistance(dStart));
2937
+ const p1 = withPerp(sampler.sampleAtDistance(dEnd));
2938
+ const glyphMid = sampler.sampleAtDistance(base + (c.advStart + c.glyphW / 2) * k);
2939
+ return {
2940
+ x: p0.x,
2941
+ y: p0.y,
2942
+ width: c.advEnd - c.advStart,
2943
+ ascent: c.ascent,
2944
+ fontSize: c.fontSize,
2945
+ endX: p1.x,
2946
+ endY: p1.y,
2947
+ rotation: glyphMid.angle * 180 / Math.PI
2948
+ };
2949
+ });
2950
+ }
2714
2951
  function collectAlongPathCells(node, glyphs, soleFont, warnings) {
2715
2952
  const cells = [];
2716
2953
  let adv = 0;
2717
2954
  const walk = (el, parentStyle) => {
2718
- var _a, _b;
2955
+ var _a, _b, _c;
2719
2956
  const s = resolveStyle(el, parentStyle);
2720
2957
  const content = (_a = str(el[TEXT_ATTR])) != null ? _a : str(el[TEXT_CONTENT_ATTR]);
2721
2958
  if (content && !((_b = el.children) == null ? void 0 : _b.length)) {
2722
2959
  const gf = glyphFontFor(s, glyphs, soleFont, warnings);
2723
2960
  if (gf) {
2724
2961
  const scale = s.fontSize / gf.unitsPerEm;
2962
+ const ascentEm = (_c = gf.ascent) != null ? _c : 0.9 * gf.unitsPerEm;
2725
2963
  const paint = paintOf(s);
2726
2964
  for (let i = 0; i < content.length; i++) {
2727
2965
  const ch = content.charAt(i);
2728
2966
  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);
2967
+ if (g && g.d) {
2968
+ const glyphAdv = g.width * scale;
2969
+ cells.push({ glyphD: g.d, widthEm: g.width, scale, paint, midBase: adv + glyphAdv / 2 });
2970
+ adv += glyphAdv;
2971
+ } else if (/\S/.test(ch)) {
2972
+ const wEm = g && g.width > 0 ? g.width : MISSING_GLYPH_ADVANCE_EM * gf.unitsPerEm;
2973
+ const boxAdv = wEm * scale;
2974
+ cells.push({ glyphD: missingGlyphBoxEm(wEm, ascentEm), widthEm: wEm, scale, paint, midBase: adv + boxAdv / 2 });
2975
+ adv += boxAdv;
2976
+ } else {
2977
+ adv += (g ? g.width : 0) * scale;
2978
+ }
2979
+ adv += s.letterSpacing + (ch === " " ? s.wordSpacing : 0);
2732
2980
  }
2733
2981
  }
2734
2982
  }
@@ -2737,12 +2985,19 @@ function collectAlongPathCells(node, glyphs, soleFont, warnings) {
2737
2985
  walk(node, rootStyleOf(node));
2738
2986
  return { cells, width: adv };
2739
2987
  }
2740
- function alongAffine(sampler, dist, scale, widthEm) {
2988
+ function alongAffine(sampler, dist, scale, widthEm, perp = 0) {
2741
2989
  const { x, y, angle } = sampler.sampleAtDistance(dist);
2742
2990
  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];
2991
+ return [scale * cos, scale * sin, -scale * sin, scale * cos, x - scale * cos * hw - perp * sin, y - scale * sin * hw + perp * cos];
2992
+ }
2993
+ function alongPathNodeOffsets(node) {
2994
+ var _a, _b, _c;
2995
+ return {
2996
+ along: ((_a = parseLen(node.x)) != null ? _a : 0) + ((_b = parseLen(node.dx)) != null ? _b : 0),
2997
+ perp: (_c = parseLen(node.dy)) != null ? _c : 0
2998
+ };
2744
2999
  }
2745
- function materialiseGlyphTextAlongPath(node, pathD, startOffset, opts, textLength) {
3000
+ function materialiseGlyphTextAlongPath(node, pathD, startOffset, opts, textLength, pathOverflow) {
2746
3001
  var _a;
2747
3002
  const { glyphs, create = jsonElementFactory, warnings } = opts;
2748
3003
  const sampler = pathD ? createPathSampler(pathD) : null;
@@ -2753,19 +3008,25 @@ function materialiseGlyphTextAlongPath(node, pathD, startOffset, opts, textLengt
2753
3008
  const soleFont = soleFontOf(glyphs);
2754
3009
  const { cells, width } = collectAlongPathCells(node, glyphs, soleFont, warnings);
2755
3010
  if (!cells.length) return toGroup(node, [], create);
3011
+ const { along: alongOffset, perp } = alongPathNodeOffsets(node);
2756
3012
  if (textLength && textLength > 0 && width > 0) {
2757
3013
  const k = textLength / width;
2758
3014
  for (const c of cells) c.midBase *= k;
2759
3015
  }
3016
+ const isClip = pathOverflow === "clip" && !sampler.closed;
2760
3017
  const so = readAnimatable(startOffset);
2761
3018
  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) => ({
3019
+ return toGroup(node, buildAnimatedAlongPath(cells, sampler, so.keyframes, so.loop, create, isClip, alongOffset, perp), create);
3020
+ }
3021
+ const base = alongOffset + (so.kind === "animated" /* Animated */ ? Number((_a = so.keyframes[0]) == null ? void 0 : _a.value) || 0 : so.kind === "static" /* Static */ ? Number(so.value) || 0 : 0);
3022
+ const placeCells = isClip ? cells.filter((c) => {
3023
+ const d = base + c.midBase;
3024
+ return d >= 0 && d <= sampler.totalLength;
3025
+ }) : cells;
3026
+ const placements = placeCells.map((c) => ({
2766
3027
  glyphD: c.glyphD,
2767
3028
  paint: c.paint,
2768
- m: alongAffine(sampler, base + c.midBase, c.scale, c.widthEm)
3029
+ m: alongAffine(sampler, base + c.midBase, c.scale, c.widthEm, perp)
2769
3030
  }));
2770
3031
  return toGroup(node, buildPaths(placements, create, warnings), create);
2771
3032
  }
@@ -2775,37 +3036,51 @@ function roundN(v, n) {
2775
3036
  const f = __pow(10, n);
2776
3037
  return Math.round(v * f) / f;
2777
3038
  }
2778
- function buildAnimatedAlongPath(cells, sampler, sokfs, loop, create) {
3039
+ function buildAnimatedAlongPath(cells, sampler, sokfs, loop, create, isClip, alongOffset = 0, perp = 0) {
2779
3040
  const step = Math.max(sampler.totalLength / ALONG_PATH_MAX_STEPS, 0.5);
2780
3041
  const timeOf = (kf) => Number(kf.time) || 0;
2781
- const offOf = (kf) => Number(kf.value) || 0;
3042
+ const offOf = (kf) => alongOffset + (Number(kf.value) || 0);
3043
+ const onPath = (dist) => dist >= 0 && dist <= sampler.totalLength;
2782
3044
  const out = [];
2783
3045
  for (const c of cells) {
2784
3046
  const centred = [c.scale, 0, 0, c.scale, -c.scale * (c.widthEm / 2), 0];
2785
3047
  const d = transformPathData(c.glyphD, centred);
2786
3048
  const sampleKf = (dist, time) => {
2787
3049
  const { x, y, angle } = sampler.sampleAtDistance(dist);
3050
+ const cos = Math.cos(angle), sin = Math.sin(angle);
2788
3051
  return {
2789
3052
  time,
2790
3053
  value: {
2791
- ["translate" /* Translate */]: [roundN(x, 3), roundN(y, 3)],
3054
+ ["translate" /* Translate */]: [roundN(x - perp * sin, 3), roundN(y + perp * cos, 3)],
2792
3055
  ["rotate" /* Rotate */]: roundN(angle * 180 / Math.PI, 3)
2793
3056
  }
2794
3057
  };
2795
3058
  };
2796
- const kfs = [sampleKf(offOf(sokfs[0]) + c.midBase, timeOf(sokfs[0]))];
3059
+ const kfs = [];
3060
+ const opKfs = [];
3061
+ const pushKf = (dist, time) => {
3062
+ kfs.push(sampleKf(dist, time));
3063
+ if (isClip) opKfs.push({ time, value: onPath(dist) ? 1 : 0 });
3064
+ };
3065
+ pushKf(offOf(sokfs[0]) + c.midBase, timeOf(sokfs[0]));
2797
3066
  for (let k = 1; k < sokfs.length; k++) {
2798
3067
  const t0 = timeOf(sokfs[k - 1]), t1 = timeOf(sokfs[k]);
2799
3068
  const o0 = offOf(sokfs[k - 1]), o1 = offOf(sokfs[k]);
2800
3069
  const n = Math.min(ALONG_PATH_MAX_STEPS_PER_SEGMENT, Math.max(1, Math.ceil(Math.abs(o1 - o0) / step)));
2801
3070
  for (let s = 1; s <= n; s++) {
2802
3071
  const f = s / n;
2803
- kfs.push(sampleKf(o0 + f * (o1 - o0) + c.midBase, t0 + f * (t1 - t0)));
3072
+ pushKf(o0 + f * (o1 - o0) + c.midBase, t0 + f * (t1 - t0));
2804
3073
  }
2805
3074
  }
2806
3075
  const transform = { keyframes: kfs };
2807
3076
  if (loop !== void 0) transform.loop = loop;
2808
- out.push(create("path", __spreadProps(__spreadValues({ d }, paintProps(c.paint)), { animate: { transform } }), []));
3077
+ const animate = { transform };
3078
+ if (isClip && opKfs.some((k) => k.value === 0)) {
3079
+ const op = { keyframes: opKfs };
3080
+ if (loop !== void 0) op.loop = loop;
3081
+ animate.opacity = op;
3082
+ }
3083
+ out.push(create("path", __spreadProps(__spreadValues({ d }, paintProps(c.paint)), { animate }), []));
2809
3084
  }
2810
3085
  return out;
2811
3086
  }
@@ -2849,7 +3124,7 @@ function toGroup(node, children, create) {
2849
3124
  return create("g", gProps, children);
2850
3125
  }
2851
3126
  function materialiseGlyphText(node, opts) {
2852
- if (opts.alongPath) return materialiseGlyphTextAlongPath(node, opts.alongPath.pathD, opts.alongPath.startOffset, opts, opts.alongPath.textLength);
3127
+ if (opts.alongPath) return materialiseGlyphTextAlongPath(node, opts.alongPath.pathD, opts.alongPath.startOffset, opts, opts.alongPath.textLength, opts.alongPath.pathOverflow);
2853
3128
  return materialiseGlyphTextHorizontal(node, opts);
2854
3129
  }
2855
3130
  function applyTextGlyphsEffect(node, fx, ctx) {
@@ -2860,12 +3135,12 @@ function applyTextGlyphsEffect(node, fx, ctx) {
2860
3135
  }
2861
3136
  return materialiseGlyphTextHorizontal(node, { glyphs: ctx.glyphs, warnings: ctx.warnings });
2862
3137
  }
2863
- function applyTextGlyphsAlongPath(node, ctx, pathD, startOffset, textLength) {
3138
+ function applyTextGlyphsAlongPath(node, ctx, pathD, startOffset, textLength, pathOverflow) {
2864
3139
  if (!ctx.glyphs) {
2865
3140
  ctx.warnings.push("textGlyphs: no definitions.glyphs");
2866
3141
  return null;
2867
3142
  }
2868
- return materialiseGlyphTextAlongPath(node, pathD, startOffset, { glyphs: ctx.glyphs, warnings: ctx.warnings }, textLength);
3143
+ return materialiseGlyphTextAlongPath(node, pathD, startOffset, { glyphs: ctx.glyphs, warnings: ctx.warnings }, textLength, pathOverflow);
2869
3144
  }
2870
3145
 
2871
3146
  // src/PxMotionPath.ts
@@ -3046,9 +3321,18 @@ function buildOutKfValue(prevV, nextV, p, translate, rotateDegFromAutoOrient, au
3046
3321
  if (pv === void 0 && nv === void 0) continue;
3047
3322
  value[k] = interpolatePart(pv, nv, p);
3048
3323
  }
3049
- if (rotateDegFromAutoOrient !== void 0) value.rotate = rotateDegFromAutoOrient;
3324
+ if (rotateDegFromAutoOrient !== void 0) {
3325
+ value.rotate = rotateDegFromAutoOrient + explicitRotateAt(prevV, nextV, p);
3326
+ }
3050
3327
  return value;
3051
3328
  }
3329
+ function explicitRotateAt(prevV, nextV, p) {
3330
+ const pv = typeof (prevV == null ? void 0 : prevV.rotate) === "number" ? prevV.rotate : void 0;
3331
+ const nv = typeof (nextV == null ? void 0 : nextV.rotate) === "number" ? nextV.rotate : void 0;
3332
+ if (pv === void 0 && nv === void 0) return 0;
3333
+ const r = interpolatePart(pv, nv, p);
3334
+ return typeof r === "number" ? r : 0;
3335
+ }
3052
3336
  function derivAngleForFirstKf(kf0, kf1) {
3053
3337
  const p0 = getKfTranslate(kf0);
3054
3338
  const p1 = getKfTranslate(kf1);
@@ -3069,10 +3353,12 @@ function insertSharpCornerStepKfIfNeeded(out, prevKf, nextKf, prevPos, nextPos,
3069
3353
  const lastV = (_a = lastKf.v) != null ? _a : lastKf.value;
3070
3354
  const prevExit = lastV == null ? void 0 : lastV.rotate;
3071
3355
  if (typeof prevExit !== "number") return;
3356
+ const boundaryV = getKfValueParts(prevKf);
3357
+ const prevExitTangent = prevExit - explicitRotateAt(boundaryV, boundaryV, 0);
3072
3358
  const seg = getSegmentCache(prevKf, nextKf, prevPos, nextPos);
3073
3359
  const tanAtStart = bezier2D_derivativeAt(seg.P0, seg.P1, seg.P2, seg.P3, 0);
3074
3360
  const nextEntry = Math.atan2(tanAtStart[1], tanAtStart[0]) * 180 / Math.PI;
3075
- const delta = wrappedAngleDelta(nextEntry, prevExit);
3361
+ const delta = wrappedAngleDelta(nextEntry, prevExitTangent);
3076
3362
  if (Math.abs(delta) <= rotationTol) return;
3077
3363
  const dupValue = buildOutKfValue(
3078
3364
  getKfValueParts(prevKf),
@@ -4271,18 +4557,17 @@ function applyPlayerEffects_exceptRetime(node, ctx) {
4271
4557
  const originalId = typeof node.id === "string" ? node.id : void 0;
4272
4558
  const innerIdForContentRef = originalId ? ctx.contentRefInnerIds.get(originalId) : void 0;
4273
4559
  if (!fx && !innerIdForContentRef) return node;
4274
- const { transformation, repeater, maskedBy, trimPath, clone: cloneFx, fillGradient, strokeGradient, textAlongPath, text } = fx != null ? fx : {};
4560
+ const { transformation, repeater, maskedBy, clipPath, trimPath, clone: cloneFx, fillGradient, strokeGradient, textPath, text } = fx != null ? fx : {};
4275
4561
  const isCombinedShape = fx == null ? void 0 : fx.isCombinedShape;
4276
4562
  if (fx) delete node.effects;
4277
4563
  let n = node;
4278
4564
  let consumedByGlyphs = false;
4279
4565
  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;
4566
+ if (textPath) {
4567
+ const pathD = typeof textPath.path === "string" ? textPath.path : void 0;
4568
+ const rawTL = textPath.textLength;
4284
4569
  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);
4570
+ const glyphed = applyTextGlyphsAlongPath(n, ctx, pathD, textPath.startOffset, textLength, textPath.pathOverflow);
4286
4571
  if (glyphed) {
4287
4572
  n = glyphed;
4288
4573
  consumedByGlyphs = true;
@@ -4292,12 +4577,13 @@ function applyPlayerEffects_exceptRetime(node, ctx) {
4292
4577
  consumedByGlyphs = true;
4293
4578
  }
4294
4579
  }
4295
- if (!consumedByGlyphs) n = applyTextAlongPathEffect(n, textAlongPath, ctx);
4580
+ if (!consumedByGlyphs) n = applyTextPathEffect(n, textPath, ctx);
4296
4581
  n = applyFillGradientEffect(n, fillGradient, ctx);
4297
4582
  n = applyStrokeGradientEffect(n, strokeGradient, ctx);
4298
4583
  n = applyTrimPathEffect(n, trimPath, isCombinedShape, ctx);
4299
4584
  n = applyRepeaterEffect(n, repeater, ctx);
4300
4585
  n = applyMaskedByEffect(n, maskedBy, transformation, ctx);
4586
+ n = applyClipPathEffect(n, clipPath, ctx);
4301
4587
  if (innerIdForContentRef) {
4302
4588
  applyRefHref(n, cloneFx, ctx);
4303
4589
  n = splitForContentRef(n, transformation, originalId, innerIdForContentRef, ctx);
@@ -4626,6 +4912,8 @@ function getNormalizedProps(props) {
4626
4912
  function renderNode(node, defs) {
4627
4913
  if (!node) return null;
4628
4914
  const _a = node, { type, children, style } = _a, props = __objRest(_a, ["type", "children", "style"]);
4915
+ const feFuncType = props.funcType;
4916
+ if (feFuncType !== void 0) delete props.funcType;
4629
4917
  const nodeDefs = getDefs(node) || defs;
4630
4918
  const resolvedStyle = resolveStyle2(style, nodeDefs);
4631
4919
  let childElements;
@@ -4638,13 +4926,15 @@ function renderNode(node, defs) {
4638
4926
  }
4639
4927
  }
4640
4928
  }
4641
- return createElement(
4929
+ const element = createElement(
4642
4930
  type || "g",
4643
4931
  getNormalizedProps(props),
4644
4932
  resolvedStyle,
4645
4933
  childElements,
4646
4934
  props[TEXT_ATTR] || props[TEXT_CONTENT_ATTR]
4647
4935
  );
4936
+ if (element && feFuncType !== void 0) element.setAttribute("type", feFuncType);
4937
+ return element;
4648
4938
  }
4649
4939
 
4650
4940
  // src/PxAnimatorTriggers.ts
@@ -4655,7 +4945,12 @@ function setupAnimationTriggers(api, config) {
4655
4945
  console.warn("setupAnimationTriggers: No root element found for animation.");
4656
4946
  return api;
4657
4947
  }
4948
+ let reversed = false;
4658
4949
  const start = () => {
4950
+ if (reversed) {
4951
+ reversed = false;
4952
+ api.setPlaybackRate(1);
4953
+ }
4659
4954
  api.play();
4660
4955
  };
4661
4956
  const handleEndAction = () => {
@@ -4667,6 +4962,8 @@ function setupAnimationTriggers(api, config) {
4667
4962
  api.cancel();
4668
4963
  break;
4669
4964
  case "reverse":
4965
+ reversed = true;
4966
+ api.setPlaybackRate(-1);
4670
4967
  api.play();
4671
4968
  break;
4672
4969
  case "continue":
@@ -4729,6 +5026,7 @@ function getSelector(id) {
4729
5026
  return "#" + id;
4730
5027
  }
4731
5028
  function createBasicFrameLoopAnimator(doc, adapter, callbacks) {
5029
+ var _a;
4732
5030
  const config = getAnimatorConfig(doc) || {};
4733
5031
  const bindings = getNormalisedBindings(doc, PxAnimatorEngine.frames);
4734
5032
  const _iterations = config.iterations;
@@ -4739,18 +5037,24 @@ function createBasicFrameLoopAnimator(doc, adapter, callbacks) {
4739
5037
  const duration = +(config.duration || DEFAULT_DURATION_MS);
4740
5038
  const totalDuration = duration && iterations ? duration * (iterations === Infinity ? Infinity : iterations) : duration ? (iterations != null ? iterations : 1) * duration : 0;
4741
5039
  const direction = config.direction || "normal";
5040
+ const fill = (_a = config.fill) != null ? _a : "forwards";
5041
+ const fillsForwards = fill === "forwards" || fill === "both";
5042
+ const fillsBackwards = fill === "backwards" || fill === "both";
4742
5043
  let timerId = null;
4743
5044
  let playing = false;
4744
- let timeBeforeLastStartMs = config.delay || 0;
5045
+ let timeBeforeLastStartMs = -(config.delay || 0);
4745
5046
  let lastStartedTs = 0;
4746
5047
  let playbackRate = 1;
4747
5048
  let finishCalled = false;
4748
5049
  const frameRate = config.frameRate;
4749
5050
  const minFrameIntervalMs = frameRate && frameRate > 0 ? 1e3 / frameRate : 0;
4750
5051
  let lastRenderTs = 0;
4751
- const getAnimCurrentTime = () => {
5052
+ const getRawAnimTime = () => {
4752
5053
  const runningElapsed = lastStartedTs ? (Date.now() - lastStartedTs) * playbackRate : 0;
4753
- let time = timeBeforeLastStartMs + runningElapsed;
5054
+ return timeBeforeLastStartMs + runningElapsed;
5055
+ };
5056
+ const getAnimCurrentTime = () => {
5057
+ let time = getRawAnimTime();
4754
5058
  if (Number.isFinite(totalDuration) && time > totalDuration) time = totalDuration;
4755
5059
  if (time < 0) time = 0;
4756
5060
  return time;
@@ -4795,40 +5099,59 @@ function createBasicFrameLoopAnimator(doc, adapter, callbacks) {
4795
5099
  }
4796
5100
  }
4797
5101
  const tick = () => {
4798
- var _a;
5102
+ var _a2, _b;
4799
5103
  if (!adapter.isConnected()) {
4800
5104
  pauseAnim();
4801
5105
  return;
4802
5106
  }
4803
5107
  const currentTime = getAnimCurrentTime();
4804
- if (minFrameIntervalMs > 0) {
4805
- const now = Date.now();
4806
- if (lastRenderTs && now - lastRenderTs < minFrameIntervalMs) {
4807
- return;
4808
- }
4809
- lastRenderTs = now;
4810
- }
4811
- if (duration <= 0) {
5108
+ const rawTime = getRawAnimTime();
5109
+ if (rawTime < 0 && playbackRate > 0) {
5110
+ if (fillsBackwards) renderFrame(0);
5111
+ return;
4812
5112
  }
4813
- if (totalDuration && Number.isFinite(totalDuration) && currentTime >= totalDuration) {
4814
- renderFrame(totalDuration);
5113
+ if (playbackRate < 0 && rawTime <= 0) {
5114
+ pauseAnim();
5115
+ renderFrame(0);
4815
5116
  if (!finishCalled) {
4816
5117
  finishCalled = true;
4817
- (_a = callbacks == null ? void 0 : callbacks.onFinish) == null ? void 0 : _a.call(callbacks);
5118
+ (_a2 = callbacks == null ? void 0 : callbacks.onFinish) == null ? void 0 : _a2.call(callbacks);
4818
5119
  }
5120
+ return;
5121
+ }
5122
+ if (playbackRate > 0 && totalDuration && Number.isFinite(totalDuration) && currentTime >= totalDuration) {
4819
5123
  pauseAnim();
5124
+ renderFrame(fillsForwards ? totalDuration : 0);
5125
+ if (!finishCalled) {
5126
+ finishCalled = true;
5127
+ (_b = callbacks == null ? void 0 : callbacks.onFinish) == null ? void 0 : _b.call(callbacks);
5128
+ }
4820
5129
  return;
4821
5130
  }
5131
+ if (minFrameIntervalMs > 0) {
5132
+ const now = Date.now();
5133
+ if (lastRenderTs && now - lastRenderTs < minFrameIntervalMs) {
5134
+ return;
5135
+ }
5136
+ lastRenderTs = now;
5137
+ }
4822
5138
  renderFrame(currentTime);
4823
5139
  };
4824
5140
  if (config.delay) {
4825
- renderFrame(getAnimCurrentTime());
5141
+ if (config.delay < 0) {
5142
+ renderFrame(getAnimCurrentTime());
5143
+ } else if (fillsBackwards) {
5144
+ renderFrame(0);
5145
+ }
4826
5146
  }
4827
5147
  const _isPlaying = () => {
4828
5148
  if (!playing) return false;
4829
5149
  if (!adapter.isConnected()) {
4830
5150
  return false;
4831
5151
  }
5152
+ if (playbackRate < 0) {
5153
+ return getRawAnimTime() > 0;
5154
+ }
4832
5155
  if (Number.isFinite(totalDuration) && getAnimCurrentTime() >= totalDuration) return false;
4833
5156
  return true;
4834
5157
  };
@@ -4848,37 +5171,53 @@ function createBasicFrameLoopAnimator(doc, adapter, callbacks) {
4848
5171
  };
4849
5172
  const startAnim = () => {
4850
5173
  if (playing) return;
4851
- if (Number.isFinite(totalDuration) && timeBeforeLastStartMs >= totalDuration) {
4852
- timeBeforeLastStartMs = 0;
4853
- finishCalled = false;
5174
+ if (playbackRate >= 0) {
5175
+ if (Number.isFinite(totalDuration) && timeBeforeLastStartMs >= totalDuration) {
5176
+ timeBeforeLastStartMs = 0;
5177
+ }
5178
+ } else {
5179
+ if (timeBeforeLastStartMs <= 0) {
5180
+ if (!Number.isFinite(totalDuration)) {
5181
+ console.warn("play: cannot start reverse playback of an infinite animation from time 0");
5182
+ return;
5183
+ }
5184
+ timeBeforeLastStartMs = totalDuration;
5185
+ }
4854
5186
  }
5187
+ finishCalled = false;
4855
5188
  playing = true;
4856
5189
  lastStartedTs = Date.now();
4857
5190
  loopAnim(true);
4858
5191
  };
4859
5192
  const pauseAnim = () => {
4860
5193
  if (!playing) return;
4861
- timeBeforeLastStartMs = getAnimCurrentTime();
5194
+ let raw = getRawAnimTime();
5195
+ if (Number.isFinite(totalDuration) && raw > totalDuration) raw = totalDuration;
5196
+ timeBeforeLastStartMs = raw;
4862
5197
  lastStartedTs = 0;
4863
5198
  playing = false;
4864
5199
  if (timerId !== null) {
4865
5200
  cancelAnimationFrame(timerId);
4866
5201
  timerId = null;
4867
5202
  }
4868
- renderFrame(timeBeforeLastStartMs);
5203
+ if (raw >= 0) {
5204
+ renderFrame(raw);
5205
+ } else if (fillsBackwards) {
5206
+ renderFrame(0);
5207
+ }
4869
5208
  };
4870
5209
  const cancelAnim = () => {
4871
- var _a;
5210
+ var _a2;
4872
5211
  pauseAnim();
4873
5212
  timeBeforeLastStartMs = 0;
4874
5213
  lastStartedTs = 0;
4875
5214
  playing = false;
4876
5215
  finishCalled = false;
4877
5216
  renderFrame(timeBeforeLastStartMs);
4878
- (_a = callbacks == null ? void 0 : callbacks.onCancel) == null ? void 0 : _a.call(callbacks);
5217
+ (_a2 = callbacks == null ? void 0 : callbacks.onCancel) == null ? void 0 : _a2.call(callbacks);
4879
5218
  };
4880
5219
  const finishAnim = (callOnFinish = true) => {
4881
- var _a;
5220
+ var _a2;
4882
5221
  if (Number.isFinite(totalDuration)) {
4883
5222
  timeBeforeLastStartMs = totalDuration;
4884
5223
  } else {
@@ -4886,10 +5225,14 @@ function createBasicFrameLoopAnimator(doc, adapter, callbacks) {
4886
5225
  }
4887
5226
  lastStartedTs = 0;
4888
5227
  playing = false;
4889
- renderFrame(timeBeforeLastStartMs);
5228
+ if (timerId !== null) {
5229
+ cancelAnimationFrame(timerId);
5230
+ timerId = null;
5231
+ }
5232
+ renderFrame(fillsForwards ? timeBeforeLastStartMs : 0);
4890
5233
  if (callOnFinish && !finishCalled) {
4891
5234
  finishCalled = true;
4892
- (_a = callbacks == null ? void 0 : callbacks.onFinish) == null ? void 0 : _a.call(callbacks);
5235
+ (_a2 = callbacks == null ? void 0 : callbacks.onFinish) == null ? void 0 : _a2.call(callbacks);
4893
5236
  }
4894
5237
  };
4895
5238
  const api = {
@@ -4899,14 +5242,14 @@ function createBasicFrameLoopAnimator(doc, adapter, callbacks) {
4899
5242
  return _isPlaying();
4900
5243
  },
4901
5244
  "play": () => {
4902
- var _a;
5245
+ var _a2;
4903
5246
  startAnim();
4904
- (_a = callbacks == null ? void 0 : callbacks.onPlay) == null ? void 0 : _a.call(callbacks);
5247
+ (_a2 = callbacks == null ? void 0 : callbacks.onPlay) == null ? void 0 : _a2.call(callbacks);
4905
5248
  },
4906
5249
  "pause": () => {
4907
- var _a;
5250
+ var _a2;
4908
5251
  pauseAnim();
4909
- (_a = callbacks == null ? void 0 : callbacks.onPause) == null ? void 0 : _a.call(callbacks);
5252
+ (_a2 = callbacks == null ? void 0 : callbacks.onPause) == null ? void 0 : _a2.call(callbacks);
4910
5253
  },
4911
5254
  "cancel": () => {
4912
5255
  cancelAnim();
@@ -4919,7 +5262,9 @@ function createBasicFrameLoopAnimator(doc, adapter, callbacks) {
4919
5262
  console.warn("setPlaybackRate: rate must be finite and non-zero");
4920
5263
  return;
4921
5264
  }
4922
- const current = getAnimCurrentTime();
5265
+ let current = getRawAnimTime();
5266
+ if (Number.isFinite(totalDuration) && current > totalDuration) current = totalDuration;
5267
+ if (rate < 0 !== playbackRate < 0) finishCalled = false;
4923
5268
  playbackRate = rate;
4924
5269
  timeBeforeLastStartMs = current;
4925
5270
  if (playing) lastStartedTs = Date.now();
@@ -4932,10 +5277,13 @@ function createBasicFrameLoopAnimator(doc, adapter, callbacks) {
4932
5277
  if (Number.isFinite(totalDuration) && newTime > totalDuration) newTime = totalDuration;
4933
5278
  timeBeforeLastStartMs = newTime;
4934
5279
  if (playing) lastStartedTs = Date.now();
5280
+ if (!Number.isFinite(totalDuration) || newTime < totalDuration) finishCalled = false;
4935
5281
  renderFrame(getAnimCurrentTime());
4936
5282
  },
4937
5283
  "destroy": () => {
5284
+ var _a2;
4938
5285
  api.cancel();
5286
+ (_a2 = callbacks == null ? void 0 : callbacks.onRemove) == null ? void 0 : _a2.call(callbacks);
4939
5287
  }
4940
5288
  };
4941
5289
  return api;
@@ -5100,6 +5448,8 @@ function createWebApiAnimator(doc, callbacks, rootElement, forceEvenIfHasUnsuppo
5100
5448
  if (typeof _iterations === "number") iterations = _iterations;
5101
5449
  if (_iterations === "infinite") iterations = Infinity;
5102
5450
  const unsupportedSet = /* @__PURE__ */ new Set();
5451
+ let finishNotified = false;
5452
+ let removeCalled = false;
5103
5453
  if (!(bindings == null ? void 0 : bindings.length)) {
5104
5454
  console.warn("createWebApiAnimator: No animation bindings defined");
5105
5455
  }
@@ -5116,7 +5466,11 @@ function createWebApiAnimator(doc, callbacks, rootElement, forceEvenIfHasUnsuppo
5116
5466
  }
5117
5467
  const keyframesMap = convertToWebApiKeyframes(animDef, unsupportedSet, config);
5118
5468
  const positiveDelay = config.delay && config.delay > 0 ? config.delay : void 0;
5119
- const seekPosition = config.delay && config.delay < 0 && config.duration ? -config.delay % config.duration : void 0;
5469
+ let seekPosition;
5470
+ if (config.delay && config.delay < 0 && config.duration) {
5471
+ const rawSeek = -config.delay;
5472
+ seekPosition = iterations === Infinity ? rawSeek % config.duration : Math.min(rawSeek, config.duration * (iterations != null ? iterations : 1));
5473
+ }
5120
5474
  const effectOptions = {
5121
5475
  duration: config.duration,
5122
5476
  delay: positiveDelay,
@@ -5137,13 +5491,17 @@ function createWebApiAnimator(doc, callbacks, rootElement, forceEvenIfHasUnsuppo
5137
5491
  const anim = new Animation(effect, document.timeline);
5138
5492
  if (callbacks == null ? void 0 : callbacks.onFinish) anim.onfinish = () => {
5139
5493
  var _a2;
5140
- return (_a2 = callbacks.onFinish) == null ? void 0 : _a2.call(callbacks);
5494
+ if (finishNotified) return;
5495
+ finishNotified = true;
5496
+ (_a2 = callbacks.onFinish) == null ? void 0 : _a2.call(callbacks);
5141
5497
  };
5142
5498
  if (callbacks == null ? void 0 : callbacks.onRemove) anim.onremove = () => {
5143
5499
  var _a2;
5144
- return (_a2 = callbacks.onRemove) == null ? void 0 : _a2.call(callbacks);
5500
+ if (removeCalled) return;
5501
+ removeCalled = true;
5502
+ (_a2 = callbacks.onRemove) == null ? void 0 : _a2.call(callbacks);
5145
5503
  };
5146
- if (seekPosition) {
5504
+ if (seekPosition !== void 0) {
5147
5505
  anim.currentTime = seekPosition;
5148
5506
  }
5149
5507
  animations.push(anim);
@@ -5167,6 +5525,7 @@ function createWebApiAnimator(doc, callbacks, rootElement, forceEvenIfHasUnsuppo
5167
5525
  },
5168
5526
  "play": () => {
5169
5527
  var _a2;
5528
+ finishNotified = false;
5170
5529
  animations.forEach((a) => a.play());
5171
5530
  (_a2 = callbacks == null ? void 0 : callbacks.onPlay) == null ? void 0 : _a2.call(callbacks);
5172
5531
  },
@@ -5177,6 +5536,7 @@ function createWebApiAnimator(doc, callbacks, rootElement, forceEvenIfHasUnsuppo
5177
5536
  },
5178
5537
  "cancel": () => {
5179
5538
  var _a2;
5539
+ finishNotified = false;
5180
5540
  animations.forEach((a) => a.cancel());
5181
5541
  (_a2 = callbacks == null ? void 0 : callbacks.onCancel) == null ? void 0 : _a2.call(callbacks);
5182
5542
  },
@@ -5206,13 +5566,19 @@ function createWebApiAnimator(doc, callbacks, rootElement, forceEvenIfHasUnsuppo
5206
5566
  return res !== null ? +res : null;
5207
5567
  },
5208
5568
  "setCurrentTime": (time) => {
5569
+ finishNotified = false;
5209
5570
  animations.forEach((a) => {
5210
5571
  a.currentTime = time;
5211
5572
  });
5212
5573
  },
5213
5574
  "destroy": () => {
5575
+ var _a2;
5214
5576
  api.cancel();
5215
5577
  animations.splice(0, animations.length);
5578
+ if (!removeCalled) {
5579
+ removeCalled = true;
5580
+ (_a2 = callbacks == null ? void 0 : callbacks.onRemove) == null ? void 0 : _a2.call(callbacks);
5581
+ }
5216
5582
  }
5217
5583
  };
5218
5584
  if (config.trigger) {
@@ -5356,7 +5722,6 @@ function createAnimatorImpl(doc, adapter, callbacks, containerElement) {
5356
5722
  const effectsWarnings = validateNodeEffects(doc);
5357
5723
  for (const w of effectsWarnings) console.warn("[PxAnimator] effects shape warning:", w);
5358
5724
  const animatorConfig = getAnimatorConfig(doc) || {};
5359
- animatorConfig.debug = true;
5360
5725
  const engine = animatorConfig.mode === PxAnimatorMode.frames ? PxAnimatorEngine.frames : PxAnimatorEngine.webapi;
5361
5726
  doc = materialiseAllInTree(doc, engine);
5362
5727
  let rootElement = null;
@@ -5385,37 +5750,55 @@ function createAnimator(options) {
5385
5750
  return createAnimatorImpl(data, adapter, callbacks, container);
5386
5751
  }
5387
5752
  let animator = null;
5753
+ let pending = [];
5754
+ let destroyed = false;
5755
+ const enqueue = (call) => {
5756
+ if (animator) {
5757
+ call(animator);
5758
+ } else if (pending) {
5759
+ pending.push(call);
5760
+ }
5761
+ };
5388
5762
  fetch(src).then((res) => res.json()).then((json) => {
5763
+ if (destroyed) return;
5389
5764
  if (isPxElementFileFormat(json)) {
5390
5765
  animator = createAnimatorImpl(json, adapter, callbacks, container);
5766
+ const queued = pending;
5767
+ pending = null;
5768
+ queued == null ? void 0 : queued.forEach((call) => call(animator));
5391
5769
  } else {
5392
- console.error("Invalid animation document format");
5770
+ console.error('createAnimator: invalid animation document format at "' + src + '"');
5393
5771
  }
5772
+ }).catch((err) => {
5773
+ pending = null;
5774
+ console.error('createAnimator: failed to load "' + src + '"', err);
5394
5775
  });
5395
5776
  return {
5396
5777
  "isReady": () => !!animator,
5397
5778
  "getRootElement": () => animator ? animator.getRootElement() : null,
5398
5779
  "isPlaying": () => (animator == null ? void 0 : animator.isPlaying()) || false,
5399
5780
  "play": () => {
5400
- animator == null ? void 0 : animator.play();
5781
+ enqueue((api) => api.play());
5401
5782
  },
5402
5783
  "pause": () => {
5403
- animator == null ? void 0 : animator.pause();
5784
+ enqueue((api) => api.pause());
5404
5785
  },
5405
5786
  "cancel": () => {
5406
- animator == null ? void 0 : animator.cancel();
5787
+ enqueue((api) => api.cancel());
5407
5788
  },
5408
5789
  "finish": () => {
5409
- animator == null ? void 0 : animator.finish();
5790
+ enqueue((api) => api.finish());
5410
5791
  },
5411
5792
  "setPlaybackRate": (rate) => {
5412
- animator == null ? void 0 : animator.setPlaybackRate(rate);
5793
+ enqueue((api) => api.setPlaybackRate(rate));
5413
5794
  },
5414
- "getCurrentTime": () => (animator == null ? void 0 : animator.getCurrentTime()) || 0,
5795
+ "getCurrentTime": () => animator ? animator.getCurrentTime() : null,
5415
5796
  "setCurrentTime": (time) => {
5416
- animator == null ? void 0 : animator.setCurrentTime(time);
5797
+ enqueue((api) => api.setCurrentTime(time));
5417
5798
  },
5418
5799
  "destroy": () => {
5800
+ destroyed = true;
5801
+ pending = null;
5419
5802
  animator == null ? void 0 : animator.destroy();
5420
5803
  }
5421
5804
  };
@@ -5679,8 +6062,8 @@ export {
5679
6062
  PxRetimeEffectSchema,
5680
6063
  PxStrokeGradientEffectSchema,
5681
6064
  PxSvgNodeExtra,
5682
- PxTextAlongPathEffectSchema,
5683
6065
  PxTextEffectSchema,
6066
+ PxTextPathEffectSchema,
5684
6067
  PxTransformPartsSchema,
5685
6068
  PxTransformValueSchema,
5686
6069
  PxTransformationEffectSchema,
@@ -5696,10 +6079,12 @@ export {
5696
6079
  createAnimatorImpl,
5697
6080
  createBasicFrameLoopAnimator,
5698
6081
  createFrameLoopAnimator,
6082
+ createPathSampler,
5699
6083
  createWebApiAnimator,
5700
6084
  describeSchema,
5701
6085
  diffInEffect,
5702
6086
  evaluateMotionPathSegment,
6087
+ extendedPathForBrowser,
5703
6088
  generateNewIds,
5704
6089
  getAnimatorConfig,
5705
6090
  getBindings,
@@ -5709,6 +6094,7 @@ export {
5709
6094
  isPxElementFileFormat,
5710
6095
  isPxElementFileFormatDeep,
5711
6096
  jsonElementFactory,
6097
+ layoutGlyphTextChars,
5712
6098
  loadTagAnimators,
5713
6099
  materialiseAllInTree,
5714
6100
  materialiseAnimatedUseInstances,
@@ -5725,6 +6111,7 @@ export {
5725
6111
  renderNode,
5726
6112
  schemaKeys,
5727
6113
  setupAnimationTriggers,
6114
+ shiftAnimatable,
5728
6115
  toRGBA,
5729
6116
  validateNodeEffects,
5730
6117
  visualModelAt