pixi-effects 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +17 -10
  3. package/ai/SKILL.md +1 -1
  4. package/ai/reference/cheatsheet.md +8 -2
  5. package/ai/reference/pitfalls.md +1 -0
  6. package/ai/reference/recipes.md +36 -1
  7. package/ai/template.html +4 -3
  8. package/ai/tools/check.mjs +14 -5
  9. package/ai/tools/render.mjs +202 -0
  10. package/ai/tools/view.mjs +269 -0
  11. package/dist/{Base-DnRAxroQ.d.cts → Base-CSIulyIq.d.cts} +1 -1
  12. package/dist/{Base-CPSFBkPm.d.ts → Base-DVgwyIUj.d.ts} +1 -1
  13. package/dist/Composition-3KHQPRBA.cjs +14 -0
  14. package/dist/{Composition-63EXHNJJ.cjs.map → Composition-3KHQPRBA.cjs.map} +1 -1
  15. package/dist/Composition-DT657FO5.js +5 -0
  16. package/dist/{Composition-TUCRSMJU.js.map → Composition-DT657FO5.js.map} +1 -1
  17. package/dist/Controller.cjs +77 -2
  18. package/dist/Controller.cjs.map +1 -1
  19. package/dist/Controller.d.cts +16 -2
  20. package/dist/Controller.d.ts +16 -2
  21. package/dist/Controller.js +77 -2
  22. package/dist/Controller.js.map +1 -1
  23. package/dist/{Movie-CuU_aaCm.d.cts → Movie-B0OUKlZI.d.ts} +54 -2
  24. package/dist/{Movie-VyDiUYOj.d.ts → Movie-BK9daiTt.d.cts} +54 -2
  25. package/dist/{chunk-5Q5XZG7Z.js → chunk-36Z4RIRK.js} +3 -3
  26. package/dist/{chunk-5Q5XZG7Z.js.map → chunk-36Z4RIRK.js.map} +1 -1
  27. package/dist/{chunk-UBPZLGLU.js → chunk-4O2NYDJG.js} +365 -44
  28. package/dist/chunk-4O2NYDJG.js.map +1 -0
  29. package/dist/{chunk-23M5QTWW.cjs → chunk-4VSIJLU3.cjs} +21 -21
  30. package/dist/{chunk-23M5QTWW.cjs.map → chunk-4VSIJLU3.cjs.map} +1 -1
  31. package/dist/{chunk-2RR43OIN.cjs → chunk-6I55T4OH.cjs} +403 -62
  32. package/dist/chunk-6I55T4OH.cjs.map +1 -0
  33. package/dist/index.cjs +234 -24
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.cts +4 -4
  36. package/dist/index.d.ts +4 -4
  37. package/dist/index.js +226 -16
  38. package/dist/index.js.map +1 -1
  39. package/dist/three.cjs +6 -5
  40. package/dist/three.cjs.map +1 -1
  41. package/dist/three.d.cts +2 -2
  42. package/dist/three.d.ts +2 -2
  43. package/dist/three.js +2 -1
  44. package/dist/three.js.map +1 -1
  45. package/dist/{types-7VtTEKUW.d.ts → types-oJijbn-K.d.cts} +36 -7
  46. package/dist/{types-7VtTEKUW.d.cts → types-oJijbn-K.d.ts} +36 -7
  47. package/docs/api.md +7 -0
  48. package/docs/dsl.md +74 -8
  49. package/llms-full.txt +127 -12
  50. package/package.json +7 -4
  51. package/dist/Composition-63EXHNJJ.cjs +0 -13
  52. package/dist/Composition-TUCRSMJU.js +0 -4
  53. package/dist/chunk-2RR43OIN.cjs.map +0 -1
  54. package/dist/chunk-UBPZLGLU.js.map +0 -1
@@ -1,8 +1,8 @@
1
+ import { suggestName } from './chunk-DKGG7WD4.js';
2
+ import * as PIXI from 'pixi.js';
1
3
  import { Rectangle, Container, Graphics, Assets, Texture, Sprite, Text, Filter, GlProgram, defaultFilterVert, GpuProgram, UniformGroup, FillGradient, Color, GraphicsPath } from 'pixi.js';
2
4
  import { gsap } from 'gsap';
3
5
 
4
- // src/sequences/Base.ts
5
-
6
6
  // src/expr/Parser.ts
7
7
  var FUNCS = {
8
8
  min: Math.min,
@@ -696,6 +696,124 @@ var ChromaKeyFilter = class extends Filter {
696
696
  u[2] = rgb[2];
697
697
  }
698
698
  };
