@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.
- package/bands/bands.js +3 -1
- package/bundle/bundle.js +56 -0
- package/list/list.js +12 -1
- package/lottie/lottie.layers.js +64 -11
- package/lottie/lottie.paint.js +30 -0
- package/lottie/lottie.paths.js +19 -4
- package/lottie/lottie.props.js +18 -8
- package/lottie/lottie.shapes.js +2 -1
- package/mixer/mixer.js +31 -8
- package/mixer/mixer.mix.js +17 -0
- package/motion/motion.convert.js +32 -1
- package/motion/motion.flow.js +20 -2
- package/motion/motion.machine.js +28 -13
- package/motion/motion.values.js +13 -9
- package/motion.js +196 -20
- package/package.json +10 -16
- package/player/player.js +25 -5
- package/readme.md +78 -3
- package/script/luau/lib/base/base.js +202 -0
- package/script/luau/lib/base/base.table.js +189 -0
- package/script/luau/lib/lib.args.js +152 -0
- package/script/luau/lib/lib.js +23 -0
- package/script/luau/lib/math/math.bit32.js +144 -0
- package/script/luau/lib/math/math.js +288 -0
- package/script/luau/lib/math/math.vector.js +125 -0
- package/script/luau/lib/string/string.format.js +143 -0
- package/script/luau/lib/string/string.js +160 -0
- package/script/luau/lib/string/string.pattern.js +505 -0
- package/script/luau/load/load.js +362 -0
- package/script/luau/load/load.ops.js +79 -0
- package/script/luau/luau.convert.js +259 -0
- package/script/luau/luau.js +132 -0
- package/script/luau/luau.md +111 -0
- package/script/luau/vm/vm.js +167 -0
- package/script/luau/vm/vm.meta.js +401 -0
- package/script/luau/vm/vm.num.js +236 -0
- package/script/luau/vm/vm.run.js +842 -0
- package/script/luau/vm/vm.value.js +406 -0
- package/script/play/play.js +199 -0
- package/script/play/play.objects.js +295 -0
- package/script/play/play.scope.js +91 -0
- package/script/rive/api/api.statics.js +31 -0
- package/script/rive/api/api.type.js +188 -0
- package/script/rive/api/artboard/artboard.js +89 -0
- package/script/rive/api/assets/assets.js +72 -0
- package/script/rive/api/data/data.value.js +99 -0
- package/script/rive/api/draw/draw.contours.js +163 -0
- package/script/rive/api/draw/draw.measure.js +196 -0
- package/script/rive/api/draw/draw.paint.js +235 -0
- package/script/rive/api/draw/draw.path.js +153 -0
- package/script/rive/api/draw/draw.renderer.js +117 -0
- package/script/rive/api/math/math.color.js +96 -0
- package/script/rive/api/math/math.mat2d.js +180 -0
- package/script/rive/api/math/math.vector.js +121 -0
- package/script/rive/api/media/media.audio.js +113 -0
- package/script/rive/api/media/media.draw.js +73 -0
- package/script/rive/api/media/media.image.js +61 -0
- package/script/rive/api/media/media.mesh.js +50 -0
- package/script/rive/api/pointer/pointer.event.js +64 -0
- package/script/rive/api/pointer/pointer.route.js +69 -0
- package/script/rive/api/text/text.font.js +129 -0
- package/script/rive/api/text/text.js +278 -0
- package/script/rive/api/text/text.layout.js +259 -0
- package/script/rive/context/context.js +76 -0
- package/script/rive/context/context.memory.js +196 -0
- package/script/rive/context/context.viewmodel.js +302 -0
- package/script/rive/context/lists/lists.js +160 -0
- package/script/rive/context/lists/lists.values.js +281 -0
- package/script/rive/host/host.globals.js +44 -0
- package/script/rive/host/host.js +533 -0
- package/script/rive/host/host.node.js +45 -0
- package/script/rive/rive.js +23 -0
- package/script/rive/rive.md +180 -0
- package/script/script.js +11 -0
- package/skin/skin.deform.js +514 -0
- package/skin/skin.ik.js +260 -0
- package/skin/skin.js +297 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pointer events into scripted nodes. `routePointer` takes one pointer event
|
|
3
|
+
* in the player's space and offers it to the nodes that listen, top-most
|
|
4
|
+
* first, until one claims it with `event:hit()`; a node that is not hit but
|
|
5
|
+
* was under the pointer before gets a `pointerExit`.
|
|
6
|
+
*
|
|
7
|
+
* ```js
|
|
8
|
+
* const nodes = [ { host, world: [1, 0, 0, 1, 40, 20], contains: (x, y) => hits.test('7', x, y) } ]
|
|
9
|
+
* routePointer(nodes, 'down', x, y, id, seconds)
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* The player's pointer module (`pointer/`) calls this where it hit-tests its
|
|
13
|
+
* listeners; `contains` is its hit test of the node's shape.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {object} PointerNode
|
|
18
|
+
* @property {{ pointer: (type: string, x: number, y: number, id?: number, time?: number) => { hit: boolean, translucent: boolean } }} host the script's host
|
|
19
|
+
* @property {number[]} [world] the node's world matrix [xx, xy, yx, yy, tx, ty]; identity when absent
|
|
20
|
+
* @property {(x: number, y: number) => boolean} [contains] whether the point is on the node (in the player's space); absent counts as always
|
|
21
|
+
* @property {boolean} [inside] kept by the router
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {number[] | undefined} m
|
|
26
|
+
* @param {number} x
|
|
27
|
+
* @param {number} y
|
|
28
|
+
* @returns {number[]} [x, y] in the node's space
|
|
29
|
+
*/
|
|
30
|
+
function local(m, x, y) {
|
|
31
|
+
if (!m) return [ x, y ]
|
|
32
|
+
const det = m[0] * m[3] - m[1] * m[2]
|
|
33
|
+
if (det === 0) return [ x, y ]
|
|
34
|
+
const dx = x - m[4]
|
|
35
|
+
const dy = y - m[5]
|
|
36
|
+
return [ (m[3] * dx - m[2] * dy) / det, (m[0] * dy - m[1] * dx) / det ]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {PointerNode[]} nodes in draw order, top-most last
|
|
41
|
+
* @param {'down' | 'move' | 'up' | 'exit'} type
|
|
42
|
+
* @param {number} x
|
|
43
|
+
* @param {number} y
|
|
44
|
+
* @param {number} [id]
|
|
45
|
+
* @param {number} [time]
|
|
46
|
+
* @returns {boolean} whether a node claimed the event
|
|
47
|
+
*/
|
|
48
|
+
export function routePointer(nodes, type, x, y, id, time) {
|
|
49
|
+
let claimed = false
|
|
50
|
+
for (let n = nodes.length - 1; n >= 0; n--) {
|
|
51
|
+
const node = nodes[n]
|
|
52
|
+
const on = type !== 'exit' && !claimed && (!node.contains || node.contains(x, y))
|
|
53
|
+
if (!on) {
|
|
54
|
+
if (node.inside) {
|
|
55
|
+
node.inside = false
|
|
56
|
+
const p = local(node.world, x, y)
|
|
57
|
+
node.host.pointer('exit', p[0], p[1], id, time)
|
|
58
|
+
}
|
|
59
|
+
continue
|
|
60
|
+
}
|
|
61
|
+
const p = local(node.world, x, y)
|
|
62
|
+
const result = node.host.pointer(type, p[0], p[1], id, time)
|
|
63
|
+
node.inside = type !== 'up' || node.inside
|
|
64
|
+
if (result.hit) {
|
|
65
|
+
if (!result.translucent) claimed = true
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return claimed
|
|
69
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Font`: a font a script measures and draws with. The host holds no font
|
|
3
|
+
* data: the player gives it a `FontInfo`, the metrics and advances of a font
|
|
4
|
+
* its bundle carries (from `bundle/bundle.fonts.js`' registered face measured
|
|
5
|
+
* on a canvas, or from opentype.js at conversion), and the host lays text out
|
|
6
|
+
* with them. Without one, text is laid out with a fixed fallback.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { LuaTable } from '../../../luau/vm/vm.value.js'
|
|
10
|
+
import { decodeUtf8 } from '../../../luau/luau.convert.js'
|
|
11
|
+
import { defineType, num, text } from '../api.type.js'
|
|
12
|
+
import { statics } from '../api.statics.js'
|
|
13
|
+
import { newPath } from '../draw/draw.path.js'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {object} FontInfo
|
|
17
|
+
* @property {string} name
|
|
18
|
+
* @property {number} [ascent] per 1 point of size, negative up (default -0.8)
|
|
19
|
+
* @property {number} [descent] positive down (default 0.2)
|
|
20
|
+
* @property {number} [capHeight] negative up
|
|
21
|
+
* @property {number} [xHeight] negative up
|
|
22
|
+
* @property {number} [weight] 1 to 1000, 400 regular
|
|
23
|
+
* @property {boolean} [isItalic]
|
|
24
|
+
* @property {(codepoint: number) => number} [advance] the advance of a character in em (default 0.5)
|
|
25
|
+
* @property {(codepoint: number) => boolean} [hasGlyph]
|
|
26
|
+
* @property {(codepoint: number) => number} [glyphId] default the codepoint itself
|
|
27
|
+
* @property {(glyphId: number) => any[]} [glyphPath] the outline as Path commands `{ t, p }` in 1 point units
|
|
28
|
+
* @property {Record<string, number>} [axes]
|
|
29
|
+
* @property {string[]} [features]
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {FontInfo} info
|
|
34
|
+
* @returns {Required<Pick<FontInfo, 'name' | 'ascent' | 'descent' | 'capHeight' | 'xHeight' | 'weight' | 'isItalic'>> & FontInfo}
|
|
35
|
+
*/
|
|
36
|
+
export function completeFont(info) {
|
|
37
|
+
return /** @type {any} */ ({
|
|
38
|
+
ascent: -0.8,
|
|
39
|
+
descent: 0.2,
|
|
40
|
+
capHeight: -0.7,
|
|
41
|
+
xHeight: -0.5,
|
|
42
|
+
weight: 400,
|
|
43
|
+
isItalic: false,
|
|
44
|
+
...info,
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* A `FontInfo` whose advances are measured by a function of one character,
|
|
50
|
+
* such as `(c) => ctx.measureText(c).width / ctx.font size`.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} name
|
|
53
|
+
* @param {(character: string) => number} measure the character's advance in em
|
|
54
|
+
* @param {Partial<FontInfo>} [metrics]
|
|
55
|
+
* @returns {FontInfo}
|
|
56
|
+
*/
|
|
57
|
+
export function measuredFont(name, measure, metrics) {
|
|
58
|
+
return { name, ...metrics, advance: (cp) => measure(String.fromCharCode(cp)) }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const Font = defineType('Font', {
|
|
62
|
+
fields: {
|
|
63
|
+
ascent: { get: (f) => f.ascent },
|
|
64
|
+
descent: { get: (f) => f.descent },
|
|
65
|
+
capHeight: { get: (f) => f.capHeight },
|
|
66
|
+
xHeight: { get: (f) => f.xHeight },
|
|
67
|
+
weight: { get: (f) => f.weight },
|
|
68
|
+
isItalic: { get: (f) => f.isItalic },
|
|
69
|
+
axes: { get: (f) => tableOf(f.axes ? Object.keys(f.axes) : []) },
|
|
70
|
+
features: { get: (f) => tableOf(f.features || []) },
|
|
71
|
+
},
|
|
72
|
+
methods: {
|
|
73
|
+
lineHeight: (f, args) => (f.descent - f.ascent) * num(args, 1, 'lineHeight'),
|
|
74
|
+
hasGlyph: (f, args) => (f.hasGlyph ? f.hasGlyph(num(args, 1, 'hasGlyph')) : true),
|
|
75
|
+
axisValue: (f, args) => (f.axes ? f.axes[text(args, 1, 'axisValue')] || 0 : 0),
|
|
76
|
+
withOptions: (f, args) => {
|
|
77
|
+
const axes = args[1] instanceof LuaTable ? merged(f.axes, plain(args[1])) : f.axes
|
|
78
|
+
const features = args[2] instanceof LuaTable ? Object.keys(plain(args[2])) : f.features
|
|
79
|
+
return Font.wrap({ ...f, axes, features })
|
|
80
|
+
},
|
|
81
|
+
glyphPath: (f, args) => newPath(f.glyphPath ? f.glyphPath(num(args, 1, 'glyphPath')) : []),
|
|
82
|
+
},
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @param {Record<string, number> | undefined} base
|
|
87
|
+
* @param {Record<string, number>} over
|
|
88
|
+
* @returns {Record<string, number>}
|
|
89
|
+
*/
|
|
90
|
+
function merged(base, over) {
|
|
91
|
+
/** @type {Record<string, number>} */
|
|
92
|
+
const out = {}
|
|
93
|
+
for (const k in base) out[k] = base[k]
|
|
94
|
+
for (const k in over) out[k] = over[k]
|
|
95
|
+
return out
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @param {string[]} names
|
|
100
|
+
* @returns {LuaTable}
|
|
101
|
+
*/
|
|
102
|
+
function tableOf(names) {
|
|
103
|
+
const t = new LuaTable()
|
|
104
|
+
t.setRange(1, names, 0, names.length)
|
|
105
|
+
return t
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @param {LuaTable} t
|
|
110
|
+
* @returns {Record<string, number>} its string keys with number values
|
|
111
|
+
*/
|
|
112
|
+
function plain(t) {
|
|
113
|
+
/** @type {Record<string, number>} */
|
|
114
|
+
const out = {}
|
|
115
|
+
for (let at = t.advance(0); at !== null; at = t.advance(at)) {
|
|
116
|
+
if (typeof t.ck === 'string' && typeof t.cv === 'number') out[decodeUtf8(t.ck)] = t.cv
|
|
117
|
+
}
|
|
118
|
+
return out
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Installs the `Font` global.
|
|
123
|
+
*
|
|
124
|
+
* @param {import('../../../luau/vm/vm.js').VM} vm
|
|
125
|
+
*/
|
|
126
|
+
export function installFont(vm) {
|
|
127
|
+
// there is no decoder here: a font a script brings as bytes is nil, as for bytes that are not a font
|
|
128
|
+
vm.globals.set('Font', statics('Font', { decode: () => undefined }))
|
|
129
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Text`: styled runs a script lays out and draws. `Text.new()`, `append`,
|
|
3
|
+
* `clear`, `draw`, `bounds`, `line`, `lines`, `hitTest`, `caret`,
|
|
4
|
+
* `selectionRects`, and the layout fields (`sizing`, `overflow`, `align`,
|
|
5
|
+
* `wrap`, `wordBreak`, `origin`, `direction`, `maxWidth`, `maxHeight`,
|
|
6
|
+
* `paragraphSpacing`). Layout is `text.layout.js`, with the metrics of the
|
|
7
|
+
* fonts the player gave the host; the draw is recorded as
|
|
8
|
+
*
|
|
9
|
+
* `{ kind: 'text', runs, lines, text, transform, clips, opacity, color, paint, ...layout }`
|
|
10
|
+
*
|
|
11
|
+
* where each run is `{ text, font, size, lineHeight, letterSpacing, paint, foregroundColor }`
|
|
12
|
+
* (`font` an asset name, `paint` a snapshot or null) and each line
|
|
13
|
+
* `{ x, top, baseline, bottom, width, firstIndex, lastIndex, text }`. A run
|
|
14
|
+
* with no paint draws only when `Text:draw` is given one.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { LuaTable, Vector } from '../../../luau/vm/vm.value.js'
|
|
18
|
+
import { decodeUtf8 } from '../../../luau/luau.convert.js'
|
|
19
|
+
import { badArg, defineType, num, text, wrapped } from '../api.type.js'
|
|
20
|
+
import { statics } from '../api.statics.js'
|
|
21
|
+
import { Mat2D } from '../math/math.mat2d.js'
|
|
22
|
+
import { Paint, snapshotPaint } from '../draw/draw.paint.js'
|
|
23
|
+
import { newPath } from '../draw/draw.path.js'
|
|
24
|
+
import { Renderer } from '../draw/draw.renderer.js'
|
|
25
|
+
import { stamp } from '../media/media.draw.js'
|
|
26
|
+
import { Font } from './text.font.js'
|
|
27
|
+
import { layoutText } from './text.layout.js'
|
|
28
|
+
|
|
29
|
+
/** @import { Layout, LayoutOptions, Run } from './text.layout.js' */
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {object} TextData
|
|
33
|
+
* @property {{ run: Run, paint: any, foreground: number }[]} runs
|
|
34
|
+
* @property {LayoutOptions} o
|
|
35
|
+
* @property {Layout | null} laid
|
|
36
|
+
* @property {() => any} fallback the font for a run with none
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/** @type {Record<string, string>} the fields that hold a name */
|
|
40
|
+
const NAMES = { sizing: 'autoWidth', overflow: 'visible', align: 'left', wrap: 'wrap', wordBreak: 'normal', origin: 'top', direction: 'ltr' }
|
|
41
|
+
/** @type {string[]} */
|
|
42
|
+
const NUMBERS = [ 'maxWidth', 'maxHeight', 'paragraphSpacing' ]
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {TextData} h
|
|
46
|
+
* @returns {Layout}
|
|
47
|
+
*/
|
|
48
|
+
function laid(h) {
|
|
49
|
+
if (!h.laid) {
|
|
50
|
+
const runs = []
|
|
51
|
+
for (let n = 0; n < h.runs.length; n++) runs.push(h.runs[n].run)
|
|
52
|
+
h.laid = layoutText(runs, h.o)
|
|
53
|
+
}
|
|
54
|
+
return h.laid
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** @type {Record<string, { get: (h: any) => any, set: (h: any, v: any) => void }>} */
|
|
58
|
+
const fields = {
|
|
59
|
+
isEmpty: { get: (h) => laid(h).length === 0, set: () => {} },
|
|
60
|
+
lineCount: { get: (h) => laid(h).lines.length, set: () => {} },
|
|
61
|
+
length: { get: (h) => laid(h).length, set: () => {} },
|
|
62
|
+
}
|
|
63
|
+
for (const name in NAMES) {
|
|
64
|
+
fields[name] = {
|
|
65
|
+
get: (h) => h.o[name],
|
|
66
|
+
set: (h, v) => {
|
|
67
|
+
if (typeof v !== 'string') throw badArg(0, name, 'string', [ v ])
|
|
68
|
+
h.o[name] = decodeUtf8(v)
|
|
69
|
+
h.laid = null
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
for (let n = 0; n < NUMBERS.length; n++) {
|
|
74
|
+
const name = NUMBERS[n]
|
|
75
|
+
fields[name] = {
|
|
76
|
+
get: (h) => h.o[name],
|
|
77
|
+
set: (h, v) => {
|
|
78
|
+
if (typeof v !== 'number') throw badArg(0, name, 'number', [ v ])
|
|
79
|
+
h.o[name] = v
|
|
80
|
+
h.laid = null
|
|
81
|
+
},
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @param {any} style a Lua table
|
|
87
|
+
* @param {TextData} h
|
|
88
|
+
* @returns {{ run: Run, paint: any, foreground: number }}
|
|
89
|
+
*/
|
|
90
|
+
function runOf(style, h) {
|
|
91
|
+
const t = style instanceof LuaTable ? style : new LuaTable()
|
|
92
|
+
const f = t.getStr('font')
|
|
93
|
+
const size = t.getStr('size')
|
|
94
|
+
const lineHeight = t.getStr('lineHeight')
|
|
95
|
+
const letterSpacing = t.getStr('letterSpacing')
|
|
96
|
+
const fg = t.getStr('foregroundColor')
|
|
97
|
+
return {
|
|
98
|
+
run: {
|
|
99
|
+
text: '',
|
|
100
|
+
font: Font.is(f) ? Font.unwrap(f) : h.fallback(),
|
|
101
|
+
size: typeof size === 'number' ? size : 16,
|
|
102
|
+
lineHeight: typeof lineHeight === 'number' ? lineHeight : 0,
|
|
103
|
+
letterSpacing: typeof letterSpacing === 'number' ? letterSpacing : 0,
|
|
104
|
+
},
|
|
105
|
+
paint: Paint.unwrap(t.getStr('paint')) || null,
|
|
106
|
+
foreground: typeof fg === 'number' ? fg : 0xff000000,
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* @param {TextData} h
|
|
112
|
+
* @param {number} index 1-based codepoint index
|
|
113
|
+
* @returns {{ line: import('./text.layout.js').Line, x: number } | null}
|
|
114
|
+
*/
|
|
115
|
+
function locate(h, index) {
|
|
116
|
+
const lines = laid(h).lines
|
|
117
|
+
for (let n = 0; n < lines.length; n++) {
|
|
118
|
+
const l = lines[n]
|
|
119
|
+
if (index >= l.firstIndex && index <= l.lastIndex + 1) {
|
|
120
|
+
for (let k = 0; k < l.glyphs.length; k++) if (l.glyphs[k].textIndex === index) return { line: l, x: l.glyphs[k].x }
|
|
121
|
+
return { line: l, x: l.x + l.width }
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return null
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export const Glyph = defineType('Glyph', {
|
|
128
|
+
fields: {
|
|
129
|
+
id: { get: (g) => g.id },
|
|
130
|
+
font: { get: (g) => Font.wrap(g.run.font) },
|
|
131
|
+
size: { get: (g) => g.run.size },
|
|
132
|
+
x: { get: (g) => g.x },
|
|
133
|
+
y: { get: (g) => g.y },
|
|
134
|
+
advance: { get: (g) => g.advance },
|
|
135
|
+
textIndex: { get: (g) => g.textIndex },
|
|
136
|
+
direction: { get: () => 'ltr' },
|
|
137
|
+
isColor: { get: () => false },
|
|
138
|
+
transform: { get: (g) => Mat2D.wrap([ g.run.size, 0, 0, g.run.size, g.x, g.y ]) },
|
|
139
|
+
},
|
|
140
|
+
methods: {
|
|
141
|
+
path: (g) => {
|
|
142
|
+
const f = g.run.font
|
|
143
|
+
const cmds = f.glyphPath ? f.glyphPath(g.id) : []
|
|
144
|
+
return newPath(cmds.map((/** @type {any} */ c) => ({ t: c.t, p: c.p.map((/** @type {number} */ v, /** @type {number} */ i) => v * g.run.size + (i % 2 ? g.y : g.x)) })))
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
export const TextLine = defineType('TextLine', {
|
|
150
|
+
fields: {
|
|
151
|
+
index: { get: (l) => l.index },
|
|
152
|
+
x: { get: (l) => l.x },
|
|
153
|
+
top: { get: (l) => l.top },
|
|
154
|
+
baseline: { get: (l) => l.baseline },
|
|
155
|
+
bottom: { get: (l) => l.bottom },
|
|
156
|
+
width: { get: (l) => l.width },
|
|
157
|
+
direction: { get: () => 'ltr' },
|
|
158
|
+
firstIndex: { get: (l) => l.firstIndex },
|
|
159
|
+
lastIndex: { get: (l) => l.lastIndex },
|
|
160
|
+
glyphCount: { get: (l) => l.glyphs.length },
|
|
161
|
+
},
|
|
162
|
+
methods: {
|
|
163
|
+
glyph: (l, args) => {
|
|
164
|
+
const g = l.glyphs[num(args, 1, 'glyph') - 1]
|
|
165
|
+
return g ? Glyph.wrap(g) : undefined
|
|
166
|
+
},
|
|
167
|
+
glyphs: (l) => list(l.glyphs.map((/** @type {any} */ g) => Glyph.wrap(g))),
|
|
168
|
+
},
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* @param {any[]} items
|
|
173
|
+
* @returns {LuaTable}
|
|
174
|
+
*/
|
|
175
|
+
function list(items) {
|
|
176
|
+
const t = new LuaTable()
|
|
177
|
+
t.setRange(1, items, 0, items.length)
|
|
178
|
+
return t
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export const Text = defineType('Text', {
|
|
182
|
+
fields,
|
|
183
|
+
methods: {
|
|
184
|
+
append: (h, args) => {
|
|
185
|
+
const r = runOf(args[2], h)
|
|
186
|
+
r.run.text = text(args, 1, 'append')
|
|
187
|
+
h.runs.push(r)
|
|
188
|
+
h.laid = null
|
|
189
|
+
},
|
|
190
|
+
clear: (h) => {
|
|
191
|
+
h.runs.length = 0
|
|
192
|
+
h.laid = null
|
|
193
|
+
},
|
|
194
|
+
draw: (h, args) => {
|
|
195
|
+
const rec = wrapped(args, 1, Renderer, 'draw')
|
|
196
|
+
const given = args[2] === undefined ? null : wrapped(args, 2, Paint, 'draw')
|
|
197
|
+
const l = laid(h)
|
|
198
|
+
const out = /** @type {any} */ (stamp(rec, 'text'))
|
|
199
|
+
const color = rec.state.color
|
|
200
|
+
out.runs = h.runs.map((/** @type {any} */ r) => {
|
|
201
|
+
const p = given || r.paint
|
|
202
|
+
return {
|
|
203
|
+
text: r.run.text,
|
|
204
|
+
font: r.run.font.name,
|
|
205
|
+
size: r.run.size,
|
|
206
|
+
lineHeight: r.run.lineHeight,
|
|
207
|
+
letterSpacing: r.run.letterSpacing,
|
|
208
|
+
paint: p ? snapshotPaint(p, color) : null,
|
|
209
|
+
foregroundColor: r.foreground,
|
|
210
|
+
}
|
|
211
|
+
})
|
|
212
|
+
if (!out.runs.some((/** @type {any} */ r) => r.paint)) return
|
|
213
|
+
out.paint = given ? snapshotPaint(given, color) : null
|
|
214
|
+
out.lines = l.lines.map((n) => ({ x: n.x, top: n.top, baseline: n.baseline, bottom: n.bottom, width: n.width, firstIndex: n.firstIndex, lastIndex: n.lastIndex, text: n.text }))
|
|
215
|
+
out.text = out.lines.map((/** @type {any} */ n) => n.text).join('\n')
|
|
216
|
+
out.width = l.width
|
|
217
|
+
out.height = l.height
|
|
218
|
+
for (const k in h.o) out[k] = h.o[/** @type {keyof LayoutOptions} */ (k)]
|
|
219
|
+
rec.drawings.push(out)
|
|
220
|
+
},
|
|
221
|
+
bounds: (h) => {
|
|
222
|
+
const l = laid(h)
|
|
223
|
+
const top = l.lines.length ? l.lines[0].top : 0
|
|
224
|
+
return [ new Vector(0, top, 0), new Vector(l.width, top + l.height, 0) ]
|
|
225
|
+
},
|
|
226
|
+
line: (h, args) => {
|
|
227
|
+
const l = laid(h).lines[num(args, 1, 'line') - 1]
|
|
228
|
+
return l ? TextLine.wrap(l) : undefined
|
|
229
|
+
},
|
|
230
|
+
lines: (h) => list(laid(h).lines.map((l) => TextLine.wrap(l))),
|
|
231
|
+
hitTest: (h, args) => {
|
|
232
|
+
const p = args[1]
|
|
233
|
+
if (!(p instanceof Vector)) throw badArg(1, 'hitTest', 'Vector', args)
|
|
234
|
+
const lines = laid(h).lines
|
|
235
|
+
let line = lines[0]
|
|
236
|
+
for (let n = 0; n < lines.length; n++) if (p.y >= lines[n].top) line = lines[n]
|
|
237
|
+
if (!line) return 1
|
|
238
|
+
for (let k = 0; k < line.glyphs.length; k++) {
|
|
239
|
+
const g = line.glyphs[k]
|
|
240
|
+
if (p.x < g.x + g.advance / 2) return g.textIndex
|
|
241
|
+
}
|
|
242
|
+
return line.lastIndex + 1
|
|
243
|
+
},
|
|
244
|
+
caret: (h, args) => {
|
|
245
|
+
const at = locate(h, num(args, 1, 'caret'))
|
|
246
|
+
return at ? [ at.x, at.line.top, at.line.bottom ] : undefined
|
|
247
|
+
},
|
|
248
|
+
selectionRects: (h, args) => {
|
|
249
|
+
const from = num(args, 1, 'selectionRects')
|
|
250
|
+
const to = num(args, 2, 'selectionRects')
|
|
251
|
+
const out = []
|
|
252
|
+
const lines = laid(h).lines
|
|
253
|
+
for (let n = 0; n < lines.length; n++) {
|
|
254
|
+
for (let k = 0; k < lines[n].glyphs.length; k++) {
|
|
255
|
+
const g = lines[n].glyphs[k]
|
|
256
|
+
if (g.textIndex < from || g.textIndex >= to) continue
|
|
257
|
+
const r = new LuaTable()
|
|
258
|
+
r.set('min', new Vector(g.x, lines[n].top, 0))
|
|
259
|
+
r.set('max', new Vector(g.x + g.advance, lines[n].bottom, 0))
|
|
260
|
+
out.push(r)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return list(out)
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Installs the `Text` global. A run with no font uses `fallback()`'s.
|
|
270
|
+
*
|
|
271
|
+
* @param {import('../../../luau/vm/vm.js').VM} vm
|
|
272
|
+
* @param {() => any} fallback a completed FontInfo
|
|
273
|
+
*/
|
|
274
|
+
export function installText(vm, fallback) {
|
|
275
|
+
vm.globals.set('Text', statics('Text', {
|
|
276
|
+
new: () => Text.wrap({ runs: [], o: { ...NAMES, maxWidth: 0, maxHeight: 0, paragraphSpacing: 0 }, laid: null, fallback }),
|
|
277
|
+
}))
|
|
278
|
+
}
|