@vunk/plus 1.6.8 → 1.6.10

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 (161) hide show
  1. package/components/SendOutlined.mjs +8 -6
  2. package/components/SharedSystems.mjs +290 -72
  3. package/components/WebGLRenderer.mjs +32 -20
  4. package/components/WebGPURenderer.mjs +48 -10
  5. package/components/agent-chat-provider/index.d.ts +7 -0
  6. package/components/agent-chat-provider/index.mjs +6 -3
  7. package/components/agent-chat-provider/src/api.d.ts +3 -0
  8. package/components/agent-chat-provider/src/const-roles.d.ts +8 -0
  9. package/components/agent-chat-provider/src/const.d.ts +1 -0
  10. package/components/agent-chat-provider/src/index.vue.d.ts +22 -0
  11. package/components/agent-chat-provider/src/types.d.ts +50 -0
  12. package/components/agent-chat-provider/src/use.d.ts +5 -0
  13. package/components/avatar/index.d.ts +4 -0
  14. package/components/avatar/index.mjs +2 -2
  15. package/components/avatar/src/ctx.d.ts +6 -0
  16. package/components/avatar/src/index.vue.d.ts +180 -0
  17. package/components/avatar/src/types.d.ts +1 -0
  18. package/components/broadcasting-markdown/index.d.ts +7 -0
  19. package/components/broadcasting-markdown/index.mjs +78 -158
  20. package/components/broadcasting-markdown/src/const.d.ts +13 -0
  21. package/components/broadcasting-markdown/src/ctx.d.ts +129 -0
  22. package/components/broadcasting-markdown/src/howler-speech.vue.d.ts +104 -0
  23. package/components/broadcasting-markdown/src/index.vue.d.ts +439 -0
  24. package/components/broadcasting-markdown/src/paragraph.vue.d.ts +79 -0
  25. package/components/broadcasting-markdown/src/speech-error.vue.d.ts +67 -0
  26. package/components/broadcasting-markdown/src/types.d.ts +26 -0
  27. package/components/broadcasting-markdown/src/use.d.ts +21 -0
  28. package/components/browserAll.mjs +333 -562
  29. package/components/bubble-list/index.d.ts +5 -0
  30. package/components/bubble-list/index.mjs +91 -72
  31. package/components/bubble-list/src/ctx.d.ts +32 -0
  32. package/components/bubble-list/src/index.vue.d.ts +810 -0
  33. package/components/bubble-list/src/types.d.ts +4 -0
  34. package/components/bubble-templates/index.d.ts +4 -0
  35. package/components/bubble-templates/index.mjs +83 -68
  36. package/components/bubble-templates/src/index.vue.d.ts +595 -0
  37. package/components/bubble-templates/src/types.d.ts +40 -0
  38. package/components/chat-independent/index.d.ts +4 -0
  39. package/components/chat-independent/index.mjs +136 -115
  40. package/components/chat-independent/src/const.d.ts +13 -0
  41. package/components/chat-independent/src/ctx.d.ts +21 -0
  42. package/components/chat-independent/src/index.vue.d.ts +2429 -0
  43. package/components/chat-independent/src/types.d.ts +13 -0
  44. package/components/colorToUniform.mjs +2 -0
  45. package/components/index.d.ts +0 -0
  46. package/components/index2.mjs +11896 -8381
  47. package/components/init.mjs +1724 -998
  48. package/components/lottie-animation/index.d.ts +4 -0
  49. package/components/lottie-animation/index.mjs +2 -2
  50. package/components/lottie-animation/src/ctx.d.ts +10 -0
  51. package/components/lottie-animation/src/index.vue.d.ts +21 -0
  52. package/components/lottie-animation/src/types.d.ts +4 -0
  53. package/components/metahuman-broadcasting/index.d.ts +5 -0
  54. package/components/metahuman-broadcasting/index.mjs +57 -40
  55. package/components/metahuman-broadcasting/src/ctx.d.ts +57 -0
  56. package/components/metahuman-broadcasting/src/index.vue.d.ts +885 -0
  57. package/components/metahuman-broadcasting/src/types.d.ts +1 -0
  58. package/components/monaco-editor/index.d.ts +5 -0
  59. package/components/monaco-editor/index.mjs +2 -2
  60. package/components/monaco-editor/src/ctx.d.ts +23 -0
  61. package/components/monaco-editor/src/index.vue.d.ts +40 -0
  62. package/components/monaco-editor/src/types.d.ts +1 -0
  63. package/components/monaco-environment/index.d.ts +4 -0
  64. package/components/monaco-environment/index.mjs +1 -1
  65. package/components/monaco-environment/src/ctx.d.ts +8 -0
  66. package/components/monaco-environment/src/index.vue.d.ts +2 -0
  67. package/components/monaco-environment/src/types.d.ts +1 -0
  68. package/components/pixi-frame/index.d.ts +11 -0
  69. package/components/pixi-frame/index.mjs +2 -2
  70. package/components/pixi-frame/src/apng.vue.d.ts +161 -0
  71. package/components/pixi-frame/src/bitmap.vue.d.ts +158 -0
  72. package/components/pixi-frame/src/core.vue.d.ts +173 -0
  73. package/components/pixi-frame/src/ctx.d.ts +55 -0
  74. package/components/pixi-frame/src/index.vue.d.ts +591 -0
  75. package/components/pixi-frame/src/provider.vue.d.ts +2 -0
  76. package/components/pixi-frame/src/types.d.ts +17 -0
  77. package/components/pixi-frame/src/use.d.ts +16 -0
  78. package/components/pixi-frame/src/useSprite.d.ts +12 -0
  79. package/components/pixi-frame/src/video.vue.d.ts +159 -0
  80. package/components/pixi-frame/src/view.vue.d.ts +29 -0
  81. package/components/recorder-button/index.d.ts +4 -0
  82. package/components/recorder-button/index.mjs +2 -2
  83. package/components/recorder-button/src/ctx.d.ts +41 -0
  84. package/components/recorder-button/src/index.vue.d.ts +333 -0
  85. package/components/recorder-button/src/speech-to-text.d.ts +1 -0
  86. package/components/recorder-button/src/types.d.ts +7 -0
  87. package/components/routes-menu-content/index.d.ts +4 -0
  88. package/components/routes-menu-content/index.mjs +5 -5
  89. package/components/routes-menu-content/src/ctx.d.ts +17 -0
  90. package/components/routes-menu-content/src/index.vue.d.ts +350 -0
  91. package/components/routes-menu-content/src/item/core/ctx.d.ts +18 -0
  92. package/components/routes-menu-content/src/item/core/index.vue.d.ts +185 -0
  93. package/components/routes-menu-content/src/item/ctx.d.ts +14 -0
  94. package/components/routes-menu-content/src/item/index.vue.d.ts +316 -0
  95. package/components/routes-menu-content/src/types.d.ts +1 -0
  96. package/components/sender/index.d.ts +4 -0
  97. package/components/sender/index.mjs +180 -86
  98. package/components/sender/src/SenderHeader.vue.d.ts +270 -0
  99. package/components/sender/src/ctx.d.ts +64 -0
  100. package/components/sender/src/index.vue.d.ts +779 -0
  101. package/components/sender/src/types.d.ts +1 -0
  102. package/components/speech-button/index.d.ts +4 -0
  103. package/components/speech-button/index.mjs +13 -6
  104. package/components/speech-button/src/LoadingIcon.vue.d.ts +2 -0
  105. package/components/speech-button/src/RecordingIcon.vue.d.ts +2 -0
  106. package/components/speech-button/src/ctx.d.ts +10 -0
  107. package/components/speech-button/src/index.vue.d.ts +22 -0
  108. package/components/speech-button/src/types.d.ts +1 -0
  109. package/components/table-columns/index.d.ts +5 -0
  110. package/components/table-columns/index.mjs +6 -3
  111. package/components/table-columns/src/ctx.d.ts +59 -0
  112. package/components/table-columns/src/el-ctx.d.ts +57 -0
  113. package/components/table-columns/src/index.vue.d.ts +266 -0
  114. package/components/table-columns/src/types.d.ts +21 -0
  115. package/components/tables-v1/index.d.ts +5 -0
  116. package/components/tables-v1/index.mjs +3 -3
  117. package/components/tables-v1/src/ctx.d.ts +280 -0
  118. package/components/tables-v1/src/el-ctx.d.ts +262 -0
  119. package/components/tables-v1/src/index.vue.d.ts +3054 -0
  120. package/components/tables-v1/src/types.d.ts +3 -0
  121. package/components/tables-v1-checkbox/index.d.ts +5 -0
  122. package/components/tables-v1-checkbox/index.mjs +33 -269
  123. package/components/tables-v1-checkbox/src/ctx.d.ts +313 -0
  124. package/components/tables-v1-checkbox/src/index.vue.d.ts +3831 -0
  125. package/components/tables-v1-checkbox/src/types.d.ts +1 -0
  126. package/components/text/index.d.ts +4 -0
  127. package/components/text/index.mjs +52 -42
  128. package/components/text/src/ctx.d.ts +341 -0
  129. package/components/text/src/index.vue.d.ts +4593 -0
  130. package/components/text/src/types.d.ts +1 -0
  131. package/components/typing-markdown/index.d.ts +4 -0
  132. package/components/typing-markdown/index.mjs +2 -2
  133. package/components/typing-markdown/src/core.d.ts +2 -0
  134. package/components/typing-markdown/src/ctx.d.ts +41 -0
  135. package/components/typing-markdown/src/index.vue.d.ts +75 -0
  136. package/components/typing-markdown/src/types.d.ts +1 -0
  137. package/components/vditor/index.d.ts +5 -0
  138. package/components/vditor/index.mjs +9 -5
  139. package/components/vditor/src/ctx.d.ts +38 -0
  140. package/components/vditor/src/handle-fullscreen.vue.d.ts +18 -0
  141. package/components/vditor/src/handle-value.vue.d.ts +18 -0
  142. package/components/vditor/src/index.vue.d.ts +143 -0
  143. package/components/vditor/src/types.d.ts +9 -0
  144. package/components/vditor/src/use.d.ts +4 -0
  145. package/components/webworkerAll.mjs +1 -1
  146. package/composables/el-breakpoints/index.d.ts +5 -5
  147. package/composables/recorder/index.mjs +191 -288
  148. package/icons/end/index.mjs +3 -3
  149. package/icons/end/src/avatar.d.ts +2 -11
  150. package/icons/end/src/icon.d.ts +2 -56
  151. package/icons/keyboard/index.mjs +3 -3
  152. package/icons/keyboard/src/avatar.d.ts +2 -11
  153. package/icons/keyboard/src/icon.d.ts +2 -56
  154. package/icons/start/index.mjs +3 -3
  155. package/icons/start/src/avatar.d.ts +2 -11
  156. package/icons/start/src/icon.d.ts +2 -56
  157. package/icons/voice/index.mjs +2 -2
  158. package/icons/voice/src/avatar.d.ts +2 -11
  159. package/icons/voice/src/icon.d.ts +2 -56
  160. package/package.json +1 -1
  161. package/shared/audioToFrames/index.mjs +10 -2
@@ -1,10 +1,9 @@
1
- import { E as ExtensionType, U as UPDATE_PRIORITY, T as Ticker, k as UniformGroup, c as BindGroup, w as warn, H as TexturePool, t as Texture, P as Point, a3 as Geometry, R as RendererType, F as Bounds, M as Matrix, a9 as ViewContainer, aa as GraphicsContext, a0 as deprecation, a1 as v8_0_0, b as Buffer, B as BufferUsage, Z as Color, ab as TextStyle, ac as generateTextStyleKey, y as BigPool, ad as BatchableGraphics, ae as getAdjustedBlendModeBlend, K as getAttributeInfoFromFormat, af as ViewableBuffer, s as Shader, u as GlProgram, G as GpuProgram, ag as TextureStyle, m as compileHighShaderGpuProgram, p as roundPixelsBit, a4 as compileHighShaderGlProgram, a7 as roundPixelsBitGl, q as getMaxTexturesPerBatch, n as colorBit, o as generateTextureBatchBit, a5 as colorBitGl, a6 as generateTextureBatchBitGl, a8 as getBatchSamplersUniformGroup, ah as BitmapFontManager, ai as getBitmapTextLayout, aj as Cache, ak as updateQuadBounds, D as DOMAdapter, l as CanvasPool, O as Rectangle, al as CanvasTextMetrics, am as nextPow2, an as fontStringFromTextStyle, ao as getCanvasFillStyle, e as extensions, ap as GraphicsContextSystem } from './index2.mjs';
1
+ import { E as ExtensionType, U as UPDATE_PRIORITY, T as Ticker, a2 as Geometry, k as UniformGroup, c as BindGroup, z as TexturePool, s as Texture, M as Matrix, R as RendererType, w as warn, F as Bounds, a9 as ViewContainer, aa as GraphicsContext, $ as deprecation, a0 as v8_0_0, b as Buffer, B as BufferUsage, D as DOMAdapter, ab as nextPow2, N as Rectangle, ac as CanvasTextMetrics, l as CanvasPool, ad as fontStringFromTextStyle, Y as Color, ae as getCanvasFillStyle, af as TextStyle, x as BigPool, ag as BatchableGraphics, ah as getAdjustedBlendModeBlend, J as getAttributeInfoFromFormat, ai as ViewableBuffer, q as Shader, t as GlProgram, G as GpuProgram, aj as TextureStyle, m as compileHighShaderGpuProgram, a4 as compileHighShaderGlProgram, p as roundPixelsBit, a7 as roundPixelsBitGl, n as colorBit, o as generateTextureBatchBit, a5 as colorBitGl, a6 as generateTextureBatchBitGl, a8 as getBatchSamplersUniformGroup, ak as BitmapFontManager, al as getBitmapTextLayout, am as Cache, an as updateQuadBounds, e as extensions, ao as GraphicsContextSystem } from './index2.mjs';
2
2
  import { S as State, c as color32BitToUniform, a as localUniformBit, b as localUniformBitGl, B as BatchableSprite } from './colorToUniform.mjs';
3
3
 
