@px-petals/roku.motion-web 0.0.0-stage → 1.1.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 (99) hide show
  1. package/LICENSE +201 -0
  2. package/audio/audio.cues.js +27 -0
  3. package/audio/audio.js +259 -0
  4. package/bands/bands.js +392 -0
  5. package/bundle/bundle.fonts.js +55 -0
  6. package/bundle/bundle.js +131 -0
  7. package/focus/focus.direction.js +46 -0
  8. package/focus/focus.dom.js +158 -0
  9. package/focus/focus.js +277 -0
  10. package/focus/focus.tree.js +146 -0
  11. package/hit/hit.geometry.js +113 -0
  12. package/hit/hit.js +129 -0
  13. package/list/list.flex.js +181 -0
  14. package/list/list.items.js +80 -0
  15. package/list/list.js +518 -0
  16. package/lottie/lottie.js +124 -0
  17. package/lottie/lottie.layers.js +416 -0
  18. package/lottie/lottie.paint.js +238 -0
  19. package/lottie/lottie.paths.js +474 -0
  20. package/lottie/lottie.props.js +265 -0
  21. package/lottie/lottie.shapes.js +259 -0
  22. package/mixer/mixer.js +307 -0
  23. package/mixer/mixer.mix.js +107 -0
  24. package/mixer/mixer.stage.js +431 -0
  25. package/motion/motion.convert.js +404 -0
  26. package/motion/motion.flow.js +421 -0
  27. package/motion/motion.keys.js +150 -0
  28. package/motion/motion.machine.js +382 -0
  29. package/motion/motion.nested.js +345 -0
  30. package/motion/motion.values.js +368 -0
  31. package/motion.js +1155 -0
  32. package/package.json +47 -4
  33. package/player/overlay/overlay.js +325 -0
  34. package/player/overlay/overlay.layout.js +208 -0
  35. package/player/player.fit.js +101 -0
  36. package/player/player.js +421 -0
  37. package/player/player.raster.js +127 -0
  38. package/pointer/pointer.js +292 -0
  39. package/readme.md +345 -0
  40. package/script/luau/lib/base/base.js +202 -0
  41. package/script/luau/lib/base/base.table.js +189 -0
  42. package/script/luau/lib/lib.args.js +152 -0
  43. package/script/luau/lib/lib.js +23 -0
  44. package/script/luau/lib/math/math.bit32.js +144 -0
  45. package/script/luau/lib/math/math.js +288 -0
  46. package/script/luau/lib/math/math.vector.js +125 -0
  47. package/script/luau/lib/string/string.format.js +143 -0
  48. package/script/luau/lib/string/string.js +160 -0
  49. package/script/luau/lib/string/string.pattern.js +505 -0
  50. package/script/luau/load/load.js +362 -0
  51. package/script/luau/load/load.ops.js +79 -0
  52. package/script/luau/luau.convert.js +259 -0
  53. package/script/luau/luau.js +132 -0
  54. package/script/luau/luau.md +111 -0
  55. package/script/luau/vm/vm.js +167 -0
  56. package/script/luau/vm/vm.meta.js +401 -0
  57. package/script/luau/vm/vm.num.js +236 -0
  58. package/script/luau/vm/vm.run.js +842 -0
  59. package/script/luau/vm/vm.value.js +406 -0
  60. package/script/play/play.js +199 -0
  61. package/script/play/play.objects.js +295 -0
  62. package/script/play/play.scope.js +91 -0
  63. package/script/rive/api/api.statics.js +31 -0
  64. package/script/rive/api/api.type.js +188 -0
  65. package/script/rive/api/artboard/artboard.js +89 -0
  66. package/script/rive/api/assets/assets.js +72 -0
  67. package/script/rive/api/data/data.value.js +99 -0
  68. package/script/rive/api/draw/draw.contours.js +163 -0
  69. package/script/rive/api/draw/draw.measure.js +196 -0
  70. package/script/rive/api/draw/draw.paint.js +235 -0
  71. package/script/rive/api/draw/draw.path.js +153 -0
  72. package/script/rive/api/draw/draw.renderer.js +117 -0
  73. package/script/rive/api/math/math.color.js +96 -0
  74. package/script/rive/api/math/math.mat2d.js +180 -0
  75. package/script/rive/api/math/math.vector.js +121 -0
  76. package/script/rive/api/media/media.audio.js +113 -0
  77. package/script/rive/api/media/media.draw.js +73 -0
  78. package/script/rive/api/media/media.image.js +61 -0
  79. package/script/rive/api/media/media.mesh.js +50 -0
  80. package/script/rive/api/pointer/pointer.event.js +64 -0
  81. package/script/rive/api/pointer/pointer.route.js +69 -0
  82. package/script/rive/api/text/text.font.js +129 -0
  83. package/script/rive/api/text/text.js +278 -0
  84. package/script/rive/api/text/text.layout.js +259 -0
  85. package/script/rive/context/context.js +76 -0
  86. package/script/rive/context/context.memory.js +196 -0
  87. package/script/rive/context/context.viewmodel.js +302 -0
  88. package/script/rive/context/lists/lists.js +160 -0
  89. package/script/rive/context/lists/lists.values.js +281 -0
  90. package/script/rive/host/host.globals.js +44 -0
  91. package/script/rive/host/host.js +533 -0
  92. package/script/rive/host/host.node.js +45 -0
  93. package/script/rive/rive.js +23 -0
  94. package/script/rive/rive.md +180 -0
  95. package/script/script.js +11 -0
  96. package/skin/skin.deform.js +514 -0
  97. package/skin/skin.ik.js +260 -0
  98. package/skin/skin.js +297 -0
  99. package/README.md +0 -3
