@px-petals/roku.motion-web 1.0.0 → 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 (77) hide show
  1. package/bands/bands.js +3 -1
  2. package/bundle/bundle.js +56 -0
  3. package/list/list.js +12 -1
  4. package/lottie/lottie.layers.js +64 -11
  5. package/lottie/lottie.paint.js +30 -0
  6. package/lottie/lottie.paths.js +19 -4
  7. package/lottie/lottie.props.js +18 -8
  8. package/lottie/lottie.shapes.js +2 -1
  9. package/mixer/mixer.js +31 -8
  10. package/mixer/mixer.mix.js +17 -0
  11. package/motion/motion.convert.js +32 -1
  12. package/motion/motion.flow.js +20 -2
  13. package/motion/motion.machine.js +28 -13
  14. package/motion/motion.values.js +13 -9
  15. package/motion.js +196 -20
  16. package/package.json +10 -16
  17. package/player/player.js +25 -5
  18. package/readme.md +78 -3
  19. package/script/luau/lib/base/base.js +202 -0
  20. package/script/luau/lib/base/base.table.js +189 -0
  21. package/script/luau/lib/lib.args.js +152 -0
  22. package/script/luau/lib/lib.js +23 -0
  23. package/script/luau/lib/math/math.bit32.js +144 -0
  24. package/script/luau/lib/math/math.js +288 -0
  25. package/script/luau/lib/math/math.vector.js +125 -0
  26. package/script/luau/lib/string/string.format.js +143 -0
  27. package/script/luau/lib/string/string.js +160 -0
  28. package/script/luau/lib/string/string.pattern.js +505 -0
  29. package/script/luau/load/load.js +362 -0
  30. package/script/luau/load/load.ops.js +79 -0
  31. package/script/luau/luau.convert.js +259 -0
  32. package/script/luau/luau.js +132 -0
  33. package/script/luau/luau.md +111 -0
  34. package/script/luau/vm/vm.js +167 -0
  35. package/script/luau/vm/vm.meta.js +401 -0
  36. package/script/luau/vm/vm.num.js +236 -0
  37. package/script/luau/vm/vm.run.js +842 -0
  38. package/script/luau/vm/vm.value.js +406 -0
  39. package/script/play/play.js +199 -0
  40. package/script/play/play.objects.js +295 -0
  41. package/script/play/play.scope.js +91 -0
  42. package/script/rive/api/api.statics.js +31 -0
  43. package/script/rive/api/api.type.js +188 -0
  44. package/script/rive/api/artboard/artboard.js +89 -0
  45. package/script/rive/api/assets/assets.js +72 -0
  46. package/script/rive/api/data/data.value.js +99 -0
  47. package/script/rive/api/draw/draw.contours.js +163 -0
  48. package/script/rive/api/draw/draw.measure.js +196 -0
  49. package/script/rive/api/draw/draw.paint.js +235 -0
  50. package/script/rive/api/draw/draw.path.js +153 -0
  51. package/script/rive/api/draw/draw.renderer.js +117 -0
  52. package/script/rive/api/math/math.color.js +96 -0
  53. package/script/rive/api/math/math.mat2d.js +180 -0
  54. package/script/rive/api/math/math.vector.js +121 -0
  55. package/script/rive/api/media/media.audio.js +113 -0
  56. package/script/rive/api/media/media.draw.js +73 -0
  57. package/script/rive/api/media/media.image.js +61 -0
  58. package/script/rive/api/media/media.mesh.js +50 -0
  59. package/script/rive/api/pointer/pointer.event.js +64 -0
  60. package/script/rive/api/pointer/pointer.route.js +69 -0
  61. package/script/rive/api/text/text.font.js +129 -0
  62. package/script/rive/api/text/text.js +278 -0
  63. package/script/rive/api/text/text.layout.js +259 -0
  64. package/script/rive/context/context.js +76 -0
  65. package/script/rive/context/context.memory.js +196 -0
  66. package/script/rive/context/context.viewmodel.js +302 -0
  67. package/script/rive/context/lists/lists.js +160 -0
  68. package/script/rive/context/lists/lists.values.js +281 -0
  69. package/script/rive/host/host.globals.js +44 -0
  70. package/script/rive/host/host.js +533 -0
  71. package/script/rive/host/host.node.js +45 -0
  72. package/script/rive/rive.js +23 -0
  73. package/script/rive/rive.md +180 -0
  74. package/script/script.js +11 -0
  75. package/skin/skin.deform.js +514 -0
  76. package/skin/skin.ik.js +260 -0
  77. package/skin/skin.js +297 -0
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Rive's `Paint` (how a path is filled or stroked) and `Gradient`. A paint
3
+ * is mutable; the renderer copies it when a path is drawn, so a script can
4
+ * change one between draws.
5
+ */
6
+
7
+ import { LuaTable, NativeFunction, Vector, fail } from '../../../luau/vm/vm.value.js'
8
+ import { decodeUtf8 } from '../../../luau/luau.convert.js'
9
+ import { defineType, badArg, num, wrapped } from '../api.type.js'
10
+ import { Mat2D } from '../math/math.mat2d.js'
11
+ import { channels } from '../math/math.color.js'
12
+
13
+ /**
14
+ * @typedef {object} Stop
15
+ * @property {number} position 0 to 1
16
+ * @property {number} color ARGB
17
+ */
18
+
19
+ /**
20
+ * @typedef {object} GradientData
21
+ * @property {'linear' | 'radial'} kind
22
+ * @property {number[]} from [x, y]: the start of a linear gradient, the centre of a radial one
23
+ * @property {number[]} [to] [x, y]: the end of a linear one
24
+ * @property {number} [radius]
25
+ * @property {Stop[]} stops
26
+ */
27
+
28
+ /**
29
+ * @typedef {object} PaintData
30
+ * @property {'fill' | 'stroke'} style
31
+ * @property {number} color ARGB
32
+ * @property {number} thickness
33
+ * @property {string} join miter, round, bevel
34
+ * @property {string} cap butt, round, square
35
+ * @property {string} blendMode
36
+ * @property {number} feather
37
+ * @property {GradientData | null} gradient
38
+ * @property {number[] | null} gradientTransform
39
+ */
40
+
41
+ const STYLES = [ 'fill', 'stroke' ]
42
+ const JOINS = [ 'miter', 'round', 'bevel' ]
43
+ const CAPS = [ 'butt', 'round', 'square' ]
44
+ const BLENDS = [ 'srcOver', 'additive', 'screen', 'overlay', 'darken', 'lighten', 'colorDodge', 'colorBurn', 'hardLight', 'softLight', 'difference', 'exclusion', 'multiply', 'hue', 'saturation', 'color', 'luminosity' ]
45
+
46
+ /** @returns {PaintData} */
47
+ export function defaultPaint() {
48
+ return { style: 'fill', color: 0xff000000, thickness: 1, join: 'miter', cap: 'butt', blendMode: 'srcOver', feather: 0, gradient: null, gradientTransform: null }
49
+ }
50
+
51
+ /**
52
+ * @param {string} name
53
+ * @param {string[]} allowed
54
+ * @returns {{ get: (h: any) => any, set: (h: any, v: any) => void }}
55
+ */
56
+ function choice(name, allowed) {
57
+ return {
58
+ get: (h) => h[name],
59
+ set: (h, v) => {
60
+ const s = typeof v === 'string' ? decodeUtf8(v) : ''
61
+ if (allowed.indexOf(s) < 0) throw fail("invalid Paint." + name + ' (' + allowed.join(', ') + ' expected)')
62
+ h[name] = s
63
+ },
64
+ }
65
+ }
66
+
67
+ /**
68
+ * @param {string} name
69
+ * @returns {{ get: (h: any) => any, set: (h: any, v: any) => void }}
70
+ */
71
+ function numeric(name) {
72
+ return {
73
+ get: (h) => h[name],
74
+ set: (h, v) => {
75
+ if (typeof v !== 'number') throw fail('Paint.' + name + ' must be a number')
76
+ h[name] = v
77
+ },
78
+ }
79
+ }
80
+
81
+ export const Gradient = defineType('Gradient', {})
82
+
83
+ export const Paint = defineType('Paint', {
84
+ fields: {
85
+ style: choice('style', STYLES),
86
+ join: choice('join', JOINS),
87
+ cap: choice('cap', CAPS),
88
+ blendMode: choice('blendMode', BLENDS),
89
+ color: numeric('color'),
90
+ thickness: numeric('thickness'),
91
+ feather: numeric('feather'),
92
+ gradient: {
93
+ get: (h) => (h.gradient ? Gradient.wrap(h.gradient) : undefined),
94
+ set: (h, v) => {
95
+ h.gradient = v === undefined ? null : wrapped([ v ], 0, Gradient, 'gradient')
96
+ },
97
+ },
98
+ gradientTransform: {
99
+ get: (h) => (h.gradientTransform ? Mat2D.wrap(h.gradientTransform.slice()) : undefined),
100
+ set: (h, v) => {
101
+ h.gradientTransform = v === undefined ? null : wrapped([ v ], 0, Mat2D, 'gradientTransform').slice()
102
+ },
103
+ },
104
+ },
105
+ methods: {
106
+ copy: (h, args) => applyDefinition(copyPaint(h), args[1]),
107
+ },
108
+ })
109
+
110
+ /**
111
+ * @param {PaintData} h
112
+ * @returns {PaintData}
113
+ */
114
+ function copyPaint(h) {
115
+ return { style: h.style, color: h.color, thickness: h.thickness, join: h.join, cap: h.cap, blendMode: h.blendMode, feather: h.feather, gradient: h.gradient, gradientTransform: h.gradientTransform }
116
+ }
117
+
118
+ /**
119
+ * Sets the fields a definition table names.
120
+ *
121
+ * @param {PaintData} h
122
+ * @param {any} def a table of fields, or nil
123
+ * @returns {any} the paint, wrapped
124
+ */
125
+ function applyDefinition(h, def) {
126
+ const w = Paint.wrap(h)
127
+ if (def === undefined) return w
128
+ if (!(def instanceof LuaTable)) throw badArg(0, 'Paint.with', 'table', [ def ])
129
+ for (let pos = 0, next = def.advance(pos); next !== null; next = def.advance(pos)) {
130
+ pos = next
131
+ const key = typeof def.ck === 'string' ? def.ck : ''
132
+ if (key === '') continue
133
+ // the metatable's __newindex validates each field
134
+ const setter = /** @type {LuaTable} */ (Paint.meta).getStr('__newindex')
135
+ if (setter instanceof NativeFunction) setter.fn(null, [ w, key, def.cv ])
136
+ }
137
+ return w
138
+ }
139
+
140
+ /**
141
+ * @param {any} t a table of `{ position, color }`
142
+ * @returns {Stop[]}
143
+ */
144
+ function stopsOf(t) {
145
+ if (!(t instanceof LuaTable)) throw badArg(2, 'Gradient', 'table of stops', [ undefined, undefined, t ])
146
+ /** @type {Stop[]} */
147
+ const stops = []
148
+ for (let n = 1; n <= t.length(); n++) {
149
+ const s = t.get(n)
150
+ if (!(s instanceof LuaTable)) throw fail('Gradient stop ' + n + ' must be a table')
151
+ const position = s.getStr('position')
152
+ const color = s.getStr('color')
153
+ if (typeof position !== 'number' || typeof color !== 'number') throw fail('Gradient stop ' + n + ' needs a position and a color')
154
+ stops.push({ position, color })
155
+ }
156
+ return stops
157
+ }
158
+
159
+ /**
160
+ * @param {any[]} args
161
+ * @param {number} i
162
+ * @param {string} fname
163
+ * @returns {number[]}
164
+ */
165
+ function point(args, i, fname) {
166
+ const v = args[i]
167
+ if (!(v instanceof Vector)) throw badArg(i, fname, 'Vector', args)
168
+ return [ v.x, v.y ]
169
+ }
170
+
171
+ /** @type {Record<string, (args: any[]) => any>} */
172
+ const PAINT_STATICS = {
173
+ new: () => Paint.wrap(defaultPaint()),
174
+ with: (a) => applyDefinition(defaultPaint(), a[0]),
175
+ }
176
+
177
+ /** @type {Record<string, (args: any[]) => any>} */
178
+ const GRADIENT_STATICS = {
179
+ linear: (a) => Gradient.wrap({ kind: 'linear', from: point(a, 0, 'linear'), to: point(a, 1, 'linear'), stops: stopsOf(a[2]) }),
180
+ radial: (a) => Gradient.wrap({ kind: 'radial', from: point(a, 0, 'radial'), radius: num(a, 1, 'radial'), stops: stopsOf(a[2]) }),
181
+ }
182
+
183
+ /**
184
+ * @param {string} prefix
185
+ * @param {Record<string, (args: any[]) => any>} fns
186
+ * @returns {LuaTable}
187
+ */
188
+ function table(prefix, fns) {
189
+ const t = new LuaTable()
190
+ for (const name in fns) {
191
+ const fn = fns[name]
192
+ t.set(name, new NativeFunction(prefix + '.' + name, function (vm, args) {
193
+ return fn(args)
194
+ }))
195
+ }
196
+ return t
197
+ }
198
+
199
+ /**
200
+ * Installs the `Paint` and `Gradient` globals.
201
+ *
202
+ * @param {import('../../../luau/vm/vm.js').VM} vm
203
+ */
204
+ export function installPaint(vm) {
205
+ vm.globals.set('Paint', table('Paint', PAINT_STATICS))
206
+ vm.globals.set('Gradient', table('Gradient', GRADIENT_STATICS))
207
+ }
208
+
209
+ /**
210
+ * A paint as the plain record a drawing carries.
211
+ *
212
+ * @param {PaintData} h
213
+ * @param {number} [modulate] a colour the renderer multiplies in (ARGB)
214
+ * @returns {{ style: string, color: number, rgba: number[], thickness: number, join: string, cap: string, blendMode: string, feather: number, gradient: GradientData | null, gradientTransform: number[] | null }}
215
+ */
216
+ export function snapshotPaint(h, modulate) {
217
+ const ch = channels(h.color)
218
+ if (modulate !== undefined && modulate !== 0xffffffff) {
219
+ const m = channels(modulate)
220
+ for (let i = 0; i < 4; i++) ch[i] = Math.round((ch[i] * m[i]) / 255)
221
+ }
222
+ const color = ch[3] * 16777216 + ch[0] * 65536 + ch[1] * 256 + ch[2]
223
+ return {
224
+ style: h.style,
225
+ color,
226
+ rgba: [ ch[0] / 255, ch[1] / 255, ch[2] / 255, ch[3] / 255 ],
227
+ thickness: h.thickness,
228
+ join: h.join,
229
+ cap: h.cap,
230
+ blendMode: h.blendMode,
231
+ feather: h.feather,
232
+ gradient: h.gradient,
233
+ gradientTransform: h.gradientTransform ? h.gradientTransform.slice() : null,
234
+ }
235
+ }
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Rive's `Path` (and the `PathData` a path effect receives, which is the same
3
+ * thing here): a list of drawing commands a script builds with `moveTo`,
4
+ * `lineTo`, `quadTo`, `cubicTo` and `close`. It reads as an array of
5
+ * `PathCommand`s too (`#path`, `path[i]`, `ipairs`), each a table with a `type`
6
+ * and its points as vectors, which the wrapper keeps in step with the commands.
7
+ */
8
+
9
+ import { LuaTable, NativeFunction, Vector, fail } from '../../../luau/vm/vm.value.js'
10
+ import { Wrapped, defineType, wrapped } from '../api.type.js'
11
+ import { Mat2D, apply } from '../math/math.mat2d.js'
12
+ import { vec } from '../math/math.vector.js'
13
+ import { commandsToContours, contoursToCommands } from './draw.contours.js'
14
+ import { measureOf } from './draw.measure.js'
15
+
16
+ /** @import { Command, Contour } from './draw.contours.js' */
17
+
18
+ /** @typedef {{ cmds: Command[], wrapper: Wrapped | null }} PathData */
19
+
20
+ /**
21
+ * @param {Command} cmd
22
+ * @returns {LuaTable} the `PathCommand` a script sees
23
+ */
24
+ function commandTable(cmd) {
25
+ const t = new LuaTable()
26
+ t.set('type', cmd.t)
27
+ const points = []
28
+ for (let i = 0; i < cmd.p.length; i += 2) points.push(new Vector(cmd.p[i], cmd.p[i + 1], 0))
29
+ t.setRange(1, points, 0, points.length)
30
+ return t
31
+ }
32
+
33
+ /**
34
+ * Appends a command to a path and its wrapper's array.
35
+ *
36
+ * @param {Wrapped} w
37
+ * @param {Command} cmd
38
+ */
39
+ function push(w, cmd) {
40
+ w.host.cmds.push(cmd)
41
+ w.set(w.host.cmds.length, commandTable(cmd))
42
+ }
43
+
44
+ /**
45
+ * @param {any[]} args
46
+ * @param {number} first the index of the first Vector argument
47
+ * @param {number} n how many vectors
48
+ * @param {string} fname
49
+ * @returns {number[]} their x, y flattened
50
+ */
51
+ function points(args, first, n, fname) {
52
+ const out = []
53
+ for (let k = 0; k < n; k++) {
54
+ const v = vec(args, first + k, fname)
55
+ out.push(v.x, v.y)
56
+ }
57
+ return out
58
+ }
59
+
60
+ /**
61
+ * @param {any} dest the Path a script passed to `extract`
62
+ * @param {Command[]} cmds
63
+ */
64
+ function append(dest, cmds) {
65
+ const w = dest instanceof Wrapped && Path.is(dest) ? dest : null
66
+ if (!w) throw fail("invalid argument #3 to 'extract' (Path expected)")
67
+ for (let n = 0; n < cmds.length; n++) push(w, cmds[n])
68
+ }
69
+
70
+ /** The Lua type; `args[0]` is the wrapper in a method */
71
+ export const Path = defineType('Path', {
72
+ methods: {
73
+ moveTo: (h, args) => push(h.wrapper, { t: 'moveTo', p: points(args, 1, 1, 'moveTo') }),
74
+ lineTo: (h, args) => push(h.wrapper, { t: 'lineTo', p: points(args, 1, 1, 'lineTo') }),
75
+ quadTo: (h, args) => push(h.wrapper, { t: 'quadTo', p: points(args, 1, 2, 'quadTo') }),
76
+ cubicTo: (h, args) => push(h.wrapper, { t: 'cubicTo', p: points(args, 1, 3, 'cubicTo') }),
77
+ close: (h) => push(h.wrapper, { t: 'close', p: [] }),
78
+ reset: (h) => {
79
+ h.cmds.length = 0
80
+ const w = h.wrapper
81
+ w.arr.length = 0
82
+ },
83
+ add: (h, args) => {
84
+ const other = wrapped(args, 1, Path, 'add')
85
+ const m = args[2] === undefined ? null : wrapped(args, 2, Mat2D, 'add')
86
+ // a path added to itself adds what it held
87
+ const source = other.cmds.slice()
88
+ for (let n = 0; n < source.length; n++) push(h.wrapper, m ? transformCommand(source[n], m) : source[n])
89
+ },
90
+ contours: (h) => measureOf(commandsToContours(h.cmds), true, append),
91
+ measure: (h) => measureOf(commandsToContours(h.cmds), false, append),
92
+ },
93
+ })
94
+
95
+ /**
96
+ * @param {Command} cmd
97
+ * @param {number[]} m
98
+ * @returns {Command}
99
+ */
100
+ function transformCommand(cmd, m) {
101
+ const p = []
102
+ for (let i = 0; i < cmd.p.length; i += 2) {
103
+ const q = apply(m, cmd.p[i], cmd.p[i + 1])
104
+ p.push(q[0], q[1])
105
+ }
106
+ return { t: cmd.t, p }
107
+ }
108
+
109
+ /**
110
+ * A new empty path.
111
+ *
112
+ * @param {Command[]} [cmds] commands to start with
113
+ * @returns {Wrapped}
114
+ */
115
+ export function newPath(cmds) {
116
+ /** @type {PathData} */
117
+ const host = { cmds: [], wrapper: null }
118
+ const w = Path.wrap(host)
119
+ host.wrapper = w
120
+ if (cmds) for (let n = 0; n < cmds.length; n++) push(w, cmds[n])
121
+ return w
122
+ }
123
+
124
+ /**
125
+ * @param {Contour[]} contours Lottie vertex arrays
126
+ * @returns {Wrapped}
127
+ */
128
+ export function pathFromContours(contours) {
129
+ return newPath(contoursToCommands(contours))
130
+ }
131
+
132
+ /**
133
+ * @param {any} v a value a script returned
134
+ * @returns {Command[] | undefined} its commands when it is a path
135
+ */
136
+ export function commandsOf(v) {
137
+ const h = Path.unwrap(v)
138
+ return h === undefined ? undefined : h.cmds
139
+ }
140
+
141
+ /**
142
+ * Installs the `Path` global.
143
+ *
144
+ * @param {import('../../../luau/vm/vm.js').VM} vm
145
+ */
146
+ export function installPath(vm) {
147
+ const table = new LuaTable()
148
+ table.set('new', new NativeFunction('Path.new', function () {
149
+ return newPath()
150
+ }))
151
+ vm.globals.set('Path', table)
152
+ }
153
+
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Rive's `Renderer` as a recorder: the script's `draw(self, renderer)` calls
3
+ * land in a list of drawings, each a path as Lottie vertex arrays under the
4
+ * transform, clips and opacity in force when it was drawn, with a copy of the
5
+ * paint. A player turns the list into shapes (a group with the path, a fill or
6
+ * stroke, and the clips as masks).
7
+ */
8
+
9
+ import { Mat2D, identity, multiply } from '../math/math.mat2d.js'
10
+ import { defineType, wrapped, num } from '../api.type.js'
11
+ import { channels } from '../math/math.color.js'
12
+ import { Paint, snapshotPaint } from './draw.paint.js'
13
+ import { Path } from './draw.path.js'
14
+ import { commandsToContours, transformContours } from './draw.contours.js'
15
+ import { drawImage, drawImageMesh } from '../media/media.draw.js'
16
+
17
+ /** @import { Contour } from './draw.contours.js' */
18
+
19
+ /**
20
+ * Also in the list, from other APIs: `{ kind: 'image' }` (media/media.draw.js), `{ kind: 'text' }` (text/text.js) and `{ kind: 'artboard' }` (artboard/artboard.js).
21
+ *
22
+ * @typedef {object} Drawing
23
+ * @property {'path'} kind
24
+ * @property {Contour[]} path vertex arrays with the transform already applied
25
+ * @property {number[]} transform the matrix that was in force, [xx, xy, yx, yy, tx, ty]
26
+ * @property {Contour[][]} clips the clip paths in force, each transformed
27
+ * @property {number} opacity 0 to 1, from `modulateOpacity`
28
+ * @property {ReturnType<typeof snapshotPaint>} paint
29
+ */
30
+
31
+ /**
32
+ * @typedef {object} RenderState
33
+ * @property {number[]} m
34
+ * @property {Contour[][]} clips
35
+ * @property {number} opacity
36
+ * @property {number} color the colour modulation, ARGB
37
+ */
38
+
39
+ /** What a `draw` call records; the host keeps one and `reset`s it for each frame */
40
+ export class Recorder {
41
+ /** @param {any[]} [into] a list to fill instead of a new one (it is emptied) */
42
+ reset(into) {
43
+ if (into) into.length = 0
44
+ this.drawings = into || []
45
+ this.state.m = identity()
46
+ this.state.clips = []
47
+ this.state.opacity = 1
48
+ this.state.color = 0xffffffff
49
+ this.stack.length = 0
50
+ }
51
+
52
+ constructor() {
53
+ /** @type {any[]} */
54
+ this.drawings = []
55
+ /** @type {RenderState} */
56
+ this.state = { m: identity(), clips: [], opacity: 1, color: 0xffffffff }
57
+ /** @type {RenderState[]} */
58
+ this.stack = []
59
+ }
60
+ }
61
+
62
+ /**
63
+ * @param {number} a ARGB
64
+ * @param {number} b ARGB
65
+ * @returns {number} the channel-wise product
66
+ */
67
+ function modulate(a, b) {
68
+ const p = channels(a)
69
+ const q = channels(b)
70
+ return Math.round((p[3] * q[3]) / 255) * 16777216 + Math.round((p[0] * q[0]) / 255) * 65536 + Math.round((p[1] * q[1]) / 255) * 256 + Math.round((p[2] * q[2]) / 255)
71
+ }
72
+
73
+ export const Renderer = defineType('Renderer', {
74
+ methods: {
75
+ save: (r) => {
76
+ r.stack.push({ m: r.state.m, clips: r.state.clips, opacity: r.state.opacity, color: r.state.color })
77
+ },
78
+ restore: (r) => {
79
+ const s = r.stack.pop()
80
+ if (s) r.state = s
81
+ },
82
+ transform: (r, args) => {
83
+ r.state = { m: multiply(r.state.m, wrapped(args, 1, Mat2D, 'transform')), clips: r.state.clips, opacity: r.state.opacity, color: r.state.color }
84
+ },
85
+ modulateOpacity: (r, args) => {
86
+ r.state = { m: r.state.m, clips: r.state.clips, opacity: r.state.opacity * num(args, 1, 'modulateOpacity'), color: r.state.color }
87
+ },
88
+ modulateColor: (r, args) => {
89
+ r.state = { m: r.state.m, clips: r.state.clips, opacity: r.state.opacity, color: modulate(r.state.color, num(args, 1, 'modulateColor')) }
90
+ },
91
+ setColorModulation: (r, args) => {
92
+ r.state = { m: r.state.m, clips: r.state.clips, opacity: r.state.opacity, color: num(args, 1, 'setColorModulation') }
93
+ },
94
+ clipPath: (r, args) => {
95
+ const path = wrapped(args, 1, Path, 'clipPath')
96
+ r.state = { m: r.state.m, clips: r.state.clips.concat([ commandsToContours(path.cmds, r.state.m) ]), opacity: r.state.opacity, color: r.state.color }
97
+ },
98
+ drawPath: (r, args) => {
99
+ const path = wrapped(args, 1, Path, 'drawPath')
100
+ const paint = wrapped(args, 2, Paint, 'drawPath')
101
+ const s = r.state
102
+ r.drawings.push({
103
+ kind: 'path',
104
+ path: transformContours(commandsToContours(path.cmds), s.m),
105
+ transform: s.m.slice(),
106
+ clips: s.clips,
107
+ opacity: s.opacity,
108
+ paint: snapshotPaint(paint, s.color),
109
+ })
110
+ },
111
+ // images are recorded (media/media.draw.js); text and artboards draw themselves into the same list
112
+ drawImage: (r, args) => drawImage(r, args),
113
+ drawImageMesh: (r, args) => drawImageMesh(r, args),
114
+ // instances carry a MeshInstances table this recorder has no use for
115
+ drawImageMeshInstanced: () => {},
116
+ },
117
+ })
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Rive's `Color`: an ARGB packed into one unsigned number (`0xAARRGGBB`),
3
+ * as a view-model colour property holds it. The constructors keep the low 8
4
+ * bits of each channel.
5
+ */
6
+
7
+ import { LuaTable, NativeFunction } from '../../../luau/vm/vm.value.js'
8
+ import { num, optNum } from '../api.type.js'
9
+
10
+ /**
11
+ * @param {number} r
12
+ * @param {number} g
13
+ * @param {number} b
14
+ * @param {number} a
15
+ * @returns {number}
16
+ */
17
+ export function argb(r, g, b, a) {
18
+ return (a & 255) * 16777216 + ((r & 255) << 16) + ((g & 255) << 8) + (b & 255)
19
+ }
20
+
21
+ /**
22
+ * @param {number} c
23
+ * @returns {number[]} [r, g, b, a], each 0 to 255
24
+ */
25
+ export function channels(c) {
26
+ const n = c % 4294967296
27
+ return [ (n >>> 16) & 255, (n >>> 8) & 255, n & 255, Math.floor(n / 16777216) & 255 ]
28
+ }
29
+
30
+ /**
31
+ * @param {number} c
32
+ * @returns {number[]} [r, g, b, a], each 0 to 1
33
+ */
34
+ export function colorFloats(c) {
35
+ const ch = channels(c)
36
+ return [ ch[0] / 255, ch[1] / 255, ch[2] / 255, ch[3] / 255 ]
37
+ }
38
+
39
+ /**
40
+ * @param {number} index 0 red, 1 green, 2 blue, 3 alpha
41
+ * @returns {(args: any[]) => any}
42
+ */
43
+ function channelFn(index) {
44
+ const name = [ 'red', 'green', 'blue', 'alpha' ][index]
45
+ return function (args) {
46
+ const ch = channels(num(args, 0, name))
47
+ if (args[1] === undefined) return ch[index]
48
+ ch[index] = num(args, 1, name) & 255
49
+ return argb(ch[0], ch[1], ch[2], ch[3])
50
+ }
51
+ }
52
+
53
+ /** @type {Record<string, (args: any[]) => any>} */
54
+ const FNS = {
55
+ rgb: (a) => argb(num(a, 0, 'rgb'), num(a, 1, 'rgb'), num(a, 2, 'rgb'), 255),
56
+ rgba: (a) => argb(num(a, 0, 'rgba'), num(a, 1, 'rgba'), num(a, 2, 'rgba'), num(a, 3, 'rgba')),
57
+ lerp: (a) => {
58
+ const p = channels(num(a, 0, 'lerp'))
59
+ const q = channels(num(a, 1, 'lerp'))
60
+ const t = num(a, 2, 'lerp')
61
+ const m = []
62
+ for (let i = 0; i < 4; i++) m.push(Math.round(p[i] + (q[i] - p[i]) * t))
63
+ return argb(m[0], m[1], m[2], m[3])
64
+ },
65
+ red: channelFn(0),
66
+ green: channelFn(1),
67
+ blue: channelFn(2),
68
+ alpha: channelFn(3),
69
+ opacity: (a) => {
70
+ const ch = channels(num(a, 0, 'opacity'))
71
+ if (a[1] === undefined) return ch[3] / 255
72
+ return argb(ch[0], ch[1], ch[2], Math.round(Math.max(0, Math.min(1, optNum(a, 1, 'opacity', 1))) * 255))
73
+ },
74
+ toFloat: (a) => {
75
+ const t = new LuaTable()
76
+ const f = colorFloats(num(a, 0, 'toFloat'))
77
+ t.setRange(1, f, 0, 4)
78
+ return t
79
+ },
80
+ }
81
+
82
+ /**
83
+ * Installs the `Color` global.
84
+ *
85
+ * @param {import('../../../luau/vm/vm.js').VM} vm
86
+ */
87
+ export function installColor(vm) {
88
+ const color = new LuaTable()
89
+ for (const name in FNS) {
90
+ const fn = FNS[name]
91
+ color.set(name, new NativeFunction('Color.' + name, function (vm2, args) {
92
+ return fn(args)
93
+ }))
94
+ }
95
+ vm.globals.set('Color', color)
96
+ }