@lightningjs/renderer 1.0.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +17 -0
  2. package/dist/exports/canvas.d.ts +18 -0
  3. package/dist/exports/canvas.js +37 -0
  4. package/dist/exports/canvas.js.map +1 -0
  5. package/dist/exports/index.d.ts +2 -4
  6. package/dist/exports/index.js +1 -3
  7. package/dist/exports/index.js.map +1 -1
  8. package/dist/exports/inspector.d.ts +1 -0
  9. package/dist/exports/inspector.js +20 -0
  10. package/dist/exports/inspector.js.map +1 -0
  11. package/dist/exports/webgl.d.ts +19 -0
  12. package/dist/exports/webgl.js +38 -0
  13. package/dist/exports/webgl.js.map +1 -0
  14. package/dist/src/common/EventEmitter.d.ts +1 -1
  15. package/dist/src/common/IAnimationController.d.ts +1 -1
  16. package/dist/src/common/IEventEmitter.d.ts +8 -0
  17. package/dist/src/common/IEventEmitter.js +18 -0
  18. package/dist/src/common/IEventEmitter.js.map +1 -0
  19. package/dist/src/core/CoreNode.d.ts +58 -0
  20. package/dist/src/core/CoreNode.js +57 -1
  21. package/dist/src/core/CoreNode.js.map +1 -1
  22. package/dist/src/core/CoreTextNode.d.ts +2 -2
  23. package/dist/src/core/CoreTextNode.js +20 -30
  24. package/dist/src/core/CoreTextNode.js.map +1 -1
  25. package/dist/src/core/Stage.d.ts +11 -5
  26. package/dist/src/core/Stage.js +54 -26
  27. package/dist/src/core/Stage.js.map +1 -1
  28. package/dist/src/core/TextureMemoryManager.js +4 -2
  29. package/dist/src/core/TextureMemoryManager.js.map +1 -1
  30. package/dist/src/core/animations/CoreAnimation.js +1 -1
  31. package/dist/src/core/animations/CoreAnimation.js.map +1 -1
  32. package/dist/src/core/lib/ImageWorker.d.ts +1 -1
  33. package/dist/src/core/lib/ImageWorker.js +25 -3
  34. package/dist/src/core/lib/ImageWorker.js.map +1 -1
  35. package/dist/src/core/lib/textureSvg.d.ts +16 -0
  36. package/dist/src/core/lib/textureSvg.js +63 -0
  37. package/dist/src/core/lib/textureSvg.js.map +1 -0
  38. package/dist/src/core/text-rendering/TrFontManager.js +11 -4
  39. package/dist/src/core/text-rendering/TrFontManager.js.map +1 -1
  40. package/dist/src/core/text-rendering/font-face-types/WebTrFontFace.js +13 -5
  41. package/dist/src/core/text-rendering/font-face-types/WebTrFontFace.js.map +1 -1
  42. package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.d.ts +1 -0
  43. package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.js +1 -0
  44. package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.js.map +1 -1
  45. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.d.ts +1 -0
  46. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js +1 -0
  47. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js.map +1 -1
  48. package/dist/src/core/text-rendering/renderers/TextRenderer.d.ts +1 -0
  49. package/dist/src/core/text-rendering/renderers/TextRenderer.js.map +1 -1
  50. package/dist/src/core/textures/ImageTexture.d.ts +52 -0
  51. package/dist/src/core/textures/ImageTexture.js +78 -31
  52. package/dist/src/core/textures/ImageTexture.js.map +1 -1
  53. package/dist/src/core/textures/Texture.d.ts +1 -0
  54. package/dist/src/core/textures/Texture.js +1 -0
  55. package/dist/src/core/textures/Texture.js.map +1 -1
  56. package/dist/src/main-api/Inspector.js +2 -2
  57. package/dist/src/main-api/Inspector.js.map +1 -1
  58. package/dist/src/main-api/Renderer.d.ts +50 -8
  59. package/dist/src/main-api/Renderer.js +8 -7
  60. package/dist/src/main-api/Renderer.js.map +1 -1
  61. package/dist/tsconfig.dist.tsbuildinfo +1 -1
  62. package/dist/tsconfig.tsbuildinfo +1 -0
  63. package/exports/canvas.ts +37 -0
  64. package/exports/index.ts +3 -3
  65. package/exports/inspector.ts +20 -0
  66. package/exports/webgl.ts +38 -0
  67. package/package.json +5 -7
  68. package/src/common/EventEmitter.ts +1 -1
  69. package/src/common/IAnimationController.ts +1 -1
  70. package/src/common/IEventEmitter.ts +28 -0
  71. package/src/core/CoreNode.test.ts +1 -0
  72. package/src/core/CoreNode.ts +120 -1
  73. package/src/core/CoreTextNode.ts +58 -65
  74. package/src/core/Stage.ts +88 -36
  75. package/src/core/TextureMemoryManager.ts +4 -2
  76. package/src/core/animations/CoreAnimation.ts +2 -1
  77. package/src/core/lib/ImageWorker.ts +44 -7
  78. package/src/core/lib/textureSvg.ts +78 -0
  79. package/src/core/text-rendering/TrFontManager.ts +15 -4
  80. package/src/core/text-rendering/font-face-types/WebTrFontFace.ts +15 -8
  81. package/src/core/text-rendering/renderers/CanvasTextRenderer.ts +2 -0
  82. package/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.ts +2 -0
  83. package/src/core/text-rendering/renderers/TextRenderer.ts +1 -0
  84. package/src/core/textures/ImageTexture.ts +159 -35
  85. package/src/core/textures/Texture.ts +2 -0
  86. package/src/main-api/Inspector.ts +2 -2
  87. package/src/main-api/Renderer.ts +59 -15
