@lightningjs/renderer 3.0.0-beta3 → 3.0.0-beta5

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 (69) hide show
  1. package/dist/src/core/CoreNode.d.ts +3 -2
  2. package/dist/src/core/CoreNode.js +14 -8
  3. package/dist/src/core/CoreNode.js.map +1 -1
  4. package/dist/src/core/CoreTextNode.d.ts +2 -0
  5. package/dist/src/core/CoreTextNode.js +8 -0
  6. package/dist/src/core/CoreTextNode.js.map +1 -1
  7. package/dist/src/core/CoreTextureManager.d.ts +2 -0
  8. package/dist/src/core/CoreTextureManager.js +7 -5
  9. package/dist/src/core/CoreTextureManager.js.map +1 -1
  10. package/dist/src/core/Stage.d.ts +5 -0
  11. package/dist/src/core/Stage.js +10 -5
  12. package/dist/src/core/Stage.js.map +1 -1
  13. package/dist/src/core/lib/validateImageBitmap.d.ts +2 -1
  14. package/dist/src/core/lib/validateImageBitmap.js +4 -4
  15. package/dist/src/core/lib/validateImageBitmap.js.map +1 -1
  16. package/dist/src/core/platforms/Platform.d.ts +37 -0
  17. package/dist/src/core/platforms/Platform.js +22 -0
  18. package/dist/src/core/platforms/Platform.js.map +1 -0
  19. package/dist/src/core/platforms/web/WebPlatform.d.ts +9 -0
  20. package/dist/src/core/platforms/web/WebPlatform.js +58 -0
  21. package/dist/src/core/platforms/web/WebPlatform.js.map +1 -0
  22. package/dist/src/core/renderers/CoreRenderer.d.ts +3 -1
  23. package/dist/src/core/renderers/CoreRenderer.js.map +1 -1
  24. package/dist/src/core/renderers/canvas/CanvasRenderer.js.map +1 -1
  25. package/dist/src/core/renderers/webgl/WebGlRenderer.d.ts +3 -1
  26. package/dist/src/core/renderers/webgl/WebGlRenderer.js +86 -60
  27. package/dist/src/core/renderers/webgl/WebGlRenderer.js.map +1 -1
  28. package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.js +5 -0
  29. package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.js.map +1 -1
  30. package/dist/src/core/text-rendering/renderers/LightningTextTextureRenderer.d.ts +1 -1
  31. package/dist/src/core/text-rendering/renderers/LightningTextTextureRenderer.js +50 -2
  32. package/dist/src/core/text-rendering/renderers/LightningTextTextureRenderer.js.map +1 -1
  33. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js +6 -2
  34. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js.map +1 -1
  35. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.d.ts +1 -1
  36. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.js +66 -8
  37. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.js.map +1 -1
  38. package/dist/src/core/text-rendering/renderers/TextRenderer.d.ts +13 -0
  39. package/dist/src/core/text-rendering/renderers/TextRenderer.js +3 -0
  40. package/dist/src/core/text-rendering/renderers/TextRenderer.js.map +1 -1
  41. package/dist/src/core/textures/ImageTexture.d.ts +1 -0
  42. package/dist/src/core/textures/ImageTexture.js +5 -3
  43. package/dist/src/core/textures/ImageTexture.js.map +1 -1
  44. package/dist/src/core/textures/Texture.d.ts +9 -2
  45. package/dist/src/core/textures/Texture.js +18 -6
  46. package/dist/src/core/textures/Texture.js.map +1 -1
  47. package/dist/src/main-api/Renderer.d.ts +12 -0
  48. package/dist/src/main-api/Renderer.js +14 -2
  49. package/dist/src/main-api/Renderer.js.map +1 -1
  50. package/dist/tsconfig.dist.tsbuildinfo +1 -1
  51. package/package.json +1 -1
  52. package/src/core/CoreNode.ts +18 -10
  53. package/src/core/CoreTextNode.ts +10 -0
  54. package/src/core/CoreTextureManager.ts +9 -5
  55. package/src/core/Stage.ts +20 -4
  56. package/src/core/lib/validateImageBitmap.ts +17 -6
  57. package/src/core/platforms/Platform.ts +77 -0
  58. package/src/core/platforms/web/WebPlatform.ts +84 -0
  59. package/src/core/renderers/CoreRenderer.ts +3 -1
  60. package/src/core/renderers/canvas/CanvasRenderer.ts +1 -1
  61. package/src/core/renderers/webgl/WebGlRenderer.ts +105 -75
  62. package/src/core/text-rendering/renderers/CanvasTextRenderer.ts +5 -0
  63. package/src/core/text-rendering/renderers/LightningTextTextureRenderer.ts +51 -3
  64. package/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.ts +6 -0
  65. package/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.ts +96 -7
  66. package/src/core/text-rendering/renderers/TextRenderer.ts +17 -0
  67. package/src/core/textures/ImageTexture.ts +19 -8
  68. package/src/core/textures/Texture.ts +28 -7
  69. package/src/main-api/Renderer.ts +28 -2