699
+ var CORE = { blur: "BlurFilter", noise: "NoiseFilter", alpha: "AlphaFilter" };
700
+ var CORE_TYPES = [...Object.keys(CORE), "colorMatrix"];
701
+ var LIBRARY_TYPES = [
702
+ "adjustment",
703
+ "advancedBloom",
704
+ "ascii",
705
+ "backdropBlur",
706
+ "bevel",
707
+ "bloom",
708
+ "bulgePinch",
709
+ "colorGradient",
710
+ "colorMap",
711
+ "colorOverlay",
712
+ "colorReplace",
713
+ "convolution",
714
+ "crossHatch",
715
+ "crt",
716
+ "dot",
717
+ "dropShadow",
718
+ "emboss",
719
+ "glitch",
720
+ "glow",
721
+ "godray",
722
+ "grayscale",
723
+ "hslAdjustment",
724
+ "kawaseBlur",
725
+ "motionBlur",
726
+ "multiColorReplace",
727
+ "oldFilm",
728
+ "outline",
729
+ "pixelate",
730
+ "radialBlur",
731
+ "reflection",
732
+ "rgbSplit",
733
+ "shockwave",
734
+ "simpleLightmap",
735
+ "simplexNoise",
736
+ "tiltShift",
737
+ "tiltShiftAxis",
738
+ "twist",
739
+ "zoomBlur"
740
+ ];
741
+ var FILTER_TYPES = [...CORE_TYPES, ...LIBRARY_TYPES];
742
+ var NOT_NAMED = /* @__PURE__ */ new Set(["custom", "chromaKey"]);
743
+ var lower = (s) => s.toLowerCase();
744
+ var isLibraryType = (t) => LIBRARY_TYPES.some((n) => lower(n) === lower(t));
745
+ var isCoreType = (t) => CORE_TYPES.some((n) => lower(n) === lower(t));
746
+ function isNamedFilterType(t) {
747
+ return !NOT_NAMED.has(t);
748
+ }
749
+ function unknownType(t) {
750
+ const guess = suggestName(t, FILTER_TYPES);
751
+ return new Error(`pixi-effects: unknown filter type "${t}"${guess ? ` \u2014 did you mean "${guess}"?` : ""} (valid: ${[...FILTER_TYPES, "chromaKey", "custom"].join(", ")})`);
752
+ }
753
+ var library = null;
754
+ function registerFilterLibrary(mod) {
755
+ library = /* @__PURE__ */ new Map();
756
+ for (const [key, value] of Object.entries(mod)) {
757
+ if (key.endsWith("Filter") && typeof value === "function") library.set(lower(key.slice(0, -"Filter".length)), value);
758
+ }
759
+ }
760
+ var MISSING = (type, why) => new Error(
761
+ `pixi-effects: the filter "${type}" comes from the pixi-filters package${why ? ` (${why})` : ""}. Add it to your import map ("pixi-filters": "https://esm.sh/pixi-filters@6.1.5?external=pixi.js") or run \`npm i pixi-filters\`.`
762
+ );
763
+ function needsFilterLibrary(spec) {
764
+ let needs = false;
765
+ const visit = (node) => {
766
+ if (!node || typeof node !== "object") return;
767
+ if (Array.isArray(node)) {
768
+ node.forEach(visit);
769
+ return;
770
+ }
771
+ const o = node;
772
+ if (Array.isArray(o.filters)) {
773
+ for (const f of o.filters) {
774
+ if (typeof f?.type !== "string" || !isNamedFilterType(f.type)) continue;
775
+ if (isLibraryType(f.type)) needs = true;
776
+ else if (!isCoreType(f.type)) throw unknownType(f.type);
777
+ }
778
+ }
779
+ for (const key of ["sequences", "mask"]) visit(o[key]);
780
+ };
781
+ visit(spec);
782
+ return needs;
783
+ }
784
+ async function ensureFilterLibrary(spec, load = () => import('pixi-filters')) {
785
+ if (!needsFilterLibrary(spec) || library) return;
786
+ try {
787
+ registerFilterLibrary(await load());
788
+ } catch (e) {
789
+ throw MISSING("(see the type in your filters)", e.message);
790
+ }
791
+ }
792
+ var SINGLE_VALUE = { pixelate: "size", emboss: "strength" };
793
+ var MATRIX_PRESETS = ["sepia", "grayscale", "negative", "polaroid", "technicolor", "vintage", "kodachrome", "browni"];
794
+ function colorMatrix(params) {
795
+ const f = new PIXI.ColorMatrixFilter();
796
+ const preset = params.preset;
797
+ if (preset !== void 0) {
798
+ if (!MATRIX_PRESETS.includes(preset)) {
799
+ const guess = suggestName(preset, MATRIX_PRESETS);
800
+ throw new Error(`pixi-effects: colorMatrix preset "${preset}" is not one of ${MATRIX_PRESETS.join(", ")}${guess ? ` \u2014 did you mean "${guess}"?` : ""}`);
801
+ }
802
+ f[preset === "grayscale" ? "desaturate" : preset].call(f);
803
+ }
804
+ if (Array.isArray(params.matrix)) f.matrix = params.matrix;
805
+ return f;
806
+ }
807
+ function createNamedFilter(type, params) {
808
+ if (lower(type) === "colormatrix") return colorMatrix(params);
809
+ const core = Object.entries(CORE).find(([k]) => lower(k) === lower(type));
810
+ if (core) return new PIXI[core[1]](params);
811
+ if (!isLibraryType(type)) throw unknownType(type);
812
+ const Ctor = library?.get(lower(type));
813
+ if (!Ctor) throw MISSING(type, library ? "not found in the loaded package" : "not loaded yet");
814
+ const single = SINGLE_VALUE[Object.keys(SINGLE_VALUE).find((k) => lower(k) === lower(type)) ?? ""];
815
+ return single ? new Ctor(params[single]) : new Ctor(params);
816
+ }
699
817
 
