@lightningjs/renderer 1.0.1 → 2.1.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 (103) hide show
  1. package/README.md +17 -0
  2. package/dist/exports/canvas.d.ts +20 -0
  3. package/dist/exports/canvas.js +39 -0
  4. package/dist/exports/canvas.js.map +1 -0
  5. package/dist/exports/index.d.ts +3 -5
  6. package/dist/exports/index.js +2 -4
  7. package/dist/exports/index.js.map +1 -1
  8. package/dist/exports/inspector.d.ts +4 -0
  9. package/dist/exports/inspector.js +23 -0
  10. package/dist/exports/inspector.js.map +1 -0
  11. package/dist/exports/utils.d.ts +2 -0
  12. package/dist/exports/utils.js +2 -0
  13. package/dist/exports/utils.js.map +1 -1
  14. package/dist/exports/webgl.d.ts +19 -0
  15. package/dist/exports/webgl.js +38 -0
  16. package/dist/exports/webgl.js.map +1 -0
  17. package/dist/src/common/EventEmitter.d.ts +1 -1
  18. package/dist/src/common/IAnimationController.d.ts +1 -1
  19. package/dist/src/common/IEventEmitter.d.ts +8 -0
  20. package/dist/src/common/IEventEmitter.js +18 -0
  21. package/dist/src/common/IEventEmitter.js.map +1 -0
  22. package/dist/src/core/CoreNode.d.ts +66 -2
  23. package/dist/src/core/CoreNode.js +128 -24
  24. package/dist/src/core/CoreNode.js.map +1 -1
  25. package/dist/src/core/CoreTextNode.d.ts +2 -2
  26. package/dist/src/core/CoreTextNode.js +20 -30
  27. package/dist/src/core/CoreTextNode.js.map +1 -1
  28. package/dist/src/core/Stage.d.ts +11 -5
  29. package/dist/src/core/Stage.js +58 -30
  30. package/dist/src/core/Stage.js.map +1 -1
  31. package/dist/src/core/TextureMemoryManager.js +4 -2
  32. package/dist/src/core/TextureMemoryManager.js.map +1 -1
  33. package/dist/src/core/animations/CoreAnimation.js +1 -1
  34. package/dist/src/core/animations/CoreAnimation.js.map +1 -1
  35. package/dist/src/core/lib/ImageWorker.d.ts +1 -1
  36. package/dist/src/core/lib/ImageWorker.js +25 -3
  37. package/dist/src/core/lib/ImageWorker.js.map +1 -1
  38. package/dist/src/core/lib/textureSvg.d.ts +16 -0
  39. package/dist/src/core/lib/textureSvg.js +63 -0
  40. package/dist/src/core/lib/textureSvg.js.map +1 -0
  41. package/dist/src/core/renderers/canvas/CanvasCoreTexture.js +2 -1
  42. package/dist/src/core/renderers/canvas/CanvasCoreTexture.js.map +1 -1
  43. package/dist/src/core/renderers/webgl/WebGlCoreCtxTexture.js +2 -1
  44. package/dist/src/core/renderers/webgl/WebGlCoreCtxTexture.js.map +1 -1
  45. package/dist/src/core/renderers/webgl/WebGlCoreRenderer.js +10 -6
  46. package/dist/src/core/renderers/webgl/WebGlCoreRenderer.js.map +1 -1
  47. package/dist/src/core/renderers/webgl/shaders/DynamicShader.js +2 -1
  48. package/dist/src/core/renderers/webgl/shaders/DynamicShader.js.map +1 -1
  49. package/dist/src/core/text-rendering/TrFontManager.js +11 -4
  50. package/dist/src/core/text-rendering/TrFontManager.js.map +1 -1
  51. package/dist/src/core/text-rendering/font-face-types/WebTrFontFace.js +13 -5
  52. package/dist/src/core/text-rendering/font-face-types/WebTrFontFace.js.map +1 -1
  53. package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.d.ts +1 -0
  54. package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.js +1 -0
  55. package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.js.map +1 -1
  56. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.d.ts +1 -0
  57. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js +1 -0
  58. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js.map +1 -1
  59. package/dist/src/core/text-rendering/renderers/TextRenderer.d.ts +1 -0
  60. package/dist/src/core/text-rendering/renderers/TextRenderer.js.map +1 -1
  61. package/dist/src/core/textures/ImageTexture.d.ts +52 -0
  62. package/dist/src/core/textures/ImageTexture.js +78 -31
  63. package/dist/src/core/textures/ImageTexture.js.map +1 -1
  64. package/dist/src/core/textures/Texture.d.ts +1 -0
  65. package/dist/src/core/textures/Texture.js +1 -0
  66. package/dist/src/core/textures/Texture.js.map +1 -1
  67. package/dist/src/main-api/Inspector.js +2 -2
  68. package/dist/src/main-api/Inspector.js.map +1 -1
  69. package/dist/src/main-api/Renderer.d.ts +50 -8
  70. package/dist/src/main-api/Renderer.js +8 -7
  71. package/dist/src/main-api/Renderer.js.map +1 -1
  72. package/dist/tsconfig.dist.tsbuildinfo +1 -1
  73. package/dist/tsconfig.tsbuildinfo +1 -0
  74. package/exports/canvas.ts +39 -0
  75. package/exports/index.ts +4 -4
  76. package/exports/inspector.ts +24 -0
  77. package/exports/utils.ts +2 -0
  78. package/exports/webgl.ts +38 -0
  79. package/package.json +5 -7
  80. package/src/common/EventEmitter.ts +1 -1
  81. package/src/common/IAnimationController.ts +1 -1
  82. package/src/common/IEventEmitter.ts +28 -0
  83. package/src/core/CoreNode.test.ts +1 -0
  84. package/src/core/CoreNode.ts +229 -40
  85. package/src/core/CoreTextNode.ts +58 -65
  86. package/src/core/Stage.ts +92 -40
  87. package/src/core/TextureMemoryManager.ts +4 -2
  88. package/src/core/animations/CoreAnimation.ts +2 -1
  89. package/src/core/lib/ImageWorker.ts +44 -7
  90. package/src/core/lib/textureSvg.ts +78 -0
  91. package/src/core/renderers/canvas/CanvasCoreTexture.ts +4 -1
  92. package/src/core/renderers/webgl/WebGlCoreCtxTexture.ts +2 -1
  93. package/src/core/renderers/webgl/WebGlCoreRenderer.ts +11 -6
  94. package/src/core/renderers/webgl/shaders/DynamicShader.ts +1 -0
  95. package/src/core/text-rendering/TrFontManager.ts +15 -4
  96. package/src/core/text-rendering/font-face-types/WebTrFontFace.ts +15 -8
  97. package/src/core/text-rendering/renderers/CanvasTextRenderer.ts +2 -0
  98. package/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.ts +2 -0
  99. package/src/core/text-rendering/renderers/TextRenderer.ts +1 -0
  100. package/src/core/textures/ImageTexture.ts +159 -35
  101. package/src/core/textures/Texture.ts +2 -0
  102. package/src/main-api/Inspector.ts +2 -2
  103. package/src/main-api/Renderer.ts +59 -15
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;
@@ -361,20 +381,20 @@ export class Stage {
361
381
  }