@@ -0,0 +1,265 @@
1
+ /**
2
+ * Lottie properties as functions of the frame: a static value, or keyframes
3
+ * eased by their cubic beziers. A property carrying a slot id (`sid`) reads
4
+ * the live slot table first, so data bindings change what draws without
5
+ * recompiling the animation.
6
+ *
7
+ * Both keyframe forms are read: the current one, where a segment ends at the
8
+ * next keyframe's `s`, and the older one lottie-roku's sources still use,
9
+ * where a keyframe carries its own end value in `e`.
10
+ */
11
+
12
+ import { mixValues, settledWeight } from '../mixer/mixer.mix.js'
13
+
14
+ /**
15
+ * @typedef {(frame: number) => any} Evaluator
16
+ */
17
+
18
+ /**
19
+ * Live slot values, by slot id; `undefined` leaves a property to its keyframes, and a function gives the value at a frame.
20
+ * @typedef {Record<string, any>} Slots
21
+ */
22
+
23
+ /**
24
+ * @param {any} property a Lottie property: `{ a, k }`, optionally with `sid`
25
+ * @param {Slots} slots read on every call, so changing it changes the result
26
+ * @param {any} [fallback] the value when the property is absent
27
+ * @returns {Evaluator}
28
+ */
29
+ export function evaluator(property, slots, fallback) {
30
+ if (!property) return () => fallback
31
+
32
+ const keyed = property.pz ? posedEvaluator(property, keyedEvaluator(property, fallback), slots) : keyedEvaluator(property, fallback)
33
+ const sid = property.sid
34
+ if (typeof sid !== 'string') return keyed
35
+
36
+ // a nested machine that mixes several animations names where its poses are
37
+ const poseKey = property.pz ? 'pz:' + property.pz.n : ''
38
+ return (frame) => {
39
+ const live = slots[sid]
40
+ // a live value can depend on the frame: a skin the animation keys is asked for its path, in the poses of the machine it is drawn in
41
+ if (typeof live === 'function') return live(frame, poseKey ? slots[poseKey] : null)
42
+ return live === undefined ? keyed(frame) : live
43
+ }
44
+ }
45
+
46
+ /**
47
+ * A property of a nested machine that plays several animations at once. The
48
+ * machine says which it plays, as `{ start, frame, weight }` in the slot
49
+ * `pz:<id>`: the first frame of the animation's segment on the stitched
50
+ * timeline, how far in it is, and its weight. Rive applies the poses in
51
+ * order, each mixing the value toward its own by its weight, onto what the
52
+ * last frame left: held, that settles where each pose that keys the property
53
+ * counts by its weight times what the poses after it leave (`settledWeight`),
54
+ * and the setup value no longer shows. One that does not key it leaves it
55
+ * alone; with none keying it, it is the keyframes at the time remap's frame.
56
+ *
57
+ * @param {any} property `pz` is `{ n, k }`: the machine's id, and the segments that key the property
58
+ * @param {Evaluator} keyed
59
+ * @param {Slots} slots
60
+ * @returns {Evaluator}
61
+ */
62
+ function posedEvaluator(property, keyed, slots) {
63
+ const key = 'pz:' + property.pz.n
64
+ const keying = property.pz.k
65
+ return (frame) => {
66
+ const poses = slots[key]
67
+ const value = keyed(frame)
68
+ if (!poses) return value
69
+
70
+ let mixed = value
71
+ let total = 0
72
+ for (let n = 0; n < poses.length; n++) {
73
+ const pose = poses[n]
74
+ const weight = keying.indexOf(pose.start) < 0 ? 0 : settledWeight(poses, n, keying)
75
+ if (weight <= 0) continue
76
+ // a running weighted average: each pose's share of what has been counted so far
77
+ total += weight
78
+ mixed = mixValues(mixed, keyed(pose.start + pose.frame), weight / total)
79
+ }
80
+ return mixed
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Whether a property's value changes over time.
86
+ *
87
+ * @param {any} property
88
+ */
89
+ export function isAnimated(property) {
90
+ return !!property && property.a === 1 && Array.isArray(property.k) && property.k.length > 0 && typeof property.k[0] === 'object' && property.k[0] !== null && 't' in property.k[0]
91
+ }
92
+
93
+ /**
94
+ * @param {any} property
95
+ * @param {any} fallback
96
+ * @returns {Evaluator}
97
+ */
98
+ function keyedEvaluator(property, fallback) {
99
+ // the pose mixer replaces a keyed property's keyframes with its own evaluator
100
+ if (typeof property.mx === 'function') return property.mx
101
+
102
+ if (!isAnimated(property)) {
103
+ const value = property.k === undefined ? fallback : property.k
104
+ return () => value
105
+ }
106
+
107
+ const keyframes = property.k
108
+ return (frame) => valueAt(keyframes, frame)
109
+ }
110
+
111
+ /**
112
+ * @param {any[]} keyframes
113
+ * @param {number} frame
114
+ * @returns {any}
115
+ */
116
+ function valueAt(keyframes, frame) {
117
+ const first = keyframes[0]
118
+ if (frame <= first.t || keyframes.length === 1) return startValue(first)
119
+
120
+ for (let n = 0; n < keyframes.length - 1; n++) {
121
+ const from = keyframes[n]
122
+ const to = keyframes[n + 1]
123
+ if (frame >= to.t) continue
124
+
125
+ if (from.h === 1) return startValue(from)
126
+ const end = from.e !== undefined ? from.e : startValue(to)
127
+ const span = to.t - from.t
128
+ const progress = span > 0 ? (frame - from.t) / span : 1
129
+ return blend(startValue(from), end, from, progress)
130
+ }
131
+
132
+ const last = keyframes[keyframes.length - 1]
133
+ // an old-form last keyframe may hold no `s`; the one before ends where it ends
134
+ if (last.s === undefined) {
135
+ const before = keyframes[keyframes.length - 2]
136
+ return before.e !== undefined ? before.e : startValue(before)
137
+ }
138
+ return startValue(last)
139
+ }
140
+
141
+ /** @param {any} keyframe */
142
+ function startValue(keyframe) {
143
+ return keyframe.s
144
+ }
145
+
146
+ /**
147
+ * Eases each dimension by its own bezier when the keyframe gives one per
148
+ * dimension, else all by the same one.
149
+ *
150
+ * @param {any} from
151
+ * @param {any} to
152
+ * @param {any} keyframe the segment's first keyframe, holding `o` and `i`
153
+ * @param {number} progress 0-1 through the segment
154
+ */
155
+ function blend(from, to, keyframe, progress) {
156
+ if (isPath(from)) return blendPath(from, to, easeAt(keyframe, 0, progress))
157
+ if (!Array.isArray(from)) return lerp(from, to, easeAt(keyframe, 0, progress))
158
+
159
+ const result = []
160
+ for (let d = 0; d < from.length; d++) {
161
+ const target = Array.isArray(to) && d < to.length ? to[d] : from[d]
162
+ result.push(lerp(from[d], target, easeAt(keyframe, d, progress)))
163
+ }
164
+ return result
165
+ }
166
+
167
+ /**
168
+ * @param {any} value
169
+ */
170
+ function isPath(value) {
171
+ return Array.isArray(value) && value.length > 0 && typeof value[0] === 'object' && value[0] !== null && Array.isArray(value[0].v)
172
+ }
173
+
174
+ /**
175
+ * A shape keyframe's value is `[{ i, o, v, c }]`; its vertices blend pairwise.
176
+ *
177
+ * @param {any[]} from
178
+ * @param {any[]} to
179
+ * @param {number} t
180
+ */
181
+ function blendPath(from, to, t) {
182
+ const a = from[0]
183
+ const b = Array.isArray(to) && to[0] ? to[0] : a
184
+ return [ { c: a.c, v: blendPoints(a.v, b.v, t), i: blendPoints(a.i, b.i, t), o: blendPoints(a.o, b.o, t) } ]
185
+ }
186
+
187
+ /**
188
+ * @param {number[][]} a
189
+ * @param {number[][]} b
190
+ * @param {number} t
191
+ */
192
+ function blendPoints(a, b, t) {
193
+ const points = []
194
+ for (let n = 0; n < a.length; n++) {
195
+ const q = b[n] || a[n]
196
+ points.push([ lerp(a[n][0], q[0], t), lerp(a[n][1], q[1], t) ])
197
+ }
198
+ return points
199
+ }
200
+
201
+ /**
202
+ * @param {number} a
203
+ * @param {number} b
204
+ * @param {number} t
205
+ */
206
+ function lerp(a, b, t) {
207
+ return a + (b - a) * t
208
+ }
209
+
210
+ /**
211
+ * The eased progress for one dimension.
212
+ *
213
+ * @param {any} keyframe
214
+ * @param {number} dimension
215
+ * @param {number} progress
216
+ * @returns {number}
217
+ */
218
+ function easeAt(keyframe, dimension, progress) {
219
+ const out = keyframe.o
220
+ const into = keyframe.i
221
+ if (!out || !into) return progress
222
+
223
+ const pick = (/** @type {any} */ value) => (Array.isArray(value) ? (dimension < value.length ? value[dimension] : value[0]) : value)
224
+ return cubicBezier(pick(out.x), pick(out.y), pick(into.x), pick(into.y), progress)
225
+ }
226
+
227
+ /**
228
+ * The y of a CSS-style cubic bezier easing curve (0,0)-(x1,y1)-(x2,y2)-(1,1)
229
+ * at x: Newton's method on x, bisection when it stalls.
230
+ *
231
+ * @param {number} x1
232
+ * @param {number} y1
233
+ * @param {number} x2
234
+ * @param {number} y2
235
+ * @param {number} x
236
+ * @returns {number}
237
+ */
238
+ export function cubicBezier(x1, y1, x2, y2, x) {
239
+ const isLinear = x1 === y1 && x2 === y2
240
+ if (isLinear || x <= 0 || x >= 1) return x <= 0 ? 0 : x >= 1 ? 1 : x
241
+
242
+ const curve = (/** @type {number} */ t, /** @type {number} */ p1, /** @type {number} */ p2) => 3 * (1 - t) * (1 - t) * t * p1 + 3 * (1 - t) * t * t * p2 + t * t * t
243
+ const slope = (/** @type {number} */ t, /** @type {number} */ p1, /** @type {number} */ p2) => 3 * (1 - t) * (1 - t) * p1 + 6 * (1 - t) * t * (p2 - p1) + 3 * t * t * (1 - p2)
244
+
245
+ let t = x
246
+ for (let n = 0; n < 8; n++) {
247
+ const error = curve(t, x1, x2) - x
248
+ if (Math.abs(error) < 1e-6) return curve(t, y1, y2)
249
+ const d = slope(t, x1, x2)
250
+ if (Math.abs(d) < 1e-6) break
251
+ t -= error / d
252
+ }
253
+
254
+ let low = 0
255
+ let high = 1
256
+ t = x
257
+ for (let n = 0; n < 40; n++) {
258
+ const value = curve(t, x1, x2)
259
+ if (Math.abs(value - x) < 1e-6) break
260
+ if (value < x) low = t
261
+ else high = t
262
+ t = (low + high) / 2
263
+ }
264
+ return curve(t, y1, y2)
265
+ }
@@ -0,0 +1,259 @@
1
+ /**
2
+ * A shape layer's items, compiled once and run per frame into draw
3
+ * operations.
4
+ *
5
+ * Lottie's rules, in a group's item list:
6
+ * - earlier items are on top, so operations draw in reverse;
7
+ * - a paint covers every path above it in its group, and in groups above it;
8
+ * - a modifier (trim, merge) changes those same paths, so a subgroup's own
9
+ * paint draws them trimmed too.
10
+ *
11
+ * Geometry therefore lives in a shared holder: a trim rewrites the holder's
12
+ * paths, and every paint that captured it draws the result.
13
+ */
14
+
15
+ import { evaluator } from './lottie.props.js'
16
+ import { ellipse, fromLottie, IDENTITY, multiply, rectangle, star, transformMatrix, trimEach } from './lottie.paths.js'
17
+
18
+ /** @import { Path, Matrix } from './lottie.paths.js' */
19
+ /** @import { Slots } from './lottie.props.js' */
20
+
21
+ /**
22
+ * Geometry gathered from items above a paint or modifier.
23
+ * @typedef {{ paths: Path[], merge?: { mode: number, parts: Entry[] } }} Holder
24
+ * @typedef {{ holder: Holder, matrix: Matrix }} Entry `matrix` maps the holder's space into the gathering group's
25
+ * @typedef {{ paint: any, entries: Entry[], matrix: Matrix, opacity: number }} Operation `matrix` maps the paint's group into the layer
26
+ */
27
+
28
+ /**
29
+ * @param {any[]} items a shape layer's `shapes`, or a group's `it`
30
+ * @param {Slots} slots
31
+ * @returns {any[]} compiled items
32
+ */
33
+ export function compileItems(items, slots) {
34
+ const compiled = []
35
+ for (let n = 0; n < (items || []).length; n++) {
36
+ const item = items[n]
37
+ if (item.hd) continue
38
+ const one = compileItem(item, slots)
39
+ if (one) compiled.push(one)
40
+ }
41
+ return compiled
42
+ }
43
+
44
+ /**
45
+ * @param {any} item
46
+ * @param {Slots} slots
47
+ */
48
+ function compileItem(item, slots) {
49
+ const at = (/** @type {any} */ property, /** @type {any} */ fallback) => evaluator(property, slots, fallback)
50
+ const reversed = item.d === 3
51
+
52
+ switch (item.ty) {
53
+ case 'gr':
54
+ return { kind: 'group', rid: typeof item.rid === 'string' ? item.rid : '', items: compileItems(item.it, slots), transform: compileTransform((item.it || []).filter((/** @type {any} */ i) => i.ty === 'tr')[0], slots) }
55
+ case 'sh': {
56
+ const shape = at(item.ks, { v: [], i: [], o: [], c: false })
57
+ return { kind: 'geometry', path: (/** @type {number} */ f) => fromLottie(unwrap(shape(f))) }
58
+ }
59
+ case 'rc': {
60
+ const p = at(item.p, [ 0, 0 ])
61
+ const s = at(item.s, [ 0, 0 ])
62
+ const r = at(item.r, 0)
63
+ return { kind: 'geometry', path: (/** @type {number} */ f) => rectangle(p(f), s(f), scalar(r(f)), reversed) }
64
+ }
65
+ case 'el': {
66
+ const p = at(item.p, [ 0, 0 ])
67
+ const s = at(item.s, [ 0, 0 ])
68
+ return { kind: 'geometry', path: (/** @type {number} */ f) => ellipse(p(f), s(f), reversed) }
69
+ }
70
+ case 'sr': {
71
+ const p = at(item.p, [ 0, 0 ])
72
+ const pt = at(item.pt, 5)
73
+ const r = at(item.r, 0)
74
+ const or = at(item.or, 0)
75
+ const ir = at(item.ir, 0)
76
+ const os = at(item.os, 0)
77
+ const is = at(item.is, 0)
78
+ return {
79
+ kind: 'geometry',
80
+ path: (/** @type {number} */ f) => star({ type: item.sy, points: scalar(pt(f)), position: p(f), rotation: scalar(r(f)), outer: scalar(or(f)), inner: scalar(ir(f)), outerRound: scalar(os(f)), innerRound: scalar(is(f)), reversed }),
81
+ }
82
+ }
83
+ case 'tm':
84
+ return { kind: 'trim', start: at(item.s, 0), end: at(item.e, 100), offset: at(item.o, 0), together: item.m === 2 }
85
+ case 'mm':
86
+ return { kind: 'merge', mode: item.mm || 1 }
87
+ case 'fl':
88
+ case 'st':
89
+ case 'gf':
90
+ case 'gs':
91
+ return { kind: 'paint', paint: compilePaint(item, slots) }
92
+ default:
93
+ // `tr` is read by its group; repeaters, round corners and the rest are not drawn
94
+ return null
95
+ }
96
+ }
97
+
98
+ /**
99
+ * A shape keyframe value arrives wrapped in a one-element array.
100
+ *
101
+ * @param {any} value
102
+ */
103
+ function unwrap(value) {
104
+ return Array.isArray(value) ? value[0] || { v: [] } : value
105
+ }
106
+
107
+ /** @param {any} value */
108
+ export function scalar(value) {
109
+ return Array.isArray(value) ? value[0] : value
110
+ }
111
+
112
+ /**
113
+ * @param {any} tr
114
+ * @param {Slots} slots
115
+ * @returns {(frame: number) => { matrix: Matrix, opacity: number }}
116
+ */
117
+ export function compileTransform(tr, slots) {
118
+ if (!tr) return () => ({ matrix: IDENTITY, opacity: 1 })
119
+
120
+ const at = (/** @type {any} */ property, /** @type {any} */ fallback) => evaluator(property, slots, fallback)
121
+ const a = at(tr.a, [ 0, 0 ])
122
+ const p = splitPosition(tr.p, slots)
123
+ const s = at(tr.s, [ 100, 100 ])
124
+ const r = at(tr.r || tr.rz, 0)
125
+ const o = at(tr.o, 100)
126
+ const sk = at(tr.sk, 0)
127
+ const sa = at(tr.sa, 0)
128
+ return (frame) => ({
129
+ matrix: transformMatrix(a(frame), p(frame), s(frame), scalar(r(frame)), scalar(sk(frame)), scalar(sa(frame))),
130
+ opacity: scalar(o(frame)) / 100,
131
+ })
132
+ }
133
+
134
+ /**
135
+ * A position, given whole or as separate `x` and `y` (`s: true`).
136
+ *
137
+ * @param {any} position
138
+ * @param {Slots} slots
139
+ */
140
+ function splitPosition(position, slots) {
141
+ if (position && position.s) {
142
+ const x = evaluator(position.x, slots, 0)
143
+ const y = evaluator(position.y, slots, 0)
144
+ return (/** @type {number} */ frame) => [ scalar(x(frame)), scalar(y(frame)) ]
145
+ }
146
+ return evaluator(position, slots, [ 0, 0 ])
147
+ }
148
+
149
+ /**
150
+ * @param {any} item
151
+ * @param {Slots} slots
152
+ */
153
+ function compilePaint(item, slots) {
154
+ const at = (/** @type {any} */ property, /** @type {any} */ fallback) => evaluator(property, slots, fallback)
155
+ const isStroke = item.ty === 'st' || item.ty === 'gs'
156
+ const isGradient = item.ty === 'gf' || item.ty === 'gs'
157
+ return {
158
+ isStroke,
159
+ isGradient,
160
+ rule: item.r === 2 ? 'evenodd' : 'nonzero',
161
+ opacity: at(item.o, 100),
162
+ color: at(item.c, [ 0, 0, 0, 1 ]),
163
+ width: at(item.w, 1),
164
+ cap: [ 'butt', 'butt', 'round', 'square' ][item.lc || 1],
165
+ join: [ 'miter', 'miter', 'round', 'bevel' ][item.lj || 1],
166
+ miter: item.ml || 4,
167
+ dashes: (item.d || []).map((/** @type {any} */ d) => ({ kind: d.n, value: at(d.v, 0) })),
168
+ gradient: isGradient
169
+ ? { type: item.t || 1, start: at(item.s, [ 0, 0 ]), end: at(item.e, [ 0, 0 ]), stops: at(item.g && item.g.k, []), count: (item.g && item.g.p) || 0, highlight: at(item.h, 0), angle: at(item.a, 0) }
170
+ : null,
171
+ }
172
+ }
173
+
174
+ // Running
175
+ // ■■■■■■■
176
+
177
+ /**
178
+ * Where a frame's hit-test geometry goes: groups tagged with a watched `rid` hand over their paths.
179
+ * @typedef {object} HitSink
180
+ * @property {import('../hit/hit.js').Hits} hits
181
+ * @property {Matrix} matrix the layer's space onto the canvas
182
+ * @property {string} prefix what a group's `rid` is named under: a nested machine's artboard has its own, so it is `<the layer's nid>/`; "" in the animation's own
183
+ */
184
+
185
+ /**
186
+ * Runs a group's items at a frame.
187
+ *
188
+ * @param {any[]} items compiled items
189
+ * @param {number} frame
190
+ * @param {Matrix} matrix the group's space into the layer's
191
+ * @param {number} opacity the group's opacity, compounded
192
+ * @param {HitSink | null} [sink] records the paths of groups a pointer listener targets
193
+ * @returns {{ entries: Entry[], operations: Operation[] }} the paths above, for the parent's paints, and what to draw, top first
194
+ */
195
+ export function runGroup(items, frame, matrix, opacity, sink) {
196
+ /** @type {Entry[]} */
197
+ let entries = []
198
+ /** @type {Operation[]} */
199
+ const operations = []
200
+
201
+ for (let n = 0; n < items.length; n++) {
202
+ const item = items[n]
203
+ if (item.kind === 'geometry') {
204
+ entries.push({ holder: { paths: [ item.path(frame) ] }, matrix: IDENTITY })
205
+ } else if (item.kind === 'group') {
206
+ const local = item.transform(frame)
207
+ const inner = runGroup(item.items, frame, multiply(matrix, local.matrix), opacity * local.opacity, sink)
208
+ // a group that draws nothing (a Solo's hidden child, or anything faded out) is not a place a pointer can hit
209
+ if (sink && item.rid && opacity * local.opacity > 0) {
210
+ const rid = sink.prefix + item.rid
211
+ if (sink.hits.wants(rid)) sink.hits.add(rid, inner.entries, multiply(sink.matrix, multiply(matrix, local.matrix)))
212
+ }
213
+ for (let k = 0; k < inner.operations.length; k++) operations.push(inner.operations[k])
214
+ for (let k = 0; k < inner.entries.length; k++) entries.push({ holder: inner.entries[k].holder, matrix: multiply(local.matrix, inner.entries[k].matrix) })
215
+ } else if (item.kind === 'trim') {
216
+ trimEntries(entries, scalar(item.start(frame)), scalar(item.end(frame)), scalar(item.offset(frame)), item.together)
217
+ } else if (item.kind === 'merge') {
218
+ entries = entries.length > 1 ? [ { holder: { paths: [], merge: { mode: item.mode, parts: entries } }, matrix: IDENTITY } ] : entries
219
+ } else if (item.kind === 'paint') {
220
+ operations.push({ paint: item.paint, entries: entries.slice(), matrix, opacity })
221
+ }
222
+ }
223
+ return { entries, operations }
224
+ }
225
+
226
+ /**
227
+ * Trims every holder above, in its own space. Together, the holders' paths
228
+ * are trimmed as one list and each holder keeps its own share.
229
+ *
230
+ * @param {Entry[]} entries
231
+ * @param {number} start
232
+ * @param {number} end
233
+ * @param {number} offset
234
+ * @param {boolean} together
235
+ */
236
+ function trimEntries(entries, start, end, offset, together) {
237
+ /** @type {Holder[]} */
238
+ const holders = []
239
+ for (let n = 0; n < entries.length; n++) {
240
+ const holder = entries[n].holder
241
+ if (!holder.merge) holders.push(holder)
242
+ else for (let k = 0; k < holder.merge.parts.length; k++) holders.push(holder.merge.parts[k].holder)
243
+ }
244
+
245
+ const all = []
246
+ const owners = []
247
+ for (let n = 0; n < holders.length; n++) {
248
+ for (let k = 0; k < holders[n].paths.length; k++) {
249
+ all.push(holders[n].paths[k])
250
+ owners.push(n)
251
+ }
252
+ }
253
+
254
+ const kept = trimEach(all, start, end, offset, together)
255
+ for (let n = 0; n < holders.length; n++) holders[n].paths = []
256
+ for (let k = 0; k < kept.length; k++) {
257
+ for (let j = 0; j < kept[k].length; j++) holders[owners[k]].paths.push(kept[k][j])
258
+ }
259
+ }