@vpmedia/phaser 1.116.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.
Files changed (202) hide show
  1. package/dist/index.js +195 -229
  2. package/dist/index.js.map +1 -1
  3. package/dist/phaser/core/animation.d.ts.map +1 -1
  4. package/dist/phaser/core/animation_manager.d.ts +16 -15
  5. package/dist/phaser/core/animation_manager.d.ts.map +1 -1
  6. package/dist/phaser/core/animation_parser.d.ts.map +1 -1
  7. package/dist/phaser/core/array_set.d.ts.map +1 -1
  8. package/dist/phaser/core/cache.d.ts +76 -47
  9. package/dist/phaser/core/cache.d.ts.map +1 -1
  10. package/dist/phaser/core/device_util.d.ts.map +1 -1
  11. package/dist/phaser/core/dom.d.ts.map +1 -1
  12. package/dist/phaser/core/factory.d.ts.map +1 -1
  13. package/dist/phaser/core/game.d.ts.map +1 -1
  14. package/dist/phaser/core/input.d.ts +17 -17
  15. package/dist/phaser/core/input.d.ts.map +1 -1
  16. package/dist/phaser/core/input_handler.d.ts +11 -13
  17. package/dist/phaser/core/input_handler.d.ts.map +1 -1
  18. package/dist/phaser/core/input_mouse.d.ts.map +1 -1
  19. package/dist/phaser/core/input_mspointer.d.ts +8 -7
  20. package/dist/phaser/core/input_mspointer.d.ts.map +1 -1
  21. package/dist/phaser/core/input_pointer.d.ts +19 -8
  22. package/dist/phaser/core/input_pointer.d.ts.map +1 -1
  23. package/dist/phaser/core/input_touch.d.ts.map +1 -1
  24. package/dist/phaser/core/loader.d.ts +30 -1
  25. package/dist/phaser/core/loader.d.ts.map +1 -1
  26. package/dist/phaser/core/loader_parser.d.ts +24 -4
  27. package/dist/phaser/core/loader_parser.d.ts.map +1 -1
  28. package/dist/phaser/core/raf.d.ts.map +1 -1
  29. package/dist/phaser/core/scale_manager.d.ts +37 -12
  30. package/dist/phaser/core/scale_manager.d.ts.map +1 -1
  31. package/dist/phaser/core/scene.d.ts.map +1 -1
  32. package/dist/phaser/core/scene_manager.d.ts.map +1 -1
  33. package/dist/phaser/core/signal.d.ts.map +1 -1
  34. package/dist/phaser/core/signal_binding.d.ts.map +1 -1
  35. package/dist/phaser/core/sound.d.ts +15 -5
  36. package/dist/phaser/core/sound.d.ts.map +1 -1
  37. package/dist/phaser/core/sound_manager.d.ts +24 -22
  38. package/dist/phaser/core/sound_manager.d.ts.map +1 -1
  39. package/dist/phaser/core/sound_sprite.d.ts.map +1 -1
  40. package/dist/phaser/core/stage.d.ts.map +1 -1
  41. package/dist/phaser/core/time.d.ts.map +1 -1
  42. package/dist/phaser/core/timer.d.ts +1 -1
  43. package/dist/phaser/core/timer.d.ts.map +1 -1
  44. package/dist/phaser/core/tween.d.ts +3 -3
  45. package/dist/phaser/core/tween.d.ts.map +1 -1
  46. package/dist/phaser/core/tween_data.d.ts +26 -26
  47. package/dist/phaser/core/tween_data.d.ts.map +1 -1
  48. package/dist/phaser/core/tween_manager.d.ts +8 -6
  49. package/dist/phaser/core/tween_manager.d.ts.map +1 -1
  50. package/dist/phaser/core/world.d.ts.map +1 -1
  51. package/dist/phaser/display/bitmap_text.d.ts +13 -8
  52. package/dist/phaser/display/bitmap_text.d.ts.map +1 -1
  53. package/dist/phaser/display/button.d.ts.map +1 -1
  54. package/dist/phaser/display/canvas/buffer.d.ts.map +1 -1
  55. package/dist/phaser/display/canvas/graphics.d.ts.map +1 -1
  56. package/dist/phaser/display/canvas/masker.d.ts.map +1 -1
  57. package/dist/phaser/display/canvas/pool.d.ts.map +1 -1
  58. package/dist/phaser/display/canvas/renderer.d.ts.map +1 -1
  59. package/dist/phaser/display/canvas/tinter.d.ts +3 -2
  60. package/dist/phaser/display/canvas/tinter.d.ts.map +1 -1
  61. package/dist/phaser/display/canvas/util.d.ts.map +1 -1
  62. package/dist/phaser/display/display_object.d.ts.map +1 -1
  63. package/dist/phaser/display/graphics.d.ts.map +1 -1
  64. package/dist/phaser/display/graphics_data.d.ts.map +1 -1
  65. package/dist/phaser/display/graphics_data_util.d.ts.map +1 -1
  66. package/dist/phaser/display/group.d.ts.map +1 -1
  67. package/dist/phaser/display/image.d.ts +2 -2
  68. package/dist/phaser/display/image.d.ts.map +1 -1
  69. package/dist/phaser/display/sprite_util.d.ts.map +1 -1
  70. package/dist/phaser/display/text.d.ts.map +1 -1
  71. package/dist/phaser/display/webgl/abstract_filter.d.ts.map +1 -1
  72. package/dist/phaser/display/webgl/base_texture.d.ts +12 -10
  73. package/dist/phaser/display/webgl/base_texture.d.ts.map +1 -1
  74. package/dist/phaser/display/webgl/blend_manager.d.ts.map +1 -1
  75. package/dist/phaser/display/webgl/earcut.d.ts.map +1 -1
  76. package/dist/phaser/display/webgl/fast_sprite_batch.d.ts +13 -12
  77. package/dist/phaser/display/webgl/fast_sprite_batch.d.ts.map +1 -1
  78. package/dist/phaser/display/webgl/filter_manager.d.ts.map +1 -1
  79. package/dist/phaser/display/webgl/filter_texture.d.ts.map +1 -1
  80. package/dist/phaser/display/webgl/graphics.d.ts +9 -7
  81. package/dist/phaser/display/webgl/graphics.d.ts.map +1 -1
  82. package/dist/phaser/display/webgl/graphics_data.d.ts.map +1 -1
  83. package/dist/phaser/display/webgl/mask_manager.d.ts.map +1 -1
  84. package/dist/phaser/display/webgl/render_texture.d.ts.map +1 -1
  85. package/dist/phaser/display/webgl/renderer.d.ts +12 -10
  86. package/dist/phaser/display/webgl/renderer.d.ts.map +1 -1
  87. package/dist/phaser/display/webgl/shader/normal.d.ts.map +1 -1
  88. package/dist/phaser/display/webgl/shader_manager.d.ts.map +1 -1
  89. package/dist/phaser/display/webgl/sprite_batch.d.ts.map +1 -1
  90. package/dist/phaser/display/webgl/stencil_manager.d.ts.map +1 -1
  91. package/dist/phaser/display/webgl/texture.d.ts +18 -33
  92. package/dist/phaser/display/webgl/texture.d.ts.map +1 -1
  93. package/dist/phaser/display/webgl/texture_util.d.ts.map +1 -1
  94. package/dist/phaser/display/webgl/util.d.ts.map +1 -1
  95. package/dist/phaser/geom/circle.d.ts.map +1 -1
  96. package/dist/phaser/geom/ellipse.d.ts.map +1 -1
  97. package/dist/phaser/geom/line.d.ts.map +1 -1
  98. package/dist/phaser/geom/matrix.d.ts +1 -1
  99. package/dist/phaser/geom/matrix.d.ts.map +1 -1
  100. package/dist/phaser/geom/point.d.ts.map +1 -1
  101. package/dist/phaser/geom/polygon.d.ts.map +1 -1
  102. package/dist/phaser/geom/rectangle.d.ts.map +1 -1
  103. package/dist/phaser/geom/rounded_rectangle.d.ts.map +1 -1
  104. package/dist/phaser/geom/util/circle.d.ts.map +1 -1
  105. package/dist/phaser/geom/util/ellipse.d.ts.map +1 -1
  106. package/dist/phaser/geom/util/line.d.ts +5 -4
  107. package/dist/phaser/geom/util/line.d.ts.map +1 -1
  108. package/dist/phaser/geom/util/matrix.d.ts.map +1 -1
  109. package/dist/phaser/geom/util/point.d.ts.map +1 -1
  110. package/dist/phaser/geom/util/polygon.d.ts.map +1 -1
  111. package/dist/phaser/geom/util/rectangle.d.ts +10 -1
  112. package/dist/phaser/geom/util/rectangle.d.ts.map +1 -1
  113. package/dist/phaser/geom/util/rounded_rectangle.d.ts.map +1 -1
  114. package/package.json +1 -1
  115. package/src/phaser/core/animation.ts +20 -20
  116. package/src/phaser/core/animation_manager.ts +27 -24
  117. package/src/phaser/core/animation_parser.ts +2 -2
  118. package/src/phaser/core/array_set.ts +7 -7
  119. package/src/phaser/core/cache.test.ts +201 -0
  120. package/src/phaser/core/cache.ts +174 -130
  121. package/src/phaser/core/device_util.ts +13 -13
  122. package/src/phaser/core/dom.ts +26 -22
  123. package/src/phaser/core/factory.ts +6 -6
  124. package/src/phaser/core/game.ts +10 -10
  125. package/src/phaser/core/input.ts +36 -38
  126. package/src/phaser/core/input_handler.ts +90 -93
  127. package/src/phaser/core/input_mouse.ts +19 -19
  128. package/src/phaser/core/input_mspointer.ts +25 -28
  129. package/src/phaser/core/input_pointer.ts +37 -28
  130. package/src/phaser/core/input_touch.ts +17 -17
  131. package/src/phaser/core/loader.ts +132 -92
  132. package/src/phaser/core/loader_parser.test.ts +115 -0
  133. package/src/phaser/core/loader_parser.ts +66 -38
  134. package/src/phaser/core/raf.ts +3 -3
  135. package/src/phaser/core/scale_manager.ts +86 -67
  136. package/src/phaser/core/scene.ts +7 -7
  137. package/src/phaser/core/scene_manager.ts +16 -16
  138. package/src/phaser/core/signal.ts +29 -19
  139. package/src/phaser/core/signal_binding.ts +4 -4
  140. package/src/phaser/core/sound.ts +45 -33
  141. package/src/phaser/core/sound_manager.ts +72 -73
  142. package/src/phaser/core/sound_sprite.ts +1 -1
  143. package/src/phaser/core/stage.ts +7 -7
  144. package/src/phaser/core/time.ts +16 -16
  145. package/src/phaser/core/timer.ts +36 -26
  146. package/src/phaser/core/tween.ts +29 -29
  147. package/src/phaser/core/tween_data.ts +41 -37
  148. package/src/phaser/core/tween_manager.ts +50 -43
  149. package/src/phaser/core/world.ts +1 -1
  150. package/src/phaser/display/bitmap_text.test.ts +212 -0
  151. package/src/phaser/display/bitmap_text.ts +52 -37
  152. package/src/phaser/display/button.ts +13 -13
  153. package/src/phaser/display/canvas/buffer.ts +3 -3
  154. package/src/phaser/display/canvas/graphics.ts +3 -3
  155. package/src/phaser/display/canvas/masker.ts +2 -2
  156. package/src/phaser/display/canvas/pool.ts +6 -6
  157. package/src/phaser/display/canvas/renderer.ts +6 -6
  158. package/src/phaser/display/canvas/tinter.ts +39 -8
  159. package/src/phaser/display/canvas/util.ts +15 -9
  160. package/src/phaser/display/display_object.ts +38 -38
  161. package/src/phaser/display/graphics.ts +30 -30
  162. package/src/phaser/display/graphics_data.ts +1 -1
  163. package/src/phaser/display/graphics_data_util.ts +1 -1
  164. package/src/phaser/display/group.ts +16 -16
  165. package/src/phaser/display/image.ts +30 -26
  166. package/src/phaser/display/sprite_util.ts +12 -4
  167. package/src/phaser/display/text.ts +55 -55
  168. package/src/phaser/display/webgl/abstract_filter.ts +1 -1
  169. package/src/phaser/display/webgl/base_texture.ts +22 -18
  170. package/src/phaser/display/webgl/blend_manager.ts +3 -3
  171. package/src/phaser/display/webgl/earcut.ts +3 -3
  172. package/src/phaser/display/webgl/fast_sprite_batch.ts +26 -24
  173. package/src/phaser/display/webgl/filter_manager.ts +7 -7
  174. package/src/phaser/display/webgl/filter_texture.ts +3 -3
  175. package/src/phaser/display/webgl/graphics.ts +43 -41
  176. package/src/phaser/display/webgl/graphics_data.ts +2 -2
  177. package/src/phaser/display/webgl/mask_manager.ts +2 -2
  178. package/src/phaser/display/webgl/render_texture.ts +6 -6
  179. package/src/phaser/display/webgl/renderer.ts +20 -25
  180. package/src/phaser/display/webgl/shader/normal.ts +3 -3
  181. package/src/phaser/display/webgl/shader_manager.ts +2 -2
  182. package/src/phaser/display/webgl/sprite_batch.ts +9 -9
  183. package/src/phaser/display/webgl/stencil_manager.ts +4 -4
  184. package/src/phaser/display/webgl/texture.ts +23 -42
  185. package/src/phaser/display/webgl/texture_util.ts +2 -2
  186. package/src/phaser/display/webgl/util.ts +11 -7
  187. package/src/phaser/geom/circle.ts +21 -21
  188. package/src/phaser/geom/ellipse.ts +7 -7
  189. package/src/phaser/geom/line.ts +29 -29
  190. package/src/phaser/geom/matrix.ts +13 -13
  191. package/src/phaser/geom/point.ts +36 -30
  192. package/src/phaser/geom/polygon.ts +6 -6
  193. package/src/phaser/geom/rectangle.ts +42 -42
  194. package/src/phaser/geom/rounded_rectangle.ts +2 -2
  195. package/src/phaser/geom/util/circle.ts +7 -7
  196. package/src/phaser/geom/util/ellipse.ts +1 -1
  197. package/src/phaser/geom/util/line.ts +13 -5
  198. package/src/phaser/geom/util/matrix.ts +3 -3
  199. package/src/phaser/geom/util/point.ts +20 -20
  200. package/src/phaser/geom/util/polygon.ts +1 -1
  201. package/src/phaser/geom/util/rectangle.ts +34 -17
  202. package/src/phaser/geom/util/rounded_rectangle.ts +1 -1