362
382
 
363
383
  addQuads(node: CoreNode) {
364
- assertTruthy(this.renderer && node.globalTransform);
384
+ assertTruthy(this.renderer);
365
385
 
366
- if (node.isRenderable) {
386
+ if (node.isRenderable === true) {
367
387
  node.renderQuads(this.renderer);
368
388
  }
369
389
 
370
390
  for (let i = 0; i < node.children.length; i++) {
371
391
  const child = node.children[i];
372
392
 
373
- if (!child) {
393
+ if (child === undefined) {
374
394
  continue;
375
395
  }
376
396
 
377
- if (child?.worldAlpha === 0) {
397
+ if (child.worldAlpha === 0) {
378
398
  continue;
379
399
  }
380
400
 
@@ -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,
@@ -25,6 +25,17 @@ interface getImageReturn {
25
25
  premultiplyAlpha: boolean | null;
26
26
  }
27
27
 
28
+ interface ImageWorkerMessage {
29
+ id: number;
30
+ src: string;
31
+ data: getImageReturn;
32
+ error: string;
33
+ sx: number | null;
34
+ sy: number | null;
35
+ sw: number | null;
36
+ sh: number | null;
37
+ }
38
+
28
39
  /**
29
40
  * Note that, within the createImageWorker function, we must only use ES5 code to keep it ES5-valid after babelifying, as
30
41
  * the converted code of this section is converted to a blob and used as the js of the web worker thread.
@@ -43,6 +54,10 @@ function createImageWorker() {
43
54
  function getImage(
44
55
  src: string,
45
56
  premultiplyAlpha: boolean | null,
57
+ x: number | null,
58
+ y: number | null,
59
+ width: number | null,
60
+ height: number | null,
46
61
  ): Promise<getImageReturn> {
47
62
  return new Promise(function (resolve, reject) {
48
63
  var xhr = new XMLHttpRequest();
@@ -60,6 +75,21 @@ function createImageWorker() {
60
75
  ? premultiplyAlpha
61
76
  : hasAlphaChannel(blob.type);
62
77
 
78
+ if (width !== null && height !== null) {
79
+ createImageBitmap(blob, x || 0, y || 0, width, height, {
80
+ premultiplyAlpha: withAlphaChannel ? 'premultiply' : 'none',
81
+ colorSpaceConversion: 'none',
82
+ imageOrientation: 'none',
83
+ })
84
+ .then(function (data) {
85
+ resolve({ data, premultiplyAlpha: premultiplyAlpha });
86
+ })
87
+ .catch(function (error) {
88
+ reject(error);
89
+ });
90
+ return;
91
+ }
92
+
63
93
  createImageBitmap(blob, {
64
94
  premultiplyAlpha: withAlphaChannel ? 'premultiply' : 'none',
65
95
  colorSpaceConversion: 'none',
@@ -87,8 +117,12 @@ function createImageWorker() {
87
117
  var src = event.data.src;
88
118
  var id = event.data.id;
89
119
  var premultiplyAlpha = event.data.premultiplyAlpha;
120
+ var x = event.data.sx;
121
+ var y = event.data.sy;
122
+ var width = event.data.sw;
123
+ var height = event.data.sh;
90
124
 
91
- getImage(src, premultiplyAlpha)
125
+ getImage(src, premultiplyAlpha, x, y, width, height)
92
126
  .then(function (data) {
93
127
  self.postMessage({ id: id, src: src, data: data });
94
128
  })
@@ -114,12 +148,7 @@ export class ImageWorkerManager {
114
148
  }
115
149
 
116
150
  private handleMessage(event: MessageEvent) {
117
- const { id, data, error } = event.data as {
118
- id: number;
119
- src: string;
120
- data?: any;
121
- error?: string;
122
- };
151
+ const { id, data, error } = event.data as ImageWorkerMessage;
123
152
  const msg = this.messageManager[id];
124
153
  if (msg) {
125
154
  const [resolve, reject] = msg;
@@ -155,6 +184,10 @@ export class ImageWorkerManager {
155
184
  getImage(
156
185
  src: string,
157
186
  premultiplyAlpha: boolean | null,
187
+ sx: number | null,
188
+ sy: number | null,
189
+ sw: number | null,
190
+ sh: number | null,
158
191
  ): Promise<TextureData> {
159
192
  return new Promise((resolve, reject) => {
160
193
  try {
@@ -167,6 +200,10 @@ export class ImageWorkerManager {
167
200
  id,
168
201
  src: src,
169
202
  premultiplyAlpha,
203
+ sx,
204
+ sy,
205
+ sw,
206
+ sh,
170
207
  });
171
208
  }
172
209
  }
@@ -0,0 +1,78 @@
1
+ /*
2
+ * If not stated otherwise in this file or this component's LICENSE file the
3
+ * following copyright and licenses apply:
4
+ *
5
+ * Copyright 2023 Comcast Cable Communications Management, LLC.
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the License);
8
+ * you may not use this file except in compliance with the License.
9
+ * You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ */
19
+
20
+ import { assertTruthy } from '../../utils.js';
21
+ import { type TextureData } from '../textures/Texture.js';
22
+
23
+ /**
24
+ * Tests if the given location is a SVG
25
+ * @param url
26
+ * @remarks
27
+ * This function is used to determine if the given image url is a SVG
28
+ * image
29
+ * @returns
30
+ */
31
+ export function isSvgImage(url: string): boolean {
32
+ return /\.(svg)$/.test(url);
33
+ }
34
+
35
+ /**
36
+ * Loads a SVG image
37
+ * @param url
38
+ * @returns
39
+ */
40
+ export const loadSvg = (
41
+ url: string,
42
+ width: number | null,
43
+ height: number | null,
44
+ sx: number | null,
45
+ sy: number | null,
46
+ sw: number | null,
47
+ sh: number | null,
48
+ ): Promise<TextureData> => {
49
+ return new Promise((resolve, reject) => {
50
+ const canvas = document.createElement('canvas');
51
+ const ctx = canvas.getContext('2d');
52
+ assertTruthy(ctx);
53
+
54
+ ctx.imageSmoothingEnabled = true;
55
+ const img = new Image();
56
+ img.onload = () => {
57
+ const x = sx ?? 0;
58
+ const y = sy ?? 0;
59
+ const w = width || img.width;
60
+ const h = height || img.height;
61
+
62
+ canvas.width = w;
63
+ canvas.height = h;
64
+ ctx.drawImage(img, 0, 0, w, h);
65
+
66
+ resolve({
67
+ data: ctx.getImageData(x, y, sw ?? w, sh ?? h),
68
+ premultiplyAlpha: false,
69
+ });
70
+ };
71
+
72
+ img.onerror = (err) => {
73
+ reject(err);
74
+ };
75
+
76
+ img.src = url;
77
+ });
78
+ };
@@ -131,7 +131,10 @@ export class CanvasCoreTexture extends CoreContextTexture {
131
131
  if (ctx) ctx.putImageData(data, 0, 0);
132
132
  this.image = canvas;
133
133
  return { width: data.width, height: data.height };
134
- } else if (data instanceof ImageBitmap) {
134
+ } else if (
135
+ typeof ImageBitmap !== 'undefined' &&
136
+ data instanceof ImageBitmap
137
+ ) {
135
138
  this.image = data;
136
139
  return { width: data.width, height: data.height };
137
140
  }
@@ -134,7 +134,8 @@ export class WebGlCoreCtxTexture extends CoreContextTexture {
134
134
  // If textureData is null, the texture is empty (0, 0) and we don't need to
135
135
  // upload any data to the GPU.
136
136
  if (
137
- textureData.data instanceof ImageBitmap ||
137
+ (typeof ImageBitmap !== 'undefined' &&
138
+ textureData.data instanceof ImageBitmap) ||
138
139
  textureData.data instanceof ImageData ||
139
140
  // not using typeof HTMLImageElement due to web worker
140
141
  isHTMLImageElement(textureData.data)
@@ -254,13 +254,18 @@ export class WebGlCoreRenderer extends CoreRenderer {
254
254
 
255
255
  /**
256
256
  * If the shader props contain any automatic properties, update it with the
257
- * current dimensions that will be used to render the quad.
257
+ * current dimensions and or alpha that will be used to render the quad.
258
258
  */
259
- if (shaderProps && hasOwn(shaderProps, '$dimensions')) {
260
- const dimensions = shaderProps.$dimensions as Dimensions;
261
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
262
- dimensions.width = width;
263
- dimensions.height = height;
259
+ if (shaderProps !== null) {
260
+ if (hasOwn(shaderProps, '$dimensions')) {
261
+ const dimensions = shaderProps.$dimensions as Dimensions;
262
+ dimensions.width = width;
263
+ dimensions.height = height;
264
+ }
265
+
266
+ if (hasOwn(shaderProps, '$alpha')) {
267
+ shaderProps.$alpha = alpha;
268
+ }
264
269
  }
265
270
 
266
271
  texture = texture ?? this.defaultTexture;
@@ -172,6 +172,7 @@ export class DynamicShader extends WebGlCoreShader {
172
172
  propsB: Required<DynamicShaderProps>,
173
173
  ): boolean {
174
174
  if (
175
+ propsA.$alpha !== propsB.$alpha ||
175
176
  propsA.$dimensions.width !== propsB.$dimensions.width ||
176
177
  propsA.$dimensions.height !== propsB.$dimensions.height ||
177
178
  propsA.effects.length !== propsB.effects.length
@@ -19,7 +19,8 @@
19
19
 
20
20
  import type { TrFontFace } from './font-face-types/TrFontFace.js';
21
21
  import type { TextRendererMap, TrFontProps } from './renderers/TextRenderer.js';
22
- import memize from 'memize';
22
+
23
+ const fontCache = new Map<string, TrFontFace>();
23
24
 
24
25
  const weightConversions: { [key: string]: number } = {
25
26
  normal: 400,
@@ -36,7 +37,7 @@ const fontWeightToNumber = (weight: string | number): number => {
36
37
  return weightConversions[weight] || 400;
37
38
  };
38
39
 
39
- function rawResolveFontToUse(
40
+ function resolveFontToUse(
40
41
  familyMapsByPriority: FontFamilyMap[],
41
42
  family: string,
42
43
  weightIn: string | number,
@@ -119,7 +120,6 @@ function rawResolveFontToUse(
119
120
 
120
121
  return;
121
122
  }
122
- const resolveFontToUse = memize(rawResolveFontToUse);
123
123
 
124
124
  /**
125
125
  * Structure mapping font family names to a set of font faces.
@@ -159,12 +159,23 @@ export class TrFontManager {
159
159
  props: TrFontProps,
160
160
  ): TrFontFace | undefined {
161
161
  const { fontFamily, fontWeight, fontStyle, fontStretch } = props;
162
- return resolveFontToUse(
162
+ const fontCacheString = `${fontFamily}${fontStyle}${fontWeight}${fontStretch}`;
163
+
164
+ if (fontCache.has(fontCacheString) === true) {
165
+ return fontCache.get(fontCacheString);
166
+ }
167
+
168
+ const resolvedFont = resolveFontToUse(
163
169
  familyMapsByPriority,
164
170
  fontFamily,
165
171
  fontWeight,
166
172
  fontStyle,
167
173
  fontStretch,
168
174
  );
175
+ if (resolvedFont !== undefined) {
176
+ fontCache.set(fontCacheString, resolvedFont);
177
+ }
178
+
179
+ return resolvedFont;
169
180
  }
170
181
  }
@@ -67,14 +67,21 @@ export class WebTrFontFace extends TrFontFace {
67
67
  cssDescriptors,
68
68
  );
69
69
 
70
- fontFace
71
- .load()
72
- .then(() => {
73
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
74
- (this.loaded as boolean) = true;
75
- this.emit('loaded');
76
- })
77
- .catch(console.error);
70
+ if (fontUrlWithoutParentheses.length > 0) {
71
+ fontFace
72
+ .load()
73
+ .then(() => {
74
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
75
+ (this.loaded as boolean) = true;
76
+ this.emit('loaded');
77
+ })
78
+ .catch(console.error);
79
+ } else {
80
+ // Default font
81
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
82
+ (this.loaded as boolean) = true;
83
+ this.emit('loaded');
84
+ }
78
85
 
79
86
  this.fontFace = fontFace;
80
87
  this.fontUrl = fontUrl;
@@ -90,6 +90,8 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
90
90
  private fontFamilies: FontFamilyMap = {};
91
91
  private fontFamilyArray: FontFamilyMap[] = [this.fontFamilies];
92
92
 
93
+ public type: 'canvas' | 'sdf' = 'canvas';
94
+
93
95
  constructor(stage: Stage) {
94
96
  super(stage);
95
97
  if (typeof OffscreenCanvas !== 'undefined') {
@@ -142,6 +142,8 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
142
142
  private sdfShader: SdfShader;
143
143
  private rendererBounds: Bound;
144
144
 
145
+ public type: 'canvas' | 'sdf' = 'sdf';
146
+
145
147
  constructor(stage: Stage) {
146
148
  super(stage);
147
149
  this.sdfShader = this.stage.shManager.loadShader('SdfShader').shader;
@@ -422,6 +422,7 @@ export abstract class TextRenderer<
422
422
  StateT extends TextRendererState = TextRendererState,
423
423
  > {
424
424
  readonly set: Readonly<TrPropSetters<StateT>>;
425
+ abstract type: 'canvas' | 'sdf';
425
426
 
426
427
  constructor(protected stage: Stage) {
427
428
  const propSetters = {