@vpmedia/phaser 1.117.0 → 1.118.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/dist/index.js +140 -134
- package/dist/index.js.map +1 -1
- package/dist/phaser/core/animation_manager.d.ts +1 -1
- package/dist/phaser/core/animation_manager.d.ts.map +1 -1
- package/dist/phaser/core/cache.d.ts +70 -42
- package/dist/phaser/core/cache.d.ts.map +1 -1
- package/dist/phaser/core/input.d.ts +1 -1
- package/dist/phaser/core/input.d.ts.map +1 -1
- package/dist/phaser/core/input_mspointer.d.ts +8 -7
- package/dist/phaser/core/input_mspointer.d.ts.map +1 -1
- package/dist/phaser/core/loader.d.ts +30 -1
- package/dist/phaser/core/loader.d.ts.map +1 -1
- package/dist/phaser/core/loader_parser.d.ts +24 -4
- package/dist/phaser/core/loader_parser.d.ts.map +1 -1
- package/dist/phaser/core/sound.d.ts +13 -3
- package/dist/phaser/core/sound.d.ts.map +1 -1
- package/dist/phaser/core/tween.d.ts +3 -3
- package/dist/phaser/core/tween.d.ts.map +1 -1
- package/dist/phaser/core/tween_manager.d.ts +8 -6
- package/dist/phaser/core/tween_manager.d.ts.map +1 -1
- package/dist/phaser/display/bitmap_text.d.ts +13 -8
- package/dist/phaser/display/bitmap_text.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/phaser/core/animation_manager.ts +1 -1
- package/src/phaser/core/cache.test.ts +201 -0
- package/src/phaser/core/cache.ts +126 -86
- package/src/phaser/core/input.ts +1 -1
- package/src/phaser/core/input_mspointer.ts +10 -13
- package/src/phaser/core/loader.ts +82 -42
- package/src/phaser/core/loader_parser.test.ts +115 -0
- package/src/phaser/core/loader_parser.ts +64 -36
- package/src/phaser/core/sound.ts +29 -17
- package/src/phaser/core/sound_manager.ts +1 -1
- package/src/phaser/core/tween.ts +6 -6
- package/src/phaser/core/tween_manager.ts +42 -35
- package/src/phaser/display/bitmap_text.test.ts +212 -0
- package/src/phaser/display/bitmap_text.ts +39 -24
package/src/phaser/core/tween.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
+
import type { TweenTarget } from './tween_manager.js';
|
|
1
2
|
import * as MathUtils from '../util/math.js';
|
|
2
3
|
import { TWEEN_COMPLETE, TWEEN_LOOPED, TWEEN_PENDING, TWEEN_RUNNING } from './const.js';
|
|
3
4
|
import { Signal } from './signal.js';
|
|
4
5
|
import { TweenData } from './tween_data.js';
|
|
5
6
|
import type { Game } from './game.js';
|
|
6
|
-
import type { DisplayObject } from '../display/display_object.js';
|
|
7
7
|
import type { TweenManager } from './tween_manager.js';
|
|
8
8
|
|
|
9
9
|
export class Tween {
|
|
10
10
|
public game!: Game;
|
|
11
|
-
public target!:
|
|
11
|
+
public target!: TweenTarget;
|
|
12
12
|
public manager!: TweenManager;
|
|
13
13
|
public timeline!: TweenData[];
|
|
14
14
|
public reverse!: boolean;
|
|
@@ -36,7 +36,7 @@ export class Tween {
|
|
|
36
36
|
* @param {Game} game - Reference to the Phaser Game instance.
|
|
37
37
|
* @param {TweenManager} manager - Reference to the Tween Manager.
|
|
38
38
|
*/
|
|
39
|
-
public constructor(target:
|
|
39
|
+
public constructor(target: TweenTarget, game: Game, manager: TweenManager) {
|
|
40
40
|
this.game = game;
|
|
41
41
|
this.target = target;
|
|
42
42
|
/** @type {TweenManager} */
|
|
@@ -100,7 +100,7 @@ export class Tween {
|
|
|
100
100
|
yoyo = false
|
|
101
101
|
): this {
|
|
102
102
|
if (typeof ease === 'string' && this.manager.easeMap[ease]) {
|
|
103
|
-
ease = this.manager.easeMap[ease]
|
|
103
|
+
ease = this.manager.easeMap[ease]!;
|
|
104
104
|
}
|
|
105
105
|
if (this.isRunning) {
|
|
106
106
|
return this;
|
|
@@ -133,7 +133,7 @@ export class Tween {
|
|
|
133
133
|
yoyo = false
|
|
134
134
|
): this {
|
|
135
135
|
if (typeof ease === 'string' && this.manager.easeMap[ease]) {
|
|
136
|
-
ease = this.manager.easeMap[ease]
|
|
136
|
+
ease = this.manager.easeMap[ease]!;
|
|
137
137
|
}
|
|
138
138
|
if (this.isRunning) {
|
|
139
139
|
this.game.logger.warn('Tween.from cannot be called after Tween.start');
|
|
@@ -284,7 +284,7 @@ export class Tween {
|
|
|
284
284
|
*/
|
|
285
285
|
public easing(ease: string | Function, index: number): this {
|
|
286
286
|
if (typeof ease === 'string' && this.manager.easeMap[ease]) {
|
|
287
|
-
ease = this.manager.easeMap[ease]
|
|
287
|
+
ease = this.manager.easeMap[ease]!;
|
|
288
288
|
}
|
|
289
289
|
return this.updateTweenData('easingFunction', ease, index);
|
|
290
290
|
}
|
|
@@ -35,11 +35,14 @@ import {
|
|
|
35
35
|
SinusoidalOut,
|
|
36
36
|
} from './tween_easing.js';
|
|
37
37
|
|
|
38
|
+
/** What a tween can be attached to: any object, or a list of them. */
|
|
39
|
+
export type TweenTarget = object | object[];
|
|
40
|
+
|
|
38
41
|
export class TweenManager {
|
|
39
42
|
public game!: Game;
|
|
40
|
-
public _tweens!:
|
|
41
|
-
public _add!:
|
|
42
|
-
public easeMap!:
|
|
43
|
+
public _tweens!: Tween[];
|
|
44
|
+
public _add!: Tween[];
|
|
45
|
+
public easeMap!: Record<string, (k: number) => number>;
|
|
43
46
|
/**
|
|
44
47
|
* Creates a new TweenManager instance.
|
|
45
48
|
* @param {Game} game - The game instance this manager belongs to.
|
|
@@ -112,8 +115,8 @@ export class TweenManager {
|
|
|
112
115
|
* This method removes all active and pending tweens.
|
|
113
116
|
*/
|
|
114
117
|
public removeAll(): void {
|
|
115
|
-
for (
|
|
116
|
-
|
|
118
|
+
for (const tween of this._tweens) {
|
|
119
|
+
tween.pendingDelete = true;
|
|
117
120
|
}
|
|
118
121
|
this._add = [];
|
|
119
122
|
}
|
|
@@ -123,27 +126,28 @@ export class TweenManager {
|
|
|
123
126
|
* @param {object} obj - The object to remove tweens from.
|
|
124
127
|
* @param {object[]} children - Optional array of child objects to remove tweens from.
|
|
125
128
|
*/
|
|
126
|
-
public removeFrom(obj:
|
|
127
|
-
let i;
|
|
128
|
-
let len;
|
|
129
|
+
public removeFrom(obj: TweenTarget, children: object[] | null = null): void {
|
|
129
130
|
if (Array.isArray(obj)) {
|
|
130
|
-
for (
|
|
131
|
-
this.removeFrom(
|
|
131
|
+
for (const entry of obj) {
|
|
132
|
+
this.removeFrom(entry);
|
|
132
133
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const group = obj as { type?: number; children?: TweenTarget[] };
|
|
137
|
+
if (group.type === GROUP && children && group.children) {
|
|
138
|
+
for (const child of group.children) {
|
|
139
|
+
this.removeFrom(child);
|
|
136
140
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
for (const tween of this._tweens.slice()) {
|
|
144
|
+
if (obj === tween.target) {
|
|
145
|
+
this.remove(tween);
|
|
142
146
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
+
}
|
|
148
|
+
for (const tween of this._add.slice()) {
|
|
149
|
+
if (obj === tween.target) {
|
|
150
|
+
this.remove(tween);
|
|
147
151
|
}
|
|
148
152
|
}
|
|
149
153
|
}
|
|
@@ -162,7 +166,7 @@ export class TweenManager {
|
|
|
162
166
|
* @param {object} object - The object to create a tween for.
|
|
163
167
|
* @returns {Tween} The created Tween object.
|
|
164
168
|
*/
|
|
165
|
-
public create(object:
|
|
169
|
+
public create(object: TweenTarget): Tween {
|
|
166
170
|
return new Tween(object, this.game, this);
|
|
167
171
|
}
|
|
168
172
|
|
|
@@ -171,13 +175,16 @@ export class TweenManager {
|
|
|
171
175
|
* @param {Tween | null | undefined} tween - The tween to remove.
|
|
172
176
|
*/
|
|
173
177
|
public remove(tween: Tween | null | undefined): void {
|
|
178
|
+
if (!tween) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
174
181
|
let i = this._tweens.indexOf(tween);
|
|
175
182
|
if (i !== -1) {
|
|
176
|
-
this._tweens[i]
|
|
183
|
+
this._tweens[i]!.pendingDelete = true;
|
|
177
184
|
} else {
|
|
178
185
|
i = this._add.indexOf(tween);
|
|
179
186
|
if (i !== -1) {
|
|
180
|
-
this._add[i]
|
|
187
|
+
this._add[i]!.pendingDelete = true;
|
|
181
188
|
}
|
|
182
189
|
}
|
|
183
190
|
}
|
|
@@ -194,7 +201,7 @@ export class TweenManager {
|
|
|
194
201
|
}
|
|
195
202
|
let i = 0;
|
|
196
203
|
while (i < numTweens) {
|
|
197
|
-
if (this._tweens[i]
|
|
204
|
+
if (this._tweens[i]!.update(this.game.time.time)) {
|
|
198
205
|
i += 1;
|
|
199
206
|
} else {
|
|
200
207
|
this._tweens.splice(i, 1);
|
|
@@ -215,7 +222,7 @@ export class TweenManager {
|
|
|
215
222
|
* @returns {boolean} True if the object is being tweened, false otherwise.
|
|
216
223
|
*/
|
|
217
224
|
public isTweening(object: unknown): boolean {
|
|
218
|
-
return
|
|
225
|
+
return this._tweens.some((tween: Tween): boolean => tween.target === object);
|
|
219
226
|
}
|
|
220
227
|
|
|
221
228
|
/**
|
|
@@ -223,8 +230,8 @@ export class TweenManager {
|
|
|
223
230
|
* This method pauses all active tweens.
|
|
224
231
|
*/
|
|
225
232
|
public _pauseAll(): void {
|
|
226
|
-
for (
|
|
227
|
-
|
|
233
|
+
for (const tween of this._tweens) {
|
|
234
|
+
tween._pause();
|
|
228
235
|
}
|
|
229
236
|
}
|
|
230
237
|
|
|
@@ -233,8 +240,8 @@ export class TweenManager {
|
|
|
233
240
|
* This method resumes all paused tweens.
|
|
234
241
|
*/
|
|
235
242
|
public _resumeAll(): void {
|
|
236
|
-
for (
|
|
237
|
-
|
|
243
|
+
for (const tween of this._tweens) {
|
|
244
|
+
tween._resume();
|
|
238
245
|
}
|
|
239
246
|
}
|
|
240
247
|
|
|
@@ -243,8 +250,8 @@ export class TweenManager {
|
|
|
243
250
|
* This method pauses all active tweens.
|
|
244
251
|
*/
|
|
245
252
|
public pauseAll(): void {
|
|
246
|
-
for (
|
|
247
|
-
|
|
253
|
+
for (const tween of this._tweens) {
|
|
254
|
+
tween.pause();
|
|
248
255
|
}
|
|
249
256
|
}
|
|
250
257
|
|
|
@@ -253,8 +260,8 @@ export class TweenManager {
|
|
|
253
260
|
* This method resumes all paused tweens.
|
|
254
261
|
*/
|
|
255
262
|
public resumeAll(): void {
|
|
256
|
-
for (
|
|
257
|
-
|
|
263
|
+
for (const tween of this._tweens) {
|
|
264
|
+
tween.resume();
|
|
258
265
|
}
|
|
259
266
|
}
|
|
260
267
|
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { SCALE_LINEAR } from '../core/const.js';
|
|
3
|
+
import { getRegistry } from '../core/registry.js';
|
|
4
|
+
import { Rectangle } from '../geom/rectangle.js';
|
|
5
|
+
import { BaseTexture } from './webgl/base_texture.js';
|
|
6
|
+
import { Texture } from './webgl/texture.js';
|
|
7
|
+
import type { BitmapFontCacheEntry } from '../core/cache.js';
|
|
8
|
+
import type { Game } from '../core/game.js';
|
|
9
|
+
import type { BitmapFontData } from '../core/loader_parser.js';
|
|
10
|
+
import { BitmapText } from './bitmap_text.js';
|
|
11
|
+
|
|
12
|
+
getRegistry().TEXTURE_SCALE_MODE = SCALE_LINEAR;
|
|
13
|
+
|
|
14
|
+
const createBaseTexture = (): BaseTexture => {
|
|
15
|
+
const base = new BaseTexture(null);
|
|
16
|
+
base.hasLoaded = true;
|
|
17
|
+
base.width = 256;
|
|
18
|
+
base.height = 256;
|
|
19
|
+
return base;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const atlas = createBaseTexture();
|
|
23
|
+
|
|
24
|
+
const glyph = (code: number, width: number): [number, BitmapFontData['chars'][number]] => [
|
|
25
|
+
code,
|
|
26
|
+
{
|
|
27
|
+
x: 0,
|
|
28
|
+
y: 0,
|
|
29
|
+
width,
|
|
30
|
+
height: 10,
|
|
31
|
+
xOffset: 0,
|
|
32
|
+
yOffset: 0,
|
|
33
|
+
xAdvance: width,
|
|
34
|
+
kerning: {},
|
|
35
|
+
texture: new Texture(atlas, new Rectangle(0, 0, width, 10)),
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
const FONT: BitmapFontData = {
|
|
40
|
+
font: 'test',
|
|
41
|
+
size: 10,
|
|
42
|
+
lineHeight: 12,
|
|
43
|
+
chars: Object.fromEntries([
|
|
44
|
+
glyph(32, 5), // space
|
|
45
|
+
glyph(65, 10), // A
|
|
46
|
+
glyph(66, 10), // B
|
|
47
|
+
]),
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const createGame = (font: BitmapFontData | null = FONT): Game =>
|
|
51
|
+
({
|
|
52
|
+
cache: {
|
|
53
|
+
getBitmapFont: (): BitmapFontCacheEntry | null =>
|
|
54
|
+
font === null ? null : ({ url: '', data: null, font, base: {} } as unknown as BitmapFontCacheEntry),
|
|
55
|
+
},
|
|
56
|
+
stage: { currentRenderOrderID: 0 },
|
|
57
|
+
}) as unknown as Game;
|
|
58
|
+
|
|
59
|
+
const createText = (text: string, size = 10): BitmapText => new BitmapText(createGame(), 0, 0, 'test', text, size);
|
|
60
|
+
|
|
61
|
+
describe('BitmapText', () => {
|
|
62
|
+
beforeEach(() => {
|
|
63
|
+
getRegistry().CACHE_MISSING_IMAGE = new Texture(atlas, new Rectangle(0, 0, 1, 1));
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('updateText', () => {
|
|
67
|
+
it('measures a single line from the glyph advances', () => {
|
|
68
|
+
const bitmapText = createText('AB');
|
|
69
|
+
expect(bitmapText.textWidth).toBe(20);
|
|
70
|
+
expect(bitmapText.textHeight).toBe(12);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('scales the metrics with the font size', () => {
|
|
74
|
+
const bitmapText = createText('AB', 20);
|
|
75
|
+
expect(bitmapText.textWidth).toBe(40);
|
|
76
|
+
expect(bitmapText.textHeight).toBe(24);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('splits on a newline and takes the widest line as the width', () => {
|
|
80
|
+
const bitmapText = createText('AB\nA');
|
|
81
|
+
expect(bitmapText.textWidth).toBe(20);
|
|
82
|
+
expect(bitmapText.textHeight).toBe(24);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('creates one glyph sprite per character', () => {
|
|
86
|
+
expect(createText('AB')._glyphs).toHaveLength(2);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('reuses the glyph pool when the text shrinks', () => {
|
|
90
|
+
const bitmapText = createText('AB');
|
|
91
|
+
const pooled = bitmapText._glyphs;
|
|
92
|
+
bitmapText.text = 'A';
|
|
93
|
+
expect(bitmapText._glyphs).toBe(pooled);
|
|
94
|
+
expect(bitmapText._glyphs).toHaveLength(2);
|
|
95
|
+
expect(bitmapText.children).toHaveLength(1);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('drops the glyphs that left the display list when purged', () => {
|
|
99
|
+
const bitmapText = createText('AB');
|
|
100
|
+
bitmapText.text = 'A';
|
|
101
|
+
expect(bitmapText.purgeGlyphs()).toBe(1);
|
|
102
|
+
expect(bitmapText._glyphs).toHaveLength(1);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('lays out an empty string as a single empty line', () => {
|
|
106
|
+
const bitmapText = createText('');
|
|
107
|
+
expect(bitmapText.textWidth).toBe(0);
|
|
108
|
+
expect(bitmapText.textHeight).toBe(12);
|
|
109
|
+
expect(bitmapText._glyphs).toHaveLength(0);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('does nothing when the font is not in the cache', () => {
|
|
113
|
+
const bitmapText = new BitmapText(createGame(null), 0, 0, 'missing', 'AB');
|
|
114
|
+
expect(bitmapText.textWidth).toBe(0);
|
|
115
|
+
expect(bitmapText._glyphs).toHaveLength(0);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('scanLine', () => {
|
|
120
|
+
it('reports the whole text when nothing forces a wrap', () => {
|
|
121
|
+
const line = createText('AB').scanLine(FONT, 1, 'AB');
|
|
122
|
+
expect(line).toMatchObject({ width: 20, text: 'AB', end: true, chars: [0, 10] });
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('stops at a newline and reports the line as unfinished', () => {
|
|
126
|
+
const line = createText('AB').scanLine(FONT, 1, 'AB\nA');
|
|
127
|
+
expect(line.text).toBe('AB');
|
|
128
|
+
expect(line.end).toBe(false);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('wraps at the last space once maxWidth is exceeded', () => {
|
|
132
|
+
const bitmapText = createText('A A');
|
|
133
|
+
bitmapText.maxWidth = 20;
|
|
134
|
+
expect(bitmapText.scanLine(FONT, 1, 'A A').text).toBe('A');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('substitutes a space for a character the font does not carry', () => {
|
|
138
|
+
const line = createText('A').scanLine(FONT, 1, 'Z');
|
|
139
|
+
expect(line.width).toBe(5);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('skips a character the font cannot even substitute for', () => {
|
|
143
|
+
const spaceless: BitmapFontData = { ...FONT, chars: Object.fromEntries([glyph(65, 10)]) };
|
|
144
|
+
expect(createText('A').scanLine(spaceless, 1, 'AZ').width).toBe(10);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe('cleanText', () => {
|
|
149
|
+
it('drops characters the font does not carry', () => {
|
|
150
|
+
expect(createText('A').cleanText('AZB')).toBe('AB');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('replaces them with the given stand-in', () => {
|
|
154
|
+
expect(createText('A').cleanText('AZB', '?')).toBe('A?B');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('keeps line breaks', () => {
|
|
158
|
+
expect(createText('A').cleanText('A\nB')).toBe('A\nB');
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe('properties', () => {
|
|
163
|
+
it('re-lays out when the alignment changes', () => {
|
|
164
|
+
const bitmapText = createText('A\nAB');
|
|
165
|
+
bitmapText.align = 'right';
|
|
166
|
+
expect(bitmapText.align).toBe('right');
|
|
167
|
+
expect(bitmapText._glyphs[0]?.position.x).toBe(10);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('ignores an alignment it does not know', () => {
|
|
171
|
+
const bitmapText = createText('A');
|
|
172
|
+
bitmapText.align = 'sideways';
|
|
173
|
+
expect(bitmapText.align).toBe('left');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('coerces a string font size', () => {
|
|
177
|
+
const bitmapText = createText('A');
|
|
178
|
+
bitmapText.fontSize = '20';
|
|
179
|
+
expect(bitmapText.fontSize).toBe(20);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('ignores a font size of zero or less', () => {
|
|
183
|
+
const bitmapText = createText('A');
|
|
184
|
+
bitmapText.fontSize = -5;
|
|
185
|
+
expect(bitmapText.fontSize).toBe(10);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('renders the tint as a padded hex string', () => {
|
|
189
|
+
const bitmapText = createText('A');
|
|
190
|
+
bitmapText.tint = 0x00ff00;
|
|
191
|
+
expect(bitmapText.fill).toBe('#00FF00');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('reads a hex string back into the tint', () => {
|
|
195
|
+
const bitmapText = createText('A');
|
|
196
|
+
bitmapText.fill = '#FF0000';
|
|
197
|
+
expect(bitmapText.tint).toBe(0xff0000);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('trims the font key before looking it up', () => {
|
|
201
|
+
const bitmapText = createText('A');
|
|
202
|
+
bitmapText.font = ' other ';
|
|
203
|
+
expect(bitmapText.font).toBe('other');
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('stringifies whatever the text is set to', () => {
|
|
207
|
+
const bitmapText = createText('A');
|
|
208
|
+
bitmapText.text = 65;
|
|
209
|
+
expect(bitmapText.text).toBe('65');
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
});
|
|
@@ -2,7 +2,18 @@ import { BITMAP_TEXT, SCALE_LINEAR, SCALE_NEAREST } from '../core/const.js';
|
|
|
2
2
|
import { Point } from '../geom/point.js';
|
|
3
3
|
import { DisplayObject } from './display_object.js';
|
|
4
4
|
import { Image } from './image.js';
|
|
5
|
+
import type { BitmapFontCacheEntry } from '../core/cache.js';
|
|
5
6
|
import type { Game } from '../core/game.js';
|
|
7
|
+
import type { BitmapFontData } from '../core/loader_parser.js';
|
|
8
|
+
|
|
9
|
+
/** One measured line of bitmap text: its width, its content and where each glyph starts. */
|
|
10
|
+
export type BitmapTextLine = {
|
|
11
|
+
width: number;
|
|
12
|
+
text: string;
|
|
13
|
+
end: boolean;
|
|
14
|
+
chars: number[];
|
|
15
|
+
y: number;
|
|
16
|
+
};
|
|
6
17
|
|
|
7
18
|
export class BitmapText extends DisplayObject {
|
|
8
19
|
declare public type: number;
|
|
@@ -14,7 +25,7 @@ export class BitmapText extends DisplayObject {
|
|
|
14
25
|
public _glyphs!: Image[];
|
|
15
26
|
public _maxWidth!: number;
|
|
16
27
|
public _text!: string;
|
|
17
|
-
public _data!:
|
|
28
|
+
public _data!: BitmapFontCacheEntry | null;
|
|
18
29
|
public _font!: string;
|
|
19
30
|
public _fontSize!: number;
|
|
20
31
|
public _align!: string;
|
|
@@ -97,14 +108,14 @@ export class BitmapText extends DisplayObject {
|
|
|
97
108
|
* @param {string} text - The text to scan.
|
|
98
109
|
* @returns {{width: number, text: string, end: boolean, chars: number[]}} An object containing the width, processed text, end status, and character positions.
|
|
99
110
|
*/
|
|
100
|
-
public scanLine(data:
|
|
111
|
+
public scanLine(data: BitmapFontData, scale: number, text: string): Omit<BitmapTextLine, 'y'> {
|
|
101
112
|
let x = 0;
|
|
102
113
|
let w = 0;
|
|
103
114
|
let lastSpace = -1;
|
|
104
115
|
let wrappedWidth = 0;
|
|
105
116
|
let prevCharCode = null;
|
|
106
117
|
const maxWidth = this._maxWidth > 0 ? this._maxWidth : null;
|
|
107
|
-
const chars = [];
|
|
118
|
+
const chars: number[] = [];
|
|
108
119
|
// Let's scan the text and work out if any of the lines are > maxWidth
|
|
109
120
|
let end = true;
|
|
110
121
|
for (let i = 0; i < text.length; i += 1) {
|
|
@@ -126,15 +137,18 @@ export class BitmapText extends DisplayObject {
|
|
|
126
137
|
charCode = 32;
|
|
127
138
|
charData = data.chars[charCode];
|
|
128
139
|
}
|
|
140
|
+
if (charData === undefined) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
129
143
|
// Adjust for kerning from previous character to this one
|
|
130
|
-
const kerning = prevCharCode
|
|
144
|
+
const kerning = (prevCharCode === null ? 0 : charData.kerning[prevCharCode]) ?? 0;
|
|
131
145
|
// Record the last space in the string and the current width
|
|
132
146
|
if (/(\s)/.test(text.charAt(i))) {
|
|
133
147
|
lastSpace = i;
|
|
134
148
|
wrappedWidth = w;
|
|
135
149
|
}
|
|
136
150
|
// What will the line width be if we add this character to it?
|
|
137
|
-
c = (kerning + charData.texture
|
|
151
|
+
c = (kerning + (charData.texture?.width ?? 0) + charData.xOffset) * scale;
|
|
138
152
|
// Do we need to line-wrap?
|
|
139
153
|
if (maxWidth && w + c >= maxWidth && lastSpace > -1) {
|
|
140
154
|
// The last space was at "lastSpace" which was "i - lastSpace" characters ago
|
|
@@ -165,7 +179,7 @@ export class BitmapText extends DisplayObject {
|
|
|
165
179
|
* @returns {string} The cleaned text.
|
|
166
180
|
*/
|
|
167
181
|
public cleanText(text: string, replace = ''): string {
|
|
168
|
-
const data = this._data
|
|
182
|
+
const data = this._data?.font;
|
|
169
183
|
if (!data) {
|
|
170
184
|
return '';
|
|
171
185
|
}
|
|
@@ -190,26 +204,26 @@ export class BitmapText extends DisplayObject {
|
|
|
190
204
|
* Updates the internal text rendering based on current properties and content.
|
|
191
205
|
*/
|
|
192
206
|
public updateText(): void {
|
|
193
|
-
const data = this._data
|
|
207
|
+
const data = this._data?.font;
|
|
194
208
|
if (!data) {
|
|
195
209
|
return;
|
|
196
210
|
}
|
|
197
|
-
let text
|
|
211
|
+
let text = this.text;
|
|
198
212
|
const scale = this._fontSize / data.size;
|
|
199
|
-
const lines = [];
|
|
213
|
+
const lines: BitmapTextLine[] = [];
|
|
200
214
|
let y = 0;
|
|
201
215
|
this.textWidth = 0;
|
|
202
|
-
let
|
|
216
|
+
let end = false;
|
|
203
217
|
do {
|
|
204
|
-
line = this.scanLine(data, scale, text);
|
|
205
|
-
line.y = y;
|
|
218
|
+
const line: BitmapTextLine = { ...this.scanLine(data, scale, text), y };
|
|
206
219
|
lines.push(line);
|
|
207
220
|
if (line.width > this.textWidth) {
|
|
208
221
|
this.textWidth = line.width;
|
|
209
222
|
}
|
|
210
223
|
y += data.lineHeight * scale;
|
|
211
224
|
text = text.slice(line.text.length + 1);
|
|
212
|
-
|
|
225
|
+
end = line.end;
|
|
226
|
+
} while (!end);
|
|
213
227
|
this.textHeight = y;
|
|
214
228
|
let t = 0;
|
|
215
229
|
let align = 0;
|
|
@@ -228,6 +242,9 @@ export class BitmapText extends DisplayObject {
|
|
|
228
242
|
charCode = 32;
|
|
229
243
|
charData = data.chars[charCode];
|
|
230
244
|
}
|
|
245
|
+
if (charData?.texture === undefined) {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
231
248
|
let g = this._glyphs[t];
|
|
232
249
|
if (g) {
|
|
233
250
|
// Sprite already exists in the glyphs pool, so we'll reuse it for this letter
|
|
@@ -235,10 +252,10 @@ export class BitmapText extends DisplayObject {
|
|
|
235
252
|
} else {
|
|
236
253
|
// We need a new sprite as the pool is empty or exhausted
|
|
237
254
|
g = new Image(this.game, 0, 0, charData.texture);
|
|
238
|
-
g.name = currentLine.text[c];
|
|
255
|
+
g.name = currentLine.text[c] ?? '';
|
|
239
256
|
this._glyphs.push(g);
|
|
240
257
|
}
|
|
241
|
-
g.position.x = currentLine.chars[c] + align - ax;
|
|
258
|
+
g.position.x = (currentLine.chars[c] ?? 0) + align - ax;
|
|
242
259
|
g.position.y = currentLine.y + charData.yOffset * scale - ay;
|
|
243
260
|
g.scale.setTo(scale, scale);
|
|
244
261
|
g.tint = this.tint;
|
|
@@ -398,10 +415,10 @@ export class BitmapText extends DisplayObject {
|
|
|
398
415
|
* Sets the font size of this bitmap text.
|
|
399
416
|
* @param {number} value - The new font size to use.
|
|
400
417
|
*/
|
|
401
|
-
public set fontSize(value:
|
|
402
|
-
|
|
403
|
-
if (
|
|
404
|
-
this._fontSize =
|
|
418
|
+
public set fontSize(value: number | string) {
|
|
419
|
+
const size = Math.trunc(Number(value));
|
|
420
|
+
if (size !== this._fontSize && size > 0) {
|
|
421
|
+
this._fontSize = size;
|
|
405
422
|
this.updateText();
|
|
406
423
|
}
|
|
407
424
|
}
|
|
@@ -450,7 +467,7 @@ export class BitmapText extends DisplayObject {
|
|
|
450
467
|
* @returns {boolean} True if smoothing is enabled, false otherwise.
|
|
451
468
|
*/
|
|
452
469
|
public get smoothed(): boolean {
|
|
453
|
-
return !this._data
|
|
470
|
+
return !this._data?.base.scaleMode;
|
|
454
471
|
}
|
|
455
472
|
|
|
456
473
|
/**
|
|
@@ -458,10 +475,8 @@ export class BitmapText extends DisplayObject {
|
|
|
458
475
|
* @param {boolean} value - Whether to enable smoothing (true) or not (false).
|
|
459
476
|
*/
|
|
460
477
|
public set smoothed(value: boolean) {
|
|
461
|
-
if (
|
|
462
|
-
this._data.base.scaleMode = SCALE_LINEAR;
|
|
463
|
-
} else {
|
|
464
|
-
this._data.base.scaleMode = SCALE_NEAREST;
|
|
478
|
+
if (this._data) {
|
|
479
|
+
this._data.base.scaleMode = value ? SCALE_LINEAR : SCALE_NEAREST;
|
|
465
480
|
}
|
|
466
481
|
}
|
|
467
482
|
}
|