@@ -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!: any;
41
- public _add!: any;
42
- public easeMap!: any;
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.
@@ -111,9 +114,9 @@ export class TweenManager {
111
114
  * Remove all tweens from the manager.
112
115
  * This method removes all active and pending tweens.
113
116
  */
114
- public removeAll() {
115
- for (let i = 0; i < this._tweens.length; i += 1) {
116
- this._tweens[i].pendingDelete = true;
117
+ public removeAll(): void {
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: any, children: any[] | null = null) {
127
- let i;
128
- let len;
129
+ public removeFrom(obj: TweenTarget, children: object[] | null = null): void {
129
130
  if (Array.isArray(obj)) {
130
- for (i = 0, len = obj.length; i < len; i += 1) {
131
- this.removeFrom(obj[i]);
131
+ for (const entry of obj) {
132
+ this.removeFrom(entry);
132
133
  }
133
- } else if (obj.type === GROUP && children) {
134
- for (i = 0, len = obj.children.length; i < len; i += 1) {
135
- this.removeFrom(obj.children[i]);
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
- } else {
138
- for (i = 0, len = this._tweens.length; i < len; i += 1) {
139
- if (obj === this._tweens[i].target) {
140
- this.remove(this._tweens[i]);
141
- }
141
+ return;
142
+ }
143
+ for (const tween of this._tweens.slice()) {
144
+ if (obj === tween.target) {
145
+ this.remove(tween);
142
146
  }
143
- for (i = 0, len = this._add.length; i < len; i += 1) {
144
- if (obj === this._add[i].target) {
145
- this.remove(this._add[i]);
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
  }
@@ -152,7 +156,7 @@ export class TweenManager {
152
156
  * Add a tween to the manager.
153
157
  * @param {Tween} tween - The tween to add.
154
158
  */
155
- public add(tween: Tween) {
159
+ public add(tween: Tween): void {
156
160
  tween.manager = this;
157
161
  this._add.push(tween);
158
162
  }
@@ -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: any) {
169
+ public create(object: TweenTarget): Tween {
166
170
  return new Tween(object, this.game, this);
167
171
  }
168
172
 
@@ -170,14 +174,17 @@ export class TweenManager {
170
174
  * Remove a tween from the manager.
171
175
  * @param {Tween | null | undefined} tween - The tween to remove.
172
176
  */
173
- public remove(tween: Tween | null | undefined) {
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].pendingDelete = true;
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].pendingDelete = true;
187
+ this._add[i]!.pendingDelete = true;
181
188
  }
182
189
  }
183
190
  }
@@ -186,7 +193,7 @@ export class TweenManager {
186
193
  * Update all tweens managed by this manager.
187
194
  * @returns {boolean} True if any tweens were updated, false otherwise.
188
195
  */
189
- public update() {
196
+ public update(): boolean {
190
197
  const addTweens = this._add.length;
191
198
  let numTweens = this._tweens.length;
192
199
  if (numTweens === 0 && addTweens === 0) {
@@ -194,7 +201,7 @@ export class TweenManager {
194
201
  }
195
202
  let i = 0;
196
203
  while (i < numTweens) {
197
- if (this._tweens[i].update(this.game.time.time)) {
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,16 +222,16 @@ 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 (this._tweens as Tween[]).some((tween: Tween) => tween.target === object);
225
+ return this._tweens.some((tween: Tween): boolean => tween.target === object);
219
226
  }
220
227
 
221
228
  /**
222
229
  * Pause all tweens managed by this manager.
223
230
  * This method pauses all active tweens.
224
231
  */
225
- public _pauseAll() {
226
- for (let i = this._tweens.length - 1; i >= 0; i -= 1) {
227
- this._tweens[i]._pause();
232
+ public _pauseAll(): void {
233
+ for (const tween of this._tweens) {
234
+ tween._pause();
228
235
  }
229
236
  }
230
237
 
@@ -232,9 +239,9 @@ export class TweenManager {
232
239
  * Resume all tweens managed by this manager.
233
240
  * This method resumes all paused tweens.
234
241
  */
235
- public _resumeAll() {
236
- for (let i = this._tweens.length - 1; i >= 0; i -= 1) {
237
- this._tweens[i]._resume();
242
+ public _resumeAll(): void {
243
+ for (const tween of this._tweens) {
244
+ tween._resume();
238
245
  }
239
246
  }
240
247
 
@@ -242,9 +249,9 @@ export class TweenManager {
242
249
  * Pause all tweens managed by this manager.
243
250
  * This method pauses all active tweens.
244
251
  */
245
- public pauseAll() {
246
- for (let i = this._tweens.length - 1; i >= 0; i -= 1) {
247
- this._tweens[i].pause();
252
+ public pauseAll(): void {
253
+ for (const tween of this._tweens) {
254
+ tween.pause();
248
255
  }
249
256
  }
250
257
 
@@ -252,9 +259,9 @@ export class TweenManager {
252
259
  * Resume all tweens managed by this manager.
253
260
  * This method resumes all paused tweens.
254
261
  */
255
- public resumeAll() {
256
- for (let i = this._tweens.length - 1; i >= 0; i -= 1) {
257
- this._tweens[i].resume(true);
262
+ public resumeAll(): void {
263
+ for (const tween of this._tweens) {
264
+ tween.resume();
258
265
  }
259
266
  }
260
267
  }
@@ -14,7 +14,7 @@ export class World extends Group {
14
14
  * Initialize the world.
15
15
  * This method is called when the game boots and sets up the world.
16
16
  */
17
- public boot() {
17
+ public boot(): void {
18
18
  this.game.stage.addChild(this);
19
19
  }
20
20
  }
@@ -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!: any;
28
+ public _data!: BitmapFontCacheEntry | null;
18
29
  public _font!: string;
19
30
  public _fontSize!: number;
20
31
  public _align!: string;
@@ -52,7 +63,7 @@ export class BitmapText extends DisplayObject {
52
63
  /**
53
64
  * Destroys this bitmap text and cleans up resources.
54
65
  */
55
- public override destroy() {
66
+ public override destroy(): void {
56
67
  this._prevAnchor = null!;
57
68
  this._glyphs = null!;
58
69
  this._text = null!;
@@ -63,7 +74,7 @@ export class BitmapText extends DisplayObject {
63
74
  /**
64
75
  * Called before the update cycle for this bitmap text.
65
76
  */
66
- public override preUpdate() {
77
+ public override preUpdate(): void {
67
78
  if (this.pendingDestroy) {
68
79
  this.destroy();
69
80
  return;
@@ -86,7 +97,7 @@ export class BitmapText extends DisplayObject {
86
97
  * Sets the text to display.
87
98
  * @param {string} text - The new text to display.
88
99
  */
89
- public setText(text: string) {
100
+ public setText(text: string): void {
90
101
  this.text = text;
91
102
  }
92
103
 
@@ -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: any, scale: number, text: string) {
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 && charData.kerning[prevCharCode] ? charData.kerning[prevCharCode] : 0;
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.width + charData.xOffset) * scale;
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
@@ -164,8 +178,8 @@ export class BitmapText extends DisplayObject {
164
178
  * @param {string} replace - The character to use for replacement of invalid characters (default: '').
165
179
  * @returns {string} The cleaned text.
166
180
  */
167
- public cleanText(text: string, replace = '') {
168
- const data = this._data.font;
181
+ public cleanText(text: string, replace = ''): string {
182
+ const data = this._data?.font;
169
183
  if (!data) {
170
184
  return '';
171
185
  }
@@ -189,27 +203,27 @@ export class BitmapText extends DisplayObject {
189
203
  /**
190
204
  * Updates the internal text rendering based on current properties and content.
191
205
  */
192
- public updateText() {
193
- const data = this._data.font;
206
+ public updateText(): void {
207
+ const data = this._data?.font;
194
208
  if (!data) {
195
209
  return;
196
210
  }
197
- let text: any = this.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 line: any = { end: text.length === 0 };
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
- } while (line.end === false);
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;
@@ -260,7 +277,7 @@ export class BitmapText extends DisplayObject {
260
277
  * Removes unused glyphs from the pool and returns the number removed.
261
278
  * @returns {number} The number of glyphs that were removed from the pool.
262
279
  */
263
- public purgeGlyphs() {
280
+ public purgeGlyphs(): number {
264
281
  const len = this._glyphs.length;
265
282
  const kept = [];
266
283
  for (const glyph of this._glyphs) {
@@ -295,7 +312,7 @@ export class BitmapText extends DisplayObject {
295
312
  * @param {number} _position - The character position to apply the color to.
296
313
  * @returns {BitmapText} This bitmap text instance for chaining.
297
314
  */
298
- public addColor(value: string, _position: number) {
315
+ public addColor(value: string, _position: number): this {
299
316
  const color = typeof value === 'string' ? Number.parseInt(value.replace('#', ''), 16) : value;
300
317
  if (color !== this._tint) {
301
318
  this._tint = color;
@@ -308,7 +325,7 @@ export class BitmapText extends DisplayObject {
308
325
  * Gets the text alignment property.
309
326
  * @returns {string} The current text alignment (left, center, right).
310
327
  */
311
- public get align() {
328
+ public get align(): string {
312
329
  return this._align;
313
330
  }
314
331
 
@@ -327,7 +344,7 @@ export class BitmapText extends DisplayObject {
327
344
  * Gets the tint color of this bitmap text.
328
345
  * @returns {number} The current tint color in RGB format.
329
346
  */
330
- public get tint() {
347
+ public get tint(): number {
331
348
  return this._tint;
332
349
  }
333
350
 
@@ -346,7 +363,7 @@ export class BitmapText extends DisplayObject {
346
363
  * Gets the fill color of this bitmap text as a hex string.
347
364
  * @returns {string} The current fill color in hex format.
348
365
  */
349
- public get fill() {
366
+ public get fill(): string {
350
367
  if (typeof this.tint === 'number') {
351
368
  let colorStr = this.tint.toString(16);
352
369
  while (colorStr.length < 6) {
@@ -369,7 +386,7 @@ export class BitmapText extends DisplayObject {
369
386
  * Gets the font key used by this bitmap text.
370
387
  * @returns {string} The current font key.
371
388
  */
372
- public get font() {
389
+ public get font(): string {
373
390
  return this._font;
374
391
  }
375
392
 
@@ -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: any) {
402
- value = Math.trunc(Number(value));
403
- if (value !== this._fontSize && value > 0) {
404
- this._fontSize = value;
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
  }
@@ -430,7 +447,7 @@ export class BitmapText extends DisplayObject {
430
447
  * Gets the maximum width of this bitmap text.
431
448
  * @returns {number} The current maximum width.
432
449
  */
433
- public get maxWidth() {
450
+ public get maxWidth(): number {
434
451
  return this._maxWidth;
435
452
  }
436
453
 
@@ -449,8 +466,8 @@ export class BitmapText extends DisplayObject {
449
466
  * Gets whether smoothing is enabled for this bitmap text's font.
450
467
  * @returns {boolean} True if smoothing is enabled, false otherwise.
451
468
  */
452
- public get smoothed() {
453
- return !this._data.base.scaleMode;
469
+ public get smoothed(): boolean {
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 (value) {
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
  }