@@ -55,6 +55,7 @@ export function layoutText(
55
55
  forceFullLayoutCalc: TextRendererState['forceFullLayoutCalc'],
56
56
  scrollable: TrProps['scrollable'],
57
57
  overflowSuffix: TrProps['overflowSuffix'],
58
+ wordBreak: TrProps['wordBreak'],
58
59
  maxLines: TrProps['maxLines'],
59
60
  ): {
60
61
  bufferNumFloats: number;
@@ -121,6 +122,14 @@ export function layoutText(
121
122
  xStart: -1,
122
123
  };
123
124
 
125
+ let previousWord: {
126
+ bufferOffset: number;
127
+ xStart: number;
128
+ } = {
129
+ bufferOffset: -1,
130
+ xStart: -1,
131
+ };
132
+
124
133
  const shaper = trFontFace.shaper;
125
134
 
126
135
  const shaperProps: FontShaperProps = {
@@ -169,9 +178,6 @@ export function layoutText(
169
178
  scrollable ||
170
179
  curY + vertexLineHeight + trFontFace.maxCharHeight <=
171
180
  vertexTruncateHeight);
172
- const lineVertexW = nextLineWillFit
173
- ? vertexW
174
- : vertexW - overflowSuffVertexWidth;
175
181
  /**
176
182
  * Vertex X position to the beginning of the last word boundary. This becomes -1 when we start traversing a word.
177
183
  */
@@ -205,6 +211,8 @@ export function layoutText(
205
211
  if (lastWord.codepointIndex !== -1) {
206
212
  lastWord.codepointIndex = -1;
207
213
  xStartLastWordBoundary = curX;
214
+ previousWord.bufferOffset = lastWord.bufferOffset;
215
+ previousWord.xStart = lastWord.xStart;
208
216
  }
209
217
  } else if (lastWord.codepointIndex === -1) {
210
218
  lastWord.codepointIndex = glyph.cluster;
@@ -215,12 +223,41 @@ export function layoutText(
215
223
  if (glyph.mapped) {
216
224
  // Mapped glyph
217
225
  const charEndX = curX + glyph.xOffset + glyph.width;
218
- // Word wrap check
226
+
219
227
  if (
228
+ wordBreak === 'break-all' &&
229
+ charEndX >= vertexW &&
230
+ nextLineWillFit
231
+ ) {
232
+ // wordBreak: break-all - the current letter is about to go of the edge
233
+ // of the container width and the next line will fit, so we break to the next line
234
+ glyphs = shaper.shapeText(
235
+ shaperProps,
236
+ new PeekableIterator(
237
+ getUnicodeCodepoints(text, glyph.cluster),
238
+ glyph.cluster,
239
+ ),
240
+ );
241
+ break;
242
+ } else if (
243
+ contain !== 'none' &&
244
+ wordBreak === 'break-all' &&
245
+ charEndX + overflowSuffVertexWidth >= vertexW &&
246
+ nextLineWillFit === false
247
+ ) {
248
+ // wordBreak: break-all - the current letter is about to go of the edge
249
+ // of the container width and the next line will not fit, so we add the overflow suffix
250
+ glyphs = shaper.shapeText(
251
+ shaperProps,
252
+ new PeekableIterator(getUnicodeCodepoints(overflowSuffix, 0), 0),
253
+ );
254
+ contain = 'none';
255
+ } else if (
256
+ // Word wrap check
220
257
  // We are containing the text
221
258
  contain !== 'none' &&
222
259
  // The current glyph reaches outside the contained width
223
- charEndX >= lineVertexW &&
260
+ charEndX >= vertexW &&
224
261
  // There is a last word that we can break to the next line
225
262
  lastWord.codepointIndex !== -1 &&
226
263
  // Prevents infinite loop when a single word is longer than the width
@@ -244,12 +281,51 @@ export function layoutText(
244
281
  shaperProps,
245
282
  new PeekableIterator(getUnicodeCodepoints(overflowSuffix, 0), 0),
246
283
  );
247
- curX = lastWord.xStart;
248
- bufferOffset = lastWord.bufferOffset;
284
+ if (lastWord.xStart + overflowSuffVertexWidth < vertexW) {
285
+ curX = lastWord.xStart;
286
+ bufferOffset = lastWord.bufferOffset;
287
+ } else if (
288
+ previousWord.xStart + overflowSuffVertexWidth <
289
+ vertexW
290
+ ) {
291
+ curX = previousWord.xStart;
292
+ bufferOffset = previousWord.bufferOffset;
293
+ }
294
+
249
295
  // HACK: For the rest of the line when inserting the overflow suffix,
250
296
  // set contain = 'none' to prevent an infinite loop.
251
297
  contain = 'none';
252
298
  }
299
+ } else if (
300
+ wordBreak === 'break-word' &&
301
+ charEndX >= vertexW &&
302
+ lastWord.xStart === 0 &&
303
+ nextLineWillFit
304
+ ) {
305
+ // The current word which starts the line is wider than the line width
306
+ // proceed to next line
307
+ glyphs = shaper.shapeText(
308
+ shaperProps,
309
+ new PeekableIterator(
310
+ getUnicodeCodepoints(text, glyph.cluster),
311
+ glyph.cluster,
312
+ ),
313
+ );
314
+ break;
315
+ } else if (
316
+ contain !== 'none' &&
317
+ wordBreak === 'break-word' &&
318
+ lastWord.xStart === 0 &&
319
+ charEndX + overflowSuffVertexWidth >= vertexW &&
320
+ nextLineWillFit === false
321
+ ) {
322
+ // wordBreak: break-word - the current letter is about to go of the edge
323
+ // and the next line will not fit, so we add the overflow suffix
324
+ glyphs = shaper.shapeText(
325
+ shaperProps,
326
+ new PeekableIterator(getUnicodeCodepoints(overflowSuffix, 0), 0),
327
+ );
328
+ contain = 'none';
253
329
  } else {
254
330
  // This glyph fits, so we can add it to the buffer
255
331
  const quadX = curX + glyph.xOffset;
@@ -310,6 +386,19 @@ export function layoutText(
310
386
  // The whole line fit, so we can break to the next line
311
387
  break;
312
388
  } else {
389
+ // Check if the overflow suffix will fit
390
+ if (curX + overflowSuffVertexWidth >= vertexW) {
391
+ if (lastWord.xStart + overflowSuffVertexWidth < vertexW) {
392
+ curX = lastWord.xStart;
393
+ bufferOffset = lastWord.bufferOffset;
394
+ } else if (
395
+ previousWord.xStart + overflowSuffVertexWidth <
396
+ vertexW
397
+ ) {
398
+ curX = previousWord.xStart;
399
+ bufferOffset = previousWord.bufferOffset;
400
+ }
401
+ }
313
402
  // The whole line won't fit, so we need to add the overflow suffix
314
403
  glyphs = shaper.shapeText(
315
404
  shaperProps,
@@ -318,6 +318,20 @@ export interface TrProps extends TrFontProps {
318
318
  */
319
319
  overflowSuffix: string;
320
320
 
321
+ /**
322
+ * Word Break for text
323
+ *
324
+ * @remarks
325
+ * This property sets how words should break when reaching the end of a line.
326
+ *
327
+ * - `'normal'`: Use the default line break rule.
328
+ * - `'break-all'`: To prevent overflow, word breaks should happen between any two characters.
329
+ * - `'break-word'`: To prevent overflow, word breaks should happen between words. If words are too long word breaks happen between any two characters.
330
+ *
331
+ * @default "normal"
332
+ */
333
+ wordBreak: 'normal' | 'break-all' | 'break-word';
334
+
321
335
  zIndex: number;
322
336
 
323
337
  debug: Partial<TextRendererDebugProps>;
@@ -397,6 +411,9 @@ const trPropSetterDefaults: TrPropSetters = {
397
411
  overflowSuffix: (state, value) => {
398
412
  state.props.overflowSuffix = value;
399
413
  },
414
+ wordBreak: (state, value) => {
415
+ state.props.wordBreak = value;
416
+ },
400
417
  debug: (state, value) => {
401
418
  state.props.debug = value;
402
419
  },
@@ -30,6 +30,7 @@ import {
30
30
  } from '../lib/utils.js';
31
31
  import { isSvgImage, loadSvg } from '../lib/textureSvg.js';
32
32
  import { fetchJson } from '../text-rendering/font-face-types/utils.js';
33
+ import type { Platform } from '../platforms/Platform.js';
33
34
 
34
35
  /**
35
36
  * Properties of the {@link ImageTexture}
@@ -124,12 +125,15 @@ export interface ImageTextureProps {
124
125
  * {@link ImageTextureProps.premultiplyAlpha} prop to `false`.
125
126
  */
126
127
  export class ImageTexture extends Texture {
127
- public props: Required<ImageTextureProps>;
128
+ private platform: Platform;
128
129
 
130
+ public props: Required<ImageTextureProps>;
129
131
  public override type: TextureType = TextureType.image;
130
132
 
131
133
  constructor(txManager: CoreTextureManager, props: ImageTextureProps) {
132
134
  super(txManager);
135
+
136
+ this.platform = txManager.platform;
133
137
  this.props = ImageTexture.resolveDefaults(props);
134
138
  }
135
139
 
@@ -180,23 +184,30 @@ export class ImageTexture extends Texture {
180
184
 
181
185
  if (imageBitmapSupported.full === true && sw !== null && sh !== null) {
182
186
  // createImageBitmap with crop
183
- const bitmap = await createImageBitmap(blob, sx || 0, sy || 0, sw, sh, {
184
- premultiplyAlpha: hasAlphaChannel ? 'premultiply' : 'none',
185
- colorSpaceConversion: 'none',
186
- imageOrientation: 'none',
187
- });
187
+ const bitmap = await this.platform.createImageBitmap(
188
+ blob,
189
+ sx || 0,
190
+ sy || 0,
191
+ sw,
192
+ sh,
193
+ {
194
+ premultiplyAlpha: hasAlphaChannel ? 'premultiply' : 'none',
195
+ colorSpaceConversion: 'none',
196
+ imageOrientation: 'none',
197
+ },
198
+ );
188
199
  return { data: bitmap, premultiplyAlpha: hasAlphaChannel };
189
200
  } else if (imageBitmapSupported.basic === true) {
190
201
  // basic createImageBitmap without options or crop
191
202
  // this is supported for Chrome v50 to v52/54 that doesn't support options
192
203
  return {
193
- data: await createImageBitmap(blob),
204
+ data: await this.platform.createImageBitmap(blob),
194
205
  premultiplyAlpha: hasAlphaChannel,
195
206
  };
196
207
  }
197
208
 
198
209
  // default createImageBitmap without crop but with options
199
- const bitmap = await createImageBitmap(blob, {
210
+ const bitmap = await this.platform.createImageBitmap(blob, {
200
211
  premultiplyAlpha: hasAlphaChannel ? 'premultiply' : 'none',
201
212
  colorSpaceConversion: 'none',
202
213
  imageOrientation: 'none',
@@ -22,6 +22,7 @@ import type { SubTextureProps } from './SubTexture.js';
22
22
  import type { Dimensions } from '../../common/CommonTypes.js';
23
23
  import { EventEmitter } from '../../common/EventEmitter.js';
24
24
  import type { CoreContextTexture } from '../renderers/CoreContextTexture.js';
25
+ import type { Bound } from '../lib/utils.js';
25
26
 
26
27
  /**
27
28
  * Event handler for when a Texture is freed
@@ -100,6 +101,10 @@ export interface TextureData {
100
101
  */
101
102
  premultiplyAlpha?: boolean | null;
102
103
  }
104
+ /**
105
+ * TextureCoords generally numbers between 0 - 1
106
+ */
107
+ export type TextureCoords = Bound;
103
108
 
104
109
  export type TextureState =
105
110
  | 'initial' // Before anything is loaded
@@ -136,9 +141,8 @@ export abstract class Texture extends EventEmitter {
136
141
  * Until the texture data is loaded for the first time the value will be
137
142
  * `null`.
138
143
  */
139
- readonly dimensions: Readonly<Dimensions> | null = null;
140
-
141
- readonly error: Error | null = null;
144
+ private _dimensions: Dimensions | null = null;
145
+ private _error: Error | null = null;
142
146
 
143
147
  // aggregate state
144
148
  public state: TextureState = 'initial';
@@ -159,6 +163,14 @@ export abstract class Texture extends EventEmitter {
159
163
  super();
160
164
  }
161
165
 
166
+ get dimensions(): Dimensions | null {
167
+ return this._dimensions;
168
+ }
169
+
170
+ get error(): Error | null {
171
+ return this._error;
172
+ }
173
+
162
174
  /**
163
175
  * Add/remove an owner to/from the Texture based on its renderability.
164
176
  *
@@ -259,11 +271,20 @@ export abstract class Texture extends EventEmitter {
259
271
 
260
272
  let payload: Error | Dimensions | null = null;
261
273
  if (state === 'loaded') {
262
- (this.dimensions as Dimensions) = errorOrDimensions as Dimensions;
263
- payload = this.dimensions;
274
+ if (
275
+ errorOrDimensions !== undefined &&
276
+ 'width' in errorOrDimensions === true &&
277
+ 'height' in errorOrDimensions === true &&
278
+ errorOrDimensions.width !== undefined &&
279
+ errorOrDimensions.height !== undefined
280
+ ) {
281
+ this._dimensions = errorOrDimensions;
282
+ }
283
+
284
+ payload = this._dimensions;
264
285
  } else if (state === 'failed') {
265
- (this.error as Error) = errorOrDimensions as Error;
266
- payload = this.error;
286
+ this._error = errorOrDimensions as Error;
287
+ payload = this._error;
267
288
  }
268
289
 
269
290
  // emit the new state
@@ -36,6 +36,8 @@ import type {
36
36
  OptionalShaderProps,
37
37
  ShaderMap,
38
38
  } from '../core/CoreShaderManager.js';
39
+ import { WebPlatform } from '../core/platforms/web/WebPlatform.js';
40
+ import { Platform } from '../core/platforms/Platform.js';
39
41
 
40
42
  /**
41
43
  * Configuration settings for {@link RendererMain}
@@ -272,6 +274,18 @@ export interface RendererMainSettings {
272
274
  * @defaultValue `full`
273
275
  */
274
276
  createImageBitmapSupport?: 'auto' | 'basic' | 'options' | 'full';
277
+
278
+ /**
279
+ * Provide an alternative platform abstraction layer
280
+ *
281
+ * @remarks
282
+ * By default the Lightning 3 renderer will load a webplatform, assuming it runs
283
+ * inside a web browsr. However for special cases there might be a need to provide
284
+ * an abstracted platform layer to run on non-web or non-standard JS engines
285
+ *
286
+ * @defaultValue `null`
287
+ */
288
+ platform?: typeof Platform | null;
275
289
  }
276
290
 
277
291
  /**
@@ -374,6 +388,7 @@ export class RendererMain extends EventEmitter {
374
388
  textureProcessingTimeLimit: settings.textureProcessingTimeLimit || 10,
375
389
  canvas: settings.canvas || document.createElement('canvas'),
376
390
  createImageBitmapSupport: settings.createImageBitmapSupport || 'full',
391
+ platform: settings.platform || null,
377
392
  };
378
393
  this.settings = resolvedSettings;
379
394
 
@@ -386,6 +401,18 @@ export class RendererMain extends EventEmitter {
386
401
  canvas,
387
402
  } = resolvedSettings;
388
403
 
404
+ let platform;
405
+ if (
406
+ settings.platform !== undefined &&
407
+ settings.platform !== null &&
408
+ settings.platform.prototype instanceof Platform === true
409
+ ) {
410
+ // @ts-ignore - if Platform is a valid class, it will be used
411
+ platform = new settings.platform();
412
+ } else {
413
+ platform = new WebPlatform();
414
+ }
415
+
389
416
  const deviceLogicalWidth = appWidth * deviceLogicalPixelRatio;
390
417
  const deviceLogicalHeight = appHeight * deviceLogicalPixelRatio;
391
418
 
@@ -418,6 +445,7 @@ export class RendererMain extends EventEmitter {
418
445
  strictBounds: this.settings.strictBounds,
419
446
  textureProcessingTimeLimit: this.settings.textureProcessingTimeLimit,
420
447
  createImageBitmapSupport: this.settings.createImageBitmapSupport,
448
+ platform,
421
449
  });
422
450
 
423
451
  // Extract the root node
@@ -469,8 +497,6 @@ export class RendererMain extends EventEmitter {
469
497
  return this.inspector.createNode(node) as unknown as INode<ShNode>;
470
498
  }
471
499
 
472
- // FIXME onDestroy event? node.once('beforeDestroy'
473
- // FIXME onCreate event?
474
500
  return node as unknown as INode<ShNode>;
475
501
  }
476
502