kaplay 3000.1.17
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/CHANGELOG.md +1050 -0
- package/LICENSE +19 -0
- package/README.md +209 -0
- package/dist/global.d.ts +237 -0
- package/dist/global.js +0 -0
- package/dist/kaboom.cjs +56 -0
- package/dist/kaboom.cjs.map +7 -0
- package/dist/kaboom.d.ts +5364 -0
- package/dist/kaboom.js +57 -0
- package/dist/kaboom.js.map +7 -0
- package/dist/kaboom.mjs +56 -0
- package/dist/kaboom.mjs.map +7 -0
- package/package.json +62 -0
- package/src/app.ts +906 -0
- package/src/assets/bean.png +0 -0
- package/src/assets/boom.png +0 -0
- package/src/assets/burp.mp3 +0 -0
- package/src/assets/index.d.ts +9 -0
- package/src/assets/ka.png +0 -0
- package/src/assets.ts +131 -0
- package/src/easings.ts +94 -0
- package/src/gamepad.json +111 -0
- package/src/gfx.ts +524 -0
- package/src/kaboom.ts +6539 -0
- package/src/math.ts +1118 -0
- package/src/texPacker.ts +73 -0
- package/src/types.ts +5364 -0
- package/src/utils.ts +525 -0
package/src/utils.ts
ADDED
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
export class Registry<T> extends Map<number, T> {
|
|
2
|
+
private lastID: number
|
|
3
|
+
constructor(...args) {
|
|
4
|
+
super(...args)
|
|
5
|
+
this.lastID = 0
|
|
6
|
+
}
|
|
7
|
+
push(v: T): number {
|
|
8
|
+
const id = this.lastID
|
|
9
|
+
this.set(id, v)
|
|
10
|
+
this.lastID++
|
|
11
|
+
return id
|
|
12
|
+
}
|
|
13
|
+
pushd(v: T): () => void {
|
|
14
|
+
const id = this.push(v)
|
|
15
|
+
return () => this.delete(id)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class EventController {
|
|
20
|
+
paused: boolean = false
|
|
21
|
+
readonly cancel: () => void
|
|
22
|
+
constructor(cancel: () => void) {
|
|
23
|
+
this.cancel = cancel
|
|
24
|
+
}
|
|
25
|
+
static join(events: EventController[]): EventController {
|
|
26
|
+
const ev = new EventController(() => events.forEach((e) => e.cancel()))
|
|
27
|
+
Object.defineProperty(ev, "paused", {
|
|
28
|
+
get: () => events[0].paused,
|
|
29
|
+
set: (p: boolean) => events.forEach((e) => e.paused = p),
|
|
30
|
+
})
|
|
31
|
+
ev.paused = false
|
|
32
|
+
return ev
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class Event<Args extends any[] = any[]> {
|
|
37
|
+
private handlers: Registry<(...args: Args) => void> = new Registry()
|
|
38
|
+
add(action: (...args: Args) => void): EventController {
|
|
39
|
+
const cancel = this.handlers.pushd((...args: Args) => {
|
|
40
|
+
if (ev.paused) return
|
|
41
|
+
action(...args)
|
|
42
|
+
})
|
|
43
|
+
const ev = new EventController(cancel)
|
|
44
|
+
return ev
|
|
45
|
+
}
|
|
46
|
+
addOnce(action: (...args) => void): EventController {
|
|
47
|
+
const ev = this.add((...args) => {
|
|
48
|
+
ev.cancel()
|
|
49
|
+
action(...args)
|
|
50
|
+
})
|
|
51
|
+
return ev
|
|
52
|
+
}
|
|
53
|
+
next(): Promise<Args> {
|
|
54
|
+
return new Promise((res) => this.addOnce(res))
|
|
55
|
+
}
|
|
56
|
+
trigger(...args: Args) {
|
|
57
|
+
this.handlers.forEach((action) => action(...args))
|
|
58
|
+
}
|
|
59
|
+
numListeners(): number {
|
|
60
|
+
return this.handlers.size
|
|
61
|
+
}
|
|
62
|
+
clear() {
|
|
63
|
+
this.handlers.clear()
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// TODO: only accept one argument?
|
|
68
|
+
export class EventHandler<EventMap extends Record<string, any[]>> {
|
|
69
|
+
private handlers: Partial<{
|
|
70
|
+
[Name in keyof EventMap]: Event<EventMap[Name]>
|
|
71
|
+
}> = {}
|
|
72
|
+
on<Name extends keyof EventMap>(
|
|
73
|
+
name: Name,
|
|
74
|
+
action: (...args: EventMap[Name]) => void,
|
|
75
|
+
): EventController {
|
|
76
|
+
if (!this.handlers[name]) {
|
|
77
|
+
this.handlers[name] = new Event<EventMap[Name]>()
|
|
78
|
+
}
|
|
79
|
+
return this.handlers[name].add(action)
|
|
80
|
+
}
|
|
81
|
+
onOnce<Name extends keyof EventMap>(
|
|
82
|
+
name: Name,
|
|
83
|
+
action: (...args: EventMap[Name]) => void,
|
|
84
|
+
): EventController {
|
|
85
|
+
const ev = this.on(name, (...args) => {
|
|
86
|
+
ev.cancel()
|
|
87
|
+
action(...args)
|
|
88
|
+
})
|
|
89
|
+
return ev
|
|
90
|
+
}
|
|
91
|
+
next<Name extends keyof EventMap>(name: Name): Promise<unknown> {
|
|
92
|
+
return new Promise((res) => {
|
|
93
|
+
// TODO: can only pass 1 val to resolve()
|
|
94
|
+
this.onOnce(name, (...args: EventMap[Name]) => res(args[0]))
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
trigger<Name extends keyof EventMap>(name: Name, ...args: EventMap[Name]) {
|
|
98
|
+
if (this.handlers[name]) {
|
|
99
|
+
this.handlers[name].trigger(...args)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
remove<Name extends keyof EventMap>(name: Name) {
|
|
103
|
+
delete this.handlers[name]
|
|
104
|
+
}
|
|
105
|
+
clear() {
|
|
106
|
+
this.handlers = {}
|
|
107
|
+
}
|
|
108
|
+
numListeners<Name extends keyof EventMap>(name: Name): number {
|
|
109
|
+
return this.handlers[name]?.numListeners() ?? 0
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function deepEq(o1: any, o2: any): boolean {
|
|
114
|
+
if (o1 === o2) {
|
|
115
|
+
return true
|
|
116
|
+
}
|
|
117
|
+
const t1 = typeof o1
|
|
118
|
+
const t2 = typeof o2
|
|
119
|
+
if (t1 !== t2) {
|
|
120
|
+
return false
|
|
121
|
+
}
|
|
122
|
+
if (t1 === "object" && t2 === "object" && o1 !== null && o2 !== null) {
|
|
123
|
+
if (Array.isArray(o1) !== Array.isArray(o2)) {
|
|
124
|
+
return false
|
|
125
|
+
}
|
|
126
|
+
const k1 = Object.keys(o1)
|
|
127
|
+
const k2 = Object.keys(o2)
|
|
128
|
+
if (k1.length !== k2.length) {
|
|
129
|
+
return false
|
|
130
|
+
}
|
|
131
|
+
for (const k of k1) {
|
|
132
|
+
const v1 = o1[k]
|
|
133
|
+
const v2 = o2[k]
|
|
134
|
+
if (!deepEq(v1, v2)) {
|
|
135
|
+
return false
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return true
|
|
139
|
+
}
|
|
140
|
+
return false
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function base64ToArrayBuffer(base64: string): ArrayBuffer {
|
|
144
|
+
const binstr = window.atob(base64)
|
|
145
|
+
const len = binstr.length
|
|
146
|
+
const bytes = new Uint8Array(len)
|
|
147
|
+
for (let i = 0; i < len; i++) {
|
|
148
|
+
bytes[i] = binstr.charCodeAt(i)
|
|
149
|
+
}
|
|
150
|
+
return bytes.buffer
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function dataURLToArrayBuffer(url: string): ArrayBuffer {
|
|
154
|
+
return base64ToArrayBuffer(url.split(",")[1])
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function download(filename: string, url: string) {
|
|
158
|
+
const a = document.createElement("a")
|
|
159
|
+
a.href = url
|
|
160
|
+
a.download = filename
|
|
161
|
+
a.click()
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function downloadText(filename: string, text: string) {
|
|
165
|
+
download(filename, "data:text/plain;charset=utf-8," + text)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function downloadJSON(filename: string, data: any) {
|
|
169
|
+
downloadText(filename, JSON.stringify(data))
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function downloadBlob(filename: string, blob: Blob) {
|
|
173
|
+
const url = URL.createObjectURL(blob)
|
|
174
|
+
download(filename, url)
|
|
175
|
+
URL.revokeObjectURL(url)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export const isDataURL = (str: string) => str.match(/^data:\w+\/\w+;base64,.+/)
|
|
179
|
+
export const getFileExt = (p: string) => p.split(".").pop()
|
|
180
|
+
export const getFileName = (p: string) => p.split(".").slice(0, -1).join(".")
|
|
181
|
+
|
|
182
|
+
type Func = (...args: any[]) => any
|
|
183
|
+
|
|
184
|
+
export function overload2<A extends Func, B extends Func>(fn1: A, fn2: B): A & B {
|
|
185
|
+
return ((...args) => {
|
|
186
|
+
const al = args.length
|
|
187
|
+
if (al === fn1.length) return fn1(...args)
|
|
188
|
+
if (al === fn2.length) return fn2(...args)
|
|
189
|
+
}) as A & B
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function overload3<
|
|
193
|
+
A extends Func,
|
|
194
|
+
B extends Func,
|
|
195
|
+
C extends Func,
|
|
196
|
+
>(fn1: A, fn2: B, fn3: C): A & B & C {
|
|
197
|
+
return ((...args) => {
|
|
198
|
+
const al = args.length
|
|
199
|
+
if (al === fn1.length) return fn1(...args)
|
|
200
|
+
if (al === fn2.length) return fn2(...args)
|
|
201
|
+
if (al === fn3.length) return fn3(...args)
|
|
202
|
+
}) as A & B & C
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function overload4<
|
|
206
|
+
A extends Func,
|
|
207
|
+
B extends Func,
|
|
208
|
+
C extends Func,
|
|
209
|
+
D extends Func,
|
|
210
|
+
>(fn1: A, fn2: B, fn3: C, fn4: D): A & B & C & D {
|
|
211
|
+
return ((...args) => {
|
|
212
|
+
const al = args.length
|
|
213
|
+
if (al === fn1.length) return fn1(...args)
|
|
214
|
+
if (al === fn2.length) return fn2(...args)
|
|
215
|
+
if (al === fn3.length) return fn3(...args)
|
|
216
|
+
if (al === fn4.length) return fn4(...args)
|
|
217
|
+
}) as A & B & C & D
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export const uid = (() => {
|
|
221
|
+
let id = 0
|
|
222
|
+
return () => id++
|
|
223
|
+
})()
|
|
224
|
+
|
|
225
|
+
export const getErrorMessage = (error: unknown) =>
|
|
226
|
+
(error instanceof Error) ? error.message : String(error)
|
|
227
|
+
|
|
228
|
+
const warned = new Set()
|
|
229
|
+
|
|
230
|
+
export function warn(msg: string) {
|
|
231
|
+
if (!warned.has(msg)) {
|
|
232
|
+
warned.add(msg)
|
|
233
|
+
console.warn(msg)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function deprecateMsg(oldName: string, newName: string) {
|
|
238
|
+
warn(`${oldName} is deprecated. Use ${newName} instead.`)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function deprecate(oldName: string, newName: string, newFunc: (...args) => any) {
|
|
242
|
+
return (...args) => {
|
|
243
|
+
deprecateMsg(oldName, newName)
|
|
244
|
+
return newFunc(...args)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function benchmark(task: () => void, times: number = 1) {
|
|
249
|
+
const t1 = performance.now()
|
|
250
|
+
for (let i = 0; i < times; i++) {
|
|
251
|
+
task()
|
|
252
|
+
}
|
|
253
|
+
const t2 = performance.now()
|
|
254
|
+
return t2 - t1
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function comparePerf(t1: () => void, t2: () => void, times: number = 1) {
|
|
258
|
+
return benchmark(t2, times) / benchmark(t1, times)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export class BinaryHeap<T> {
|
|
262
|
+
_items: T[]
|
|
263
|
+
_compareFn: (a: T, b: T) => boolean
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Creates a binary heap with the given compare function
|
|
267
|
+
* Not passing a compare function will give a min heap
|
|
268
|
+
*/
|
|
269
|
+
constructor(compareFn = (a: T, b: T) => a < b) {
|
|
270
|
+
this._compareFn = compareFn
|
|
271
|
+
this._items = []
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Insert an item into the binary heap
|
|
276
|
+
*/
|
|
277
|
+
insert(item: T) {
|
|
278
|
+
this._items.push(item)
|
|
279
|
+
this.moveUp(this._items.length - 1)
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Remove the smallest item from the binary heap in case of a min heap
|
|
284
|
+
* or the greatest item from the binary heap in case of a max heap
|
|
285
|
+
*/
|
|
286
|
+
remove() {
|
|
287
|
+
if (this._items.length === 0)
|
|
288
|
+
return null
|
|
289
|
+
const item = this._items[0]
|
|
290
|
+
const lastItem = this._items.pop()
|
|
291
|
+
if (this._items.length !== 0) {
|
|
292
|
+
this._items[0] = lastItem as T
|
|
293
|
+
this.moveDown(0)
|
|
294
|
+
}
|
|
295
|
+
return item
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Remove all items
|
|
300
|
+
*/
|
|
301
|
+
clear() {
|
|
302
|
+
this._items.splice(0, this._items.length)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
moveUp(pos: number) {
|
|
306
|
+
while (pos > 0) {
|
|
307
|
+
const parent = Math.floor((pos - 1) / 2)
|
|
308
|
+
if (!this._compareFn(this._items[pos], this._items[parent]))
|
|
309
|
+
if (this._items[pos] >= this._items[parent])
|
|
310
|
+
break
|
|
311
|
+
this.swap(pos, parent)
|
|
312
|
+
pos = parent
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
moveDown(pos: number) {
|
|
317
|
+
while (pos < Math.floor(this._items.length / 2)) {
|
|
318
|
+
let child = 2 * pos + 1
|
|
319
|
+
if (child < this._items.length - 1 && !this._compareFn(this._items[child], this._items[child + 1]))
|
|
320
|
+
++child
|
|
321
|
+
if (this._compareFn(this._items[pos], this._items[child]))
|
|
322
|
+
break
|
|
323
|
+
this.swap(pos, child)
|
|
324
|
+
pos = child
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
swap(index1: number, index2: number) {
|
|
329
|
+
[this._items[index1], this._items[index2]] = [this._items[index2], this._items[index1]]
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Returns the amount of items
|
|
334
|
+
*/
|
|
335
|
+
get length() {
|
|
336
|
+
return this._items.length
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const enum EnumRunesCode {
|
|
341
|
+
HIGH_SURROGATE_START = 0xd800,
|
|
342
|
+
HIGH_SURROGATE_END = 0xdbff,
|
|
343
|
+
|
|
344
|
+
LOW_SURROGATE_START = 0xdc00,
|
|
345
|
+
|
|
346
|
+
REGIONAL_INDICATOR_START = 0x1f1e6,
|
|
347
|
+
REGIONAL_INDICATOR_END = 0x1f1ff,
|
|
348
|
+
|
|
349
|
+
FITZPATRICK_MODIFIER_START = 0x1f3fb,
|
|
350
|
+
FITZPATRICK_MODIFIER_END = 0x1f3ff,
|
|
351
|
+
|
|
352
|
+
VARIATION_MODIFIER_START = 0xfe00,
|
|
353
|
+
VARIATION_MODIFIER_END = 0xfe0f,
|
|
354
|
+
|
|
355
|
+
DIACRITICAL_MARKS_START = 0x20d0,
|
|
356
|
+
DIACRITICAL_MARKS_END = 0x20ff,
|
|
357
|
+
|
|
358
|
+
SUBDIVISION_INDICATOR_START = 0x1f3f4,
|
|
359
|
+
TAGS_START = 0xe0000,
|
|
360
|
+
TAGS_END = 0xe007f,
|
|
361
|
+
|
|
362
|
+
ZWJ = 0x200d,
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const GRAPHEMES = Object.freeze([
|
|
366
|
+
0x0308, // ( ◌̈ ) COMBINING DIAERESIS
|
|
367
|
+
0x0937, // ( ष ) DEVANAGARI LETTER SSA
|
|
368
|
+
0x093F, // ( ि ) DEVANAGARI VOWEL SIGN I
|
|
369
|
+
0x0BA8, // ( ந ) TAMIL LETTER NA
|
|
370
|
+
0x0BBF, // ( ி ) TAMIL VOWEL SIGN I
|
|
371
|
+
0x0BCD, // ( ◌்) TAMIL SIGN VIRAMA
|
|
372
|
+
0x0E31, // ( ◌ั ) THAI CHARACTER MAI HAN-AKAT
|
|
373
|
+
0x0E33, // ( ำ ) THAI CHARACTER SARA AM
|
|
374
|
+
0x0E40, // ( เ ) THAI CHARACTER SARA E
|
|
375
|
+
0x0E49, // ( เ ) THAI CHARACTER MAI THO
|
|
376
|
+
0x1100, // ( ᄀ ) HANGUL CHOSEONG KIYEOK
|
|
377
|
+
0x1161, // ( ᅡ ) HANGUL JUNGSEONG A
|
|
378
|
+
0x11A8, // ( ᆨ ) HANGUL JONGSEONG KIYEOK
|
|
379
|
+
])
|
|
380
|
+
|
|
381
|
+
enum EnumCodeUnits {
|
|
382
|
+
unit_1 = 1,
|
|
383
|
+
unit_2 = 2,
|
|
384
|
+
unit_4 = 4,
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export function runes(string: string): string[] {
|
|
388
|
+
if (typeof string !== "string") {
|
|
389
|
+
throw new TypeError("string cannot be undefined or null")
|
|
390
|
+
}
|
|
391
|
+
const result: string[] = []
|
|
392
|
+
let i = 0
|
|
393
|
+
let increment = 0
|
|
394
|
+
while (i < string.length) {
|
|
395
|
+
increment += nextUnits(i + increment, string)
|
|
396
|
+
if (isGrapheme(string[i + increment])) {
|
|
397
|
+
increment++
|
|
398
|
+
}
|
|
399
|
+
if (isVariationSelector(string[i + increment])) {
|
|
400
|
+
increment++
|
|
401
|
+
}
|
|
402
|
+
if (isDiacriticalMark(string[i + increment])) {
|
|
403
|
+
increment++
|
|
404
|
+
}
|
|
405
|
+
if (isZeroWidthJoiner(string[i + increment])) {
|
|
406
|
+
increment++
|
|
407
|
+
continue
|
|
408
|
+
}
|
|
409
|
+
result.push(string.substring(i, i + increment))
|
|
410
|
+
i += increment
|
|
411
|
+
increment = 0
|
|
412
|
+
}
|
|
413
|
+
return result
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Decide how many code units make up the current character.
|
|
417
|
+
// BMP characters: 1 code unit
|
|
418
|
+
// Non-BMP characters (represented by surrogate pairs): 2 code units
|
|
419
|
+
// Emoji with skin-tone modifiers: 4 code units (2 code points)
|
|
420
|
+
// Country flags: 4 code units (2 code points)
|
|
421
|
+
// Variations: 2 code units
|
|
422
|
+
// Subdivision flags: 14 code units (7 code points)
|
|
423
|
+
function nextUnits(i: number, string: string) {
|
|
424
|
+
const current = string[i]
|
|
425
|
+
// If we don't have a value that is part of a surrogate pair, or we're at
|
|
426
|
+
// the end, only take the value at i
|
|
427
|
+
if (!isFirstOfSurrogatePair(current) || i === string.length - 1) {
|
|
428
|
+
return EnumCodeUnits.unit_1
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const currentPair = current + string[i + 1]
|
|
432
|
+
const nextPair = string.substring(i + 2, i + 5)
|
|
433
|
+
|
|
434
|
+
// Country flags are comprised of two regional indicator symbols,
|
|
435
|
+
// each represented by a surrogate pair.
|
|
436
|
+
// See http://emojipedia.org/flags/
|
|
437
|
+
// If both pairs are regional indicator symbols, take 4
|
|
438
|
+
if (isRegionalIndicator(currentPair) && isRegionalIndicator(nextPair)) {
|
|
439
|
+
return EnumCodeUnits.unit_4
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// https://unicode.org/emoji/charts/full-emoji-list.html#subdivision-flag
|
|
443
|
+
// See https://emojipedia.org/emoji-tag-sequence/
|
|
444
|
+
// If nextPair is in Tags(https://en.wikipedia.org/wiki/Tags_(Unicode_block)),
|
|
445
|
+
// then find next closest U+E007F(CANCEL TAG)
|
|
446
|
+
if (isSubdivisionFlag(currentPair) && isSupplementarySpecialpurposePlane(nextPair)) {
|
|
447
|
+
return string.slice(i).indexOf(String.fromCodePoint(EnumRunesCode.TAGS_END)) + 2
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// If the next pair make a Fitzpatrick skin tone
|
|
451
|
+
// modifier, take 4
|
|
452
|
+
// See http://emojipedia.org/modifiers/
|
|
453
|
+
// Technically, only some code points are meant to be
|
|
454
|
+
// combined with the skin tone modifiers. This function
|
|
455
|
+
// does not check the current pair to see if it is
|
|
456
|
+
// one of them.
|
|
457
|
+
if (isFitzpatrickModifier(nextPair)) {
|
|
458
|
+
return EnumCodeUnits.unit_4
|
|
459
|
+
}
|
|
460
|
+
return EnumCodeUnits.unit_2
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function isFirstOfSurrogatePair(string: string) {
|
|
464
|
+
return string && betweenInclusive(string[0].charCodeAt(0), EnumRunesCode.HIGH_SURROGATE_START, EnumRunesCode.HIGH_SURROGATE_END)
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function isRegionalIndicator(string: string) {
|
|
468
|
+
return betweenInclusive(codePointFromSurrogatePair(string), EnumRunesCode.REGIONAL_INDICATOR_START, EnumRunesCode.REGIONAL_INDICATOR_END)
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function isSubdivisionFlag(string: string) {
|
|
472
|
+
return betweenInclusive(codePointFromSurrogatePair(string), EnumRunesCode.SUBDIVISION_INDICATOR_START, EnumRunesCode.SUBDIVISION_INDICATOR_START)
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function isFitzpatrickModifier(string: string) {
|
|
476
|
+
return betweenInclusive(codePointFromSurrogatePair(string), EnumRunesCode.FITZPATRICK_MODIFIER_START, EnumRunesCode.FITZPATRICK_MODIFIER_END)
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function isVariationSelector(string: string) {
|
|
480
|
+
return typeof string === "string" && betweenInclusive(string.charCodeAt(0), EnumRunesCode.VARIATION_MODIFIER_START, EnumRunesCode.VARIATION_MODIFIER_END)
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function isDiacriticalMark(string: string) {
|
|
484
|
+
return typeof string === "string" && betweenInclusive(string.charCodeAt(0), EnumRunesCode.DIACRITICAL_MARKS_START, EnumRunesCode.DIACRITICAL_MARKS_END)
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function isSupplementarySpecialpurposePlane(string: string) {
|
|
488
|
+
const codePoint = string.codePointAt(0)
|
|
489
|
+
return (typeof string === "string" && typeof codePoint === "number" && betweenInclusive(codePoint, EnumRunesCode.TAGS_START, EnumRunesCode.TAGS_END))
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function isGrapheme(string: string) {
|
|
493
|
+
return typeof string === "string" && GRAPHEMES.includes(string.charCodeAt(0))
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function isZeroWidthJoiner(string: string) {
|
|
497
|
+
return typeof string === "string" && string.charCodeAt(0) === EnumRunesCode.ZWJ
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function codePointFromSurrogatePair(pair: string) {
|
|
501
|
+
const highOffset = pair.charCodeAt(0) - EnumRunesCode.HIGH_SURROGATE_START
|
|
502
|
+
const lowOffset = pair.charCodeAt(1) - EnumRunesCode.LOW_SURROGATE_START
|
|
503
|
+
return (highOffset << 10) + lowOffset + 0x10000
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function betweenInclusive(value: number, lower: number, upper: number) {
|
|
507
|
+
return value >= lower && value <= upper
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
export function substring(string: string, start?: number, width?: number) {
|
|
511
|
+
const chars = runes(string)
|
|
512
|
+
if (start === undefined) {
|
|
513
|
+
return string
|
|
514
|
+
}
|
|
515
|
+
if (start >= chars.length) {
|
|
516
|
+
return ""
|
|
517
|
+
}
|
|
518
|
+
const rest = chars.length - start
|
|
519
|
+
const stringWidth = width === undefined ? rest : width
|
|
520
|
+
let endIndex = start + stringWidth
|
|
521
|
+
if (endIndex > (start + rest)) {
|
|
522
|
+
endIndex = undefined
|
|
523
|
+
}
|
|
524
|
+
return chars.slice(start, endIndex).join("")
|
|
525
|
+
}
|