@@ -405,6 +405,11 @@ export interface CoreNodeProps {
405
405
  */
406
406
  texture: Texture | null;
407
407
 
408
+ /**
409
+ * Whether to prevent the node from being cleaned up
410
+ * @default false
411
+ */
412
+ preventCleanup: boolean;
408
413
  /**
409
414
  * Options to associate with the Node's Texture
410
415
  */
@@ -613,6 +618,47 @@ export interface CoreNodeProps {
613
618
  * @default `undefined`
614
619
  */
615
620
  data?: CustomDataMap;
621
+
622
+ /**
623
+ * Image Type to explicitly set the image type that is being loaded
624
+ *
625
+ * @remarks
626
+ * This property must be used with a `src` that points at an image. In some cases
627
+ * the extension doesn't provide a reliable representation of the image type. In such
628
+ * cases set the ImageType explicitly.
629
+ *
630
+ * `regular` is used for normal images such as png, jpg, etc
631
+ * `compressed` is used for ETC1/ETC2 compressed images with a PVR or KTX container
632
+ * `svg` is used for scalable vector graphics
633
+ *
634
+ * @default `undefined`
635
+ */
636
+ imageType?: 'regular' | 'compressed' | 'svg' | null;
637
+
638
+ /**
639
+ * She width of the rectangle from which the Image Texture will be extracted.
640
+ * This value can be negative. If not provided, the image's source natural
641
+ * width will be used.
642
+ */
643
+ srcWidth?: number;
644
+ /**
645
+ * The height of the rectangle from which the Image Texture will be extracted.
646
+ * This value can be negative. If not provided, the image's source natural
647
+ * height will be used.
648
+ */
649
+ srcHeight?: number;
650
+ /**
651
+ * The x coordinate of the reference point of the rectangle from which the Texture
652
+ * will be extracted. `width` and `height` are provided. And only works when
653
+ * createImageBitmap is available. Only works when createImageBitmap is supported on the browser.
654
+ */
655
+ srcX?: number;
656
+ /**
657
+ * The y coordinate of the reference point of the rectangle from which the Texture
658
+ * will be extracted. Only used when source `srcWidth` width and `srcHeight` height
659
+ * are provided. Only works when createImageBitmap is supported on the browser.
660
+ */
661
+ srcY?: number;
616
662
  }
617
663
 
618
664
  /**
@@ -704,6 +750,7 @@ export class CoreNode extends EventEmitter {
704
750
  // We do this in a microtask to allow listeners to be attached in the same
705
751
  // synchronous task after calling loadTexture()
706
752
  queueMicrotask(() => {
753
+ texture.preventCleanup = this.props.preventCleanup;
707
754
  // Preload texture if required
708
755
  if (this.textureOptions.preload) {
709
756
  texture.ctxTexture.load();
@@ -1387,6 +1434,14 @@ export class CoreNode extends EventEmitter {
1387
1434
  return this._id;
1388
1435
  }
1389
1436
 
1437
+ get data(): CustomDataMap | undefined {
1438
+ return this.props.data;
1439
+ }
1440
+
1441
+ set data(d: CustomDataMap | undefined) {
1442
+ this.props.data = d;
1443
+ }
1444
+
1390
1445
  get x(): number {
1391
1446
  return this.props.x;
1392
1447
  }
@@ -1401,12 +1456,17 @@ export class CoreNode extends EventEmitter {
1401
1456
  get absX(): number {
1402
1457
  return (
1403
1458
  this.props.x +
1459
+ -this.props.width * this.props.mountX +
1404
1460
  (this.props.parent?.absX || this.props.parent?.globalTransform?.tx || 0)
1405
1461
  );
1406
1462
  }
1407
1463
 
1408
1464
  get absY(): number {
1409
- return this.props.y + (this.props.parent?.absY ?? 0);
1465
+ return (
1466
+ this.props.y +
1467
+ -this.props.height * this.props.mountY +
1468
+ (this.props.parent?.absY ?? 0)
1469
+ );
1410
1470
  }
1411
1471
 
1412
1472
  get y(): number {
@@ -1767,6 +1827,14 @@ export class CoreNode extends EventEmitter {
1767
1827
  this.updateScaleRotateTransform();
1768
1828
  }
1769
1829
 
1830
+ get preventCleanup(): boolean {
1831
+ return this.props.preventCleanup;
1832
+ }
1833
+
1834
+ set preventCleanup(value: boolean) {
1835
+ this.props.preventCleanup = value;
1836
+ }
1837
+
1770
1838
  get rtt(): boolean {
1771
1839
  return this.props.rtt;
1772
1840
  }
@@ -1845,9 +1913,60 @@ export class CoreNode extends EventEmitter {
1845
1913
 
1846
1914
  this.texture = this.stage.txManager.loadTexture('ImageTexture', {
1847
1915
  src: imageUrl,
1916
+ width: this.props.width,
1917
+ height: this.props.height,
1918
+ type: this.props.imageType,
1919
+ sx: this.props.srcX,
1920
+ sy: this.props.srcY,
1921
+ sw: this.props.srcWidth,
1922
+ sh: this.props.srcHeight,
1848
1923
  });
1849
1924
  }
1850
1925
 
1926
+ set imageType(type: 'regular' | 'compressed' | 'svg' | null) {
1927
+ if (this.props.imageType === type) {
1928
+ return;
1929
+ }
1930
+
1931
+ this.props.imageType = type;
1932
+ }
1933
+
1934
+ get imageType() {
1935
+ return this.props.imageType || null;
1936
+ }
1937
+
1938
+ get srcHeight(): number | undefined {
1939
+ return this.props.srcHeight;
1940
+ }
1941
+
1942
+ set srcHeight(value: number) {
1943
+ this.props.srcHeight = value;
1944
+ }
1945
+
1946
+ get srcWidth(): number | undefined {
1947
+ return this.props.srcWidth;
1948
+ }
1949
+
1950
+ set srcWidth(value: number) {
1951
+ this.props.srcWidth = value;
1952
+ }
1953
+
1954
+ get srcX(): number | undefined {
1955
+ return this.props.srcX;
1956
+ }
1957
+
1958
+ set srcX(value: number) {
1959
+ this.props.srcX = value;
1960
+ }
1961
+
1962
+ get srcY(): number | undefined {
1963
+ return this.props.srcY;
1964
+ }
1965
+
1966
+ set srcY(value: number) {
1967
+ this.props.srcY = value;
1968
+ }
1969
+
1851
1970
  /**
1852
1971
  * Returns the framebuffer dimensions of the node.
1853
1972
  * If the node has a render texture, the dimensions are the same as the node's dimensions.
@@ -75,37 +75,41 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
75
75
  private _textRendererOverride: CoreTextNodeProps['textRendererOverride'] =
76
76
  null;
77
77
 
78
- constructor(stage: Stage, props: CoreTextNodeProps) {
78
+ constructor(
79
+ stage: Stage,
80
+ props: CoreTextNodeProps,
81
+ textRenderer: TextRenderer,
82
+ ) {
79
83
  super(stage, props);
80
84
  this._textRendererOverride = props.textRendererOverride;
81
- const { resolvedTextRenderer, textRendererState } =
82
- this.resolveTextRendererAndState({
83
- x: this.absX,
84
- y: this.absY,
85
- width: props.width,
86
- height: props.height,
87
- textAlign: props.textAlign,
88
- color: props.color,
89
- zIndex: props.zIndex,
90
- contain: props.contain,
91
- scrollable: props.scrollable,
92
- scrollY: props.scrollY,
93
- offsetY: props.offsetY,
94
- letterSpacing: props.letterSpacing,
95
- debug: props.debug,
96
- fontFamily: props.fontFamily,
97
- fontSize: props.fontSize,
98
- fontStretch: props.fontStretch,
99
- fontStyle: props.fontStyle,
100
- fontWeight: props.fontWeight,
101
- text: props.text,
102
- lineHeight: props.lineHeight,
103
- maxLines: props.maxLines,
104
- textBaseline: props.textBaseline,
105
- verticalAlign: props.verticalAlign,
106
- overflowSuffix: props.overflowSuffix,
107
- });
108
- this.textRenderer = resolvedTextRenderer;
85
+ this.textRenderer = textRenderer;
86
+ const textRendererState = this.createState({
87
+ x: this.absX,
88
+ y: this.absY,
89
+ width: props.width,
90
+ height: props.height,
91
+ textAlign: props.textAlign,
92
+ color: props.color,
93
+ zIndex: props.zIndex,
94
+ contain: props.contain,
95
+ scrollable: props.scrollable,
96
+ scrollY: props.scrollY,
97
+ offsetY: props.offsetY,
98
+ letterSpacing: props.letterSpacing,
99
+ debug: props.debug,
100
+ fontFamily: props.fontFamily,
101
+ fontSize: props.fontSize,
102
+ fontStretch: props.fontStretch,
103
+ fontStyle: props.fontStyle,
104
+ fontWeight: props.fontWeight,
105
+ text: props.text,
106
+ lineHeight: props.lineHeight,
107
+ maxLines: props.maxLines,
108
+ textBaseline: props.textBaseline,
109
+ verticalAlign: props.verticalAlign,
110
+ overflowSuffix: props.overflowSuffix,
111
+ });
112
+
109
113
  this.trState = textRendererState;
110
114
  }
111
115
 
@@ -199,13 +203,23 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
199
203
 
200
204
  set textRendererOverride(value: CoreTextNodeProps['textRendererOverride']) {
201
205
  this._textRendererOverride = value;
202
-
203
206
  this.textRenderer.destroyState(this.trState);
204
207
 
205
- const { resolvedTextRenderer, textRendererState } =
206
- this.resolveTextRendererAndState(this.trState.props);
207
- this.textRenderer = resolvedTextRenderer;
208
- this.trState = textRendererState;
208
+ const textRenderer = this.stage.resolveTextRenderer(
209
+ this.trState.props,
210
+ this._textRendererOverride,
211
+ );
212
+
213
+ if (!textRenderer) {
214
+ console.warn(
215
+ 'Text Renderer not found for font',
216
+ this.trState.props.fontFamily,
217
+ );
218
+ return;
219
+ }
220
+
221
+ this.textRenderer = textRenderer;
222
+ this.trState = this.createState(this.trState.props);
209
223
  }
210
224
 
211
225
  get fontSize(): CoreTextNodeProps['fontSize'] {
@@ -301,9 +315,7 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
301
315
  }
302
316
 
303
317
  set lineHeight(value: CoreTextNodeProps['lineHeight']) {
304
- if (this.textRenderer.set.lineHeight) {
305
- this.textRenderer.set.lineHeight(this.trState, value);
306
- }
318
+ this.textRenderer.set.lineHeight(this.trState, value);
307
319
  }
308
320
 
309
321
  get maxLines(): CoreTextNodeProps['maxLines'] {
@@ -311,9 +323,7 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
311
323
  }
312
324
 
313
325
  set maxLines(value: CoreTextNodeProps['maxLines']) {
314
- if (this.textRenderer.set.maxLines) {
315
- this.textRenderer.set.maxLines(this.trState, value);
316
- }
326
+ this.textRenderer.set.maxLines(this.trState, value);
317
327
  }
318
328
 
319
329
  get textBaseline(): CoreTextNodeProps['textBaseline'] {
@@ -321,9 +331,7 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
321
331
  }
322
332
 
323
333
  set textBaseline(value: CoreTextNodeProps['textBaseline']) {
324
- if (this.textRenderer.set.textBaseline) {
325
- this.textRenderer.set.textBaseline(this.trState, value);
326
- }
334
+ this.textRenderer.set.textBaseline(this.trState, value);
327
335
  }
328
336
 
329
337
  get verticalAlign(): CoreTextNodeProps['verticalAlign'] {
@@ -331,9 +339,7 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
331
339
  }
332
340
 
333
341
  set verticalAlign(value: CoreTextNodeProps['verticalAlign']) {
334
- if (this.textRenderer.set.verticalAlign) {
335
- this.textRenderer.set.verticalAlign(this.trState, value);
336
- }
342
+ this.textRenderer.set.verticalAlign(this.trState, value);
337
343
  }
338
344
 
339
345
  get overflowSuffix(): CoreTextNodeProps['overflowSuffix'] {
@@ -341,9 +347,7 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
341
347
  }
342
348
 
343
349
  set overflowSuffix(value: CoreTextNodeProps['overflowSuffix']) {
344
- if (this.textRenderer.set.overflowSuffix) {
345
- this.textRenderer.set.overflowSuffix(this.trState, value);
346
- }
350
+ this.textRenderer.set.overflowSuffix(this.trState, value);
347
351
  }
348
352
 
349
353
  get debug(): CoreTextNodeProps['debug'] {
@@ -365,7 +369,7 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
365
369
  }
366
370
 
367
371
  override checkRenderProps(): boolean {
368
- if (this.trState.props.text !== '') {
372
+ if (this.trState && this.trState.props.text !== '') {
369
373
  return true;
370
374
  }
371
375
  return super.checkRenderProps();
@@ -433,25 +437,14 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
433
437
  * @param props
434
438
  * @returns
435
439
  */
436
- private resolveTextRendererAndState(props: TrProps): {
437
- resolvedTextRenderer: TextRenderer;
438
- textRendererState: TextRendererState;
439
- } {
440
- const resolvedTextRenderer = this.stage.resolveTextRenderer(
441
- props,
442
- this._textRendererOverride,
443
- );
444
-
445
- const textRendererState = resolvedTextRenderer.createState(props, this);
440
+ private createState(props: TrProps) {
441
+ const textRendererState = this.textRenderer.createState(props, this);
446
442
 
447
443
  textRendererState.emitter.on('loaded', this.onTextLoaded);
448
444
  textRendererState.emitter.on('failed', this.onTextFailed);
449
445
 
450
- resolvedTextRenderer.scheduleUpdateState(textRendererState);
446
+ this.textRenderer.scheduleUpdateState(textRendererState);
451
447
 
452
- return {
453
- resolvedTextRenderer,
454
- textRendererState,
455
- };
448
+ return textRendererState;
456
449
  }
457
450
  }
package/src/core/Stage.ts CHANGED
@@ -17,20 +17,18 @@
17
17
  * limitations under the License.
18
18
  */
19
19
  import { startLoop, getTimeStamp } from './platform.js';
20
- import { WebGlCoreRenderer } from './renderers/webgl/WebGlCoreRenderer.js';
21
20
  import { assertTruthy, setPremultiplyMode } from '../utils.js';
22
21
  import { AnimationManager } from './animations/AnimationManager.js';
23
22
  import { CoreNode, type CoreNodeProps } from './CoreNode.js';
24
23
  import { CoreTextureManager } from './CoreTextureManager.js';
25
24
  import { TrFontManager } from './text-rendering/TrFontManager.js';
26
25
  import { CoreShaderManager, type ShaderMap } from './CoreShaderManager.js';
27
- import type {
26
+ import {
28
27
  TextRenderer,
29
- TextRendererMap,
30
- TrProps,
28
+ type TextRendererMap,
29
+ type TrProps,
31
30
  } from './text-rendering/renderers/TextRenderer.js';
32
- import { SdfTextRenderer } from './text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js';
33
- import { CanvasTextRenderer } from './text-rendering/renderers/CanvasTextRenderer.js';
31
+
34
32
  import { EventEmitter } from '../common/EventEmitter.js';
35
33
  import { ContextSpy } from './lib/ContextSpy.js';
36
34
  import type {
@@ -41,14 +39,15 @@ import {
41
39
  TextureMemoryManager,
42
40
  type TextureMemoryManagerSettings,
43
41
  } from './TextureMemoryManager.js';
44
- import type {
45
- CoreRenderer,
46
- CoreRendererOptions,
47
- } from './renderers/CoreRenderer.js';
48
- import { CanvasCoreRenderer } from './renderers/canvas/CanvasCoreRenderer.js';
42
+ import type { CoreRendererOptions } from './renderers/CoreRenderer.js';
43
+ import { CoreRenderer } from './renderers/CoreRenderer.js';
44
+ import type { WebGlCoreRenderer } from './renderers/webgl/WebGlCoreRenderer.js';
45
+ import type { CanvasCoreRenderer } from './renderers/canvas/CanvasCoreRenderer.js';
49
46
  import type { BaseShaderController } from '../main-api/ShaderController.js';
50
47
  import { CoreTextNode, type CoreTextNodeProps } from './CoreTextNode.js';
51
48
  import { santizeCustomDataMap } from '../main-api/utils.js';
49
+ import type { SdfTextRenderer } from './text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js';
50
+ import type { CanvasTextRenderer } from './text-rendering/renderers/CanvasTextRenderer.js';
52
51
 
53
52
  export interface StageOptions {
54
53
  appWidth: number;
@@ -62,9 +61,10 @@ export interface StageOptions {
62
61
  fpsUpdateInterval: number;
63
62
  enableContextSpy: boolean;
64
63
  numImageWorkers: number;
65
- renderMode: 'webgl' | 'canvas';
64
+ renderEngine: typeof WebGlCoreRenderer | typeof CanvasCoreRenderer;
66
65
  eventBus: EventEmitter;
67
66
  quadBufferSize: number;
67
+ fontEngines: (typeof CanvasTextRenderer | typeof SdfTextRenderer)[];
68
68
  }
69
69
 
70
70
  export type StageFpsUpdateHandler = (
@@ -111,6 +111,8 @@ export class Stage {
111
111
  private fpsElapsedTime = 0;
112
112
  private renderRequested = false;
113
113
  private frameEventQueue: [name: string, payload: unknown][] = [];
114
+ private fontResolveMap: Record<string, CanvasTextRenderer | SdfTextRenderer> =
115
+ {};
114
116
 
115
117
  /// Debug data
116
118
  contextSpy: ContextSpy | null = null;
@@ -128,7 +130,8 @@ export class Stage {
128
130
  enableContextSpy,
129
131
  numImageWorkers,
130
132
  textureMemory,
131
- renderMode,
133
+ renderEngine,
134
+ fontEngines,
132
135
  } = options;
133
136
 
134
137
  this.eventBus = options.eventBus;
@@ -159,26 +162,42 @@ export class Stage {
159
162
  contextSpy: this.contextSpy,
160
163
  };
161
164
 
162
- if (renderMode === 'canvas') {
163
- this.renderer = new CanvasCoreRenderer(rendererOptions);
164
- } else {
165
- this.renderer = new WebGlCoreRenderer(rendererOptions);
166
- }
165
+ this.renderer = new renderEngine(rendererOptions);
166
+ const renderMode = this.renderer.mode || 'webgl';
167
+
167
168
  this.defShaderCtr = this.renderer.getDefShaderCtr();
168
169
  setPremultiplyMode(renderMode);
169
170
 
170
171
  // Must do this after renderer is created
171
172
  this.txManager.renderer = this.renderer;
172
173
 
173
- this.textRenderers =
174
- renderMode === 'webgl'
175
- ? {
176
- canvas: new CanvasTextRenderer(this),
177
- sdf: new SdfTextRenderer(this),
178
- }
179
- : {
180
- canvas: new CanvasTextRenderer(this),
181
- };
174
+ // Create text renderers
175
+ this.textRenderers = {};
176
+ fontEngines.forEach((fontEngineConstructor) => {
177
+ const fontEngineInstance = new fontEngineConstructor(this);
178
+ const className = fontEngineInstance.type;
179
+
180
+ if (className === 'sdf' && renderMode === 'canvas') {
181
+ console.warn(
182
+ 'SdfTextRenderer is not compatible with Canvas renderer. Skipping...',
183
+ );
184
+ return;
185
+ }
186
+
187
+ if (fontEngineInstance instanceof TextRenderer) {
188
+ if (className === 'canvas') {
189
+ this.textRenderers['canvas'] =
190
+ fontEngineInstance as CanvasTextRenderer;
191
+ } else if (className === 'sdf') {
192
+ this.textRenderers['sdf'] = fontEngineInstance as SdfTextRenderer;
193
+ }
194
+ }
195
+ });
196
+
197
+ if (Object.keys(this.textRenderers).length === 0) {
198
+ console.warn('No text renderers available. Your text will not render.');
199
+ }
200
+
182
201
  this.fontManager = new TrFontManager(this.textRenderers);
183
202
 
184
203
  // create root node
@@ -217,6 +236,7 @@ export class Stage {
217
236
  rtt: false,
218
237
  src: null,
219
238
  scale: 1,
239
+ preventCleanup: false,
220
240
  });
221
241
 
222
242
  this.root = rootNode;
@@ -393,7 +413,7 @@ export class Stage {
393
413
  * Given a font name, and possible renderer override, return the best compatible text renderer.
394
414
  *
395
415
  * @remarks
396
- * Will always return at least a canvas renderer if no other suitable renderer can be resolved.
416
+ * Will try to return a canvas renderer if no other suitable renderer can be resolved.
397
417
  *
398
418
  * @param fontFamily
399
419
  * @param textRendererOverride
@@ -402,10 +422,20 @@ export class Stage {
402
422
  resolveTextRenderer(
403
423
  trProps: TrProps,
404
424
  textRendererOverride: keyof TextRendererMap | null = null,
405
- ): TextRenderer {
406
- let rendererId = textRendererOverride;
425
+ ): TextRenderer | null {
426
+ const fontCacheString = `${trProps.fontFamily}${trProps.fontStyle}${
427
+ trProps.fontWeight
428
+ }${trProps.fontStretch}${textRendererOverride ? textRendererOverride : ''}`;
429
+
430
+ // check our resolve cache first
431
+ if (this.fontResolveMap[fontCacheString] !== undefined) {
432
+ return this.fontResolveMap[fontCacheString] as unknown as TextRenderer;
433
+ }
407
434
 
435
+ // Resolve the text renderer
436
+ let rendererId = textRendererOverride;
408
437
  let overrideFallback = false;
438
+
409
439
  // Check if the override is valid (if one is provided)
410
440
  if (rendererId) {
411
441
  const possibleRenderer = this.textRenderers[rendererId];
@@ -426,16 +456,12 @@ export class Stage {
426
456
  if (!rendererId) {
427
457
  // Iterate through the text renderers and find the first one that can render the font
428
458
  for (const [trId, tr] of Object.entries(this.textRenderers)) {
429
- if (trId === 'canvas') {
430
- // Canvas is always a fallback
431
- continue;
432
- }
433
459
  if (tr.canRenderFont(trProps)) {
434
460
  rendererId = trId as keyof TextRendererMap;
435
461
  break;
436
462
  }
437
463
  }
438
- if (!rendererId) {
464
+ if (!rendererId && this.textRenderers.canvas !== undefined) {
439
465
  // If no renderer can be found, use the canvas renderer
440
466
  rendererId = 'canvas';
441
467
  }
@@ -445,10 +471,19 @@ export class Stage {
445
471
  console.warn(`Falling back to text renderer ${String(rendererId)}`);
446
472
  }
447
473
 
474
+ if (!rendererId) {
475
+ // silently fail if no renderer can be found, the error is already created
476
+ // at the constructor level
477
+ return null;
478
+ }
479
+
448
480
  // By now we are guaranteed to have a valid rendererId (at least Canvas);
449
481
  const resolvedTextRenderer = this.textRenderers[rendererId];
450
482
  assertTruthy(resolvedTextRenderer, 'resolvedTextRenderer undefined');
451
483
 
484
+ // cache the resolved renderer for future use with these trProps
485
+ this.fontResolveMap[fontCacheString] = resolvedTextRenderer;
486
+
452
487
  // Need to explicitly cast to TextRenderer because TS doesn't like
453
488
  // the covariant state argument in the setter method map
454
489
  return resolvedTextRenderer as unknown as TextRenderer;
@@ -499,7 +534,18 @@ export class Stage {
499
534
  shaderProps: null,
500
535
  };
501
536
 
502
- return new CoreTextNode(this, resolvedProps);
537
+ const resolvedTextRenderer = this.resolveTextRenderer(
538
+ resolvedProps,
539
+ props.textRendererOverride,
540
+ );
541
+
542
+ if (!resolvedTextRenderer) {
543
+ throw new Error(
544
+ `No compatible text renderer found for ${resolvedProps.fontFamily}`,
545
+ );
546
+ }
547
+
548
+ return new CoreTextNode(this, resolvedProps, resolvedTextRenderer);
503
549
  }
504
550
 
505
551
  /**
@@ -550,6 +596,10 @@ export class Stage {
550
596
  // Since setting the `src` will trigger a texture load, we need to set it after
551
597
  // we set the texture. Otherwise, problems happen.
552
598
  src: props.src ?? null,
599
+ srcHeight: props.srcHeight,
600
+ srcWidth: props.srcWidth,
601
+ srcX: props.srcX,
602
+ srcY: props.srcY,
553
603
  scale: props.scale ?? null,
554
604
  scaleX: props.scaleX ?? props.scale ?? 1,
555
605
  scaleY: props.scaleY ?? props.scale ?? 1,
@@ -562,6 +612,8 @@ export class Stage {
562
612
  rotation: props.rotation ?? 0,
563
613
  rtt: props.rtt ?? false,
564
614
  data: data,
615
+ preventCleanup: props.preventCleanup ?? false,
616
+ imageType: props.imageType,
565
617
  };
566
618
  }
567
619
  }
@@ -227,8 +227,10 @@ export class TextureMemoryManager {
227
227
  // We don't want to free renderable textures because they will just likely be reloaded in the next frame
228
228
  break;
229
229
  }
230
- texture.ctxTexture.free();
231
- txManager.removeTextureFromCache(texture);
230
+ if (texture.preventCleanup === false) {
231
+ texture.ctxTexture.free();
232
+ txManager.removeTextureFromCache(texture);
233
+ }
232
234
  if (this.memUsed <= memTarget) {
233
235
  // Stop once we've freed enough textures to reach under the target threshold
234
236
  break;
@@ -60,7 +60,8 @@ export class CoreAnimation extends EventEmitter {
60
60
  this.propValuesMap['props'] = {};
61
61
  }
62
62
  this.propValuesMap['props'][key] = {
63
- start: node[key as keyof Omit<CoreNodeAnimateProps, 'shaderProps'>],
63
+ start:
64
+ node[key as keyof Omit<CoreNodeAnimateProps, 'shaderProps'>] || 0,
64
65
  target: props[
65
66
  key as keyof Omit<CoreNodeAnimateProps, 'shaderProps'>
66
67
  ] as number,