700
818
  // src/filters/index.ts
701
819
  var registry = {
@@ -714,10 +832,9 @@ function createFilter(spec) {
714
832
  named._name = spec.name;
715
833
  return named;
716
834
  }
717
- const Cls = registry[spec.type];
718
- if (!Cls) throw new Error(`pixi-effects: unknown filter type "${spec.type}"`);
719
- const { type: _t, name, ...params } = spec;
720
- const inst = new Cls(params);
835
+ const { type, name, ...params } = spec;
836
+ const Cls = registry[type];
837
+ const inst = Cls ? new Cls(params) : createNamedFilter(type, params);
721
838
  inst._name = name;
722
839
  return inst;
723
840
  }
@@ -859,10 +976,12 @@ function summarizeWarnings(messages, shown = 3) {
859
976
  }
860
977
  function lintText(spec, warn = defaultWarn) {
861
978
  if (spec.type !== "text") return;
862
- if ((spec.text ?? "").includes("{value}") && !collectPropKeys(spec).has("value")) {
979
+ const strings = [spec.text ?? "", ...(spec.keyframes ?? []).map((kf) => String(kf.set?.text ?? ""))];
980
+ const hasPlaceholder = strings.some((t) => t.includes("{value}"));
981
+ if (hasPlaceholder && !collectPropKeys(spec).has("value")) {
863
982
  warn(`pixi-effects: ${describeLayer(spec)}: the text has a {value} placeholder but nothing sets or animates \`value\`, so it prints 0. Set initial: { value: \u2026 } or animate it; {value} is reserved for counters (there is no way to print a literal "{value}").`);
864
983
  }
865
- if (collectPropKeys(spec).has("value") && !(spec.text ?? "").includes("{value}")) {
984
+ if (collectPropKeys(spec).has("value") && !hasPlaceholder) {
866
985
  warn(`pixi-effects: ${describeLayer(spec)}: \`value\` is set or animated but the text has no {value} placeholder, so nothing shows it. Use text: '{value}' (or e.g. '{value} users').`);
867
986
  }
868
987
  }
@@ -1331,14 +1450,21 @@ function formatValue(v, f) {
1331
1450
  return sign + int + (frac !== void 0 ? "." + frac : "");
1332
1451
  }
1333
1452
  var TextSequence = class extends Sequence {
1334
- /** Current number for the `{value}` placeholder (tweened by `value` keyframes). */
1335
- _value = { value: 0 };
1453
+ /** `value`: the number for the `{value}` placeholder; `visibleChars`: how many characters show (typewriter). Both tweened by keyframes. */
1454
+ _counters = { value: 0, visibleChars: 0 };
1455
+ /** The string now on the layer (before `{value}` / `visibleChars`): `text`, or the last `set: { text }`. */
1456
+ _template = "";
1457
+ /** `visibleChars` is in play (initial or animated): otherwise the whole text shows. */
1458
+ _typed = false;
1336
1459
  _lastText = "";
1337
- /** Rewrite the displayed string from the template and the current `value`. */
1460
+ /** A typewriter that has typed nothing yet: the layer is empty on purpose (inspect must not call that "no size"). */
1461
+ get showsNothingYet() {
1462
+ return this._typed && this._lastText === "" && this._template !== "";
1463
+ }
1464
+ /** Rewrite the displayed string from the template, the current `value` and `visibleChars`. */
1338
1465
  _refreshText() {
1339
- const template = this.spec.text ?? "";
1340
- if (!template.includes("{value}")) return;
1341
- const next = template.split("{value}").join(formatValue(this._value.value, this.spec.format));
1466
+ let next = this._template.includes("{value}") ? this._template.split("{value}").join(formatValue(this._counters.value, this.spec.format)) : this._template;
1467
+ if (this._typed) next = Array.from(next).slice(0, Math.max(0, Math.floor(this._counters.visibleChars))).join("");
1342
1468
  if (next === this._lastText) return;
1343
1469
  this._lastText = next;
1344
1470
  this.target.text = next;
@@ -1350,8 +1476,10 @@ var TextSequence = class extends Sequence {
1350
1476
  fill: "#ffffff",
1351
1477
  align: "center"
1352
1478
  };
1479
+ const initialProps = this.spec.initial ?? {};
1480
+ this._template = String(initialProps.text ?? this.spec.text ?? "");
1353
1481
  const text = new Text({
1354
- text: this.spec.text ?? "",
1482
+ text: this._template,
1355
1483
  style: baseStyle,
1356
1484
  label: this.spec.name
1357
1485
  });
@@ -1363,10 +1491,10 @@ var TextSequence = class extends Sequence {
1363
1491
  this.duration = this.parent?.duration ?? this.root.duration;
1364
1492
  }
1365
1493
  if (this.spec.style) {
1366
- const scope = this.scope();
1494
+ const scope2 = this.scope();
1367
1495
  const resolved = normalizeProps(
1368
1496
  this.spec.style,
1369
- scope,
1497
+ scope2,
1370
1498
  { skipKeys: STYLE_OPAQUE_KEYS }
1371
1499
  );
1372
1500
  for (const k of Object.keys(resolved)) {
@@ -1375,22 +1503,36 @@ var TextSequence = class extends Sequence {
1375
1503
  this.intrinsicWidth = text.width;
1376
1504
  this.intrinsicHeight = text.height;
1377
1505
  }
1378
- const initialValue = this.spec.initial?.value;
1379
- if (initialValue !== void 0) this._value.value = resolveNumber(initialValue, this.scope());
1506
+ const scope = this.scope();
1507
+ if (initialProps.value !== void 0) this._counters.value = resolveNumber(initialProps.value, scope);
1508
+ this._typed = initialProps.visibleChars !== void 0 || animates(this.spec.keyframes, "visibleChars");
1509
+ if (initialProps.visibleChars !== void 0) this._counters.visibleChars = resolveNumber(initialProps.visibleChars, scope);
1510
+ const typed = this._typed;
1511
+ this._typed = false;
1380
1512
  this._refreshText();
1381
1513
  if (this._lastText !== "") {
1382
1514
  this.intrinsicWidth = text.width;
1383
1515
  this.intrinsicHeight = text.height;
1384
1516
  }
1517
+ this._typed = typed;
1518
+ this._refreshText();
1385
1519
  this.buildFilters();
1386
1520
  }
1387
1521
  bindTimeline(timeline, offset = 0) {
1388
1522
  if (!this.target) return;
1389
1523
  const colorSpace = this.spec.colorSpace ?? "rgb";
1390
- const stripped = stripKeys2(this.spec, ["fill", "value"]);
1524
+ const stripped = stripKeys2(this.spec, ["fill", "value", "text", "visibleChars"]);
1391
1525
  runSuperWithStrippedSpec(this, stripped, timeline, offset);
1392
- bindFillKeyframes(timeline, this.target, this.spec.keyframes ?? [], this.duration, offset + this.at, colorSpace);
1393
- bindValueKeyframes(timeline, this._value, () => this._refreshText(), this.spec.keyframes ?? [], this.duration, offset + this.at, this.scope());
1526
+ const keyframes = this.spec.keyframes ?? [];
1527
+ const origin = offset + this.at;
1528
+ bindFillKeyframes(timeline, this.target, keyframes, this.duration, origin, colorSpace);
1529
+ const refresh = () => this._refreshText();
1530
+ bindCounterKeyframes(timeline, this._counters, "value", refresh, keyframes, this.duration, origin, this.scope());
1531
+ bindCounterKeyframes(timeline, this._counters, "visibleChars", refresh, keyframes, this.duration, origin, this.scope());
1532
+ bindTextKeyframes(timeline, keyframes, this.duration, origin, () => this._template, (t) => {
1533
+ this._template = t;
1534
+ refresh();
1535
+ }, describeLayer(this.spec));
1394
1536
  }
1395
1537
  };
1396
1538
  function runSuperWithStrippedSpec(inst, strippedSpec, timeline, offset) {
@@ -1426,11 +1568,11 @@ function resolveNumber(v, scope) {
1426
1568
  const out = normalizeProps({ v }, scope).v;
1427
1569
  return typeof out === "number" ? out : Number(out) || 0;
1428
1570
  }
1429
- function bindValueKeyframes(timeline, state, refresh, keyframes, parentDuration, origin, scope) {
1571
+ function bindCounterKeyframes(timeline, state, key, refresh, keyframes, parentDuration, origin, scope) {
1430
1572
  for (const kf of keyframes) {
1431
- const setV = kf.set?.value;
1432
- const fromV = kf.from?.value;
1433
- const toV = kf.to?.value;
1573
+ const setV = kf.set?.[key];
1574
+ const fromV = kf.from?.[key];
1575
+ const toV = kf.to?.[key];
1434
1576
  if (setV === void 0 && fromV === void 0 && toV === void 0) continue;
1435
1577
  const at = origin + resolveAt(kf.at, parentDuration);
1436
1578
  const duration = kf.duration ?? 0;
@@ -1438,25 +1580,40 @@ function bindValueKeyframes(timeline, state, refresh, keyframes, parentDuration,
1438
1580
  const loop = loopVars(kf);
1439
1581
  if (setV !== void 0) {
1440
1582
  const v = resolveNumber(setV, scope);
1441
- revertibleSet(timeline, at, () => state.value, (n) => {
1442
- state.value = n;
1583
+ revertibleSet(timeline, at, () => state[key], (n) => {
1584
+ state[key] = n;
1443
1585
  refresh();
1444
1586
  }, v);
1445
1587
  }
1446
1588
  if (toV !== void 0 && fromV !== void 0) {
1447
1589
  timeline.fromTo(
1448
1590
  state,
1449
- { value: resolveNumber(fromV, scope) },
1450
- { value: resolveNumber(toV, scope), duration, ease, ...loop, onUpdate: refresh },
1591
+ { [key]: resolveNumber(fromV, scope) },
1592
+ { [key]: resolveNumber(toV, scope), duration, ease, ...loop, onUpdate: refresh },
1451
1593
  at
1452
1594
  );
1453
1595
  } else if (toV !== void 0) {
1454
- timeline.to(state, { value: resolveNumber(toV, scope), duration, ease, ...loop, onUpdate: refresh }, at);
1596
+ timeline.to(state, { [key]: resolveNumber(toV, scope), duration, ease, ...loop, onUpdate: refresh }, at);
1455
1597
  } else if (fromV !== void 0) {
1456
- timeline.from(state, { value: resolveNumber(fromV, scope), duration, ease, ...loop, onUpdate: refresh }, at);
1598
+ timeline.from(state, { [key]: resolveNumber(fromV, scope), duration, ease, ...loop, onUpdate: refresh }, at);
1599
+ }
1600
+ }
1601
+ }
1602
+ function bindTextKeyframes(timeline, keyframes, parentDuration, origin, read, write, who) {
1603
+ for (const kf of keyframes) {
1604
+ for (const [what, props] of [["to", kf.to], ["from", kf.from]]) {
1605
+ if (props && props.text !== void 0) {
1606
+ console.warn(`pixi-effects: ${who}: a keyframe's ${what}: { text } cannot tween a string: use set: { text: '\u2026' } at the time it should change`);
1607
+ }
1457
1608
  }
1609
+ const t = kf.set?.text;
1610
+ if (t === void 0) continue;
1611
+ revertibleSet(timeline, origin + resolveAt(kf.at, parentDuration), read, write, String(t));
1458
1612
  }
1459
1613
  }
1614
+ function animates(keyframes, key) {
1615
+ return (keyframes ?? []).some((kf) => [kf.set, kf.to, kf.from].some((p) => p && p[key] !== void 0));
1616
+ }
1460
1617
  function bindFillKeyframes(timeline, text, keyframes, parentDuration, offset, colorSpace) {
1461
1618
  const state = { fill: text.style.fill };
1462
1619
  const writeFill = () => {
@@ -1820,18 +1977,105 @@ function makeGradientFill(g) {
1820
1977
  }
1821
1978
  return new FillGradient(gradientOptions(g));
1822
1979
  }
1980
+ var CURVE_STEPS = 24;
1981
+ var ELLIPSE_STEPS = 128;
1982
+ function flattenSvgPath(d) {
1983
+ const prims = new GraphicsPath(d).shapePath.shapePrimitives;
1984
+ const out = [];
1985
+ for (const { shape } of prims) {
1986
+ const poly = shape;
1987
+ if (poly.points && poly.points.length >= 4) out.push({ pts: poly.points.slice(), closed: poly.closePath === true });
1988
+ }
1989
+ return out;
1990
+ }
1991
+ function rectOutline(x, y, w, h, radius) {
1992
+ const r = Math.max(0, Math.min(radius, Math.abs(w) / 2, Math.abs(h) / 2));
1993
+ if (r === 0) return { pts: [x, y, x + w, y, x + w, y + h, x, y + h], closed: true };
1994
+ const pts = [];
1995
+ const corner = (cx, cy, from) => {
1996
+ for (let i = 0; i <= CURVE_STEPS; i++) {
1997
+ const a = from + i / CURVE_STEPS * (Math.PI / 2);
1998
+ pts.push(cx + r * Math.cos(a), cy + r * Math.sin(a));
1999
+ }
2000
+ };
2001
+ corner(x + w - r, y + r, -Math.PI / 2);
2002
+ corner(x + w - r, y + h - r, 0);
2003
+ corner(x + r, y + h - r, Math.PI / 2);
2004
+ corner(x + r, y + r, Math.PI);
2005
+ const startAt = pts.length - 2;
2006
+ const rotated = pts.slice(startAt).concat(pts.slice(0, startAt));
2007
+ return { pts: rotated, closed: true };
2008
+ }
2009
+ function ellipseOutline(cx, cy, rx, ry) {
2010
+ const pts = [];
2011
+ for (let i = 0; i < ELLIPSE_STEPS; i++) {
2012
+ const a = -Math.PI / 2 + i / ELLIPSE_STEPS * Math.PI * 2;
2013
+ pts.push(cx + rx * Math.cos(a), cy + ry * Math.sin(a));
2014
+ }
2015
+ return { pts, closed: true };
2016
+ }
2017
+ function trimPolylines(lines, start, end) {
2018
+ const s = Math.max(0, Math.min(1, start)), e = Math.max(0, Math.min(1, end));
2019
+ if (!(e > s)) return [];
2020
+ const lengths = lines.map(polylineLength);
2021
+ const total = lengths.reduce((a, b) => a + b, 0);
2022
+ if (!(total > 0)) return [];
2023
+ const from = s * total, to = e * total;
2024
+ const out = [];
2025
+ let offset = 0;
2026
+ lines.forEach((line, i) => {
2027
+ const len = lengths[i];
2028
+ const a = Math.max(from - offset, 0), b = Math.min(to - offset, len);
2029
+ offset += len;
2030
+ if (!(b > a) || len === 0) return;
2031
+ if (line.closed && a <= 0 && b >= len) {
2032
+ out.push({ pts: line.pts.slice(), closed: true });
2033
+ return;
2034
+ }
2035
+ out.push({ pts: slice(line, a, b), closed: false });
2036
+ });
2037
+ return out;
2038
+ }
2039
+ function points(line) {
2040
+ return line.closed ? [...line.pts, line.pts[0], line.pts[1]] : line.pts;
2041
+ }
2042
+ function polylineLength(line) {
2043
+ const p = points(line);
2044
+ let len = 0;
2045
+ for (let i = 2; i < p.length; i += 2) len += Math.hypot(p[i] - p[i - 2], p[i + 1] - p[i - 1]);
2046
+ return len;
2047
+ }
2048
+ function slice(line, a, b) {
2049
+ const p = points(line);
2050
+ const out = [];
2051
+ let at = 0;
2052
+ for (let i = 2; i < p.length; i += 2) {
2053
+ const x0 = p[i - 2], y0 = p[i - 1], x1 = p[i], y1 = p[i + 1];
2054
+ const seg = Math.hypot(x1 - x0, y1 - y0);
2055
+ const segEnd = at + seg;
2056
+ if (segEnd > a && at < b && seg > 0) {
2057
+ const t0 = Math.max(a - at, 0) / seg, t1 = Math.min(b - at, seg) / seg;
2058
+ if (out.length === 0) out.push(x0 + (x1 - x0) * t0, y0 + (y1 - y0) * t0);
2059
+ out.push(x0 + (x1 - x0) * t1, y0 + (y1 - y0) * t1);
2060
+ }
2061
+ at = segEnd;
2062
+ if (at >= b) break;
2063
+ }
2064
+ return out;
2065
+ }
1823
2066
 
1824
2067
  // src/sequences/Shape.ts
1825
2068
  var STYLE_KEYS = ["fillColor", "fillAlpha", "strokeColor", "strokeAlpha", "strokeWidth"];
1826
2069
  var COLOR_KEYS = /* @__PURE__ */ new Set(["fillColor", "strokeColor"]);
2070
+ var TRIM_KEYS = ["trimStart", "trimEnd"];
1827
2071
  var GEOMETRY_KEYS_BY_SHAPE = {
1828
- rect: ["width", "height", "cornerRadius", "anchorX", "anchorY"],
1829
- circle: ["radius", "anchorX", "anchorY"],
1830
- ellipse: ["radiusX", "radiusY", "anchorX", "anchorY"],
2072
+ rect: ["width", "height", "cornerRadius", "anchorX", "anchorY", ...TRIM_KEYS],
2073
+ circle: ["radius", "anchorX", "anchorY", ...TRIM_KEYS],
2074
+ ellipse: ["radiusX", "radiusY", "anchorX", "anchorY", ...TRIM_KEYS],
1831
2075
  arc: ["radius", "innerRadius", "startAngle", "endAngle"],
1832
- line: [],
1833
- polygon: [],
1834
- path: []
2076
+ line: [...TRIM_KEYS],
2077
+ polygon: [...TRIM_KEYS],
2078
+ path: [...TRIM_KEYS]
1835
2079
  };
1836
2080
  var ANCHORED_SHAPES = /* @__PURE__ */ new Set(["rect", "circle", "ellipse", "arc"]);
1837
2081
  var ShapeSequence = class extends Sequence {
@@ -1864,13 +2108,26 @@ var ShapeSequence = class extends Sequence {
1864
2108
  if (this.spec.shape === "arc") {
1865
2109
  this._state.startAngle ??= 0;
1866
2110
  this._state.endAngle ??= 360;
2111
+ const top = this.spec, ini = this.spec.initial ?? {};
2112
+ if ("trimStart" in top || "trimEnd" in top || "trimStart" in ini || "trimEnd" in ini) {
2113
+ console.warn(`pixi-effects: ${describeLayer(this.spec)}: trimStart / trimEnd do not apply to an arc: animate its endAngle (and startAngle) instead`);
2114
+ }
2115
+ } else {
2116
+ this._state.trimStart ??= 0;
2117
+ this._state.trimEnd ??= 1;
1867
2118
  }
1868
2119
  const gradientSpec = this.spec.fillGradient ?? this.spec.initial?.fillGradient;
1869
2120
  if (gradientSpec) this._state.fillStyle = makeGradientFill(gradientSpec);
1870
2121
  this._drawGeometry = makeDrawGeometry(this.spec, scope, this._state);
2122
+ this._outline = makeOutline(this.spec, scope, this._state);
2123
+ const { trimStart, trimEnd } = this._state;
2124
+ this._state.trimStart = 0;
2125
+ this._state.trimEnd = 1;
1871
2126
  this._drawGeometry(graphics);
1872
2127
  applyState(graphics, this._state);
1873
2128
  const bounds = graphics.getLocalBounds();
2129
+ this._state.trimStart = trimStart;
2130
+ this._state.trimEnd = trimEnd;
1874
2131
  this.intrinsicWidth = bounds.width;
1875
2132
  this.intrinsicHeight = bounds.height;
1876
2133
  if (!ANCHORED_SHAPES.has(this.spec.shape)) {
@@ -1888,14 +2145,37 @@ var ShapeSequence = class extends Sequence {
1888
2145
  }
1889
2146
  this._redraw();
1890
2147
  };
2148
+ this._redraw();
1891
2149
  this.buildFilters();
1892
2150
  }
1893
2151
  _fresh = false;
2152
+ _outline = null;
1894
2153
  _redraw() {
1895
2154
  const graphics = this.target;
2155
+ const s = this._state;
1896
2156
  graphics.clear();
1897
- this._drawGeometry(graphics);
1898
- applyState(graphics, this._state);
2157
+ const trimmed = (s.trimStart ?? 0) > 0 || (s.trimEnd ?? 1) < 1;
2158
+ if (!trimmed || !this._outline || !(s.strokeColor !== void 0 && s.strokeWidth > 0)) {
2159
+ this._drawGeometry(graphics);
2160
+ applyState(graphics, s);
2161
+ return;
2162
+ }
2163
+ if (s.fillStyle || s.fillColor !== void 0) {
2164
+ this._drawGeometry(graphics);
2165
+ applyFill(graphics, s);
2166
+ graphics.beginPath();
2167
+ }
2168
+ const parts = trimPolylines(this._outline(), s.trimStart ?? 0, s.trimEnd ?? 1);
2169
+ if (parts.length === 0) return;
2170
+ for (const part of parts) {
2171
+ if (part.closed) {
2172
+ graphics.poly(part.pts, true);
2173
+ continue;
2174
+ }
2175
+ graphics.moveTo(part.pts[0], part.pts[1]);
2176
+ for (let i = 2; i < part.pts.length; i += 2) graphics.lineTo(part.pts[i], part.pts[i + 1]);
2177
+ }
2178
+ applyStroke(graphics, s);
1899
2179
  }
1900
2180
  /**
1901
2181
  * Draw from the tweened `_state` before the culler runs. The culler measures the geometry as last
@@ -1998,11 +2278,17 @@ function resolveLiveValue(key, value, scope) {
1998
2278
  return numOrZero(value, scope);
1999
2279
  }
2000
2280
  function applyState(g, s) {
2281
+ applyFill(g, s);
2282
+ applyStroke(g, s);
2283
+ }
2284
+ function applyFill(g, s) {
2001
2285
  if (s.fillStyle) {
2002
2286
  g.fill(s.fillStyle);
2003
2287
  } else if (s.fillColor !== void 0) {
2004
2288
  g.fill({ color: s.fillColor, alpha: s.fillAlpha });
2005
2289
  }
2290
+ }
2291
+ function applyStroke(g, s) {
2006
2292
  if (s.strokeColor !== void 0 && s.strokeWidth > 0) {
2007
2293
  g.stroke({
2008
2294
  color: s.strokeColor,
@@ -2110,6 +2396,41 @@ function makeDrawGeometry(spec, scope, state) {
2110
2396
  return () => {
2111
2397
  };
2112
2398
  }
2399
+ function makeOutline(spec, scope, s) {
2400
+ switch (spec.shape) {
2401
+ case "rect":
2402
+ return () => {
2403
+ const w = s.width ?? 0, h = s.height ?? 0;
2404
+ return [rectOutline(0 - (s.anchorX ?? 0.5) * w, 0 - (s.anchorY ?? 0.5) * h, w, h, s.cornerRadius ?? 0)];
2405
+ };
2406
+ case "circle":
2407
+ return () => {
2408
+ const r = s.radius ?? 0;
2409
+ return [ellipseOutline((0.5 - (s.anchorX ?? 0.5)) * 2 * r, (0.5 - (s.anchorY ?? 0.5)) * 2 * r, r, r)];
2410
+ };
2411
+ case "ellipse":
2412
+ return () => {
2413
+ const rx = s.radiusX ?? 0, ry = s.radiusY ?? 0;
2414
+ return [ellipseOutline((0.5 - (s.anchorX ?? 0.5)) * 2 * rx, (0.5 - (s.anchorY ?? 0.5)) * 2 * ry, rx, ry)];
2415
+ };
2416
+ case "line": {
2417
+ const lines = [{ pts: [num(spec.from[0], scope), num(spec.from[1], scope), num(spec.to[0], scope), num(spec.to[1], scope)], closed: false }];
2418
+ return () => lines;
2419
+ }
2420
+ case "polygon": {
2421
+ const pts = [];
2422
+ for (const [x, y] of spec.points) pts.push(num(x, scope), num(y, scope));
2423
+ const lines = pts.length >= 4 ? [{ pts, closed: !spec.open }] : [];
2424
+ return () => lines;
2425
+ }
2426
+ case "path": {
2427
+ let lines = null;
2428
+ return () => lines ??= flattenSvgPath(spec.d);
2429
+ }
2430
+ default:
2431
+ return null;
2432
+ }
2433
+ }
2113
2434
  function bindLiveKeyframes(timeline, state, liveKeys, keyframes, parentDuration, scope, offset, colorSpace) {
2114
2435
  for (const kf of keyframes) {
2115
2436
  const at = offset + resolveAt(kf.at, parentDuration);
@@ -2183,7 +2504,7 @@ function numOrZero(v, scope) {
2183
2504
  var _CompositionSequence = null;
2184
2505
  async function getCompositionSequence() {
2185
2506
  if (!_CompositionSequence) {
2186
- _CompositionSequence = (await import('./Composition-TUCRSMJU.js')).CompositionSequence;
2507
+ _CompositionSequence = (await import('./Composition-DT657FO5.js')).CompositionSequence;
2187
2508
  }
2188
2509
  return _CompositionSequence;
2189
2510
  }
@@ -2220,6 +2541,6 @@ async function buildSequenceTree(specs, parent, root) {
2220
2541
  return out;
2221
2542
  }
2222
2543
 
2223
- export { CameraSequence, DEFAULT_FOV, DEG, NEAR, Sequence, buildScope, buildSequenceTree, cameraBasis, collectPropKeys, describeLayer, evaluateExpr, homeCamera, homeDistance, lintText, lintTiming, projectLayer, registerSequenceType, resolveAt, summarizeWarnings };
2224
- //# sourceMappingURL=chunk-UBPZLGLU.js.map
2225
- //# sourceMappingURL=chunk-UBPZLGLU.js.map
2544
+ export { CameraSequence, DEFAULT_FOV, DEG, NEAR, Sequence, buildScope, buildSequenceTree, cameraBasis, collectPropKeys, describeLayer, ensureFilterLibrary, evaluateExpr, homeCamera, homeDistance, lintText, lintTiming, projectLayer, registerSequenceType, resolveAt, summarizeWarnings };
2545
+ //# sourceMappingURL=chunk-4O2NYDJG.js.map
2546
+ //# sourceMappingURL=chunk-4O2NYDJG.js.map