4
4
  class ResizePlugin {
5
5
  /**
6
6
  * Initialize the plugin with scope of application instance
7
- * @static
8
7
  * @private
9
8
  * @param {object} [options] - See application options
10
9
  */
@@ -12,13 +11,6 @@ class ResizePlugin {
12
11
  Object.defineProperty(
13
12
  this,
14
13
  "resizeTo",
15
- /**
16
- * The HTML element or window to automatically resize the
17
- * renderer's view element to match width and height.
18
- * @member {Window|HTMLElement}
19
- * @name resizeTo
20
- * @memberof app.Application#
21
- */
22
14
  {
23
15
  set(dom) {
24
16
  globalThis.removeEventListener("resize", this.queueResize);
@@ -70,7 +62,6 @@ class ResizePlugin {
70
62
  }
71
63
  /**
72
64
  * Clean up the ticker, scoped to application
73
- * @static
74
65
  * @private
75
66
  */
76
67
  static destroy() {
@@ -88,7 +79,6 @@ ResizePlugin.extension = ExtensionType.Application;
88
79
  class TickerPlugin {
89
80
  /**
90
81
  * Initialize the plugin with scope of application instance
91
- * @static
92
82
  * @private
93
83
  * @param {object} [options] - See application options
94
84
  */
@@ -129,7 +119,6 @@ class TickerPlugin {
129
119
  }
130
120
  /**
131
121
  * Clean up the ticker, scoped to application.
132
- * @static
133
122
  * @private
134
123
  */
135
124
  static destroy() {
@@ -212,6 +201,55 @@ const quadGeometry = new Geometry({
212
201
  },
213
202
  indexBuffer: new Uint32Array([0, 1, 2, 0, 2, 3])
214
203
  });
204
+ class FilterData {
205
+ constructor() {
206
+ /**
207
+ * Indicates whether the filter should be skipped.
208
+ * @type {boolean}
209
+ */
210
+ this.skip = false;
211
+ /**
212
+ * The texture to which the filter is applied.
213
+ * @type {Texture}
214
+ */
215
+ this.inputTexture = null;
216
+ /**
217
+ * The back texture used for blending, if required.
218
+ * @type {Texture | null}
219
+ */
220
+ this.backTexture = null;
221
+ /**
222
+ * The list of filters to be applied.
223
+ * @type {Filter[]}
224
+ */
225
+ this.filters = null;
226
+ /**
227
+ * The bounds of the filter area.
228
+ * @type {Bounds}
229
+ */
230
+ this.bounds = new Bounds();
231
+ /**
232
+ * The container to which the filter is applied.
233
+ * @type {Container}
234
+ */
235
+ this.container = null;
236
+ /**
237
+ * Indicates whether blending is required for the filter.
238
+ * @type {boolean}
239
+ */
240
+ this.blendRequired = false;
241
+ /**
242
+ * The render surface where the output of the filter is rendered.
243
+ * @type {RenderSurface}
244
+ */
245
+ this.outputRenderSurface = null;
246
+ /**
247
+ * The global frame of the filter area.
248
+ * @type {{ x: number, y: number, width: number, height: number }}
249
+ */
250
+ this.globalFrame = { x: 0, y: 0, width: 0, height: 0 };
251
+ }
252
+ }
215
253
  class FilterSystem {
216
254
  constructor(renderer) {
217
255
  this._filterStackIndex = 0;
@@ -234,147 +272,143 @@ class FilterSystem {
234
272
  get activeBackTexture() {
235
273
  return this._activeFilterData?.backTexture;
236
274
  }
275
+ /**
276
+ * Pushes a filter instruction onto the filter stack.
277
+ * @param instruction - The instruction containing the filter effect and container.
278
+ * @internal
279
+ */
237
280
  push(instruction) {
238
281
  const renderer = this.renderer;
239
282
  const filters = instruction.filterEffect.filters;
240
- if (!this._filterStack[this._filterStackIndex]) {
241
- this._filterStack[this._filterStackIndex] = this._getFilterData();
242
- }
243
- const filterData = this._filterStack[this._filterStackIndex];
244
- this._filterStackIndex++;
283
+ const filterData = this._pushFilterData();
284
+ filterData.skip = false;
285
+ filterData.filters = filters;
286
+ filterData.container = instruction.container;
287
+ filterData.outputRenderSurface = renderer.renderTarget.renderSurface;
288
+ const colorTextureSource = renderer.renderTarget.renderTarget.colorTexture.source;
289
+ const rootResolution = colorTextureSource.resolution;
290
+ const rootAntialias = colorTextureSource.antialias;
245
291
  if (filters.length === 0) {
246
292
  filterData.skip = true;
247
293
  return;
248
294
  }
249
295
  const bounds = filterData.bounds;
250
- if (instruction.renderables) {
251
- getGlobalRenderableBounds(instruction.renderables, bounds);
252
- } else if (instruction.filterEffect.filterArea) {
253
- bounds.clear();
254
- bounds.addRect(instruction.filterEffect.filterArea);
255
- bounds.applyMatrix(instruction.container.worldTransform);
256
- } else {
257
- instruction.container.getFastGlobalBounds(true, bounds);
258
- }
259
- if (instruction.container) {
260
- const renderGroup = instruction.container.renderGroup || instruction.container.parentRenderGroup;
261
- const filterFrameTransform = renderGroup.cacheToLocalTransform;
262
- if (filterFrameTransform) {
263
- bounds.applyMatrix(filterFrameTransform);
264
- }
265
- }
266
- const colorTextureSource = renderer.renderTarget.renderTarget.colorTexture.source;
267
- let resolution = Infinity;
268
- let padding = 0;
269
- let antialias = true;
270
- let blendRequired = false;
271
- let enabled = false;
272
- let clipToViewport = true;
273
- for (let i = 0; i < filters.length; i++) {
274
- const filter = filters[i];
275
- resolution = Math.min(resolution, filter.resolution === "inherit" ? colorTextureSource._resolution : filter.resolution);
276
- padding += filter.padding;
277
- if (filter.antialias === "off") {
278
- antialias = false;
279
- } else if (filter.antialias === "inherit") {
280
- antialias && (antialias = colorTextureSource.antialias);
281
- }
282
- if (!filter.clipToViewport) {
283
- clipToViewport = false;
284
- }
285
- const isCompatible = !!(filter.compatibleRenderers & renderer.type);
286
- if (!isCompatible) {
287
- enabled = false;
288
- break;
289
- }
290
- if (filter.blendRequired && !(renderer.backBuffer?.useBackBuffer ?? true)) {
291
- warn("Blend filter requires backBuffer on WebGL renderer to be enabled. Set `useBackBuffer: true` in the renderer options.");
292
- enabled = false;
293
- break;
294
- }
295
- enabled = filter.enabled || enabled;
296
- blendRequired || (blendRequired = filter.blendRequired);
297
- }
298
- if (!enabled) {
299
- filterData.skip = true;
296
+ this._calculateFilterArea(instruction, bounds);
297
+ this._calculateFilterBounds(filterData, renderer.renderTarget.rootViewPort, rootAntialias, rootResolution, 1);
298
+ if (filterData.skip) {
300
299
  return;
301
300
  }
302
- if (clipToViewport) {
303
- const viewPort = renderer.renderTarget.rootViewPort;
304
- const rootResolution = renderer.renderTarget.renderTarget.resolution;
305
- bounds.fitBounds(0, viewPort.width / rootResolution, 0, viewPort.height / rootResolution);
301
+ const previousFilterData = this._getPreviousFilterData();
302
+ const globalResolution = this._findFilterResolution(rootResolution);
303
+ let offsetX = 0;
304
+ let offsetY = 0;
305
+ if (previousFilterData) {
306
+ offsetX = previousFilterData.bounds.minX;
307
+ offsetY = previousFilterData.bounds.minY;
306
308
  }
307
- bounds.scale(resolution).ceil().scale(1 / resolution).pad(padding | 0);
308
- if (!bounds.isPositive) {
309
+ this._calculateGlobalFrame(
310
+ filterData,
311
+ offsetX,
312
+ offsetY,
313
+ globalResolution,
314
+ colorTextureSource.width,
315
+ colorTextureSource.height
316
+ );
317
+ this._setupFilterTextures(filterData, bounds, renderer, previousFilterData);
318
+ }
319
+ /**
320
+ * Applies filters to a texture.
321
+ *
322
+ * This method takes a texture and a list of filters, applies the filters to the texture,
323
+ * and returns the resulting texture.
324
+ * @param {object} params - The parameters for applying filters.
325
+ * @param {Texture} params.texture - The texture to apply filters to.
326
+ * @param {Filter[]} params.filters - The filters to apply.
327
+ * @returns {Texture} The resulting texture after all filters have been applied.
328
+ * @example
329
+ *
330
+ * ```ts
331
+ * // Create a texture and a list of filters
332
+ * const texture = new Texture(...);
333
+ * const filters = [new BlurFilter(), new ColorMatrixFilter()];
334
+ *
335
+ * // Apply the filters to the texture
336
+ * const resultTexture = filterSystem.applyToTexture({ texture, filters });
337
+ *
338
+ * // Use the resulting texture
339
+ * sprite.texture = resultTexture;
340
+ * ```
341
+ *
342
+ * Key Points:
343
+ * 1. padding is not currently supported here - so clipping may occur with filters that use padding.
344
+ * 2. If all filters are disabled or skipped, the original texture is returned.
345
+ */
346
+ generateFilteredTexture({ texture, filters }) {
347
+ const filterData = this._pushFilterData();
348
+ this._activeFilterData = filterData;
349
+ filterData.skip = false;
350
+ filterData.filters = filters;
351
+ const colorTextureSource = texture.source;
352
+ const rootResolution = colorTextureSource.resolution;
353
+ const rootAntialias = colorTextureSource.antialias;
354
+ if (filters.length === 0) {
309
355
  filterData.skip = true;
310
- return;
356
+ return texture;
311
357
  }
312
- filterData.skip = false;
313
- filterData.bounds = bounds;
314
- filterData.blendRequired = blendRequired;
315
- filterData.container = instruction.container;
316
- filterData.filterEffect = instruction.filterEffect;
317
- filterData.previousRenderSurface = renderer.renderTarget.renderSurface;
318
- filterData.inputTexture = TexturePool.getOptimalTexture(
358
+ const bounds = filterData.bounds;
359
+ bounds.addRect(texture.frame);
360
+ this._calculateFilterBounds(filterData, bounds.rectangle, rootAntialias, rootResolution, 0);
361
+ if (filterData.skip) {
362
+ return texture;
363
+ }
364
+ const globalResolution = rootResolution;
365
+ const offsetX = 0;
366
+ const offsetY = 0;
367
+ this._calculateGlobalFrame(
368
+ filterData,
369
+ offsetX,
370
+ offsetY,
371
+ globalResolution,
372
+ colorTextureSource.width,
373
+ colorTextureSource.height
374
+ );
375
+ filterData.outputRenderSurface = TexturePool.getOptimalTexture(
319
376
  bounds.width,
320
377
  bounds.height,
321
- resolution,
322
- antialias
378
+ filterData.resolution,
379
+ filterData.antialias
323
380
  );
324
- renderer.renderTarget.bind(filterData.inputTexture, true);
325
- renderer.globalUniforms.push({
326
- offset: bounds
327
- });
381
+ filterData.backTexture = Texture.EMPTY;
382
+ filterData.inputTexture = texture;
383
+ const renderer = this.renderer;
384
+ renderer.renderTarget.finishRenderPass();
385
+ this._applyFiltersToTexture(filterData, true);
386
+ const outputTexture = filterData.outputRenderSurface;
387
+ outputTexture.source.alphaMode = "premultiplied-alpha";
388
+ return outputTexture;
328
389
  }
390
+ /** @internal */
329
391
  pop() {
330
392
  const renderer = this.renderer;
331
- this._filterStackIndex--;
332
- const filterData = this._filterStack[this._filterStackIndex];
393
+ const filterData = this._popFilterData();
333
394
  if (filterData.skip) {
334
395
  return;
335
396
  }
336
- this._activeFilterData = filterData;
337
- const inputTexture = filterData.inputTexture;
338
- const bounds = filterData.bounds;
339
- let backTexture = Texture.EMPTY;
340
- renderer.renderTarget.finishRenderPass();
341
- if (filterData.blendRequired) {
342
- const previousBounds = this._filterStackIndex > 0 ? this._filterStack[this._filterStackIndex - 1].bounds : null;
343
- const renderTarget = renderer.renderTarget.getRenderTarget(filterData.previousRenderSurface);
344
- backTexture = this.getBackTexture(renderTarget, bounds, previousBounds);
345
- }
346
- filterData.backTexture = backTexture;
347
- const filters = filterData.filterEffect.filters;
348
- this._globalFilterBindGroup.setResource(inputTexture.source.style, 2);
349
- this._globalFilterBindGroup.setResource(backTexture.source, 3);
350
397
  renderer.globalUniforms.pop();
351
- if (filters.length === 1) {
352
- filters[0].apply(this, inputTexture, filterData.previousRenderSurface, false);
353
- TexturePool.returnTexture(inputTexture);
354
- } else {
355
- let flip = filterData.inputTexture;
356
- let flop = TexturePool.getOptimalTexture(
357
- bounds.width,
358
- bounds.height,
359
- flip.source._resolution,
360
- false
361
- );
362
- let i = 0;
363
- for (i = 0; i < filters.length - 1; ++i) {
364
- const filter = filters[i];
365
- filter.apply(this, flip, flop, true);
366
- const t = flip;
367
- flip = flop;
368
- flop = t;
369
- }
370
- filters[i].apply(this, flip, filterData.previousRenderSurface, false);
371
- TexturePool.returnTexture(flip);
372
- TexturePool.returnTexture(flop);
373
- }
398
+ renderer.renderTarget.finishRenderPass();
399
+ this._activeFilterData = filterData;
400
+ this._applyFiltersToTexture(filterData, false);
374
401
  if (filterData.blendRequired) {
375
- TexturePool.returnTexture(backTexture);
402
+ TexturePool.returnTexture(filterData.backTexture);
376
403
  }
404
+ TexturePool.returnTexture(filterData.inputTexture);
377
405
  }
406
+ /**
407
+ * Copies the last render surface to a texture.
408
+ * @param lastRenderSurface - The last render surface to copy from.
409
+ * @param bounds - The bounds of the area to copy.
410
+ * @param previousBounds - The previous bounds to use for offsetting the copy.
411
+ */
378
412
  getBackTexture(lastRenderSurface, bounds, previousBounds) {
379
413
  const backgroundResolution = lastRenderSurface.colorTexture.source._resolution;
380
414
  const backTexture = TexturePool.getOptimalTexture(
@@ -402,23 +436,144 @@ class FilterSystem {
402
436
  );
403
437
  return backTexture;
404
438
  }
439
+ /**
440
+ * Applies a filter to a texture.
441
+ * @param filter - The filter to apply.
442
+ * @param input - The input texture.
443
+ * @param output - The output render surface.
444
+ * @param clear - Whether to clear the output surface before applying the filter.
445
+ */
405
446
  applyFilter(filter, input, output, clear) {
406
447
  const renderer = this.renderer;
407
- const filterData = this._filterStack[this._filterStackIndex];
408
- const bounds = filterData.bounds;
409
- const offset = Point.shared;
410
- const previousRenderSurface = filterData.previousRenderSurface;
411
- const isFinalTarget = previousRenderSurface === output;
412
- let resolution = this.renderer.renderTarget.rootRenderTarget.colorTexture.source._resolution;
413
- let currentIndex = this._filterStackIndex - 1;
414
- while (currentIndex > 0 && this._filterStack[currentIndex].skip) {
415
- --currentIndex;
448
+ const filterData = this._activeFilterData;
449
+ const outputRenderSurface = filterData.outputRenderSurface;
450
+ const isFinalTarget = outputRenderSurface === output;
451
+ const rootResolution = renderer.renderTarget.rootRenderTarget.colorTexture.source._resolution;
452
+ const resolution = this._findFilterResolution(rootResolution);
453
+ let offsetX = 0;
454
+ let offsetY = 0;
455
+ if (isFinalTarget) {
456
+ const offset = this._findPreviousFilterOffset();
457
+ offsetX = offset.x;
458
+ offsetY = offset.y;
459
+ }
460
+ this._updateFilterUniforms(input, output, filterData, offsetX, offsetY, resolution, isFinalTarget, clear);
461
+ this._setupBindGroupsAndRender(filter, input, renderer);
462
+ }
463
+ /**
464
+ * Multiply _input normalized coordinates_ to this matrix to get _sprite texture normalized coordinates_.
465
+ *
466
+ * Use `outputMatrix * vTextureCoord` in the shader.
467
+ * @param outputMatrix - The matrix to output to.
468
+ * @param {Sprite} sprite - The sprite to map to.
469
+ * @returns The mapped matrix.
470
+ */
471
+ calculateSpriteMatrix(outputMatrix, sprite) {
472
+ const data = this._activeFilterData;
473
+ const mappedMatrix = outputMatrix.set(
474
+ data.inputTexture._source.width,
475
+ 0,
476
+ 0,
477
+ data.inputTexture._source.height,
478
+ data.bounds.minX,
479
+ data.bounds.minY
480
+ );
481
+ const worldTransform = sprite.worldTransform.copyTo(Matrix.shared);
482
+ const renderGroup = sprite.renderGroup || sprite.parentRenderGroup;
483
+ if (renderGroup && renderGroup.cacheToLocalTransform) {
484
+ worldTransform.prepend(renderGroup.cacheToLocalTransform);
485
+ }
486
+ worldTransform.invert();
487
+ mappedMatrix.prepend(worldTransform);
488
+ mappedMatrix.scale(
489
+ 1 / sprite.texture.frame.width,
490
+ 1 / sprite.texture.frame.height
491
+ );
492
+ mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y);
493
+ return mappedMatrix;
494
+ }
495
+ destroy() {
496
+ }
497
+ /**
498
+ * Sets up the bind groups and renders the filter.
499
+ * @param filter - The filter to apply
500
+ * @param input - The input texture
501
+ * @param renderer - The renderer instance
502
+ */
503
+ _setupBindGroupsAndRender(filter, input, renderer) {
504
+ if (renderer.renderPipes.uniformBatch) {
505
+ const batchUniforms = renderer.renderPipes.uniformBatch.getUboResource(this._filterGlobalUniforms);
506
+ this._globalFilterBindGroup.setResource(batchUniforms, 0);
507
+ } else {
508
+ this._globalFilterBindGroup.setResource(this._filterGlobalUniforms, 0);
509
+ }
510
+ this._globalFilterBindGroup.setResource(input.source, 1);
511
+ this._globalFilterBindGroup.setResource(input.source.style, 2);
512
+ filter.groups[0] = this._globalFilterBindGroup;
513
+ renderer.encoder.draw({
514
+ geometry: quadGeometry,
515
+ shader: filter,
516
+ state: filter._state,
517
+ topology: "triangle-list"
518
+ });
519
+ if (renderer.type === RendererType.WEBGL) {
520
+ renderer.renderTarget.finishRenderPass();
416
521
  }
417
- if (currentIndex > 0) {
418
- resolution = this._filterStack[currentIndex].inputTexture.source._resolution;
522
+ }
523
+ /**
524
+ * Sets up the filter textures including input texture and back texture if needed.
525
+ * @param filterData - The filter data to update
526
+ * @param bounds - The bounds for the texture
527
+ * @param renderer - The renderer instance
528
+ * @param previousFilterData - The previous filter data for back texture calculation
529
+ */
530
+ _setupFilterTextures(filterData, bounds, renderer, previousFilterData) {
531
+ filterData.backTexture = Texture.EMPTY;
532
+ if (filterData.blendRequired) {
533
+ renderer.renderTarget.finishRenderPass();
534
+ const renderTarget = renderer.renderTarget.getRenderTarget(filterData.outputRenderSurface);
535
+ filterData.backTexture = this.getBackTexture(renderTarget, bounds, previousFilterData?.bounds);
419
536
  }
420
- const filterUniforms = this._filterGlobalUniforms;
421
- const uniforms = filterUniforms.uniforms;
537
+ filterData.inputTexture = TexturePool.getOptimalTexture(
538
+ bounds.width,
539
+ bounds.height,
540
+ filterData.resolution,
541
+ filterData.antialias
542
+ );
543
+ renderer.renderTarget.bind(filterData.inputTexture, true);
544
+ renderer.globalUniforms.push({
545
+ offset: bounds
546
+ });
547
+ }
548
+ /**
549
+ * Calculates and sets the global frame for the filter.
550
+ * @param filterData - The filter data to update
551
+ * @param offsetX - The X offset
552
+ * @param offsetY - The Y offset
553
+ * @param globalResolution - The global resolution
554
+ * @param sourceWidth - The source texture width
555
+ * @param sourceHeight - The source texture height
556
+ */
557
+ _calculateGlobalFrame(filterData, offsetX, offsetY, globalResolution, sourceWidth, sourceHeight) {
558
+ const globalFrame = filterData.globalFrame;
559
+ globalFrame.x = offsetX * globalResolution;
560
+ globalFrame.y = offsetY * globalResolution;
561
+ globalFrame.width = sourceWidth * globalResolution;
562
+ globalFrame.height = sourceHeight * globalResolution;
563
+ }
564
+ /**
565
+ * Updates the filter uniforms with the current filter state.
566
+ * @param input - The input texture
567
+ * @param output - The output render surface
568
+ * @param filterData - The current filter data
569
+ * @param offsetX - The X offset for positioning
570
+ * @param offsetY - The Y offset for positioning
571
+ * @param resolution - The current resolution
572
+ * @param isFinalTarget - Whether this is the final render target
573
+ * @param clear - Whether to clear the output surface
574
+ */
575
+ _updateFilterUniforms(input, output, filterData, offsetX, offsetY, resolution, isFinalTarget, clear) {
576
+ const uniforms = this._filterGlobalUniforms.uniforms;
422
577
  const outputFrame = uniforms.uOutputFrame;
423
578
  const inputSize = uniforms.uInputSize;
424
579
  const inputPixel = uniforms.uInputPixel;
@@ -426,18 +581,8 @@ class FilterSystem {
426
581
  const globalFrame = uniforms.uGlobalFrame;
427
582
  const outputTexture = uniforms.uOutputTexture;
428
583
  if (isFinalTarget) {
429
- let lastIndex = this._filterStackIndex;
430
- while (lastIndex > 0) {
431
- lastIndex--;
432
- const filterData2 = this._filterStack[this._filterStackIndex - 1];
433
- if (!filterData2.skip) {
434
- offset.x = filterData2.bounds.minX;
435
- offset.y = filterData2.bounds.minY;
436
- break;
437
- }
438
- }
439
- outputFrame[0] = bounds.minX - offset.x;
440
- outputFrame[1] = bounds.minY - offset.y;
584
+ outputFrame[0] = filterData.bounds.minX - offsetX;
585
+ outputFrame[1] = filterData.bounds.minY - offsetY;
441
586
  } else {
442
587
  outputFrame[0] = 0;
443
588
  outputFrame[1] = 0;
@@ -457,12 +602,14 @@ class FilterSystem {
457
602
  inputClamp[2] = input.frame.width * inputSize[2] - 0.5 * inputPixel[2];
458
603
  inputClamp[3] = input.frame.height * inputSize[3] - 0.5 * inputPixel[3];
459
604
  const rootTexture = this.renderer.renderTarget.rootRenderTarget.colorTexture;
460
- globalFrame[0] = offset.x * resolution;
461
- globalFrame[1] = offset.y * resolution;
605
+ globalFrame[0] = offsetX * resolution;
606
+ globalFrame[1] = offsetY * resolution;
462
607
  globalFrame[2] = rootTexture.source.width * resolution;
463
608
  globalFrame[3] = rootTexture.source.height * resolution;
609
+ if (output instanceof Texture)
610
+ output.source.resource = null;
464
611
  const renderTarget = this.renderer.renderTarget.getRenderTarget(output);
465
- renderer.renderTarget.bind(output, !!clear);
612
+ this.renderer.renderTarget.bind(output, !!clear);
466
613
  if (output instanceof Texture) {
467
614
  outputTexture[0] = output.frame.width;
468
615
  outputTexture[1] = output.frame.height;
@@ -471,68 +618,165 @@ class FilterSystem {
471
618
  outputTexture[1] = renderTarget.height;
472
619
  }
473
620
  outputTexture[2] = renderTarget.isRoot ? -1 : 1;
474
- filterUniforms.update();
475
- if (renderer.renderPipes.uniformBatch) {
476
- const batchUniforms = renderer.renderPipes.uniformBatch.getUboResource(filterUniforms);
477
- this._globalFilterBindGroup.setResource(batchUniforms, 0);
478
- } else {
479
- this._globalFilterBindGroup.setResource(filterUniforms, 0);
480
- }
481
- this._globalFilterBindGroup.setResource(input.source, 1);
482
- this._globalFilterBindGroup.setResource(input.source.style, 2);
483
- filter.groups[0] = this._globalFilterBindGroup;
484
- renderer.encoder.draw({
485
- geometry: quadGeometry,
486
- shader: filter,
487
- state: filter._state,
488
- topology: "triangle-list"
489
- });
490
- if (renderer.type === RendererType.WEBGL) {
491
- renderer.renderTarget.finishRenderPass();
621
+ this._filterGlobalUniforms.update();
622
+ }
623
+ /**
624
+ * Finds the correct resolution by looking back through the filter stack.
625
+ * @param rootResolution - The fallback root resolution to use
626
+ * @returns The resolution from the previous filter or root resolution
627
+ */
628
+ _findFilterResolution(rootResolution) {
629
+ let currentIndex = this._filterStackIndex - 1;
630
+ while (currentIndex > 0 && this._filterStack[currentIndex].skip) {
631
+ --currentIndex;
492
632
  }
633
+ return currentIndex > 0 && this._filterStack[currentIndex].inputTexture ? this._filterStack[currentIndex].inputTexture.source._resolution : rootResolution;
493
634
  }
494
- _getFilterData() {
495
- return {
496
- skip: false,
497
- inputTexture: null,
498
- bounds: new Bounds(),
499
- container: null,
500
- filterEffect: null,
501
- blendRequired: false,
502
- previousRenderSurface: null
503
- };
635
+ /**
636
+ * Finds the offset from the previous non-skipped filter in the stack.
637
+ * @returns The offset coordinates from the previous filter
638
+ */
639
+ _findPreviousFilterOffset() {
640
+ let offsetX = 0;
641
+ let offsetY = 0;
642
+ let lastIndex = this._filterStackIndex;
643
+ while (lastIndex > 0) {
644
+ lastIndex--;
645
+ const prevFilterData = this._filterStack[lastIndex];
646
+ if (!prevFilterData.skip) {
647
+ offsetX = prevFilterData.bounds.minX;
648
+ offsetY = prevFilterData.bounds.minY;
649
+ break;
650
+ }
651
+ }
652
+ return { x: offsetX, y: offsetY };
504
653
  }
505
654
  /**
506
- * Multiply _input normalized coordinates_ to this matrix to get _sprite texture normalized coordinates_.
507
- *
508
- * Use `outputMatrix * vTextureCoord` in the shader.
509
- * @param outputMatrix - The matrix to output to.
510
- * @param {Sprite} sprite - The sprite to map to.
511
- * @returns The mapped matrix.
655
+ * Calculates the filter area bounds based on the instruction type.
656
+ * @param instruction - The filter instruction
657
+ * @param bounds - The bounds object to populate
512
658
  */
513
- calculateSpriteMatrix(outputMatrix, sprite) {
514
- const data = this._activeFilterData;
515
- const mappedMatrix = outputMatrix.set(
516
- data.inputTexture._source.width,
517
- 0,
518
- 0,
519
- data.inputTexture._source.height,
520
- data.bounds.minX,
521
- data.bounds.minY
522
- );
523
- const worldTransform = sprite.worldTransform.copyTo(Matrix.shared);
524
- const renderGroup = sprite.renderGroup || sprite.parentRenderGroup;
525
- if (renderGroup && renderGroup.cacheToLocalTransform) {
526
- worldTransform.prepend(renderGroup.cacheToLocalTransform);
659
+ _calculateFilterArea(instruction, bounds) {
660
+ if (instruction.renderables) {
661
+ getGlobalRenderableBounds(instruction.renderables, bounds);
662
+ } else if (instruction.filterEffect.filterArea) {
663
+ bounds.clear();
664
+ bounds.addRect(instruction.filterEffect.filterArea);
665
+ bounds.applyMatrix(instruction.container.worldTransform);
666
+ } else {
667
+ instruction.container.getFastGlobalBounds(true, bounds);
668
+ }
669
+ if (instruction.container) {
670
+ const renderGroup = instruction.container.renderGroup || instruction.container.parentRenderGroup;
671
+ const filterFrameTransform = renderGroup.cacheToLocalTransform;
672
+ if (filterFrameTransform) {
673
+ bounds.applyMatrix(filterFrameTransform);
674
+ }
527
675
  }
528
- worldTransform.invert();
529
- mappedMatrix.prepend(worldTransform);
530
- mappedMatrix.scale(
531
- 1 / sprite.texture.frame.width,
532
- 1 / sprite.texture.frame.height
533
- );
534
- mappedMatrix.translate(sprite.anchor.x, sprite.anchor.y);
535
- return mappedMatrix;
676
+ }
677
+ _applyFiltersToTexture(filterData, clear) {
678
+ const inputTexture = filterData.inputTexture;
679
+ const bounds = filterData.bounds;
680
+ const filters = filterData.filters;
681
+ this._globalFilterBindGroup.setResource(inputTexture.source.style, 2);
682
+ this._globalFilterBindGroup.setResource(filterData.backTexture.source, 3);
683
+ if (filters.length === 1) {
684
+ filters[0].apply(this, inputTexture, filterData.outputRenderSurface, clear);
685
+ } else {
686
+ let flip = filterData.inputTexture;
687
+ const tempTexture = TexturePool.getOptimalTexture(
688
+ bounds.width,
689
+ bounds.height,
690
+ flip.source._resolution,
691
+ false
692
+ );
693
+ let flop = tempTexture;
694
+ let i = 0;
695
+ for (i = 0; i < filters.length - 1; ++i) {
696
+ const filter = filters[i];
697
+ filter.apply(this, flip, flop, true);
698
+ const t = flip;
699
+ flip = flop;
700
+ flop = t;
701
+ }
702
+ filters[i].apply(this, flip, filterData.outputRenderSurface, clear);
703
+ TexturePool.returnTexture(tempTexture);
704
+ }
705
+ }
706
+ _calculateFilterBounds(filterData, viewPort, rootAntialias, rootResolution, paddingMultiplier) {
707
+ const renderer = this.renderer;
708
+ const bounds = filterData.bounds;
709
+ const filters = filterData.filters;
710
+ let resolution = Infinity;
711
+ let padding = 0;
712
+ let antialias = true;
713
+ let blendRequired = false;
714
+ let enabled = false;
715
+ let clipToViewport = true;
716
+ for (let i = 0; i < filters.length; i++) {
717
+ const filter = filters[i];
718
+ resolution = Math.min(resolution, filter.resolution === "inherit" ? rootResolution : filter.resolution);
719
+ padding += filter.padding;
720
+ if (filter.antialias === "off") {
721
+ antialias = false;
722
+ } else if (filter.antialias === "inherit") {
723
+ antialias && (antialias = rootAntialias);
724
+ }
725
+ if (!filter.clipToViewport) {
726
+ clipToViewport = false;
727
+ }
728
+ const isCompatible = !!(filter.compatibleRenderers & renderer.type);
729
+ if (!isCompatible) {
730
+ enabled = false;
731
+ break;
732
+ }
733
+ if (filter.blendRequired && !(renderer.backBuffer?.useBackBuffer ?? true)) {
734
+ warn("Blend filter requires backBuffer on WebGL renderer to be enabled. Set `useBackBuffer: true` in the renderer options.");
735
+ enabled = false;
736
+ break;
737
+ }
738
+ enabled = filter.enabled || enabled;
739
+ blendRequired || (blendRequired = filter.blendRequired);
740
+ }
741
+ if (!enabled) {
742
+ filterData.skip = true;
743
+ return;
744
+ }
745
+ if (clipToViewport) {
746
+ bounds.fitBounds(0, viewPort.width / rootResolution, 0, viewPort.height / rootResolution);
747
+ }
748
+ bounds.scale(resolution).ceil().scale(1 / resolution).pad((padding | 0) * paddingMultiplier);
749
+ if (!bounds.isPositive) {
750
+ filterData.skip = true;
751
+ return;
752
+ }
753
+ filterData.antialias = antialias;
754
+ filterData.resolution = resolution;
755
+ filterData.blendRequired = blendRequired;
756
+ }
757
+ _popFilterData() {
758
+ this._filterStackIndex--;
759
+ return this._filterStack[this._filterStackIndex];
760
+ }
761
+ _getPreviousFilterData() {
762
+ let previousFilterData;
763
+ let index = this._filterStackIndex - 1;
764
+ while (index > 1) {
765
+ index--;
766
+ previousFilterData = this._filterStack[index];
767
+ if (!previousFilterData.skip) {
768
+ break;
769
+ }
770
+ }
771
+ return previousFilterData;
772
+ }
773
+ _pushFilterData() {
774
+ let filterData = this._filterStack[this._filterStackIndex];
775
+ if (!filterData) {
776
+ filterData = this._filterStack[this._filterStackIndex] = new FilterData();
777
+ }
778
+ this._filterStackIndex++;
779
+ return filterData;
536
780
  }
537
781
  }
538
782
  /** @ignore */
@@ -546,6 +790,7 @@ FilterSystem.extension = {
546
790
 
547
791
  class Graphics extends ViewContainer {
548
792
  /**
793
+ * Creates a new Graphics object.
549
794
  * @param options - Options for the Graphics.
550
795
  */
551
796
  constructor(options) {
@@ -557,6 +802,7 @@ class Graphics extends ViewContainer {
557
802
  label: "Graphics",
558
803
  ...rest
559
804
  });
805
+ /** @internal */
560
806
  this.renderPipeId = "graphics";
561
807
  if (!context) {
562
808
  this._context = this._ownedContext = new GraphicsContext();
@@ -564,6 +810,7 @@ class Graphics extends ViewContainer {
564
810
  this._context = context;
565
811
  }
566
812
  this._context.on("update", this.onViewUpdate, this);
813
+ this.didViewUpdate = true;
567
814
  this.allowChildren = false;
568
815
  this.roundPixels = roundPixels ?? false;
569
816
  }
@@ -575,12 +822,53 @@ class Graphics extends ViewContainer {
575
822
  this._context.on("update", this.onViewUpdate, this);
576
823
  this.onViewUpdate();
577
824
  }
825
+ /**
826
+ * The underlying graphics context used for drawing operations.
827
+ * Controls how shapes and paths are rendered.
828
+ * @example
829
+ * ```ts
830
+ * // Create a shared context
831
+ * const sharedContext = new GraphicsContext();
832
+ *
833
+ * // Create graphics objects sharing the same context
834
+ * const graphics1 = new Graphics();
835
+ * const graphics2 = new Graphics();
836
+ *
837
+ * // Assign shared context
838
+ * graphics1.context = sharedContext;
839
+ * graphics2.context = sharedContext;
840
+ *
841
+ * // Both graphics will show the same shapes
842
+ * sharedContext
843
+ * .rect(0, 0, 100, 100)
844
+ * .fill({ color: 0xff0000 });
845
+ * ```
846
+ * @see {@link GraphicsContext} For drawing operations
847
+ * @see {@link GraphicsOptions} For context configuration
848
+ */
578
849
  get context() {
579
850
  return this._context;
580
851
  }
581
852
  /**
582
- * The local bounds of the graphic.
583
- * @type {rendering.Bounds}
853
+ * The local bounds of the graphics object.
854
+ * Returns the boundaries after all graphical operations but before any transforms.
855
+ * @example
856
+ * ```ts
857
+ * const graphics = new Graphics();
858
+ *
859
+ * // Draw a shape
860
+ * graphics
861
+ * .rect(0, 0, 100, 100)
862
+ * .fill({ color: 0xff0000 });
863
+ *
864
+ * // Get bounds information
865
+ * const bounds = graphics.bounds;
866
+ * console.log(bounds.width); // 100
867
+ * console.log(bounds.height); // 100
868
+ * ```
869
+ * @readonly
870
+ * @see {@link Bounds} For bounds operations
871
+ * @see {@link Container#getBounds} For transformed bounds
584
872
  */
585
873
  get bounds() {
586
874
  return this._context.bounds;
@@ -593,7 +881,25 @@ class Graphics extends ViewContainer {
593
881
  }
594
882
  /**
595
883
  * Checks if the object contains the given point.
596
- * @param point - The point to check
884
+ * Returns true if the point lies within the Graphics object's rendered area.
885
+ * @example
886
+ * ```ts
887
+ * const graphics = new Graphics();
888
+ *
889
+ * // Draw a shape
890
+ * graphics
891
+ * .rect(0, 0, 100, 100)
892
+ * .fill({ color: 0xff0000 });
893
+ *
894
+ * // Check point intersection
895
+ * if (graphics.containsPoint({ x: 50, y: 50 })) {
896
+ * console.log('Point is inside rectangle!');
897
+ * }
898
+ * ```
899
+ * @param point - The point to check in local coordinates
900
+ * @returns True if the point is inside the Graphics object
901
+ * @see {@link Graphics#bounds} For bounding box checks
902
+ * @see {@link PointData} For point data structure
597
903
  */
598
904
  containsPoint(point) {
599
905
  return this._context.containsPoint(point);
@@ -609,9 +915,13 @@ class Graphics extends ViewContainer {
609
915
  * then you should pass destroy({ context: false })
610
916
  *
611
917
  * If the context was passed in as an argument to the constructor then it will not be destroyed
612
- * @param {boolean} [options.texture=false] - Should destroy the texture of the graphics context
613
- * @param {boolean} [options.textureSource=false] - Should destroy the texture source of the graphics context
614
- * @param {boolean} [options.context=false] - Should destroy the context
918
+ * @example
919
+ * ```ts
920
+ * // Destroy the graphics and its context
921
+ * graphics.destroy();
922
+ * graphics.destroy(true);
923
+ * graphics.destroy({ context: true, texture: true, textureSource: true });
924
+ * ```
615
925
  */
616
926
  destroy(options) {
617
927
  if (this._ownedContext && !options) {
@@ -629,21 +939,104 @@ class Graphics extends ViewContainer {
629
939
  }
630
940
  // --------------------------------------- GraphicsContext methods ---------------------------------------
631
941
  /**
632
- * Sets the current fill style of the graphics context. The fill style can be a color, gradient,
633
- * pattern, or a more complex style defined by a FillStyle object.
634
- * @param {FillInput} args - The fill style to apply. This can be a simple color, a gradient or
635
- * pattern object, or a FillStyle or ConvertedFillStyle object.
636
- * @returns The instance of the current GraphicsContext for method chaining.
942
+ * Sets the current fill style of the graphics context.
943
+ * The fill style can be a color, gradient, pattern, or a complex style object.
944
+ * @example
945
+ * ```ts
946
+ * const graphics = new Graphics();
947
+ *
948
+ * // Basic color fill
949
+ * graphics
950
+ * .setFillStyle({ color: 0xff0000 }) // Red fill
951
+ * .rect(0, 0, 100, 100)
952
+ * .fill();
953
+ *
954
+ * // Gradient fill
955
+ * const gradient = new FillGradient({
956
+ * end: { x: 1, y: 0 },
957
+ * colorStops: [
958
+ * { offset: 0, color: 0xff0000 }, // Red at start
959
+ * { offset: 0.5, color: 0x00ff00 }, // Green at middle
960
+ * { offset: 1, color: 0x0000ff }, // Blue at end
961
+ * ],
962
+ * });
963
+ *
964
+ * graphics
965
+ * .setFillStyle(gradient)
966
+ * .circle(100, 100, 50)
967
+ * .fill();
968
+ *
969
+ * // Pattern fill
970
+ * const pattern = new FillPattern(texture);
971
+ * graphics
972
+ * .setFillStyle({
973
+ * fill: pattern,
974
+ * alpha: 0.5
975
+ * })
976
+ * .rect(0, 0, 200, 200)
977
+ * .fill();
978
+ * ```
979
+ * @param {FillInput} args - The fill style to apply
980
+ * @returns The Graphics instance for chaining
981
+ * @see {@link FillStyle} For fill style options
982
+ * @see {@link FillGradient} For gradient fills
983
+ * @see {@link FillPattern} For pattern fills
637
984
  */
638
985
  setFillStyle(...args) {
639
986
  return this._callContextMethod("setFillStyle", args);
640
987
  }
641
988
  /**
642
- * Sets the current stroke style of the graphics context. Similar to fill styles, stroke styles can
643
- * encompass colors, gradients, patterns, or more detailed configurations via a StrokeStyle object.
644
- * @param {StrokeInput} args - The stroke style to apply. Can be defined as a color, a gradient or pattern,
645
- * or a StrokeStyle or ConvertedStrokeStyle object.
646
- * @returns The instance of the current GraphicsContext for method chaining.
989
+ * Sets the current stroke style of the graphics context.
990
+ * Similar to fill styles, stroke styles can encompass colors, gradients, patterns, or more detailed configurations.
991
+ * @example
992
+ * ```ts
993
+ * const graphics = new Graphics();
994
+ *
995
+ * // Basic color stroke
996
+ * graphics
997
+ * .setStrokeStyle({
998
+ * width: 2,
999
+ * color: 0x000000
1000
+ * })
1001
+ * .rect(0, 0, 100, 100)
1002
+ * .stroke();
1003
+ *
1004
+ * // Complex stroke style
1005
+ * graphics
1006
+ * .setStrokeStyle({
1007
+ * width: 4,
1008
+ * color: 0xff0000,
1009
+ * alpha: 0.5,
1010
+ * join: 'round',
1011
+ * cap: 'round',
1012
+ * alignment: 0.5
1013
+ * })
1014
+ * .circle(100, 100, 50)
1015
+ * .stroke();
1016
+ *
1017
+ * // Gradient stroke
1018
+ * const gradient = new FillGradient({
1019
+ * end: { x: 1, y: 0 },
1020
+ * colorStops: [
1021
+ * { offset: 0, color: 0xff0000 }, // Red at start
1022
+ * { offset: 0.5, color: 0x00ff00 }, // Green at middle
1023
+ * { offset: 1, color: 0x0000ff }, // Blue at end
1024
+ * ],
1025
+ * });
1026
+ *
1027
+ * graphics
1028
+ * .setStrokeStyle({
1029
+ * width: 10,
1030
+ * fill: gradient
1031
+ * })
1032
+ * .poly([0,0, 100,50, 0,100])
1033
+ * .stroke();
1034
+ * ```
1035
+ * @param {StrokeInput} args - The stroke style to apply
1036
+ * @returns The Graphics instance for chaining
1037
+ * @see {@link StrokeStyle} For stroke style options
1038
+ * @see {@link FillGradient} For gradient strokes
1039
+ * @see {@link FillPattern} For pattern strokes
647
1040
  */
648
1041
  setStrokeStyle(...args) {
649
1042
  return this._callContextMethod("setStrokeStyle", args);
@@ -652,11 +1045,53 @@ class Graphics extends ViewContainer {
652
1045
  return this._callContextMethod("fill", args);
653
1046
  }
654
1047
  /**
655
- * Strokes the current path with the current stroke style. This method can take an optional
656
- * FillStyle parameter to define the stroke's appearance, including its color, width, and other properties.
657
- * @param {FillStyle} args - (Optional) The stroke style to apply. Can be defined as a simple color or a more
658
- * complex style object. If omitted, uses the current stroke style.
659
- * @returns The instance of the current GraphicsContext for method chaining.
1048
+ * Strokes the current path with the current stroke style or specified style.
1049
+ * Outlines the shape using the stroke settings.
1050
+ * @example
1051
+ * ```ts
1052
+ * const graphics = new Graphics();
1053
+ *
1054
+ * // Stroke with direct color
1055
+ * graphics
1056
+ * .circle(50, 50, 25)
1057
+ * .stroke({
1058
+ * width: 2,
1059
+ * color: 0xff0000
1060
+ * }); // 2px red stroke
1061
+ *
1062
+ * // Fill with texture
1063
+ * graphics
1064
+ * .rect(0, 0, 100, 100)
1065
+ * .stroke(myTexture); // Fill with texture
1066
+ *
1067
+ * // Stroke with gradient
1068
+ * const gradient = new FillGradient({
1069
+ * end: { x: 1, y: 0 },
1070
+ * colorStops: [
1071
+ * { offset: 0, color: 0xff0000 },
1072
+ * { offset: 0.5, color: 0x00ff00 },
1073
+ * { offset: 1, color: 0x0000ff },
1074
+ * ],
1075
+ * });
1076
+ *
1077
+ * graphics
1078
+ * .rect(0, 0, 100, 100)
1079
+ * .stroke({
1080
+ * width: 4,
1081
+ * fill: gradient,
1082
+ * alignment: 0.5,
1083
+ * join: 'round'
1084
+ * });
1085
+ * ```
1086
+ * @param {StrokeStyle} args - Optional stroke style to apply. Can be:
1087
+ * - A stroke style object with width, color, etc.
1088
+ * - A gradient
1089
+ * - A pattern
1090
+ * If omitted, uses current stroke style.
1091
+ * @returns The Graphics instance for chaining
1092
+ * @see {@link StrokeStyle} For stroke style options
1093
+ * @see {@link FillGradient} For gradient strokes
1094
+ * @see {@link setStrokeStyle} For setting default stroke style
660
1095
  */
661
1096
  stroke(...args) {
662
1097
  return this._callContextMethod("stroke", args);
@@ -667,15 +1102,39 @@ class Graphics extends ViewContainer {
667
1102
  /**
668
1103
  * Resets the current path. Any previous path and its commands are discarded and a new path is
669
1104
  * started. This is typically called before beginning a new shape or series of drawing commands.
670
- * @returns The instance of the current GraphicsContext for method chaining.
1105
+ * @example
1106
+ * ```ts
1107
+ * const graphics = new Graphics();
1108
+ * graphics
1109
+ * .circle(150, 150, 50)
1110
+ * .fill({ color: 0x00ff00 })
1111
+ * .beginPath() // Starts a new path
1112
+ * .circle(250, 150, 50)
1113
+ * .fill({ color: 0x0000ff });
1114
+ * ```
1115
+ * @returns The Graphics instance for chaining
1116
+ * @see {@link Graphics#moveTo} For starting a new subpath
1117
+ * @see {@link Graphics#closePath} For closing the current path
671
1118
  */
672
1119
  beginPath() {
673
1120
  return this._callContextMethod("beginPath", []);
674
1121
  }
675
1122
  /**
676
1123
  * Applies a cutout to the last drawn shape. This is used to create holes or complex shapes by
677
- * subtracting a path from the previously drawn path. If a hole is not completely in a shape, it will
678
- * fail to cut correctly!
1124
+ * subtracting a path from the previously drawn path.
1125
+ *
1126
+ * If a hole is not completely in a shape, it will fail to cut correctly.
1127
+ * @example
1128
+ * ```ts
1129
+ * const graphics = new Graphics();
1130
+ *
1131
+ * // Draw outer circle
1132
+ * graphics
1133
+ * .circle(100, 100, 50)
1134
+ * .fill({ color: 0xff0000 });
1135
+ * .circle(100, 100, 25) // Inner circle
1136
+ * .cut() // Cuts out the inner circle from the outer circle
1137
+ * ```
679
1138
  */
680
1139
  cut() {
681
1140
  return this._callContextMethod("cut", []);
@@ -693,9 +1152,23 @@ class Graphics extends ViewContainer {
693
1152
  return this._callContextMethod("bezierCurveTo", args);
694
1153
  }
695
1154
  /**
696
- * Closes the current path by drawing a straight line back to the start.
697
- * If the shape is already closed or there are no points in the path, this method does nothing.
698
- * @returns The instance of the current object for chaining.
1155
+ * Closes the current path by drawing a straight line back to the start point.
1156
+ *
1157
+ * This is useful for completing shapes and ensuring they are properly closed for fills.
1158
+ * @example
1159
+ * ```ts
1160
+ * // Create a triangle with closed path
1161
+ * const graphics = new Graphics();
1162
+ * graphics
1163
+ * .moveTo(50, 50)
1164
+ * .lineTo(100, 100)
1165
+ * .lineTo(0, 100)
1166
+ * .closePath()
1167
+ * ```
1168
+ * @returns The Graphics instance for method chaining
1169
+ * @see {@link Graphics#beginPath} For starting a new path
1170
+ * @see {@link Graphics#fill} For filling closed paths
1171
+ * @see {@link Graphics#stroke} For stroking paths
699
1172
  */
700
1173
  closePath() {
701
1174
  return this._callContextMethod("closePath", []);
@@ -751,13 +1224,69 @@ class Graphics extends ViewContainer {
751
1224
  restore(...args) {
752
1225
  return this._callContextMethod("restore", args);
753
1226
  }
754
- /** Saves the current graphics state, including transformations, fill styles, and stroke styles, onto a stack. */
1227
+ /**
1228
+ * Saves the current graphics state onto a stack. The state includes:
1229
+ * - Current transformation matrix
1230
+ * - Current fill style
1231
+ * - Current stroke style
1232
+ * @example
1233
+ * ```ts
1234
+ * const graphics = new Graphics();
1235
+ *
1236
+ * // Save state before complex operations
1237
+ * graphics.save();
1238
+ *
1239
+ * // Create transformed and styled shape
1240
+ * graphics
1241
+ * .translateTransform(100, 100)
1242
+ * .rotateTransform(Math.PI / 4)
1243
+ * .setFillStyle({
1244
+ * color: 0xff0000,
1245
+ * alpha: 0.5
1246
+ * })
1247
+ * .rect(-25, -25, 50, 50)
1248
+ * .fill();
1249
+ *
1250
+ * // Restore to original state
1251
+ * graphics.restore();
1252
+ *
1253
+ * // Continue drawing with previous state
1254
+ * graphics
1255
+ * .circle(50, 50, 25)
1256
+ * .fill();
1257
+ * ```
1258
+ * @returns The Graphics instance for method chaining
1259
+ * @see {@link Graphics#restore} For restoring the saved state
1260
+ * @see {@link Graphics#setTransform} For setting transformations
1261
+ */
755
1262
  save() {
756
1263
  return this._callContextMethod("save", []);
757
1264
  }
758
1265
  /**
759
1266
  * Returns the current transformation matrix of the graphics context.
1267
+ * This matrix represents all accumulated transformations including translate, scale, and rotate.
1268
+ * @example
1269
+ * ```ts
1270
+ * const graphics = new Graphics();
1271
+ *
1272
+ * // Apply some transformations
1273
+ * graphics
1274
+ * .translateTransform(100, 100)
1275
+ * .rotateTransform(Math.PI / 4);
1276
+ *
1277
+ * // Get the current transform matrix
1278
+ * const matrix = graphics.getTransform();
1279
+ * console.log(matrix.tx, matrix.ty); // 100, 100
1280
+ *
1281
+ * // Use the matrix for other operations
1282
+ * graphics
1283
+ * .setTransform(matrix)
1284
+ * .circle(0, 0, 50)
1285
+ * .fill({ color: 0xff0000 });
1286
+ * ```
760
1287
  * @returns The current transformation matrix.
1288
+ * @see {@link Graphics#setTransform} For setting the transform matrix
1289
+ * @see {@link Matrix} For matrix operations
761
1290
  */
762
1291
  getTransform() {
763
1292
  return this.context.getTransform();
@@ -765,7 +1294,27 @@ class Graphics extends ViewContainer {
765
1294
  /**
766
1295
  * Resets the current transformation matrix to the identity matrix, effectively removing
767
1296
  * any transformations (rotation, scaling, translation) previously applied.
768
- * @returns The instance of the current GraphicsContext for method chaining.
1297
+ * @example
1298
+ * ```ts
1299
+ * const graphics = new Graphics();
1300
+ *
1301
+ * // Apply transformations
1302
+ * graphics
1303
+ * .translateTransform(100, 100)
1304
+ * .scaleTransform(2, 2)
1305
+ * .circle(0, 0, 25)
1306
+ * .fill({ color: 0xff0000 });
1307
+ * // Reset transform to default state
1308
+ * graphics
1309
+ * .resetTransform()
1310
+ * .circle(50, 50, 25) // Will draw at actual coordinates
1311
+ * .fill({ color: 0x00ff00 });
1312
+ * ```
1313
+ * @returns The Graphics instance for method chaining
1314
+ * @see {@link Graphics#getTransform} For getting the current transform
1315
+ * @see {@link Graphics#setTransform} For setting a specific transform
1316
+ * @see {@link Graphics#save} For saving the current transform state
1317
+ * @see {@link Graphics#restore} For restoring a previous transform state
769
1318
  */
770
1319
  resetTransform() {
771
1320
  return this._callContextMethod("resetTransform", []);
@@ -786,16 +1335,79 @@ class Graphics extends ViewContainer {
786
1335
  return this._callContextMethod("translate", args);
787
1336
  }
788
1337
  /**
789
- * Clears all drawing commands from the graphics context, effectively resetting it. This includes clearing the path,
790
- * and optionally resetting transformations to the identity matrix.
791
- * @returns The instance of the current GraphicsContext for method chaining.
1338
+ * Clears all drawing commands from the graphics context, effectively resetting it.
1339
+ * This includes clearing the current path, fill style, stroke style, and transformations.
1340
+ *
1341
+ * > [!NOTE] Graphics objects are not designed to be continuously cleared and redrawn.
1342
+ * > Instead, they are intended to be used for static or semi-static graphics that
1343
+ * > can be redrawn as needed. Frequent clearing and redrawing may lead to performance issues.
1344
+ * @example
1345
+ * ```ts
1346
+ * const graphics = new Graphics();
1347
+ *
1348
+ * // Draw some shapes
1349
+ * graphics
1350
+ * .circle(100, 100, 50)
1351
+ * .fill({ color: 0xff0000 })
1352
+ * .rect(200, 100, 100, 50)
1353
+ * .fill({ color: 0x00ff00 });
1354
+ *
1355
+ * // Clear all graphics
1356
+ * graphics.clear();
1357
+ *
1358
+ * // Start fresh with new shapes
1359
+ * graphics
1360
+ * .circle(150, 150, 30)
1361
+ * .fill({ color: 0x0000ff });
1362
+ * ```
1363
+ * @returns The Graphics instance for method chaining
1364
+ * @see {@link Graphics#beginPath} For starting a new path without clearing styles
1365
+ * @see {@link Graphics#save} For saving the current state
1366
+ * @see {@link Graphics#restore} For restoring a previous state
792
1367
  */
793
1368
  clear() {
794
1369
  return this._callContextMethod("clear", []);
795
1370
  }
796
1371
  /**
797
- * The fill style to use.
1372
+ * Gets or sets the current fill style for the graphics context. The fill style determines
1373
+ * how shapes are filled when using the fill() method.
1374
+ * @example
1375
+ * ```ts
1376
+ * const graphics = new Graphics();
1377
+ *
1378
+ * // Basic color fill
1379
+ * graphics.fillStyle = {
1380
+ * color: 0xff0000, // Red
1381
+ * alpha: 1
1382
+ * };
1383
+ *
1384
+ * // Using gradients
1385
+ * const gradient = new FillGradient({
1386
+ * end: { x: 0, y: 1 }, // Vertical gradient
1387
+ * stops: [
1388
+ * { offset: 0, color: 0xff0000, alpha: 1 }, // Start color
1389
+ * { offset: 1, color: 0x0000ff, alpha: 1 } // End color
1390
+ * ]
1391
+ * });
1392
+ *
1393
+ * graphics.fillStyle = {
1394
+ * fill: gradient,
1395
+ * alpha: 0.8
1396
+ * };
1397
+ *
1398
+ * // Using patterns
1399
+ * graphics.fillStyle = {
1400
+ * texture: myTexture,
1401
+ * alpha: 1,
1402
+ * matrix: new Matrix()
1403
+ * .scale(0.5, 0.5)
1404
+ * .rotate(Math.PI / 4)
1405
+ * };
1406
+ * ```
798
1407
  * @type {ConvertedFillStyle}
1408
+ * @see {@link FillStyle} For all available fill style options
1409
+ * @see {@link FillGradient} For creating gradient fills
1410
+ * @see {@link Graphics#fill} For applying the fill to paths
799
1411
  */
800
1412
  get fillStyle() {
801
1413
  return this._context.fillStyle;
@@ -804,8 +1416,47 @@ class Graphics extends ViewContainer {
804
1416
  this._context.fillStyle = value;
805
1417
  }
806
1418
  /**
807
- * The stroke style to use.
808
- * @type {ConvertedStrokeStyle}
1419
+ * Gets or sets the current stroke style for the graphics context. The stroke style determines
1420
+ * how paths are outlined when using the stroke() method.
1421
+ * @example
1422
+ * ```ts
1423
+ * const graphics = new Graphics();
1424
+ *
1425
+ * // Basic stroke style
1426
+ * graphics.strokeStyle = {
1427
+ * width: 2,
1428
+ * color: 0xff0000,
1429
+ * alpha: 1
1430
+ * };
1431
+ *
1432
+ * // Using with gradients
1433
+ * const gradient = new FillGradient({
1434
+ * end: { x: 0, y: 1 },
1435
+ * stops: [
1436
+ * { offset: 0, color: 0xff0000, alpha: 1 },
1437
+ * { offset: 1, color: 0x0000ff, alpha: 1 }
1438
+ * ]
1439
+ * });
1440
+ *
1441
+ * graphics.strokeStyle = {
1442
+ * width: 4,
1443
+ * fill: gradient,
1444
+ * alignment: 0.5,
1445
+ * join: 'round',
1446
+ * cap: 'round'
1447
+ * };
1448
+ *
1449
+ * // Complex stroke settings
1450
+ * graphics.strokeStyle = {
1451
+ * width: 6,
1452
+ * color: 0x00ff00,
1453
+ * alpha: 0.5,
1454
+ * join: 'miter',
1455
+ * miterLimit: 10,
1456
+ * };
1457
+ * ```
1458
+ * @see {@link StrokeStyle} For all available stroke style options
1459
+ * @see {@link Graphics#stroke} For applying the stroke to paths
809
1460
  */
810
1461
  get strokeStyle() {
811
1462
  return this._context.strokeStyle;
@@ -814,12 +1465,41 @@ class Graphics extends ViewContainer {
814
1465
  this._context.strokeStyle = value;
815
1466
  }
816
1467
  /**
817
- * Creates a new Graphics object.
818
- * Note that only the context of the object is cloned, not its transform (position,scale,etc)
819
- * @param deep - Whether to create a deep clone of the graphics object. If false, the context
820
- * will be shared between the two objects (default false). If true, the context will be
821
- * cloned (recommended if you need to modify the context in any way).
822
- * @returns - A clone of the graphics object
1468
+ * Creates a new Graphics object that copies the current graphics content.
1469
+ * The clone can either share the same context (shallow clone) or have its own independent
1470
+ * context (deep clone).
1471
+ * @example
1472
+ * ```ts
1473
+ * const graphics = new Graphics();
1474
+ *
1475
+ * // Create original graphics content
1476
+ * graphics
1477
+ * .circle(100, 100, 50)
1478
+ * .fill({ color: 0xff0000 });
1479
+ *
1480
+ * // Create a shallow clone (shared context)
1481
+ * const shallowClone = graphics.clone();
1482
+ *
1483
+ * // Changes to original affect the clone
1484
+ * graphics
1485
+ * .circle(200, 100, 30)
1486
+ * .fill({ color: 0x00ff00 });
1487
+ *
1488
+ * // Create a deep clone (independent context)
1489
+ * const deepClone = graphics.clone(true);
1490
+ *
1491
+ * // Modify deep clone independently
1492
+ * deepClone
1493
+ * .translateTransform(100, 100)
1494
+ * .circle(0, 0, 40)
1495
+ * .fill({ color: 0x0000ff });
1496
+ * ```
1497
+ * @param deep - Whether to create a deep clone of the graphics object.
1498
+ * If false (default), the context will be shared between objects.
1499
+ * If true, creates an independent copy of the context.
1500
+ * @returns A new Graphics instance with either shared or copied context
1501
+ * @see {@link Graphics#context} For accessing the underlying graphics context
1502
+ * @see {@link GraphicsContext} For understanding the shared context behavior
823
1503
  */
824
1504
  clone(deep = false) {
825
1505
  if (deep) {
@@ -943,85 +1623,359 @@ const _MeshGeometry = class _MeshGeometry extends Geometry {
943
1623
  uvs = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]);
944
1624
  }
945
1625
  }
946
- const indices = options.indices || new Uint32Array([0, 1, 2, 0, 2, 3]);
947
- const shrinkToFit = options.shrinkBuffersToFit;
948
- const positionBuffer = new Buffer({
949
- data: positions,
950
- label: "attribute-mesh-positions",
951
- shrinkToFit,
952
- usage: BufferUsage.VERTEX | BufferUsage.COPY_DST
953
- });
954
- const uvBuffer = new Buffer({
955
- data: uvs,
956
- label: "attribute-mesh-uvs",
957
- shrinkToFit,
958
- usage: BufferUsage.VERTEX | BufferUsage.COPY_DST
959
- });
960
- const indexBuffer = new Buffer({
961
- data: indices,
962
- label: "index-mesh-buffer",
963
- shrinkToFit,
964
- usage: BufferUsage.INDEX | BufferUsage.COPY_DST
965
- });
966
- super({
967
- attributes: {
968
- aPosition: {
969
- buffer: positionBuffer,
970
- format: "float32x2",
971
- stride: 2 * 4,
972
- offset: 0
973
- },
974
- aUV: {
975
- buffer: uvBuffer,
976
- format: "float32x2",
977
- stride: 2 * 4,
978
- offset: 0
979
- }
980
- },
981
- indexBuffer,
982
- topology: options.topology
983
- });
984
- this.batchMode = "auto";
985
- }
986
- /** The positions of the mesh. */
987
- get positions() {
988
- return this.attributes.aPosition.buffer.data;
989
- }
990
- /**
991
- * Set the positions of the mesh.
992
- * When setting the positions, its important that the uvs array is at least as long as the positions array.
993
- * otherwise the geometry will not be valid.
994
- * @param {Float32Array} value - The positions of the mesh.
995
- */
996
- set positions(value) {
997
- this.attributes.aPosition.buffer.data = value;
998
- }
999
- /** The UVs of the mesh. */
1000
- get uvs() {
1001
- return this.attributes.aUV.buffer.data;
1626
+ const indices = options.indices || new Uint32Array([0, 1, 2, 0, 2, 3]);
1627
+ const shrinkToFit = options.shrinkBuffersToFit;
1628
+ const positionBuffer = new Buffer({
1629
+ data: positions,
1630
+ label: "attribute-mesh-positions",
1631
+ shrinkToFit,
1632
+ usage: BufferUsage.VERTEX | BufferUsage.COPY_DST
1633
+ });
1634
+ const uvBuffer = new Buffer({
1635
+ data: uvs,
1636
+ label: "attribute-mesh-uvs",
1637
+ shrinkToFit,
1638
+ usage: BufferUsage.VERTEX | BufferUsage.COPY_DST
1639
+ });
1640
+ const indexBuffer = new Buffer({
1641
+ data: indices,
1642
+ label: "index-mesh-buffer",
1643
+ shrinkToFit,
1644
+ usage: BufferUsage.INDEX | BufferUsage.COPY_DST
1645
+ });
1646
+ super({
1647
+ attributes: {
1648
+ aPosition: {
1649
+ buffer: positionBuffer,
1650
+ format: "float32x2",
1651
+ stride: 2 * 4,
1652
+ offset: 0
1653
+ },
1654
+ aUV: {
1655
+ buffer: uvBuffer,
1656
+ format: "float32x2",
1657
+ stride: 2 * 4,
1658
+ offset: 0
1659
+ }
1660
+ },
1661
+ indexBuffer,
1662
+ topology: options.topology
1663
+ });
1664
+ this.batchMode = "auto";
1665
+ }
1666
+ /** The positions of the mesh. */
1667
+ get positions() {
1668
+ return this.attributes.aPosition.buffer.data;
1669
+ }
1670
+ /**
1671
+ * Set the positions of the mesh.
1672
+ * When setting the positions, its important that the uvs array is at least as long as the positions array.
1673
+ * otherwise the geometry will not be valid.
1674
+ * @param {Float32Array} value - The positions of the mesh.
1675
+ */
1676
+ set positions(value) {
1677
+ this.attributes.aPosition.buffer.data = value;
1678
+ }
1679
+ /** The UVs of the mesh. */
1680
+ get uvs() {
1681
+ return this.attributes.aUV.buffer.data;
1682
+ }
1683
+ /**
1684
+ * Set the UVs of the mesh.
1685
+ * Its important that the uvs array you set is at least as long as the positions array.
1686
+ * otherwise the geometry will not be valid.
1687
+ * @param {Float32Array} value - The UVs of the mesh.
1688
+ */
1689
+ set uvs(value) {
1690
+ this.attributes.aUV.buffer.data = value;
1691
+ }
1692
+ /** The indices of the mesh. */
1693
+ get indices() {
1694
+ return this.indexBuffer.data;
1695
+ }
1696
+ set indices(value) {
1697
+ this.indexBuffer.data = value;
1698
+ }
1699
+ };
1700
+ _MeshGeometry.defaultOptions = {
1701
+ topology: "triangle-list",
1702
+ shrinkBuffersToFit: false
1703
+ };
1704
+ let MeshGeometry = _MeshGeometry;
1705
+
1706
+ let _internalCanvas = null;
1707
+ let _internalContext = null;
1708
+ function ensureInternalCanvas(width, height) {
1709
+ if (!_internalCanvas) {
1710
+ _internalCanvas = DOMAdapter.get().createCanvas(256, 128);
1711
+ _internalContext = _internalCanvas.getContext("2d", { willReadFrequently: true });
1712
+ _internalContext.globalCompositeOperation = "copy";
1713
+ _internalContext.globalAlpha = 1;
1714
+ }
1715
+ if (_internalCanvas.width < width || _internalCanvas.height < height) {
1716
+ _internalCanvas.width = nextPow2(width);
1717
+ _internalCanvas.height = nextPow2(height);
1718
+ }
1719
+ }
1720
+ function checkRow(data, width, y) {
1721
+ for (let x = 0, index = 4 * y * width; x < width; ++x, index += 4) {
1722
+ if (data[index + 3] !== 0)
1723
+ return false;
1724
+ }
1725
+ return true;
1726
+ }
1727
+ function checkColumn(data, width, x, top, bottom) {
1728
+ const stride = 4 * width;
1729
+ for (let y = top, index = top * stride + 4 * x; y <= bottom; ++y, index += stride) {
1730
+ if (data[index + 3] !== 0)
1731
+ return false;
1732
+ }
1733
+ return true;
1734
+ }
1735
+ function getCanvasBoundingBox(...args) {
1736
+ let options = args[0];
1737
+ if (!options.canvas) {
1738
+ options = { canvas: args[0], resolution: args[1] };
1739
+ }
1740
+ const { canvas } = options;
1741
+ const resolution = Math.min(options.resolution ?? 1, 1);
1742
+ const width = options.width ?? canvas.width;
1743
+ const height = options.height ?? canvas.height;
1744
+ let output = options.output;
1745
+ ensureInternalCanvas(width, height);
1746
+ if (!_internalContext) {
1747
+ throw new TypeError("Failed to get canvas 2D context");
1748
+ }
1749
+ _internalContext.drawImage(
1750
+ canvas,
1751
+ 0,
1752
+ 0,
1753
+ width,
1754
+ height,
1755
+ 0,
1756
+ 0,
1757
+ width * resolution,
1758
+ height * resolution
1759
+ );
1760
+ const imageData = _internalContext.getImageData(0, 0, width, height);
1761
+ const data = imageData.data;
1762
+ let left = 0;
1763
+ let top = 0;
1764
+ let right = width - 1;
1765
+ let bottom = height - 1;
1766
+ while (top < height && checkRow(data, width, top))
1767
+ ++top;
1768
+ if (top === height)
1769
+ return Rectangle.EMPTY;
1770
+ while (checkRow(data, width, bottom))
1771
+ --bottom;
1772
+ while (checkColumn(data, width, left, top, bottom))
1773
+ ++left;
1774
+ while (checkColumn(data, width, right, top, bottom))
1775
+ --right;
1776
+ ++right;
1777
+ ++bottom;
1778
+ _internalContext.globalCompositeOperation = "source-over";
1779
+ _internalContext.strokeRect(left, top, right - left, bottom - top);
1780
+ _internalContext.globalCompositeOperation = "copy";
1781
+ output ?? (output = new Rectangle());
1782
+ output.set(left / resolution, top / resolution, (right - left) / resolution, (bottom - top) / resolution);
1783
+ return output;
1784
+ }
1785
+
1786
+ const tempRect = new Rectangle();
1787
+ class CanvasTextGeneratorClass {
1788
+ /**
1789
+ * Creates a canvas with the specified text rendered to it.
1790
+ *
1791
+ * Generates a canvas of appropriate size, renders the text with the provided style,
1792
+ * and returns both the canvas/context and a Rectangle representing the text bounds.
1793
+ *
1794
+ * When trim is enabled in the style, the frame will represent the bounds of the
1795
+ * non-transparent pixels, which can be smaller than the full canvas.
1796
+ * @param options - The options for generating the text canvas
1797
+ * @param options.text - The text to render
1798
+ * @param options.style - The style to apply to the text
1799
+ * @param options.resolution - The resolution of the canvas (defaults to 1)
1800
+ * @param options.padding
1801
+ * @returns An object containing the canvas/context and the frame (bounds) of the text
1802
+ */
1803
+ getCanvasAndContext(options) {
1804
+ const { text, style, resolution = 1 } = options;
1805
+ const padding = style._getFinalPadding();
1806
+ const measured = CanvasTextMetrics.measureText(text || " ", style);
1807
+ const width = Math.ceil(Math.ceil(Math.max(1, measured.width) + padding * 2) * resolution);
1808
+ const height = Math.ceil(Math.ceil(Math.max(1, measured.height) + padding * 2) * resolution);
1809
+ const canvasAndContext = CanvasPool.getOptimalCanvasAndContext(width, height);
1810
+ this._renderTextToCanvas(text, style, padding, resolution, canvasAndContext);
1811
+ const frame = style.trim ? getCanvasBoundingBox({ canvas: canvasAndContext.canvas, width, height, resolution: 1, output: tempRect }) : tempRect.set(0, 0, width, height);
1812
+ return {
1813
+ canvasAndContext,
1814
+ frame
1815
+ };
1816
+ }
1817
+ /**
1818
+ * Returns a canvas and context to the pool.
1819
+ *
1820
+ * This should be called when you're done with the canvas to allow reuse
1821
+ * and prevent memory leaks.
1822
+ * @param canvasAndContext - The canvas and context to return to the pool
1823
+ */
1824
+ returnCanvasAndContext(canvasAndContext) {
1825
+ CanvasPool.returnCanvasAndContext(canvasAndContext);
1826
+ }
1827
+ /**
1828
+ * Renders text to its canvas, and updates its texture.
1829
+ * @param text - The text to render
1830
+ * @param style - The style of the text
1831
+ * @param padding - The padding of the text
1832
+ * @param resolution - The resolution of the text
1833
+ * @param canvasAndContext - The canvas and context to render the text to
1834
+ */
1835
+ _renderTextToCanvas(text, style, padding, resolution, canvasAndContext) {
1836
+ const { canvas, context } = canvasAndContext;
1837
+ const font = fontStringFromTextStyle(style);
1838
+ const measured = CanvasTextMetrics.measureText(text || " ", style);
1839
+ const lines = measured.lines;
1840
+ const lineHeight = measured.lineHeight;
1841
+ const lineWidths = measured.lineWidths;
1842
+ const maxLineWidth = measured.maxLineWidth;
1843
+ const fontProperties = measured.fontProperties;
1844
+ const height = canvas.height;
1845
+ context.resetTransform();
1846
+ context.scale(resolution, resolution);
1847
+ context.textBaseline = style.textBaseline;
1848
+ if (style._stroke?.width) {
1849
+ const strokeStyle = style._stroke;
1850
+ context.lineWidth = strokeStyle.width;
1851
+ context.miterLimit = strokeStyle.miterLimit;
1852
+ context.lineJoin = strokeStyle.join;
1853
+ context.lineCap = strokeStyle.cap;
1854
+ }
1855
+ context.font = font;
1856
+ let linePositionX;
1857
+ let linePositionY;
1858
+ const passesCount = style.dropShadow ? 2 : 1;
1859
+ for (let i = 0; i < passesCount; ++i) {
1860
+ const isShadowPass = style.dropShadow && i === 0;
1861
+ const dsOffsetText = isShadowPass ? Math.ceil(Math.max(1, height) + padding * 2) : 0;
1862
+ const dsOffsetShadow = dsOffsetText * resolution;
1863
+ if (isShadowPass) {
1864
+ context.fillStyle = "black";
1865
+ context.strokeStyle = "black";
1866
+ const shadowOptions = style.dropShadow;
1867
+ const dropShadowColor = shadowOptions.color;
1868
+ const dropShadowAlpha = shadowOptions.alpha;
1869
+ context.shadowColor = Color.shared.setValue(dropShadowColor).setAlpha(dropShadowAlpha).toRgbaString();
1870
+ const dropShadowBlur = shadowOptions.blur * resolution;
1871
+ const dropShadowDistance = shadowOptions.distance * resolution;
1872
+ context.shadowBlur = dropShadowBlur;
1873
+ context.shadowOffsetX = Math.cos(shadowOptions.angle) * dropShadowDistance;
1874
+ context.shadowOffsetY = Math.sin(shadowOptions.angle) * dropShadowDistance + dsOffsetShadow;
1875
+ } else {
1876
+ context.fillStyle = style._fill ? getCanvasFillStyle(style._fill, context, measured) : null;
1877
+ if (style._stroke?.width) {
1878
+ const padding2 = style._stroke.width * style._stroke.alignment;
1879
+ context.strokeStyle = getCanvasFillStyle(style._stroke, context, measured, padding2);
1880
+ }
1881
+ context.shadowColor = "black";
1882
+ }
1883
+ let linePositionYShift = (lineHeight - fontProperties.fontSize) / 2;
1884
+ if (lineHeight - fontProperties.fontSize < 0) {
1885
+ linePositionYShift = 0;
1886
+ }
1887
+ const strokeWidth = style._stroke?.width ?? 0;
1888
+ for (let i2 = 0; i2 < lines.length; i2++) {
1889
+ linePositionX = strokeWidth / 2;
1890
+ linePositionY = strokeWidth / 2 + i2 * lineHeight + fontProperties.ascent + linePositionYShift;
1891
+ if (style.align === "right") {
1892
+ linePositionX += maxLineWidth - lineWidths[i2];
1893
+ } else if (style.align === "center") {
1894
+ linePositionX += (maxLineWidth - lineWidths[i2]) / 2;
1895
+ }
1896
+ if (style._stroke?.width) {
1897
+ this._drawLetterSpacing(
1898
+ lines[i2],
1899
+ style,
1900
+ canvasAndContext,
1901
+ linePositionX + padding,
1902
+ linePositionY + padding - dsOffsetText,
1903
+ true
1904
+ );
1905
+ }
1906
+ if (style._fill !== void 0) {
1907
+ this._drawLetterSpacing(
1908
+ lines[i2],
1909
+ style,
1910
+ canvasAndContext,
1911
+ linePositionX + padding,
1912
+ linePositionY + padding - dsOffsetText
1913
+ );
1914
+ }
1915
+ }
1916
+ }
1002
1917
  }
1003
1918
  /**
1004
- * Set the UVs of the mesh.
1005
- * Its important that the uvs array you set is at least as long as the positions array.
1006
- * otherwise the geometry will not be valid.
1007
- * @param {Float32Array} value - The UVs of the mesh.
1919
+ * Render the text with letter-spacing.
1920
+ *
1921
+ * This method handles rendering text with the correct letter spacing, using either:
1922
+ * 1. Native letter spacing if supported by the browser
1923
+ * 2. Manual letter spacing calculation if not natively supported
1924
+ *
1925
+ * For manual letter spacing, it calculates the position of each character
1926
+ * based on its width and the desired spacing.
1927
+ * @param text - The text to draw
1928
+ * @param style - The text style to apply
1929
+ * @param canvasAndContext - The canvas and context to draw to
1930
+ * @param x - Horizontal position to draw the text
1931
+ * @param y - Vertical position to draw the text
1932
+ * @param isStroke - Whether to render the stroke (true) or fill (false)
1933
+ * @private
1008
1934
  */
1009
- set uvs(value) {
1010
- this.attributes.aUV.buffer.data = value;
1011
- }
1012
- /** The indices of the mesh. */
1013
- get indices() {
1014
- return this.indexBuffer.data;
1015
- }
1016
- set indices(value) {
1017
- this.indexBuffer.data = value;
1935
+ _drawLetterSpacing(text, style, canvasAndContext, x, y, isStroke = false) {
1936
+ const { context } = canvasAndContext;
1937
+ const letterSpacing = style.letterSpacing;
1938
+ let useExperimentalLetterSpacing = false;
1939
+ if (CanvasTextMetrics.experimentalLetterSpacingSupported) {
1940
+ if (CanvasTextMetrics.experimentalLetterSpacing) {
1941
+ context.letterSpacing = `${letterSpacing}px`;
1942
+ context.textLetterSpacing = `${letterSpacing}px`;
1943
+ useExperimentalLetterSpacing = true;
1944
+ } else {
1945
+ context.letterSpacing = "0px";
1946
+ context.textLetterSpacing = "0px";
1947
+ }
1948
+ }
1949
+ if (letterSpacing === 0 || useExperimentalLetterSpacing) {
1950
+ if (isStroke) {
1951
+ context.strokeText(text, x, y);
1952
+ } else {
1953
+ context.fillText(text, x, y);
1954
+ }
1955
+ return;
1956
+ }
1957
+ let currentPosition = x;
1958
+ const stringArray = CanvasTextMetrics.graphemeSegmenter(text);
1959
+ let previousWidth = context.measureText(text).width;
1960
+ let currentWidth = 0;
1961
+ for (let i = 0; i < stringArray.length; ++i) {
1962
+ const currentChar = stringArray[i];
1963
+ if (isStroke) {
1964
+ context.strokeText(currentChar, currentPosition, y);
1965
+ } else {
1966
+ context.fillText(currentChar, currentPosition, y);
1967
+ }
1968
+ let textStr = "";
1969
+ for (let j = i + 1; j < stringArray.length; ++j) {
1970
+ textStr += stringArray[j];
1971
+ }
1972
+ currentWidth = context.measureText(textStr).width;
1973
+ currentPosition += previousWidth - currentWidth + letterSpacing;
1974
+ previousWidth = currentWidth;
1975
+ }
1018
1976
  }
1019
- };
1020
- _MeshGeometry.defaultOptions = {
1021
- topology: "triangle-list",
1022
- shrinkBuffersToFit: false
1023
- };
1024
- let MeshGeometry = _MeshGeometry;
1977
+ }
1978
+ const CanvasTextGenerator = new CanvasTextGeneratorClass();
1025
1979
 
1026
1980
  function textStyleToCSS(style) {
1027
1981
  const stroke = style._stroke;
@@ -1107,28 +2061,85 @@ class HTMLTextStyle extends TextStyle {
1107
2061
  constructor(options = {}) {
1108
2062
  super(options);
1109
2063
  this._cssOverrides = [];
1110
- this.cssOverrides ?? (this.cssOverrides = options.cssOverrides);
2064
+ this.cssOverrides = options.cssOverrides ?? [];
1111
2065
  this.tagStyles = options.tagStyles ?? {};
1112
2066
  }
1113
- /** List of style overrides that will be applied to the HTML text. */
2067
+ /**
2068
+ * List of CSS style overrides to apply to the HTML text.
2069
+ * These styles are added after the built-in styles and can override any default styling.
2070
+ * @advanced
2071
+ */
1114
2072
  set cssOverrides(value) {
1115
2073
  this._cssOverrides = value instanceof Array ? value : [value];
1116
2074
  this.update();
1117
2075
  }
2076
+ /** @advanced */
1118
2077
  get cssOverrides() {
1119
2078
  return this._cssOverrides;
1120
2079
  }
1121
- _generateKey() {
1122
- this._styleKey = generateTextStyleKey(this) + this._cssOverrides.join("-");
1123
- return this._styleKey;
1124
- }
2080
+ /**
2081
+ * Updates the text style and triggers a refresh of the CSS style cache.
2082
+ * This method is called automatically when style properties are changed.
2083
+ * @example
2084
+ * ```ts
2085
+ * // Update after multiple changes
2086
+ * const text = new HTMLText({
2087
+ * text: 'Hello World',
2088
+ * style
2089
+ * });
2090
+ *
2091
+ * style.fontSize = 32;
2092
+ * style.fill = '#00ff00';
2093
+ * style.fontFamily = 'Arial';
2094
+ * style.update(); // Apply all changes at once
2095
+ * ```
2096
+ * @advanced
2097
+ * @see {@link HTMLTextStyle#cssStyle} For accessing the generated CSS
2098
+ * @see {@link HTMLTextStyle#cssOverrides} For managing CSS overrides
2099
+ */
1125
2100
  update() {
1126
2101
  this._cssStyle = null;
1127
2102
  super.update();
1128
2103
  }
1129
2104
  /**
1130
2105
  * Creates a new HTMLTextStyle object with the same values as this one.
1131
- * @returns New cloned HTMLTextStyle object
2106
+ * This creates a deep copy of all style properties, including dropShadow and tag styles.
2107
+ * @example
2108
+ * ```ts
2109
+ * // Create original style
2110
+ * const originalStyle = new HTMLTextStyle({
2111
+ * fontSize: 24,
2112
+ * fill: '#ff0000',
2113
+ * tagStyles: {
2114
+ * header: { fontSize: 32, fill: '#00ff00' }
2115
+ * }
2116
+ * });
2117
+ *
2118
+ * // Clone the style
2119
+ * const clonedStyle = originalStyle.clone();
2120
+ *
2121
+ * // Modify cloned style independently
2122
+ * clonedStyle.fontSize = 36;
2123
+ * clonedStyle.fill = '#0000ff';
2124
+ *
2125
+ * // Original style remains unchanged
2126
+ * console.log(originalStyle.fontSize); // Still 24
2127
+ * console.log(originalStyle.fill); // Still '#ff0000'
2128
+ * ```
2129
+ *
2130
+ * Properties that are cloned:
2131
+ * - Basic text properties (fontSize, fontFamily, etc.)
2132
+ * - Fill and stroke styles
2133
+ * - Drop shadow configuration
2134
+ * - CSS overrides
2135
+ * - Tag styles (deep copied)
2136
+ * - Word wrap settings
2137
+ * - Alignment and spacing
2138
+ * @returns {HTMLTextStyle} A new HTMLTextStyle instance with the same properties
2139
+ * @see {@link HTMLTextStyle} For available style properties
2140
+ * @see {@link HTMLTextStyle#cssOverrides} For CSS override handling
2141
+ * @see {@link HTMLTextStyle#tagStyles} For tag style configuration
2142
+ * @standard
1132
2143
  */
1133
2144
  clone() {
1134
2145
  return new HTMLTextStyle({
@@ -1148,9 +2159,14 @@ class HTMLTextStyle extends TextStyle {
1148
2159
  whiteSpace: this.whiteSpace,
1149
2160
  wordWrap: this.wordWrap,
1150
2161
  wordWrapWidth: this.wordWrapWidth,
1151
- cssOverrides: this.cssOverrides
2162
+ cssOverrides: this.cssOverrides,
2163
+ tagStyles: { ...this.tagStyles }
1152
2164
  });
1153
2165
  }
2166
+ /**
2167
+ * The CSS style string that will be applied to the HTML text.
2168
+ * @advanced
2169
+ */
1154
2170
  get cssStyle() {
1155
2171
  if (!this._cssStyle) {
1156
2172
  this._cssStyle = textStyleToCSS(this);
@@ -1165,6 +2181,7 @@ class HTMLTextStyle extends TextStyle {
1165
2181
  * @param {string} value - CSS style(s) to add.
1166
2182
  * @example
1167
2183
  * style.addOverride('background-color: red');
2184
+ * @advanced
1168
2185
  */
1169
2186
  addOverride(...value) {
1170
2187
  const toAdd = value.filter((v) => !this.cssOverrides.includes(v));
@@ -1178,6 +2195,7 @@ class HTMLTextStyle extends TextStyle {
1178
2195
  * @param {string} value - CSS style to remove.
1179
2196
  * @example
1180
2197
  * style.removeOverride('background-color: red');
2198
+ * @advanced
1181
2199
  */
1182
2200
  removeOverride(...value) {
1183
2201
  const toRemove = value.filter((v) => this.cssOverrides.includes(v));
@@ -1186,12 +2204,83 @@ class HTMLTextStyle extends TextStyle {
1186
2204
  this.update();
1187
2205
  }
1188
2206
  }
2207
+ /**
2208
+ * Sets the fill style for the text. HTML text only supports color fills (string or number values).
2209
+ * Texture fills are not supported and will trigger a warning in debug mode.
2210
+ * @example
2211
+ * ```ts
2212
+ * // Using hex colors
2213
+ * const text = new HTMLText({
2214
+ * text: 'Colored Text',
2215
+ * style: {
2216
+ * fill: 0xff0000 // Red color
2217
+ * }
2218
+ * });
2219
+ *
2220
+ * // Using CSS color strings
2221
+ * text.style.fill = '#00ff00'; // Hex string (Green)
2222
+ * text.style.fill = 'blue'; // Named color
2223
+ * text.style.fill = 'rgb(255,0,0)' // RGB
2224
+ * text.style.fill = '#f0f'; // Short hex
2225
+ *
2226
+ * // Invalid usage (will trigger warning in debug)
2227
+ * text.style.fill = {
2228
+ * type: 'pattern',
2229
+ * texture: Texture.from('pattern.png')
2230
+ * }; // Not supported, falls back to default
2231
+ * ```
2232
+ * @param value - The fill color to use. Must be a string or number.
2233
+ * @throws {Warning} In debug mode when attempting to use unsupported fill types
2234
+ * @see {@link TextStyle#fill} For full fill options in canvas text
2235
+ * @standard
2236
+ */
1189
2237
  set fill(value) {
1190
2238
  if (typeof value !== "string" && typeof value !== "number") {
1191
2239
  warn("[HTMLTextStyle] only color fill is not supported by HTMLText");
1192
2240
  }
1193
2241
  super.fill = value;
1194
2242
  }
2243
+ /**
2244
+ * Sets the stroke style for the text. HTML text only supports color strokes (string or number values).
2245
+ * Texture strokes are not supported and will trigger a warning in debug mode.
2246
+ * @example
2247
+ * ```ts
2248
+ * // Using hex colors
2249
+ * const text = new HTMLText({
2250
+ * text: 'Outlined Text',
2251
+ * style: {
2252
+ * stroke: 0xff0000 // Red outline
2253
+ * }
2254
+ * });
2255
+ *
2256
+ * // Using CSS color strings
2257
+ * text.style.stroke = '#00ff00'; // Hex string (Green)
2258
+ * text.style.stroke = 'blue'; // Named color
2259
+ * text.style.stroke = 'rgb(255,0,0)' // RGB
2260
+ * text.style.stroke = '#f0f'; // Short hex
2261
+ *
2262
+ * // Using stroke width
2263
+ * text.style = {
2264
+ * stroke: {
2265
+ * color: '#ff0000',
2266
+ * width: 2
2267
+ * }
2268
+ * };
2269
+ *
2270
+ * // Remove stroke
2271
+ * text.style.stroke = null;
2272
+ *
2273
+ * // Invalid usage (will trigger warning in debug)
2274
+ * text.style.stroke = {
2275
+ * type: 'pattern',
2276
+ * texture: Texture.from('pattern.png')
2277
+ * }; // Not supported, falls back to default
2278
+ * ```
2279
+ * @param value - The stroke style to use. Must be a string, number, or stroke configuration object
2280
+ * @throws {Warning} In debug mode when attempting to use unsupported stroke types
2281
+ * @see {@link TextStyle#stroke} For full stroke options in canvas text
2282
+ * @standard
2283
+ */
1195
2284
  set stroke(value) {
1196
2285
  if (value && typeof value !== "string" && typeof value !== "number") {
1197
2286
  warn("[HTMLTextStyle] only color stroke is not supported by HTMLText");
@@ -1238,20 +2327,31 @@ function measureHtmlText(text, style, fontStyleCSS, htmlTextRenderData) {
1238
2327
  };
1239
2328
  }
1240
2329
 
2330
+ class GraphicsGpuData {
2331
+ constructor() {
2332
+ this.batches = [];
2333
+ this.batched = false;
2334
+ }
2335
+ destroy() {
2336
+ this.batches.forEach((batch) => {
2337
+ BigPool.return(batch);
2338
+ });
2339
+ this.batches.length = 0;
2340
+ }
2341
+ }
1241
2342
  class GraphicsPipe {
1242
2343
  constructor(renderer, adaptor) {
1243
2344
  this.state = State.for2d();
1244
- // batchable graphics list, used to render batches
1245
- this._graphicsBatchesHash = /* @__PURE__ */ Object.create(null);
1246
- this._destroyRenderableBound = this.destroyRenderable.bind(this);
1247
2345
  this.renderer = renderer;
1248
2346
  this._adaptor = adaptor;
1249
- this._adaptor.init();
1250
- this.renderer.renderableGC.addManagedHash(this, "_graphicsBatchesHash");
2347
+ this.renderer.runners.contextChange.add(this);
2348
+ }
2349
+ contextChange() {
2350
+ this._adaptor.contextChange(this.renderer);
1251
2351
  }
1252
2352
  validateRenderable(graphics) {
1253
2353
  const context = graphics.context;
1254
- const wasBatched = !!this._graphicsBatchesHash[graphics.uid];
2354
+ const wasBatched = !!graphics._gpuData;
1255
2355
  const gpuContext = this.renderer.graphicsContext.updateGpuContext(context);
1256
2356
  if (gpuContext.isBatchable || wasBatched !== gpuContext.isBatchable) {
1257
2357
  return true;
@@ -1271,19 +2371,12 @@ class GraphicsPipe {
1271
2371
  }
1272
2372
  }
1273
2373
  updateRenderable(graphics) {
1274
- const batches = this._graphicsBatchesHash[graphics.uid];
1275
- if (batches) {
1276
- for (let i = 0; i < batches.length; i++) {
1277
- const batch = batches[i];
1278
- batch._batcher.updateElement(batch);
1279
- }
1280
- }
1281
- }
1282
- destroyRenderable(graphics) {
1283
- if (this._graphicsBatchesHash[graphics.uid]) {
1284
- this._removeBatchForRenderable(graphics.uid);
2374
+ const gpuData = this._getGpuDataForRenderable(graphics);
2375
+ const batches = gpuData.batches;
2376
+ for (let i = 0; i < batches.length; i++) {
2377
+ const batch = batches[i];
2378
+ batch._batcher.updateElement(batch);
1285
2379
  }
1286
- graphics.off("destroyed", this._destroyRenderableBound);
1287
2380
  }
1288
2381
  execute(graphics) {
1289
2382
  if (!graphics.isRenderable)
@@ -1307,59 +2400,46 @@ class GraphicsPipe {
1307
2400
  this._adaptor.execute(this, graphics);
1308
2401
  }
1309
2402
  _rebuild(graphics) {
1310
- const wasBatched = !!this._graphicsBatchesHash[graphics.uid];
2403
+ const gpuData = this._getGpuDataForRenderable(graphics);
1311
2404
  const gpuContext = this.renderer.graphicsContext.updateGpuContext(graphics.context);
1312
- if (wasBatched) {
1313
- this._removeBatchForRenderable(graphics.uid);
1314
- }
2405
+ gpuData.destroy();
1315
2406
  if (gpuContext.isBatchable) {
1316
- this._initBatchesForRenderable(graphics);
2407
+ this._updateBatchesForRenderable(graphics, gpuData);
1317
2408
  }
1318
- graphics.batched = gpuContext.isBatchable;
1319
2409
  }
1320
2410
  _addToBatcher(graphics, instructionSet) {
1321
2411
  const batchPipe = this.renderer.renderPipes.batch;
1322
- const batches = this._getBatchesForRenderable(graphics);
2412
+ const batches = this._getGpuDataForRenderable(graphics).batches;
1323
2413
  for (let i = 0; i < batches.length; i++) {
1324
2414
  const batch = batches[i];
1325
2415
  batchPipe.addToBatch(batch, instructionSet);
1326
2416
  }
1327
2417
  }
1328
- _getBatchesForRenderable(graphics) {
1329
- return this._graphicsBatchesHash[graphics.uid] || this._initBatchesForRenderable(graphics);
2418
+ _getGpuDataForRenderable(graphics) {
2419
+ return graphics._gpuData[this.renderer.uid] || this._initGpuDataForRenderable(graphics);
2420
+ }
2421
+ _initGpuDataForRenderable(graphics) {
2422
+ const gpuData = new GraphicsGpuData();
2423
+ graphics._gpuData[this.renderer.uid] = gpuData;
2424
+ return gpuData;
1330
2425
  }
1331
- _initBatchesForRenderable(graphics) {
2426
+ _updateBatchesForRenderable(graphics, gpuData) {
1332
2427
  const context = graphics.context;
1333
2428
  const gpuContext = this.renderer.graphicsContext.getGpuContext(context);
1334
2429
  const roundPixels = this.renderer._roundPixels | graphics._roundPixels;
1335
- const batches = gpuContext.batches.map((batch) => {
2430
+ gpuData.batches = gpuContext.batches.map((batch) => {
1336
2431
  const batchClone = BigPool.get(BatchableGraphics);
1337
2432
  batch.copyTo(batchClone);
1338
2433
  batchClone.renderable = graphics;
1339
2434
  batchClone.roundPixels = roundPixels;
1340
2435
  return batchClone;
1341
2436
  });
1342
- if (this._graphicsBatchesHash[graphics.uid] === void 0) {
1343
- graphics.on("destroyed", this._destroyRenderableBound);
1344
- }
1345
- this._graphicsBatchesHash[graphics.uid] = batches;
1346
- return batches;
1347
- }
1348
- _removeBatchForRenderable(graphicsUid) {
1349
- this._graphicsBatchesHash[graphicsUid].forEach((batch) => {
1350
- BigPool.return(batch);
1351
- });
1352
- this._graphicsBatchesHash[graphicsUid] = null;
1353
2437
  }
1354
2438
  destroy() {
1355
2439
  this.renderer = null;
1356
2440
  this._adaptor.destroy();
1357
2441
  this._adaptor = null;
1358
2442
  this.state = null;
1359
- for (const i in this._graphicsBatchesHash) {
1360
- this._removeBatchForRenderable(i);
1361
- }
1362
- this._graphicsBatchesHash = null;
1363
2443
  }
1364
2444
  }
1365
2445
  /** @ignore */
@@ -1525,6 +2605,10 @@ class BatchableMesh {
1525
2605
  }
1526
2606
  }
1527
2607
 
2608
+ class MeshGpuData {
2609
+ destroy() {
2610
+ }
2611
+ }
1528
2612
  class MeshPipe {
1529
2613
  constructor(renderer, adaptor) {
1530
2614
  this.localUniforms = new UniformGroup({
@@ -1535,14 +2619,9 @@ class MeshPipe {
1535
2619
  this.localUniformsBindGroup = new BindGroup({
1536
2620
  0: this.localUniforms
1537
2621
  });
1538
- this._meshDataHash = /* @__PURE__ */ Object.create(null);
1539
- this._gpuBatchableMeshHash = /* @__PURE__ */ Object.create(null);
1540
- this._destroyRenderableBound = this.destroyRenderable.bind(this);
1541
2622
  this.renderer = renderer;
1542
2623
  this._adaptor = adaptor;
1543
2624
  this._adaptor.init();
1544
- renderer.renderableGC.addManagedHash(this, "_gpuBatchableMeshHash");
1545
- renderer.renderableGC.addManagedHash(this, "_meshDataHash");
1546
2625
  }
1547
2626
  validateRenderable(mesh) {
1548
2627
  const meshData = this._getMeshData(mesh);
@@ -1584,21 +2663,12 @@ class MeshPipe {
1584
2663
  }
1585
2664
  updateRenderable(mesh) {
1586
2665
  if (mesh.batched) {
1587
- const gpuBatchableMesh = this._gpuBatchableMeshHash[mesh.uid];
2666
+ const gpuBatchableMesh = this._getBatchableMesh(mesh);
1588
2667
  gpuBatchableMesh.setTexture(mesh._texture);
1589
2668
  gpuBatchableMesh.geometry = mesh._geometry;
1590
2669
  gpuBatchableMesh._batcher.updateElement(gpuBatchableMesh);
1591
2670
  }
1592
2671
  }
1593
- destroyRenderable(mesh) {
1594
- this._meshDataHash[mesh.uid] = null;
1595
- const gpuMesh = this._gpuBatchableMeshHash[mesh.uid];
1596
- if (gpuMesh) {
1597
- BigPool.return(gpuMesh);
1598
- this._gpuBatchableMeshHash[mesh.uid] = null;
1599
- }
1600
- mesh.off("destroyed", this._destroyRenderableBound);
1601
- }
1602
2672
  execute(mesh) {
1603
2673
  if (!mesh.isRenderable)
1604
2674
  return;
@@ -1615,37 +2685,33 @@ class MeshPipe {
1615
2685
  this._adaptor.execute(this, mesh);
1616
2686
  }
1617
2687
  _getMeshData(mesh) {
1618
- return this._meshDataHash[mesh.uid] || this._initMeshData(mesh);
2688
+ var _a, _b;
2689
+ (_a = mesh._gpuData)[_b = this.renderer.uid] || (_a[_b] = new MeshGpuData());
2690
+ return mesh._gpuData[this.renderer.uid].meshData || this._initMeshData(mesh);
1619
2691
  }
1620
2692
  _initMeshData(mesh) {
1621
- this._meshDataHash[mesh.uid] = {
2693
+ mesh._gpuData[this.renderer.uid].meshData = {
1622
2694
  batched: mesh.batched,
1623
2695
  indexSize: mesh._geometry.indices?.length,
1624
2696
  vertexSize: mesh._geometry.positions?.length
1625
2697
  };
1626
- mesh.on("destroyed", this._destroyRenderableBound);
1627
- return this._meshDataHash[mesh.uid];
2698
+ return mesh._gpuData[this.renderer.uid].meshData;
1628
2699
  }
1629
2700
  _getBatchableMesh(mesh) {
1630
- return this._gpuBatchableMeshHash[mesh.uid] || this._initBatchableMesh(mesh);
2701
+ var _a, _b;
2702
+ (_a = mesh._gpuData)[_b = this.renderer.uid] || (_a[_b] = new MeshGpuData());
2703
+ return mesh._gpuData[this.renderer.uid].batchableMesh || this._initBatchableMesh(mesh);
1631
2704
  }
1632
2705
  _initBatchableMesh(mesh) {
1633
- const gpuMesh = BigPool.get(BatchableMesh);
2706
+ const gpuMesh = new BatchableMesh();
1634
2707
  gpuMesh.renderable = mesh;
1635
2708
  gpuMesh.setTexture(mesh._texture);
1636
2709
  gpuMesh.transform = mesh.groupTransform;
1637
2710
  gpuMesh.roundPixels = this.renderer._roundPixels | mesh._roundPixels;
1638
- this._gpuBatchableMeshHash[mesh.uid] = gpuMesh;
2711
+ mesh._gpuData[this.renderer.uid].batchableMesh = gpuMesh;
1639
2712
  return gpuMesh;
1640
2713
  }
1641
2714
  destroy() {
1642
- for (const i in this._gpuBatchableMeshHash) {
1643
- if (this._gpuBatchableMeshHash[i]) {
1644
- BigPool.return(this._gpuBatchableMeshHash[i]);
1645
- }
1646
- }
1647
- this._gpuBatchableMeshHash = null;
1648
- this._meshDataHash = null;
1649
2715
  this.localUniforms = null;
1650
2716
  this.localUniformsBindGroup = null;
1651
2717
  this._adaptor.destroy();
@@ -1728,7 +2794,7 @@ function generateParticleUpdateFunction(properties) {
1728
2794
  function generateUpdateFunction(properties, dynamic) {
1729
2795
  const funcFragments = [];
1730
2796
  funcFragments.push(`
1731
-
2797
+
1732
2798
  var index = 0;
1733
2799
 
1734
2800
  for (let i = 0; i < ps.length; ++i)
@@ -1925,10 +2991,9 @@ class ParticleContainerPipe {
1925
2991
  * @param adaptor
1926
2992
  */
1927
2993
  constructor(renderer, adaptor) {
2994
+ /** @internal */
1928
2995
  this.state = State.for2d();
1929
- this._gpuBufferHash = /* @__PURE__ */ Object.create(null);
1930
- // eslint-disable-next-line max-len
1931
- this._destroyRenderableBound = this.destroyRenderable.bind(this);
2996
+ /** Local uniforms that are used for rendering particles. */
1932
2997
  this.localUniforms = new UniformGroup({
1933
2998
  uTranslationMatrix: { value: new Matrix(), type: "mat3x3<f32>" },
1934
2999
  uColor: { value: new Float32Array(4), type: "vec4<f32>" },
@@ -1948,24 +3013,17 @@ class ParticleContainerPipe {
1948
3013
  instructionSet.add(renderable);
1949
3014
  }
1950
3015
  getBuffers(renderable) {
1951
- return this._gpuBufferHash[renderable.uid] || this._initBuffer(renderable);
3016
+ return renderable._gpuData[this.renderer.uid] || this._initBuffer(renderable);
1952
3017
  }
1953
3018
  _initBuffer(renderable) {
1954
- this._gpuBufferHash[renderable.uid] = new ParticleBuffer({
3019
+ renderable._gpuData[this.renderer.uid] = new ParticleBuffer({
1955
3020
  size: renderable.particleChildren.length,
1956
3021
  properties: renderable._properties
1957
3022
  });
1958
- renderable.on("destroyed", this._destroyRenderableBound);
1959
- return this._gpuBufferHash[renderable.uid];
3023
+ return renderable._gpuData[this.renderer.uid];
1960
3024
  }
1961
3025
  updateRenderable(_renderable) {
1962
3026
  }
1963
- destroyRenderable(renderable) {
1964
- const buffer = this._gpuBufferHash[renderable.uid];
1965
- buffer.destroy();
1966
- this._gpuBufferHash[renderable.uid] = null;
1967
- renderable.off("destroyed", this._destroyRenderableBound);
1968
- }
1969
3027
  execute(container) {
1970
3028
  const children = container.particleChildren;
1971
3029
  if (children.length === 0) {
@@ -2122,12 +3180,18 @@ _NineSliceGeometry.defaultOptions = {
2122
3180
  };
2123
3181
  let NineSliceGeometry = _NineSliceGeometry;
2124
3182
 
3183
+ class NineSliceSpriteGpuData extends BatchableMesh {
3184
+ constructor() {
3185
+ super();
3186
+ this.geometry = new NineSliceGeometry();
3187
+ }
3188
+ destroy() {
3189
+ this.geometry.destroy();
3190
+ }
3191
+ }
2125
3192
  class NineSliceSpritePipe {
2126
3193
  constructor(renderer) {
2127
- this._gpuSpriteHash = /* @__PURE__ */ Object.create(null);
2128
- this._destroyRenderableBound = this.destroyRenderable.bind(this);
2129
3194
  this._renderer = renderer;
2130
- this._renderer.renderableGC.addManagedHash(this, "_gpuSpriteHash");
2131
3195
  }
2132
3196
  addRenderable(sprite, instructionSet) {
2133
3197
  const gpuSprite = this._getGpuSprite(sprite);
@@ -2136,7 +3200,7 @@ class NineSliceSpritePipe {
2136
3200
  this._renderer.renderPipes.batch.addToBatch(gpuSprite, instructionSet);
2137
3201
  }
2138
3202
  updateRenderable(sprite) {
2139
- const gpuSprite = this._gpuSpriteHash[sprite.uid];
3203
+ const gpuSprite = this._getGpuSprite(sprite);
2140
3204
  if (sprite.didViewUpdate)
2141
3205
  this._updateBatchableSprite(sprite, gpuSprite);
2142
3206
  gpuSprite._batcher.updateElement(gpuSprite);
@@ -2148,40 +3212,26 @@ class NineSliceSpritePipe {
2148
3212
  sprite._texture
2149
3213
  );
2150
3214
  }
2151
- destroyRenderable(sprite) {
2152
- const batchableMesh = this._gpuSpriteHash[sprite.uid];
2153
- BigPool.return(batchableMesh.geometry);
2154
- BigPool.return(batchableMesh);
2155
- this._gpuSpriteHash[sprite.uid] = null;
2156
- sprite.off("destroyed", this._destroyRenderableBound);
2157
- }
2158
3215
  _updateBatchableSprite(sprite, batchableSprite) {
2159
3216
  batchableSprite.geometry.update(sprite);
2160
3217
  batchableSprite.setTexture(sprite._texture);
2161
3218
  }
2162
3219
  _getGpuSprite(sprite) {
2163
- return this._gpuSpriteHash[sprite.uid] || this._initGPUSprite(sprite);
3220
+ return sprite._gpuData[this._renderer.uid] || this._initGPUSprite(sprite);
2164
3221
  }
2165
3222
  _initGPUSprite(sprite) {
2166
- const batchableMesh = BigPool.get(BatchableMesh);
2167
- batchableMesh.geometry = BigPool.get(NineSliceGeometry);
3223
+ const gpuData = sprite._gpuData[this._renderer.uid] = new NineSliceSpriteGpuData();
3224
+ const batchableMesh = gpuData;
2168
3225
  batchableMesh.renderable = sprite;
2169
3226
  batchableMesh.transform = sprite.groupTransform;
2170
3227
  batchableMesh.texture = sprite._texture;
2171
3228
  batchableMesh.roundPixels = this._renderer._roundPixels | sprite._roundPixels;
2172
- this._gpuSpriteHash[sprite.uid] = batchableMesh;
2173
3229
  if (!sprite.didViewUpdate) {
2174
3230
  this._updateBatchableSprite(sprite, batchableMesh);
2175
3231
  }
2176
- sprite.on("destroyed", this._destroyRenderableBound);
2177
- return batchableMesh;
3232
+ return gpuData;
2178
3233
  }
2179
3234
  destroy() {
2180
- for (const i in this._gpuSpriteHash) {
2181
- const batchableMesh = this._gpuSpriteHash[i];
2182
- batchableMesh.geometry.destroy();
2183
- }
2184
- this._gpuSpriteHash = null;
2185
3235
  this._renderer = null;
2186
3236
  }
2187
3237
  }
@@ -2254,7 +3304,7 @@ const tilingBit = {
2254
3304
  if(unclamped.x == coord.x && unclamped.y == coord.y)
2255
3305
  {
2256
3306
  bias = -32.;
2257
- }
3307
+ }
2258
3308
 
2259
3309
  outColor = textureSampleBias(uTexture, uSampler, coord, bias);
2260
3310
  `
@@ -2269,7 +3319,7 @@ const tilingBitGl = {
2269
3319
  `
2270
3320
  uniform mat3 uTextureTransform;
2271
3321
  uniform vec4 uSizeAnchor;
2272
-
3322
+
2273
3323
  `
2274
3324
  ),
2275
3325
  main: (
@@ -2299,9 +3349,9 @@ const tilingBitGl = {
2299
3349
  coord = (uMapCoord * vec3(coord, 1.0)).xy;
2300
3350
  vec2 unclamped = coord;
2301
3351
  coord = clamp(coord, uClampFrame.xy, uClampFrame.zw);
2302
-
3352
+
2303
3353
  outColor = texture(uTexture, coord, unclamped == coord ? 0.0 : -32.0);// lod-bias very negative to force lod 0
2304
-
3354
+
2305
3355
  `
2306
3356
  )
2307
3357
  }
@@ -2404,7 +3454,7 @@ function setPositions(tilingSprite, positions) {
2404
3454
 
2405
3455
  function applyMatrix(array, stride, offset, matrix) {
2406
3456
  let index = 0;
2407
- const size = array.length / (stride);
3457
+ const size = array.length / (stride );
2408
3458
  const a = matrix.a;
2409
3459
  const b = matrix.b;
2410
3460
  const c = matrix.c;
@@ -2446,13 +3496,24 @@ function setUvs(tilingSprite, uvs) {
2446
3496
  }
2447
3497
 
2448
3498
  const sharedQuad = new QuadGeometry();
3499
+ class TilingSpriteGpuData {
3500
+ constructor() {
3501
+ this.canBatch = true;
3502
+ this.geometry = new MeshGeometry({
3503
+ indices: sharedQuad.indices.slice(),
3504
+ positions: sharedQuad.positions.slice(),
3505
+ uvs: sharedQuad.uvs.slice()
3506
+ });
3507
+ }
3508
+ destroy() {
3509
+ this.geometry.destroy();
3510
+ this.shader?.destroy();
3511
+ }
3512
+ }
2449
3513
  class TilingSpritePipe {
2450
3514
  constructor(renderer) {
2451
3515
  this._state = State.default2d;
2452
- this._tilingSpriteDataHash = /* @__PURE__ */ Object.create(null);
2453
- this._destroyRenderableBound = this.destroyRenderable.bind(this);
2454
3516
  this._renderer = renderer;
2455
- this._renderer.renderableGC.addManagedHash(this, "_tilingSpriteDataHash");
2456
3517
  }
2457
3518
  validateRenderable(renderable) {
2458
3519
  const tilingSpriteData = this._getTilingSpriteData(renderable);
@@ -2493,7 +3554,7 @@ class TilingSpritePipe {
2493
3554
  }
2494
3555
  }
2495
3556
  execute(tilingSprite) {
2496
- const { shader } = this._tilingSpriteDataHash[tilingSprite.uid];
3557
+ const { shader } = this._getTilingSpriteData(tilingSprite);
2497
3558
  shader.groups[0] = this._renderer.globalUniforms.bindGroup;
2498
3559
  const localUniforms = shader.resources.localUniforms.uniforms;
2499
3560
  localUniforms.uTransformMatrix = tilingSprite.groupTransform;
@@ -2530,29 +3591,14 @@ class TilingSpritePipe {
2530
3591
  );
2531
3592
  }
2532
3593
  }
2533
- destroyRenderable(tilingSprite) {
2534
- const tilingSpriteData = this._getTilingSpriteData(tilingSprite);
2535
- tilingSpriteData.batchableMesh = null;
2536
- tilingSpriteData.shader?.destroy();
2537
- this._tilingSpriteDataHash[tilingSprite.uid] = null;
2538
- tilingSprite.off("destroyed", this._destroyRenderableBound);
2539
- }
2540
3594
  _getTilingSpriteData(renderable) {
2541
- return this._tilingSpriteDataHash[renderable.uid] || this._initTilingSpriteData(renderable);
3595
+ return renderable._gpuData[this._renderer.uid] || this._initTilingSpriteData(renderable);
2542
3596
  }
2543
3597
  _initTilingSpriteData(tilingSprite) {
2544
- const geometry = new MeshGeometry({
2545
- indices: sharedQuad.indices,
2546
- positions: sharedQuad.positions.slice(),
2547
- uvs: sharedQuad.uvs.slice()
2548
- });
2549
- this._tilingSpriteDataHash[tilingSprite.uid] = {
2550
- canBatch: true,
2551
- renderable: tilingSprite,
2552
- geometry
2553
- };
2554
- tilingSprite.on("destroyed", this._destroyRenderableBound);
2555
- return this._tilingSpriteDataHash[tilingSprite.uid];
3598
+ const gpuData = new TilingSpriteGpuData();
3599
+ gpuData.renderable = tilingSprite;
3600
+ tilingSprite._gpuData[this._renderer.uid] = gpuData;
3601
+ return gpuData;
2556
3602
  }
2557
3603
  _updateBatchableMesh(tilingSprite) {
2558
3604
  const renderableData = this._getTilingSpriteData(tilingSprite);
@@ -2566,10 +3612,6 @@ class TilingSpritePipe {
2566
3612
  setPositions(tilingSprite, geometry.positions);
2567
3613
  }
2568
3614
  destroy() {
2569
- for (const i in this._tilingSpriteDataHash) {
2570
- this.destroyRenderable(this._tilingSpriteDataHash[i].renderable);
2571
- }
2572
- this._tilingSpriteDataHash = null;
2573
3615
  this._renderer = null;
2574
3616
  }
2575
3617
  _updateCanBatch(tilingSprite) {
@@ -2641,7 +3683,7 @@ const localUniformMSDFBit = {
2641
3683
  ),
2642
3684
  main: (
2643
3685
  /* wgsl */
2644
- `
3686
+ `
2645
3687
  outColor = vec4<f32>(calculateMSDFAlpha(outColor, localUniforms.uColor, localUniforms.uDistance));
2646
3688
  `
2647
3689
  )
@@ -2684,7 +3726,7 @@ const localUniformMSDFBitGl = {
2684
3726
  ),
2685
3727
  main: (
2686
3728
  /* glsl */
2687
- `
3729
+ `
2688
3730
  outColor = vec4(calculateMSDFAlpha(outColor, vColor, uDistance));
2689
3731
  `
2690
3732
  )
@@ -2698,12 +3740,12 @@ const mSDFBit = {
2698
3740
  /* wgsl */
2699
3741
  `
2700
3742
  fn calculateMSDFAlpha(msdfColor:vec4<f32>, shapeColor:vec4<f32>, distance:f32) -> f32 {
2701
-
3743
+
2702
3744
  // MSDF
2703
3745
  var median = msdfColor.r + msdfColor.g + msdfColor.b -
2704
3746
  min(msdfColor.r, min(msdfColor.g, msdfColor.b)) -
2705
3747
  max(msdfColor.r, max(msdfColor.g, msdfColor.b));
2706
-
3748
+
2707
3749
  // SDF
2708
3750
  median = min(median, msdfColor.a);
2709
3751
 
@@ -2721,7 +3763,7 @@ const mSDFBit = {
2721
3763
  var coverage: f32 = pow(shapeColor.a * alpha, gamma);
2722
3764
 
2723
3765
  return coverage;
2724
-
3766
+
2725
3767
  }
2726
3768
  `
2727
3769
  )
@@ -2734,18 +3776,18 @@ const mSDFBitGl = {
2734
3776
  /* glsl */
2735
3777
  `
2736
3778
  float calculateMSDFAlpha(vec4 msdfColor, vec4 shapeColor, float distance) {
2737
-
3779
+
2738
3780
  // MSDF
2739
3781
  float median = msdfColor.r + msdfColor.g + msdfColor.b -
2740
3782
  min(msdfColor.r, min(msdfColor.g, msdfColor.b)) -
2741
3783
  max(msdfColor.r, max(msdfColor.g, msdfColor.b));
2742
-
3784
+
2743
3785
  // SDF
2744
3786
  median = min(median, msdfColor.a);
2745
-
3787
+
2746
3788
  float screenPxDistance = distance * (median - 0.5);
2747
3789
  float alpha = clamp(screenPxDistance + 0.5, 0.0, 1.0);
2748
-
3790
+
2749
3791
  if (median < 0.01) {
2750
3792
  alpha = 0.0;
2751
3793
  } else if (median > 0.99) {
@@ -2755,8 +3797,8 @@ const mSDFBitGl = {
2755
3797
  // Gamma correction for coverage-like alpha
2756
3798
  float luma = dot(shapeColor.rgb, vec3(0.299, 0.587, 0.114));
2757
3799
  float gamma = mix(1.0, 1.0 / 2.2, luma);
2758
- float coverage = pow(shapeColor.a * alpha, gamma);
2759
-
3800
+ float coverage = pow(shapeColor.a * alpha, gamma);
3801
+
2760
3802
  return coverage;
2761
3803
  }
2762
3804
  `
@@ -2767,14 +3809,13 @@ const mSDFBitGl = {
2767
3809
  let gpuProgram;
2768
3810
  let glProgram;
2769
3811
  class SdfShader extends Shader {
2770
- constructor() {
3812
+ constructor(maxTextures) {
2771
3813
  const uniforms = new UniformGroup({
2772
3814
  uColor: { value: new Float32Array([1, 1, 1, 1]), type: "vec4<f32>" },
2773
3815
  uTransformMatrix: { value: new Matrix(), type: "mat3x3<f32>" },
2774
3816
  uDistance: { value: 4, type: "f32" },
2775
3817
  uRound: { value: 0, type: "f32" }
2776
3818
  });
2777
- const maxTextures = getMaxTexturesPerBatch();
2778
3819
  gpuProgram ?? (gpuProgram = compileHighShaderGpuProgram({
2779
3820
  name: "sdf-shader",
2780
3821
  bits: [
@@ -2806,10 +3847,16 @@ class SdfShader extends Shader {
2806
3847
  }
2807
3848
  }
2808
3849
 
3850
+ class BitmapTextGraphics extends Graphics {
3851
+ destroy() {
3852
+ if (this.context.customShader) {
3853
+ this.context.customShader.destroy();
3854
+ }
3855
+ super.destroy();
3856
+ }
3857
+ }
2809
3858
  class BitmapTextPipe {
2810
3859
  constructor(renderer) {
2811
- this._gpuBitmapText = {};
2812
- this._destroyRenderableBound = this.destroyRenderable.bind(this);
2813
3860
  this._renderer = renderer;
2814
3861
  this._renderer.renderableGC.addManagedHash(this, "_gpuBitmapText");
2815
3862
  }
@@ -2833,19 +3880,6 @@ class BitmapTextPipe {
2833
3880
  this._updateDistanceField(bitmapText);
2834
3881
  }
2835
3882
  }
2836
- destroyRenderable(bitmapText) {
2837
- bitmapText.off("destroyed", this._destroyRenderableBound);
2838
- this._destroyRenderableByUid(bitmapText.uid);
2839
- }
2840
- _destroyRenderableByUid(renderableUid) {
2841
- const context = this._gpuBitmapText[renderableUid].context;
2842
- if (context.customShader) {
2843
- BigPool.return(context.customShader);
2844
- context.customShader = null;
2845
- }
2846
- BigPool.return(this._gpuBitmapText[renderableUid]);
2847
- this._gpuBitmapText[renderableUid] = null;
2848
- }
2849
3883
  updateRenderable(bitmapText) {
2850
3884
  const graphicsRenderable = this._getGpuBitmapText(bitmapText);
2851
3885
  syncWithProxy(bitmapText, graphicsRenderable);
@@ -2860,14 +3894,13 @@ class BitmapTextPipe {
2860
3894
  context.clear();
2861
3895
  if (bitmapFont.distanceField.type !== "none") {
2862
3896
  if (!context.customShader) {
2863
- context.customShader = BigPool.get(SdfShader);
3897
+ context.customShader = new SdfShader(this._renderer.limits.maxBatchableTextures);
2864
3898
  }
2865
3899
  }
2866
- const chars = Array.from(bitmapText.text);
3900
+ const chars = CanvasTextMetrics.graphemeSegmenter(bitmapText.text);
2867
3901
  const style = bitmapText._style;
2868
3902
  let currentY = bitmapFont.baseLineOffset;
2869
3903
  const bitmapTextLayout = getBitmapTextLayout(chars, style, bitmapFont, true);
2870
- let index = 0;
2871
3904
  const padding = style.padding;
2872
3905
  const scale = bitmapTextLayout.scale;
2873
3906
  let tx = bitmapTextLayout.width;
@@ -2881,7 +3914,7 @@ class BitmapTextPipe {
2881
3914
  for (let i = 0; i < bitmapTextLayout.lines.length; i++) {
2882
3915
  const line = bitmapTextLayout.lines[i];
2883
3916
  for (let j = 0; j < line.charPositions.length; j++) {
2884
- const char = chars[index++];
3917
+ const char = line.chars[j];
2885
3918
  const charData = bitmapFont.chars[char];
2886
3919
  if (charData?.texture) {
2887
3920
  context.texture(
@@ -2896,14 +3929,13 @@ class BitmapTextPipe {
2896
3929
  }
2897
3930
  }
2898
3931
  _getGpuBitmapText(bitmapText) {
2899
- return this._gpuBitmapText[bitmapText.uid] || this.initGpuText(bitmapText);
3932
+ return bitmapText._gpuData[this._renderer.uid] || this.initGpuText(bitmapText);
2900
3933
  }
2901
3934
  initGpuText(bitmapText) {
2902
- const proxyRenderable = BigPool.get(Graphics);
2903
- this._gpuBitmapText[bitmapText.uid] = proxyRenderable;
3935
+ const proxyRenderable = new BitmapTextGraphics();
3936
+ bitmapText._gpuData[this._renderer.uid] = proxyRenderable;
2904
3937
  this._updateContext(bitmapText, proxyRenderable);
2905
- bitmapText.on("destroyed", this._destroyRenderableBound);
2906
- return this._gpuBitmapText[bitmapText.uid];
3938
+ return proxyRenderable;
2907
3939
  }
2908
3940
  _updateDistanceField(bitmapText) {
2909
3941
  const context = this._getGpuBitmapText(bitmapText).context;
@@ -2918,10 +3950,6 @@ class BitmapTextPipe {
2918
3950
  context.customShader.resources.localUniforms.uniforms.uDistance = distance;
2919
3951
  }
2920
3952
  destroy() {
2921
- for (const uid in this._gpuBitmapText) {
2922
- this._destroyRenderableByUid(uid);
2923
- }
2924
- this._gpuBitmapText = null;
2925
3953
  this._renderer = null;
2926
3954
  }
2927
3955
  }
@@ -2946,136 +3974,102 @@ function syncWithProxy(container, proxy) {
2946
3974
  proxy._roundPixels = container._roundPixels;
2947
3975
  }
2948
3976
 
3977
+ class BatchableHTMLText extends BatchableSprite {
3978
+ /**
3979
+ * Creates an instance of BatchableHTMLText.
3980
+ * @param renderer - The renderer instance to be used.
3981
+ */
3982
+ constructor(renderer) {
3983
+ super();
3984
+ this.generatingTexture = false;
3985
+ this._renderer = renderer;
3986
+ renderer.runners.resolutionChange.add(this);
3987
+ }
3988
+ /** Handles resolution changes for the HTML text. If the text has auto resolution enabled, it triggers a view update. */
3989
+ resolutionChange() {
3990
+ const text = this.renderable;
3991
+ if (text._autoResolution) {
3992
+ text.onViewUpdate();
3993
+ }
3994
+ }
3995
+ /** Destroys the BatchableHTMLText instance. Returns the texture promise to the renderer and cleans up references. */
3996
+ destroy() {
3997
+ this._renderer.htmlText.returnTexturePromise(this.texturePromise);
3998
+ this.texturePromise = null;
3999
+ this._renderer = null;
4000
+ }
4001
+ }
4002
+
2949
4003
  function updateTextBounds(batchableSprite, text) {
2950
4004
  const { texture, bounds } = batchableSprite;
4005
+ const padding = text._style._getFinalPadding();
2951
4006
  updateQuadBounds(bounds, text._anchor, texture);
2952
- const padding = text._style.padding;
2953
- bounds.minX -= padding;
2954
- bounds.minY -= padding;
2955
- bounds.maxX -= padding;
2956
- bounds.maxY -= padding;
4007
+ const paddingOffset = text._anchor._x * padding * 2;
4008
+ const paddingOffsetY = text._anchor._y * padding * 2;
4009
+ bounds.minX -= padding - paddingOffset;
4010
+ bounds.minY -= padding - paddingOffsetY;
4011
+ bounds.maxX -= padding - paddingOffset;
4012
+ bounds.maxY -= padding - paddingOffsetY;
2957
4013
  }
2958
4014
 
2959
4015
  class HTMLTextPipe {
2960
4016
  constructor(renderer) {
2961
- this._gpuText = /* @__PURE__ */ Object.create(null);
2962
- this._destroyRenderableBound = this.destroyRenderable.bind(this);
2963
4017
  this._renderer = renderer;
2964
- this._renderer.runners.resolutionChange.add(this);
2965
- this._renderer.renderableGC.addManagedHash(this, "_gpuText");
2966
- }
2967
- resolutionChange() {
2968
- for (const i in this._gpuText) {
2969
- const gpuText = this._gpuText[i];
2970
- if (!gpuText)
2971
- continue;
2972
- const text = gpuText.batchableSprite.renderable;
2973
- if (text._autoResolution) {
2974
- text._resolution = this._renderer.resolution;
2975
- text.onViewUpdate();
2976
- }
2977
- }
2978
4018
  }
2979
4019
  validateRenderable(htmlText) {
2980
- const gpuText = this._getGpuText(htmlText);
2981
- const newKey = htmlText._getKey();
2982
- if (gpuText.textureNeedsUploading) {
2983
- gpuText.textureNeedsUploading = false;
2984
- return true;
2985
- }
2986
- if (gpuText.currentKey !== newKey) {
2987
- return true;
2988
- }
2989
- return false;
4020
+ return htmlText._didTextUpdate;
2990
4021
  }
2991
4022
  addRenderable(htmlText, instructionSet) {
2992
- const gpuText = this._getGpuText(htmlText);
2993
- const batchableSprite = gpuText.batchableSprite;
2994
- if (htmlText._didTextUpdate) {
2995
- this._updateText(htmlText);
2996
- }
2997
- this._renderer.renderPipes.batch.addToBatch(batchableSprite, instructionSet);
2998
- }
2999
- updateRenderable(htmlText) {
3000
- const gpuText = this._getGpuText(htmlText);
3001
- const batchableSprite = gpuText.batchableSprite;
4023
+ const batchableHTMLText = this._getGpuText(htmlText);
3002
4024
  if (htmlText._didTextUpdate) {
3003
- this._updateText(htmlText);
3004
- }
3005
- batchableSprite._batcher.updateElement(batchableSprite);
3006
- }
3007
- destroyRenderable(htmlText) {
3008
- htmlText.off("destroyed", this._destroyRenderableBound);
3009
- this._destroyRenderableById(htmlText.uid);
3010
- }
3011
- _destroyRenderableById(htmlTextUid) {
3012
- const gpuText = this._gpuText[htmlTextUid];
3013
- this._renderer.htmlText.decreaseReferenceCount(gpuText.currentKey);
3014
- BigPool.return(gpuText.batchableSprite);
3015
- this._gpuText[htmlTextUid] = null;
3016
- }
3017
- _updateText(htmlText) {
3018
- const newKey = htmlText._getKey();
3019
- const gpuText = this._getGpuText(htmlText);
3020
- const batchableSprite = gpuText.batchableSprite;
3021
- if (gpuText.currentKey !== newKey) {
3022
4025
  this._updateGpuText(htmlText).catch((e) => {
3023
4026
  console.error(e);
3024
4027
  });
4028
+ htmlText._didTextUpdate = false;
4029
+ updateTextBounds(batchableHTMLText, htmlText);
3025
4030
  }
3026
- htmlText._didTextUpdate = false;
3027
- updateTextBounds(batchableSprite, htmlText);
4031
+ this._renderer.renderPipes.batch.addToBatch(batchableHTMLText, instructionSet);
4032
+ }
4033
+ updateRenderable(htmlText) {
4034
+ const batchableHTMLText = this._getGpuText(htmlText);
4035
+ batchableHTMLText._batcher.updateElement(batchableHTMLText);
3028
4036
  }
3029
4037
  async _updateGpuText(htmlText) {
3030
4038
  htmlText._didTextUpdate = false;
3031
- const gpuText = this._getGpuText(htmlText);
3032
- if (gpuText.generatingTexture)
4039
+ const batchableHTMLText = this._getGpuText(htmlText);
4040
+ if (batchableHTMLText.generatingTexture)
3033
4041
  return;
3034
- const newKey = htmlText._getKey();
3035
- this._renderer.htmlText.decreaseReferenceCount(gpuText.currentKey);
3036
- gpuText.generatingTexture = true;
3037
- gpuText.currentKey = newKey;
3038
- const resolution = htmlText.resolution ?? this._renderer.resolution;
3039
- const texture = await this._renderer.htmlText.getManagedTexture(
3040
- htmlText.text,
3041
- resolution,
3042
- htmlText._style,
3043
- htmlText._getKey()
3044
- );
3045
- const batchableSprite = gpuText.batchableSprite;
3046
- batchableSprite.texture = gpuText.texture = texture;
3047
- gpuText.generatingTexture = false;
3048
- gpuText.textureNeedsUploading = true;
3049
- htmlText.onViewUpdate();
3050
- updateTextBounds(batchableSprite, htmlText);
4042
+ if (batchableHTMLText.texturePromise) {
4043
+ this._renderer.htmlText.returnTexturePromise(batchableHTMLText.texturePromise);
4044
+ batchableHTMLText.texturePromise = null;
4045
+ }
4046
+ batchableHTMLText.generatingTexture = true;
4047
+ htmlText._resolution = htmlText._autoResolution ? this._renderer.resolution : htmlText.resolution;
4048
+ const texturePromise = this._renderer.htmlText.getTexturePromise(htmlText);
4049
+ batchableHTMLText.texturePromise = texturePromise;
4050
+ batchableHTMLText.texture = await texturePromise;
4051
+ const renderGroup = htmlText.renderGroup || htmlText.parentRenderGroup;
4052
+ if (renderGroup) {
4053
+ renderGroup.structureDidChange = true;
4054
+ }
4055
+ batchableHTMLText.generatingTexture = false;
4056
+ updateTextBounds(batchableHTMLText, htmlText);
3051
4057
  }
3052
4058
  _getGpuText(htmlText) {
3053
- return this._gpuText[htmlText.uid] || this.initGpuText(htmlText);
4059
+ return htmlText._gpuData[this._renderer.uid] || this.initGpuText(htmlText);
3054
4060
  }
3055
4061
  initGpuText(htmlText) {
3056
- const gpuTextData = {
3057
- texture: Texture.EMPTY,
3058
- currentKey: "--",
3059
- batchableSprite: BigPool.get(BatchableSprite),
3060
- textureNeedsUploading: false,
3061
- generatingTexture: false
3062
- };
3063
- const batchableSprite = gpuTextData.batchableSprite;
3064
- batchableSprite.renderable = htmlText;
3065
- batchableSprite.transform = htmlText.groupTransform;
3066
- batchableSprite.texture = Texture.EMPTY;
3067
- batchableSprite.bounds = { minX: 0, maxX: 1, minY: 0, maxY: 0 };
3068
- batchableSprite.roundPixels = this._renderer._roundPixels | htmlText._roundPixels;
4062
+ const batchableHTMLText = new BatchableHTMLText(this._renderer);
4063
+ batchableHTMLText.renderable = htmlText;
4064
+ batchableHTMLText.transform = htmlText.groupTransform;
4065
+ batchableHTMLText.texture = Texture.EMPTY;
4066
+ batchableHTMLText.bounds = { minX: 0, maxX: 1, minY: 0, maxY: 0 };
4067
+ batchableHTMLText.roundPixels = this._renderer._roundPixels | htmlText._roundPixels;
3069
4068
  htmlText._resolution = htmlText._autoResolution ? this._renderer.resolution : htmlText.resolution;
3070
- this._gpuText[htmlText.uid] = gpuTextData;
3071
- htmlText.on("destroyed", this._destroyRenderableBound);
3072
- return gpuTextData;
4069
+ htmlText._gpuData[this._renderer.uid] = batchableHTMLText;
4070
+ return batchableHTMLText;
3073
4071
  }
3074
4072
  destroy() {
3075
- for (const i in this._gpuText) {
3076
- this._destroyRenderableById(i);
3077
- }
3078
- this._gpuText = null;
3079
4073
  this._renderer = null;
3080
4074
  }
3081
4075
  }
@@ -3233,34 +4227,21 @@ function loadSVGImage(image, url, delay) {
3233
4227
 
3234
4228
  class HTMLTextSystem {
3235
4229
  constructor(renderer) {
3236
- this._activeTextures = {};
3237
4230
  this._renderer = renderer;
3238
4231
  this._createCanvas = renderer.type === RendererType.WEBGPU;
3239
4232
  }
4233
+ /**
4234
+ * @param options
4235
+ * @deprecated Use getTexturePromise instead
4236
+ */
3240
4237
  getTexture(options) {
3241
- return this._buildTexturePromise(
3242
- options.text,
3243
- options.resolution,
3244
- options.style
3245
- );
4238
+ return this.getTexturePromise(options);
3246
4239
  }
3247
- getManagedTexture(text, resolution, style, textKey) {
3248
- if (this._activeTextures[textKey]) {
3249
- this._increaseReferenceCount(textKey);
3250
- return this._activeTextures[textKey].promise;
3251
- }
3252
- const promise = this._buildTexturePromise(text, resolution, style).then((texture) => {
3253
- this._activeTextures[textKey].texture = texture;
3254
- return texture;
3255
- });
3256
- this._activeTextures[textKey] = {
3257
- texture: null,
3258
- promise,
3259
- usageCount: 1
3260
- };
3261
- return promise;
4240
+ getTexturePromise(options) {
4241
+ return this._buildTexturePromise(options);
3262
4242
  }
3263
- async _buildTexturePromise(text, resolution, style) {
4243
+ async _buildTexturePromise(options) {
4244
+ const { text, style, resolution, textureStyle } = options;
3264
4245
  const htmlTextData = BigPool.get(HTMLTextRenderData);
3265
4246
  const fontFamilies = extractFontFamilies(text, style);
3266
4247
  const fontCSS = await getFontCss(
@@ -3288,6 +4269,8 @@ class HTMLTextSystem {
3288
4269
  image.height - uvSafeOffset,
3289
4270
  resolution
3290
4271
  );
4272
+ if (textureStyle)
4273
+ texture.source.style = textureStyle;
3291
4274
  if (this._createCanvas) {
3292
4275
  this._renderer.texture.initSource(texture.source);
3293
4276
  CanvasPool.returnCanvasAndContext(canvasAndContext);
@@ -3295,38 +4278,20 @@ class HTMLTextSystem {
3295
4278
  BigPool.return(htmlTextData);
3296
4279
  return texture;
3297
4280
  }
3298
- _increaseReferenceCount(textKey) {
3299
- this._activeTextures[textKey].usageCount++;
3300
- }
3301
- decreaseReferenceCount(textKey) {
3302
- const activeTexture = this._activeTextures[textKey];
3303
- if (!activeTexture)
3304
- return;
3305
- activeTexture.usageCount--;
3306
- if (activeTexture.usageCount === 0) {
3307
- if (activeTexture.texture) {
3308
- this._cleanUp(activeTexture);
3309
- } else {
3310
- activeTexture.promise.then((texture) => {
3311
- activeTexture.texture = texture;
3312
- this._cleanUp(activeTexture);
3313
- }).catch(() => {
3314
- warn("HTMLTextSystem: Failed to clean texture");
3315
- });
3316
- }
3317
- this._activeTextures[textKey] = null;
3318
- }
3319
- }
3320
- _cleanUp(activeTexture) {
3321
- TexturePool.returnTexture(activeTexture.texture);
3322
- activeTexture.texture.source.resource = null;
3323
- activeTexture.texture.source.uploadMethodId = "unknown";
4281
+ returnTexturePromise(texturePromise) {
4282
+ texturePromise.then((texture) => {
4283
+ this._cleanUp(texture);
4284
+ }).catch(() => {
4285
+ warn("HTMLTextSystem: Failed to clean texture");
4286
+ });
3324
4287
  }
3325
- getReferenceCount(textKey) {
3326
- return this._activeTextures[textKey].usageCount;
4288
+ _cleanUp(texture) {
4289
+ TexturePool.returnTexture(texture, true);
4290
+ texture.source.resource = null;
4291
+ texture.source.uploadMethodId = "unknown";
3327
4292
  }
3328
4293
  destroy() {
3329
- this._activeTextures = null;
4294
+ this._renderer = null;
3330
4295
  }
3331
4296
  }
3332
4297
  /** @ignore */
@@ -3338,110 +4303,66 @@ HTMLTextSystem.extension = {
3338
4303
  ],
3339
4304
  name: "htmlText"
3340
4305
  };
3341
- HTMLTextSystem.defaultFontOptions = {
3342
- fontFamily: "Arial",
3343
- fontStyle: "normal",
3344
- fontWeight: "normal"
3345
- };
3346
4306
 
3347
- class CanvasTextPipe {
4307
+ class BatchableText extends BatchableSprite {
3348
4308
  constructor(renderer) {
3349
- this._gpuText = /* @__PURE__ */ Object.create(null);
3350
- this._destroyRenderableBound = this.destroyRenderable.bind(this);
4309
+ super();
3351
4310
  this._renderer = renderer;
3352
- this._renderer.runners.resolutionChange.add(this);
3353
- this._renderer.renderableGC.addManagedHash(this, "_gpuText");
4311
+ renderer.runners.resolutionChange.add(this);
3354
4312
  }
3355
4313
  resolutionChange() {
3356
- for (const i in this._gpuText) {
3357
- const gpuText = this._gpuText[i];
3358
- if (!gpuText)
3359
- continue;
3360
- const text = gpuText.batchableSprite.renderable;
3361
- if (text._autoResolution) {
3362
- text._resolution = this._renderer.resolution;
3363
- text.onViewUpdate();
3364
- }
4314
+ const text = this.renderable;
4315
+ if (text._autoResolution) {
4316
+ text.onViewUpdate();
3365
4317
  }
3366
4318
  }
4319
+ destroy() {
4320
+ this._renderer.canvasText.returnTexture(this.texture);
4321
+ this._renderer = null;
4322
+ }
4323
+ }
4324
+
4325
+ class CanvasTextPipe {
4326
+ constructor(renderer) {
4327
+ this._renderer = renderer;
4328
+ }
3367
4329
  validateRenderable(text) {
3368
- const gpuText = this._getGpuText(text);
3369
- const newKey = text._getKey();
3370
- if (gpuText.currentKey !== newKey) {
3371
- return true;
3372
- }
3373
- return false;
4330
+ return text._didTextUpdate;
3374
4331
  }
3375
4332
  addRenderable(text, instructionSet) {
3376
- const gpuText = this._getGpuText(text);
3377
- const batchableSprite = gpuText.batchableSprite;
4333
+ const batchableText = this._getGpuText(text);
3378
4334
  if (text._didTextUpdate) {
3379
- this._updateText(text);
4335
+ this._updateGpuText(text);
4336
+ text._didTextUpdate = false;
3380
4337
  }
3381
- this._renderer.renderPipes.batch.addToBatch(batchableSprite, instructionSet);
4338
+ this._renderer.renderPipes.batch.addToBatch(batchableText, instructionSet);
3382
4339
  }
3383
4340
  updateRenderable(text) {
3384
- const gpuText = this._getGpuText(text);
3385
- const batchableSprite = gpuText.batchableSprite;
3386
- if (text._didTextUpdate) {
3387
- this._updateText(text);
3388
- }
3389
- batchableSprite._batcher.updateElement(batchableSprite);
3390
- }
3391
- destroyRenderable(text) {
3392
- text.off("destroyed", this._destroyRenderableBound);
3393
- this._destroyRenderableById(text.uid);
3394
- }
3395
- _destroyRenderableById(textUid) {
3396
- const gpuText = this._gpuText[textUid];
3397
- this._renderer.canvasText.decreaseReferenceCount(gpuText.currentKey);
3398
- BigPool.return(gpuText.batchableSprite);
3399
- this._gpuText[textUid] = null;
3400
- }
3401
- _updateText(text) {
3402
- const newKey = text._getKey();
3403
- const gpuText = this._getGpuText(text);
3404
- const batchableSprite = gpuText.batchableSprite;
3405
- if (gpuText.currentKey !== newKey) {
3406
- this._updateGpuText(text);
3407
- }
3408
- text._didTextUpdate = false;
3409
- updateTextBounds(batchableSprite, text);
4341
+ const batchableText = this._getGpuText(text);
4342
+ batchableText._batcher.updateElement(batchableText);
3410
4343
  }
3411
4344
  _updateGpuText(text) {
3412
- const gpuText = this._getGpuText(text);
3413
- const batchableSprite = gpuText.batchableSprite;
3414
- if (gpuText.texture) {
3415
- this._renderer.canvasText.decreaseReferenceCount(gpuText.currentKey);
4345
+ const batchableText = this._getGpuText(text);
4346
+ if (batchableText.texture) {
4347
+ this._renderer.canvasText.returnTexture(batchableText.texture);
3416
4348
  }
3417
- gpuText.texture = batchableSprite.texture = this._renderer.canvasText.getManagedTexture(text);
3418
- gpuText.currentKey = text._getKey();
3419
- batchableSprite.texture = gpuText.texture;
4349
+ text._resolution = text._autoResolution ? this._renderer.resolution : text.resolution;
4350
+ batchableText.texture = batchableText.texture = this._renderer.canvasText.getTexture(text);
4351
+ updateTextBounds(batchableText, text);
3420
4352
  }
3421
4353
  _getGpuText(text) {
3422
- return this._gpuText[text.uid] || this.initGpuText(text);
4354
+ return text._gpuData[this._renderer.uid] || this.initGpuText(text);
3423
4355
  }
3424
4356
  initGpuText(text) {
3425
- const gpuTextData = {
3426
- texture: null,
3427
- currentKey: "--",
3428
- batchableSprite: BigPool.get(BatchableSprite)
3429
- };
3430
- gpuTextData.batchableSprite.renderable = text;
3431
- gpuTextData.batchableSprite.transform = text.groupTransform;
3432
- gpuTextData.batchableSprite.bounds = { minX: 0, maxX: 1, minY: 0, maxY: 0 };
3433
- gpuTextData.batchableSprite.roundPixels = this._renderer._roundPixels | text._roundPixels;
3434
- this._gpuText[text.uid] = gpuTextData;
3435
- text._resolution = text._autoResolution ? this._renderer.resolution : text.resolution;
3436
- this._updateText(text);
3437
- text.on("destroyed", this._destroyRenderableBound);
3438
- return gpuTextData;
4357
+ const batchableText = new BatchableText(this._renderer);
4358
+ batchableText.renderable = text;
4359
+ batchableText.transform = text.groupTransform;
4360
+ batchableText.bounds = { minX: 0, maxX: 1, minY: 0, maxY: 0 };
4361
+ batchableText.roundPixels = this._renderer._roundPixels | text._roundPixels;
4362
+ text._gpuData[this._renderer.uid] = batchableText;
4363
+ return batchableText;
3439
4364
  }
3440
4365
  destroy() {
3441
- for (const i in this._gpuText) {
3442
- this._destroyRenderableById(i);
3443
- }
3444
- this._gpuText = null;
3445
4366
  this._renderer = null;
3446
4367
  }
3447
4368
  }
@@ -3455,119 +4376,53 @@ CanvasTextPipe.extension = {
3455
4376
  name: "text"
3456
4377
  };
3457
4378
 
3458
- function checkRow(data, width, y) {
3459
- for (let x = 0, index = 4 * y * width; x < width; ++x, index += 4) {
3460
- if (data[index + 3] !== 0)
3461
- return false;
3462
- }
3463
- return true;
3464
- }
3465
- function checkColumn(data, width, x, top, bottom) {
3466
- const stride = 4 * width;
3467
- for (let y = top, index = top * stride + 4 * x; y <= bottom; ++y, index += stride) {
3468
- if (data[index + 3] !== 0)
3469
- return false;
3470
- }
3471
- return true;
3472
- }
3473
- function getCanvasBoundingBox(canvas, resolution = 1) {
3474
- const { width, height } = canvas;
3475
- const context = canvas.getContext("2d", {
3476
- willReadFrequently: true
3477
- });
3478
- if (context === null) {
3479
- throw new TypeError("Failed to get canvas 2D context");
3480
- }
3481
- const imageData = context.getImageData(0, 0, width, height);
3482
- const data = imageData.data;
3483
- let left = 0;
3484
- let top = 0;
3485
- let right = width - 1;
3486
- let bottom = height - 1;
3487
- while (top < height && checkRow(data, width, top))
3488
- ++top;
3489
- if (top === height)
3490
- return Rectangle.EMPTY;
3491
- while (checkRow(data, width, bottom))
3492
- --bottom;
3493
- while (checkColumn(data, width, left, top, bottom))
3494
- ++left;
3495
- while (checkColumn(data, width, right, top, bottom))
3496
- --right;
3497
- ++right;
3498
- ++bottom;
3499
- return new Rectangle(left / resolution, top / resolution, (right - left) / resolution, (bottom - top) / resolution);
3500
- }
3501
-
3502
4379
  class CanvasTextSystem {
3503
4380
  constructor(_renderer) {
3504
- this._activeTextures = {};
3505
4381
  this._renderer = _renderer;
3506
4382
  }
3507
- getTextureSize(text, resolution, style) {
3508
- const measured = CanvasTextMetrics.measureText(text || " ", style);
3509
- let width = Math.ceil(Math.ceil(Math.max(1, measured.width) + style.padding * 2) * resolution);
3510
- let height = Math.ceil(Math.ceil(Math.max(1, measured.height) + style.padding * 2) * resolution);
3511
- width = Math.ceil(width - 1e-6);
3512
- height = Math.ceil(height - 1e-6);
3513
- width = nextPow2(width);
3514
- height = nextPow2(height);
3515
- return { width, height };
3516
- }
3517
- getTexture(options, resolution, style, _textKey) {
4383
+ getTexture(options, _resolution, _style, _textKey) {
3518
4384
  if (typeof options === "string") {
3519
4385
  deprecation("8.0.0", "CanvasTextSystem.getTexture: Use object TextOptions instead of separate arguments");
3520
4386
  options = {
3521
4387
  text: options,
3522
- style,
3523
- resolution
4388
+ style: _style,
4389
+ resolution: _resolution
3524
4390
  };
3525
4391
  }
3526
4392
  if (!(options.style instanceof TextStyle)) {
3527
4393
  options.style = new TextStyle(options.style);
3528
4394
  }
3529
- const { texture, canvasAndContext } = this.createTextureAndCanvas(
3530
- options
3531
- );
3532
- this._renderer.texture.initSource(texture._source);
3533
- CanvasPool.returnCanvasAndContext(canvasAndContext);
3534
- return texture;
3535
- }
3536
- createTextureAndCanvas(options) {
3537
- const { text, style } = options;
4395
+ if (!(options.textureStyle instanceof TextureStyle)) {
4396
+ options.textureStyle = new TextureStyle(options.textureStyle);
4397
+ }
4398
+ if (typeof options.text !== "string") {
4399
+ options.text = options.text.toString();
4400
+ }
4401
+ const { text, style, textureStyle } = options;
3538
4402
  const resolution = options.resolution ?? this._renderer.resolution;
3539
- const measured = CanvasTextMetrics.measureText(text || " ", style);
3540
- const width = Math.ceil(Math.ceil(Math.max(1, measured.width) + style.padding * 2) * resolution);
3541
- const height = Math.ceil(Math.ceil(Math.max(1, measured.height) + style.padding * 2) * resolution);
3542
- const canvasAndContext = CanvasPool.getOptimalCanvasAndContext(width, height);
3543
- const { canvas } = canvasAndContext;
3544
- this.renderTextToCanvas(text, style, resolution, canvasAndContext);
3545
- const texture = getPo2TextureFromSource(canvas, width, height, resolution);
4403
+ const { frame, canvasAndContext } = CanvasTextGenerator.getCanvasAndContext({
4404
+ text,
4405
+ style,
4406
+ resolution
4407
+ });
4408
+ const texture = getPo2TextureFromSource(canvasAndContext.canvas, frame.width, frame.height, resolution);
4409
+ if (textureStyle)
4410
+ texture.source.style = textureStyle;
3546
4411
  if (style.trim) {
3547
- const trimmed = getCanvasBoundingBox(canvas, resolution);
3548
- texture.frame.copyFrom(trimmed);
4412
+ frame.pad(style.padding);
4413
+ texture.frame.copyFrom(frame);
3549
4414
  texture.updateUvs();
3550
4415
  }
3551
- return { texture, canvasAndContext };
3552
- }
3553
- getManagedTexture(text) {
3554
- text._resolution = text._autoResolution ? this._renderer.resolution : text.resolution;
3555
- const textKey = text._getKey();
3556
- if (this._activeTextures[textKey]) {
3557
- this._increaseReferenceCount(textKey);
3558
- return this._activeTextures[textKey].texture;
4416
+ if (style.filters) {
4417
+ const filteredTexture = this._applyFilters(texture, style.filters);
4418
+ this.returnTexture(texture);
4419
+ CanvasTextGenerator.returnCanvasAndContext(canvasAndContext);
4420
+ return filteredTexture;
3559
4421
  }
3560
- const { texture, canvasAndContext } = this.createTextureAndCanvas(text);
3561
- this._activeTextures[textKey] = {
3562
- canvasAndContext,
3563
- texture,
3564
- usageCount: 1
3565
- };
4422
+ this._renderer.texture.initSource(texture._source);
4423
+ CanvasTextGenerator.returnCanvasAndContext(canvasAndContext);
3566
4424
  return texture;
3567
4425
  }
3568
- _increaseReferenceCount(textKey) {
3569
- this._activeTextures[textKey].usageCount++;
3570
- }
3571
4426
  /**
3572
4427
  * Returns a texture that was created wit the above `getTexture` function.
3573
4428
  * Handy if you are done with a texture and want to return it to the pool.
@@ -3578,168 +4433,39 @@ class CanvasTextSystem {
3578
4433
  source.resource = null;
3579
4434
  source.uploadMethodId = "unknown";
3580
4435
  source.alphaMode = "no-premultiply-alpha";
3581
- TexturePool.returnTexture(texture);
3582
- }
3583
- decreaseReferenceCount(textKey) {
3584
- const activeTexture = this._activeTextures[textKey];
3585
- activeTexture.usageCount--;
3586
- if (activeTexture.usageCount === 0) {
3587
- CanvasPool.returnCanvasAndContext(activeTexture.canvasAndContext);
3588
- this.returnTexture(activeTexture.texture);
3589
- this._activeTextures[textKey] = null;
3590
- }
3591
- }
3592
- getReferenceCount(textKey) {
3593
- return this._activeTextures[textKey].usageCount;
4436
+ TexturePool.returnTexture(texture, true);
3594
4437
  }
3595
4438
  /**
3596
4439
  * Renders text to its canvas, and updates its texture.
3597
- *
3598
- * By default this is used internally to ensure the texture is correct before rendering,
3599
- * but it can be used called externally, for example from this class to 'pre-generate' the texture from a piece of text,
3600
- * and then shared across multiple Sprites.
3601
- * @param text
3602
- * @param style
3603
- * @param resolution
3604
- * @param canvasAndContext
4440
+ * @deprecated since 8.10.0
3605
4441
  */
3606
- renderTextToCanvas(text, style, resolution, canvasAndContext) {
3607
- const { canvas, context } = canvasAndContext;
3608
- const font = fontStringFromTextStyle(style);
3609
- const measured = CanvasTextMetrics.measureText(text || " ", style);
3610
- const lines = measured.lines;
3611
- const lineHeight = measured.lineHeight;
3612
- const lineWidths = measured.lineWidths;
3613
- const maxLineWidth = measured.maxLineWidth;
3614
- const fontProperties = measured.fontProperties;
3615
- const height = canvas.height;
3616
- context.resetTransform();
3617
- context.scale(resolution, resolution);
3618
- context.textBaseline = style.textBaseline;
3619
- if (style._stroke?.width) {
3620
- const strokeStyle = style._stroke;
3621
- context.lineWidth = strokeStyle.width;
3622
- context.miterLimit = strokeStyle.miterLimit;
3623
- context.lineJoin = strokeStyle.join;
3624
- context.lineCap = strokeStyle.cap;
3625
- }
3626
- context.font = font;
3627
- let linePositionX;
3628
- let linePositionY;
3629
- const passesCount = style.dropShadow ? 2 : 1;
3630
- for (let i = 0; i < passesCount; ++i) {
3631
- const isShadowPass = style.dropShadow && i === 0;
3632
- const dsOffsetText = isShadowPass ? Math.ceil(Math.max(1, height) + style.padding * 2) : 0;
3633
- const dsOffsetShadow = dsOffsetText * resolution;
3634
- if (isShadowPass) {
3635
- context.fillStyle = "black";
3636
- context.strokeStyle = "black";
3637
- const shadowOptions = style.dropShadow;
3638
- const dropShadowColor = shadowOptions.color;
3639
- const dropShadowAlpha = shadowOptions.alpha;
3640
- context.shadowColor = Color.shared.setValue(dropShadowColor).setAlpha(dropShadowAlpha).toRgbaString();
3641
- const dropShadowBlur = shadowOptions.blur * resolution;
3642
- const dropShadowDistance = shadowOptions.distance * resolution;
3643
- context.shadowBlur = dropShadowBlur;
3644
- context.shadowOffsetX = Math.cos(shadowOptions.angle) * dropShadowDistance;
3645
- context.shadowOffsetY = Math.sin(shadowOptions.angle) * dropShadowDistance + dsOffsetShadow;
3646
- } else {
3647
- context.fillStyle = style._fill ? getCanvasFillStyle(style._fill, context, measured) : null;
3648
- if (style._stroke?.width) {
3649
- const padding = style._stroke.width * style._stroke.alignment;
3650
- context.strokeStyle = getCanvasFillStyle(style._stroke, context, measured, padding);
3651
- }
3652
- context.shadowColor = "black";
3653
- }
3654
- let linePositionYShift = (lineHeight - fontProperties.fontSize) / 2;
3655
- if (lineHeight - fontProperties.fontSize < 0) {
3656
- linePositionYShift = 0;
3657
- }
3658
- const strokeWidth = style._stroke?.width ?? 0;
3659
- for (let i2 = 0; i2 < lines.length; i2++) {
3660
- linePositionX = strokeWidth / 2;
3661
- linePositionY = strokeWidth / 2 + i2 * lineHeight + fontProperties.ascent + linePositionYShift;
3662
- if (style.align === "right") {
3663
- linePositionX += maxLineWidth - lineWidths[i2];
3664
- } else if (style.align === "center") {
3665
- linePositionX += (maxLineWidth - lineWidths[i2]) / 2;
3666
- }
3667
- if (style._stroke?.width) {
3668
- this._drawLetterSpacing(
3669
- lines[i2],
3670
- style,
3671
- canvasAndContext,
3672
- linePositionX + style.padding,
3673
- linePositionY + style.padding - dsOffsetText,
3674
- true
3675
- );
3676
- }
3677
- if (style._fill !== void 0) {
3678
- this._drawLetterSpacing(
3679
- lines[i2],
3680
- style,
3681
- canvasAndContext,
3682
- linePositionX + style.padding,
3683
- linePositionY + style.padding - dsOffsetText
3684
- );
3685
- }
3686
- }
3687
- }
4442
+ renderTextToCanvas() {
4443
+ deprecation(
4444
+ "8.10.0",
4445
+ "CanvasTextSystem.renderTextToCanvas: no longer supported, use CanvasTextSystem.getTexture instead"
4446
+ );
3688
4447
  }
3689
4448
  /**
3690
- * Render the text with letter-spacing.
3691
- * @param text - The text to draw
3692
- * @param style
3693
- * @param canvasAndContext
3694
- * @param x - Horizontal position to draw the text
3695
- * @param y - Vertical position to draw the text
3696
- * @param isStroke - Is this drawing for the outside stroke of the
3697
- * text? If not, it's for the inside fill
4449
+ * Applies the specified filters to the given texture.
4450
+ *
4451
+ * This method takes a texture and a list of filters, applies the filters to the texture,
4452
+ * and returns the resulting texture. It also ensures that the alpha mode of the resulting
4453
+ * texture is set to 'premultiplied-alpha'.
4454
+ * @param {Texture} texture - The texture to which the filters will be applied.
4455
+ * @param {Filter[]} filters - The filters to apply to the texture.
4456
+ * @returns {Texture} The resulting texture after all filters have been applied.
3698
4457
  */
3699
- _drawLetterSpacing(text, style, canvasAndContext, x, y, isStroke = false) {
3700
- const { context } = canvasAndContext;
3701
- const letterSpacing = style.letterSpacing;
3702
- let useExperimentalLetterSpacing = false;
3703
- if (CanvasTextMetrics.experimentalLetterSpacingSupported) {
3704
- if (CanvasTextMetrics.experimentalLetterSpacing) {
3705
- context.letterSpacing = `${letterSpacing}px`;
3706
- context.textLetterSpacing = `${letterSpacing}px`;
3707
- useExperimentalLetterSpacing = true;
3708
- } else {
3709
- context.letterSpacing = "0px";
3710
- context.textLetterSpacing = "0px";
3711
- }
3712
- }
3713
- if (letterSpacing === 0 || useExperimentalLetterSpacing) {
3714
- if (isStroke) {
3715
- context.strokeText(text, x, y);
3716
- } else {
3717
- context.fillText(text, x, y);
3718
- }
3719
- return;
3720
- }
3721
- let currentPosition = x;
3722
- const stringArray = CanvasTextMetrics.graphemeSegmenter(text);
3723
- let previousWidth = context.measureText(text).width;
3724
- let currentWidth = 0;
3725
- for (let i = 0; i < stringArray.length; ++i) {
3726
- const currentChar = stringArray[i];
3727
- if (isStroke) {
3728
- context.strokeText(currentChar, currentPosition, y);
3729
- } else {
3730
- context.fillText(currentChar, currentPosition, y);
3731
- }
3732
- let textStr = "";
3733
- for (let j = i + 1; j < stringArray.length; ++j) {
3734
- textStr += stringArray[j];
3735
- }
3736
- currentWidth = context.measureText(textStr).width;
3737
- currentPosition += previousWidth - currentWidth + letterSpacing;
3738
- previousWidth = currentWidth;
3739
- }
4458
+ _applyFilters(texture, filters) {
4459
+ const currentRenderTarget = this._renderer.renderTarget.renderTarget;
4460
+ const resultTexture = this._renderer.filter.generateFilteredTexture({
4461
+ texture,
4462
+ filters
4463
+ });
4464
+ this._renderer.renderTarget.bind(currentRenderTarget, false);
4465
+ return resultTexture;
3740
4466
  }
3741
4467
  destroy() {
3742
- this._activeTextures = null;
4468
+ this._renderer = null;
3743
4469
  }
3744
4470
  }
3745
4471
  /** @ignore */