@lightningjs/renderer 0.7.0 → 0.7.2

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 (83) hide show
  1. package/dist/exports/main-api.d.ts +1 -0
  2. package/dist/src/core/CoreNode.d.ts +4 -7
  3. package/dist/src/core/CoreNode.js +29 -25
  4. package/dist/src/core/CoreNode.js.map +1 -1
  5. package/dist/src/core/CoreShaderManager.d.ts +13 -6
  6. package/dist/src/core/CoreShaderManager.js +6 -6
  7. package/dist/src/core/CoreShaderManager.js.map +1 -1
  8. package/dist/src/core/CoreTextNode.d.ts +2 -2
  9. package/dist/src/core/CoreTextNode.js +1 -1
  10. package/dist/src/core/CoreTextNode.js.map +1 -1
  11. package/dist/src/core/Stage.js +1 -1
  12. package/dist/src/core/Stage.js.map +1 -1
  13. package/dist/src/core/animations/CoreAnimation.d.ts +1 -0
  14. package/dist/src/core/animations/CoreAnimation.js +7 -0
  15. package/dist/src/core/animations/CoreAnimation.js.map +1 -1
  16. package/dist/src/core/lib/WebGlContextWrapper.d.ts +9 -0
  17. package/dist/src/core/lib/WebGlContextWrapper.js +12 -0
  18. package/dist/src/core/lib/WebGlContextWrapper.js.map +1 -1
  19. package/dist/src/core/lib/textureCompression.d.ts +16 -0
  20. package/dist/src/core/lib/textureCompression.js +129 -0
  21. package/dist/src/core/lib/textureCompression.js.map +1 -0
  22. package/dist/src/core/lib/utils.d.ts +9 -0
  23. package/dist/src/core/lib/utils.js +48 -1
  24. package/dist/src/core/lib/utils.js.map +1 -1
  25. package/dist/src/core/renderers/CoreRenderer.d.ts +2 -2
  26. package/dist/src/core/renderers/webgl/WebGlCoreCtxTexture.js +12 -0
  27. package/dist/src/core/renderers/webgl/WebGlCoreCtxTexture.js.map +1 -1
  28. package/dist/src/core/renderers/webgl/WebGlCoreRenderOp.d.ts +3 -3
  29. package/dist/src/core/renderers/webgl/WebGlCoreRenderOp.js +1 -1
  30. package/dist/src/core/renderers/webgl/WebGlCoreRenderOp.js.map +1 -1
  31. package/dist/src/core/renderers/webgl/WebGlCoreRenderer.js +5 -0
  32. package/dist/src/core/renderers/webgl/WebGlCoreRenderer.js.map +1 -1
  33. package/dist/src/core/text-rendering/font-face-types/SdfTrFontFace/SdfTrFontFace.d.ts +4 -0
  34. package/dist/src/core/text-rendering/font-face-types/SdfTrFontFace/SdfTrFontFace.js +11 -0
  35. package/dist/src/core/text-rendering/font-face-types/SdfTrFontFace/SdfTrFontFace.js.map +1 -1
  36. package/dist/src/core/text-rendering/font-face-types/SdfTrFontFace/internal/SdfFontShaper.js.map +1 -1
  37. package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.d.ts +2 -2
  38. package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.js +10 -1
  39. package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.js.map +1 -1
  40. package/dist/src/core/text-rendering/renderers/LightningTextTextureRenderer.js +21 -7
  41. package/dist/src/core/text-rendering/renderers/LightningTextTextureRenderer.js.map +1 -1
  42. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.d.ts +6 -10
  43. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js +73 -46
  44. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js.map +1 -1
  45. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.js +2 -1
  46. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.js.map +1 -1
  47. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/setRenderWindow.js +1 -1
  48. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/setRenderWindow.js.map +1 -1
  49. package/dist/src/core/text-rendering/renderers/TextRenderer.d.ts +2 -2
  50. package/dist/src/core/textures/ImageTexture.js +5 -0
  51. package/dist/src/core/textures/ImageTexture.js.map +1 -1
  52. package/dist/src/core/textures/Texture.d.ts +28 -1
  53. package/dist/src/core/textures/Texture.js.map +1 -1
  54. package/dist/src/main-api/Inspector.d.ts +15 -0
  55. package/dist/src/main-api/Inspector.js +216 -0
  56. package/dist/src/main-api/Inspector.js.map +1 -0
  57. package/dist/tsconfig.dist.tsbuildinfo +1 -1
  58. package/exports/main-api.ts +9 -0
  59. package/package.json +1 -1
  60. package/src/core/CoreNode.ts +30 -29
  61. package/src/core/CoreShaderManager.ts +39 -6
  62. package/src/core/CoreTextNode.ts +2 -2
  63. package/src/core/Stage.ts +1 -1
  64. package/src/core/animations/CoreAnimation.ts +8 -0
  65. package/src/core/lib/WebGlContextWrapper.ts +27 -0
  66. package/src/core/lib/textureCompression.ts +152 -0
  67. package/src/core/lib/utils.ts +68 -1
  68. package/src/core/renderers/CoreRenderer.ts +2 -2
  69. package/src/core/renderers/webgl/WebGlCoreCtxTexture.ts +20 -0
  70. package/src/core/renderers/webgl/WebGlCoreRenderOp.ts +3 -3
  71. package/src/core/renderers/webgl/WebGlCoreRenderer.ts +7 -1
  72. package/src/core/text-rendering/font-face-types/SdfTrFontFace/SdfTrFontFace.ts +15 -1
  73. package/src/core/text-rendering/font-face-types/SdfTrFontFace/internal/SdfFontShaper.test.ts +12 -4
  74. package/src/core/text-rendering/font-face-types/SdfTrFontFace/internal/SdfFontShaper.ts +4 -1
  75. package/src/core/text-rendering/renderers/CanvasTextRenderer.ts +16 -2
  76. package/src/core/text-rendering/renderers/LightningTextTextureRenderer.ts +42 -8
  77. package/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.ts +96 -60
  78. package/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.ts +2 -1
  79. package/src/core/text-rendering/renderers/SdfTextRenderer/internal/measureText.test.ts +4 -1
  80. package/src/core/text-rendering/renderers/SdfTextRenderer/internal/setRenderWindow.ts +1 -1
  81. package/src/core/text-rendering/renderers/TextRenderer.ts +2 -2
  82. package/src/core/textures/ImageTexture.ts +9 -0
  83. package/src/core/textures/Texture.ts +33 -1
@@ -34,7 +34,10 @@ sdfData.chars.forEach((glyph) => {
34
34
 
35
35
  describe('SdfFontShaper', () => {
36
36
  it('should be able to shape text.', () => {
37
- const shaper = new SdfFontShaper(sdfData as unknown as SdfFontData, glyphMap);
37
+ const shaper = new SdfFontShaper(
38
+ sdfData as unknown as SdfFontData,
39
+ glyphMap,
40
+ );
38
41
  const peekableCodepoints = new PeekableIterator(
39
42
  getUnicodeCodepoints('Hi!'),
40
43
  );
@@ -88,7 +91,10 @@ describe('SdfFontShaper', () => {
88
91
  });
89
92
 
90
93
  it('should be able to shape text that we know have kerning pairs.', () => {
91
- const shaper = new SdfFontShaper(sdfData as unknown as SdfFontData, glyphMap);
94
+ const shaper = new SdfFontShaper(
95
+ sdfData as unknown as SdfFontData,
96
+ glyphMap,
97
+ );
92
98
  const peekableCodepoints = new PeekableIterator(
93
99
  getUnicodeCodepoints('WeVo'),
94
100
  );
@@ -130,8 +136,10 @@ describe('SdfFontShaper', () => {
130
136
  });
131
137
 
132
138
  it('should be able to shape text with letterSpacing.', () => {
133
-
134
- const shaper = new SdfFontShaper(sdfData as unknown as SdfFontData, glyphMap);
139
+ const shaper = new SdfFontShaper(
140
+ sdfData as unknown as SdfFontData,
141
+ glyphMap,
142
+ );
135
143
  const peekableCodepoints = new PeekableIterator(
136
144
  getUnicodeCodepoints('We!'),
137
145
  );
@@ -39,7 +39,10 @@ export class SdfFontShaper extends FontShaper {
39
39
  private readonly glyphMap: Map<number, SdfFontData['chars'][0]>;
40
40
  private readonly kernings: KerningTable;
41
41
 
42
- constructor(data: SdfFontData, glyphMap: Map<number, SdfFontData['chars'][0]>) {
42
+ constructor(
43
+ data: SdfFontData,
44
+ glyphMap: Map<number, SdfFontData['chars'][0]>,
45
+ ) {
43
46
  super();
44
47
  this.data = data;
45
48
  this.glyphMap = glyphMap;
@@ -30,6 +30,7 @@ import {
30
30
  getNormalizedAlphaComponent,
31
31
  type BoundWithValid,
32
32
  createBound,
33
+ type RectWithValid,
33
34
  } from '../../lib/utils.js';
34
35
  import type { ImageTexture } from '../../textures/ImageTexture.js';
35
36
  import type { TrFontFace } from '../font-face-types/TrFontFace.js';
@@ -325,6 +326,18 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
325
326
  }
326
327
 
327
328
  if (!state.renderInfo) {
329
+ const maxLines = state.props.maxLines;
330
+ const containedMaxLines =
331
+ state.props.contain === 'both'
332
+ ? Math.floor(
333
+ (state.props.height - state.props.offsetY) /
334
+ state.props.lineHeight,
335
+ )
336
+ : 0;
337
+ const calcMaxLines =
338
+ containedMaxLines > 0 && maxLines > 0
339
+ ? Math.min(containedMaxLines, maxLines)
340
+ : Math.max(containedMaxLines, maxLines);
328
341
  state.lightning2TextRenderer.settings = {
329
342
  text: state.props.text,
330
343
  textAlign: state.props.textAlign,
@@ -342,7 +355,7 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
342
355
  state.props.contain === 'none' ? undefined : state.props.width,
343
356
  letterSpacing: state.props.letterSpacing,
344
357
  lineHeight: state.props.lineHeight,
345
- maxLines: state.props.maxLines,
358
+ maxLines: calcMaxLines,
346
359
  textBaseline: state.props.textBaseline,
347
360
  verticalAlign: state.props.verticalAlign,
348
361
  overflowSuffix: state.props.overflowSuffix,
@@ -524,7 +537,7 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
524
537
  override renderQuads(
525
538
  state: CanvasTextRendererState,
526
539
  transform: Matrix3d,
527
- clippingRect: Rect | null,
540
+ clippingRect: RectWithValid,
528
541
  alpha: number,
529
542
  ): void {
530
543
  const { stage } = this;
@@ -700,6 +713,7 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
700
713
  */
701
714
  private invalidateLayoutCache(state: CanvasTextRendererState): void {
702
715
  state.renderInfo = undefined;
716
+ state.visibleWindow.valid = false;
703
717
  this.setStatus(state, 'loading');
704
718
  this.scheduleUpdateState(state);
705
719
  }
@@ -125,6 +125,32 @@ export interface RenderInfo {
125
125
  textIndent: number;
126
126
  }
127
127
 
128
+ /**
129
+ * Calculate height for the canvas
130
+ *
131
+ * @param textBaseline
132
+ * @param fontSize
133
+ * @param lineHeight
134
+ * @param numLines
135
+ * @param offsetY
136
+ * @returns
137
+ */
138
+ function calcHeight(
139
+ textBaseline: TextBaseline,
140
+ fontSize: number,
141
+ lineHeight: number,
142
+ numLines: number,
143
+ offsetY: number | null,
144
+ ) {
145
+ const baselineOffset = textBaseline !== 'bottom' ? 0.5 * fontSize : 0;
146
+ return (
147
+ lineHeight * (numLines - 1) +
148
+ baselineOffset +
149
+ Math.max(lineHeight, fontSize) +
150
+ (offsetY || 0)
151
+ );
152
+ }
153
+
128
154
  export class LightningTextTextureRenderer {
129
155
  private _canvas: OffscreenCanvas | HTMLCanvasElement;
130
156
  private _context:
@@ -349,13 +375,13 @@ export class LightningTextTextureRenderer {
349
375
  if (h) {
350
376
  height = h;
351
377
  } else {
352
- const baselineOffset =
353
- this._settings.textBaseline != 'bottom' ? 0.5 * fontSize : 0;
354
- height =
355
- lineHeight * (lines.length - 1) +
356
- baselineOffset +
357
- Math.max(lineHeight, fontSize) +
358
- (offsetY || 0);
378
+ height = calcHeight(
379
+ this._settings.textBaseline,
380
+ fontSize,
381
+ lineHeight,
382
+ lines.length,
383
+ offsetY,
384
+ );
359
385
  }
360
386
 
361
387
  if (offsetY === null) {
@@ -414,7 +440,15 @@ export class LightningTextTextureRenderer {
414
440
  const lines = linesOverride?.lines || renderInfo.lines;
415
441
  const lineWidths = linesOverride?.lineWidths || renderInfo.lineWidths;
416
442
  const height = linesOverride
417
- ? linesOverride.lines.length * renderInfo.lineHeight
443
+ ? calcHeight(
444
+ this._settings.textBaseline,
445
+ renderInfo.fontSize,
446
+ renderInfo.lineHeight,
447
+ linesOverride.lines.length,
448
+ this._settings.offsetY === null
449
+ ? null
450
+ : this._settings.offsetY * precision,
451
+ )
418
452
  : renderInfo.height;
419
453
 
420
454
  // Add extra margin to prevent issue with clipped text when scaling.
@@ -18,13 +18,15 @@
18
18
  */
19
19
 
20
20
  import {
21
- intersectBound,
22
21
  type Bound,
23
22
  type Rect,
24
23
  createBound,
25
24
  type BoundWithValid,
26
25
  intersectRect,
27
- isBoundPositive,
26
+ type RectWithValid,
27
+ copyRect,
28
+ boundsOverlap,
29
+ convertBoundToRect,
28
30
  } from '../../../lib/utils.js';
29
31
  import {
30
32
  TextRenderer,
@@ -84,14 +86,14 @@ export interface SdfTextRendererState extends TextRendererState {
84
86
 
85
87
  renderWindow: SdfRenderWindow;
86
88
 
87
- visibleWindow: BoundWithValid;
89
+ elementBounds: BoundWithValid;
90
+
91
+ clippingRect: RectWithValid;
88
92
 
89
93
  bufferNumFloats: number;
90
94
 
91
95
  bufferNumQuads: number;
92
96
 
93
- // texCoordBuffer: Float32Array | undefined;
94
-
95
97
  vertexBuffer: Float32Array | undefined;
96
98
 
97
99
  webGlBuffers: BufferCollection | null;
@@ -104,13 +106,14 @@ export interface SdfTextRendererState extends TextRendererState {
104
106
  }
105
107
 
106
108
  /**
107
- * Ephemeral bounds object used for intersection calculations
108
- *
109
- * @remarks
110
- * Used to avoid creating a new object every time we need to intersect
111
- * element bounds.
109
+ * Ephemeral rect object used for calculations
112
110
  */
113
- const tmpElementBounds = createBound(0, 0, 0, 0);
111
+ const tmpRect: Rect = {
112
+ x: 0,
113
+ y: 0,
114
+ width: 0,
115
+ height: 0,
116
+ };
114
117
 
115
118
  /**
116
119
  * Singleton class for rendering text using signed distance fields.
@@ -178,11 +181,31 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
178
181
  },
179
182
  x: (state, value) => {
180
183
  state.props.x = value;
181
- this.invalidateVisibleWindowCache(state);
184
+ if (state.elementBounds.valid) {
185
+ this.setElementBoundsX(state);
186
+ // Only schedule an update if the text is not already rendered
187
+ // (renderWindow is invalid) and the element possibly overlaps the screen
188
+ // This is to avoid unnecessary updates when we know text is off-screen
189
+ if (
190
+ !state.renderWindow.valid &&
191
+ boundsOverlap(state.elementBounds, this.rendererBounds)
192
+ ) {
193
+ this.scheduleUpdateState(state);
194
+ }
195
+ }
182
196
  },
183
197
  y: (state, value) => {
184
198
  state.props.y = value;
185
- this.invalidateVisibleWindowCache(state);
199
+ if (state.elementBounds.valid) {
200
+ this.setElementBoundsY(state);
201
+ // See x() for explanation
202
+ if (
203
+ !state.renderWindow.valid &&
204
+ boundsOverlap(state.elementBounds, this.rendererBounds)
205
+ ) {
206
+ this.scheduleUpdateState(state);
207
+ }
208
+ }
186
209
  },
187
210
  contain: (state, value) => {
188
211
  state.props.contain = value;
@@ -304,13 +327,20 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
304
327
  numLines: 0,
305
328
  valid: false,
306
329
  },
307
- visibleWindow: {
330
+ elementBounds: {
308
331
  x1: 0,
309
332
  y1: 0,
310
333
  x2: 0,
311
334
  y2: 0,
312
335
  valid: false,
313
336
  },
337
+ clippingRect: {
338
+ x: 0,
339
+ y: 0,
340
+ width: 0,
341
+ height: 0,
342
+ valid: false,
343
+ },
314
344
  bufferNumFloats: 0,
315
345
  bufferNumQuads: 0,
316
346
  vertexBuffer: undefined,
@@ -410,22 +440,11 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
410
440
  vertexBuffer = new Float32Array(neededLength * 2);
411
441
  }
412
442
 
413
- const visibleWindow = state.visibleWindow;
414
- // If the visibleWindow is not valid, calculate it
415
- if (!visibleWindow.valid) {
416
- // Figure out whats actually in the bounds of the renderer/canvas (visibleWindow)
417
- const elementBounds = createBound(
418
- x,
419
- y,
420
- contain !== 'none' ? x + width : Infinity,
421
- contain === 'both' ? y + height : Infinity,
422
- tmpElementBounds, // Prevent allocation by using this temp object
423
- );
424
- /**
425
- * Area that is visible on the screen.
426
- */
427
- intersectBound(this.rendererBounds, elementBounds, state.visibleWindow);
428
- visibleWindow.valid = true;
443
+ const elementBounds = state.elementBounds;
444
+ if (!elementBounds.valid) {
445
+ this.setElementBoundsX(state);
446
+ this.setElementBoundsY(state);
447
+ elementBounds.valid = true;
429
448
  }
430
449
 
431
450
  // Return early if we're still viewing inside the established render window
@@ -434,10 +453,10 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
434
453
  if (!forceFullLayoutCalc && renderWindow.valid) {
435
454
  const rwScreen = renderWindow.screen;
436
455
  if (
437
- x + rwScreen.x1 <= visibleWindow.x1 &&
438
- x + rwScreen.x2 >= visibleWindow.x2 &&
439
- y - scrollY + rwScreen.y1 <= visibleWindow.y1 &&
440
- y - scrollY + rwScreen.y2 >= visibleWindow.y2
456
+ x + rwScreen.x1 <= elementBounds.x1 &&
457
+ x + rwScreen.x2 >= elementBounds.x2 &&
458
+ y - scrollY + rwScreen.y1 <= elementBounds.y1 &&
459
+ y - scrollY + rwScreen.y2 >= elementBounds.y2
441
460
  ) {
442
461
  this.setStatus(state, 'loaded');
443
462
  return;
@@ -451,14 +470,24 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
451
470
 
452
471
  // Create a new renderWindow if needed
453
472
  if (!renderWindow.valid) {
473
+ const isPossiblyOnScreen = boundsOverlap(
474
+ elementBounds,
475
+ this.rendererBounds,
476
+ );
477
+
478
+ if (!isPossiblyOnScreen) {
479
+ // If the element is not possibly on screen, we can skip the layout and rendering completely
480
+ return;
481
+ }
482
+
454
483
  setRenderWindow(
455
484
  renderWindow,
456
485
  x,
457
486
  y,
458
487
  scrollY,
459
488
  lineHeight,
460
- visibleWindow.y2 - visibleWindow.y1,
461
- visibleWindow,
489
+ contain === 'both' ? elementBounds.y2 - elementBounds.y1 : 0,
490
+ elementBounds,
462
491
  fontSizeRatio,
463
492
  );
464
493
  // console.log('newRenderWindow', renderWindow);
@@ -531,7 +560,7 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
531
560
  override renderQuads(
532
561
  state: SdfTextRendererState,
533
562
  transform: Matrix3d,
534
- clippingRect: Rect | null,
563
+ clippingRect: Readonly<RectWithValid>,
535
564
  alpha: number,
536
565
  ): void {
537
566
  if (!state.vertexBuffer) {
@@ -553,6 +582,7 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
553
582
  vertexBuffer,
554
583
  bufferUploaded,
555
584
  trFontFace,
585
+ elementBounds,
556
586
  } = state;
557
587
 
558
588
  let { webGlBuffers } = state;
@@ -599,18 +629,21 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
599
629
  }
600
630
 
601
631
  assertTruthy(trFontFace);
602
-
603
632
  if (scrollable && contain === 'both') {
604
- const visibleWindowRect: Rect = {
605
- x: state.visibleWindow.x1,
606
- y: state.visibleWindow.y1,
607
- width: state.visibleWindow.x2 - state.visibleWindow.x1,
608
- height: state.visibleWindow.y2 - state.visibleWindow.y1,
609
- };
610
-
611
- clippingRect = clippingRect
612
- ? intersectRect(clippingRect, visibleWindowRect)
613
- : visibleWindowRect;
633
+ assertTruthy(elementBounds.valid);
634
+ const elementRect = convertBoundToRect(elementBounds, tmpRect);
635
+
636
+ if (clippingRect.valid) {
637
+ state.clippingRect.valid = true;
638
+ clippingRect = intersectRect(
639
+ clippingRect,
640
+ elementRect,
641
+ state.clippingRect,
642
+ );
643
+ } else {
644
+ state.clippingRect.valid = true;
645
+ clippingRect = copyRect(elementRect, state.clippingRect);
646
+ }
614
647
  }
615
648
 
616
649
  const renderOp = new WebGlCoreRenderOp(
@@ -707,17 +740,6 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
707
740
  ) as SdfTrFontFace | undefined;
708
741
  }
709
742
 
710
- /**
711
- * Invalidate the visible window stored in the state. This will cause a new
712
- * visible window to be calculated on the next update.
713
- *
714
- * @param state
715
- */
716
- protected invalidateVisibleWindowCache(state: SdfTextRendererState): void {
717
- state.visibleWindow.valid = false;
718
- this.scheduleUpdateState(state);
719
- }
720
-
721
743
  /**
722
744
  * Invalidate the layout cache stored in the state. This will cause the text
723
745
  * to be re-layed out on the next update.
@@ -728,12 +750,26 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
728
750
  * @param state
729
751
  */
730
752
  protected invalidateLayoutCache(state: SdfTextRendererState): void {
731
- state.visibleWindow.valid = false;
732
753
  state.renderWindow.valid = false;
754
+ state.elementBounds.valid = false;
733
755
  state.textH = undefined;
734
756
  state.textW = undefined;
735
757
  state.lineCache = [];
736
758
  this.setStatus(state, 'loading');
737
759
  this.scheduleUpdateState(state);
738
760
  }
761
+
762
+ protected setElementBoundsX(state: SdfTextRendererState): void {
763
+ const { x, contain, width } = state.props;
764
+ const { elementBounds } = state;
765
+ elementBounds.x1 = x;
766
+ elementBounds.x2 = contain !== 'none' ? x + width : Infinity;
767
+ }
768
+
769
+ protected setElementBoundsY(state: SdfTextRendererState): void {
770
+ const { y, contain, height } = state.props;
771
+ const { elementBounds } = state;
772
+ elementBounds.y1 = y;
773
+ elementBounds.y2 = contain === 'both' ? y + height : Infinity;
774
+ }
739
775
  }
@@ -160,7 +160,8 @@ export function layoutText(
160
160
  (maxLines === 0 || curLineIndex + 1 < maxLines) &&
161
161
  (contain !== 'both' ||
162
162
  scrollable ||
163
- curY + vertexLineHeight + vertexLineHeight <= vertexTruncateHeight);
163
+ curY + vertexLineHeight + trFontFace.maxCharHeight <=
164
+ vertexTruncateHeight);
164
165
  const lineVertexW = nextLineWillFit
165
166
  ? vertexW
166
167
  : vertexW - overflowSuffVertexWidth;
@@ -33,7 +33,10 @@ sdfData.chars.forEach((glyph) => {
33
33
  describe('measureText', () => {
34
34
  it('should measure text width', () => {
35
35
  const PERIOD_WIDTH = 10.332;
36
- const shaper = new SdfFontShaper(sdfData as unknown as SdfFontData, glyphMap);
36
+ const shaper = new SdfFontShaper(
37
+ sdfData as unknown as SdfFontData,
38
+ glyphMap,
39
+ );
37
40
  expect(measureText('', { letterSpacing: 0 }, shaper)).toBe(0);
38
41
  expect(measureText('.', { letterSpacing: 0 }, shaper)).toBe(PERIOD_WIDTH);
39
42
  expect(measureText('..', { letterSpacing: 0 }, shaper)).toBeCloseTo(
@@ -87,7 +87,7 @@ export function setRenderWindow(
87
87
  sdf.y2 = y2 / fontSizeRatio;
88
88
 
89
89
  outRenderWindow.numLines = Math.ceil((y2 - y1) / lineHeight);
90
- outRenderWindow.firstLineIdx = Math.floor(y1 / lineHeight);
90
+ outRenderWindow.firstLineIdx = lineHeight ? Math.floor(y1 / lineHeight) : 0;
91
91
  }
92
92
  outRenderWindow.valid = true;
93
93
  }
@@ -20,7 +20,7 @@
20
20
  import type { EventEmitter } from '../../../common/EventEmitter.js';
21
21
  import type { Stage } from '../../Stage.js';
22
22
  import type { Matrix3d } from '../../lib/Matrix3d.js';
23
- import type { Rect } from '../../lib/utils.js';
23
+ import type { Rect, RectWithValid } from '../../lib/utils.js';
24
24
  import type {
25
25
  TrFontFace,
26
26
  TrFontFaceDescriptors,
@@ -498,7 +498,7 @@ export abstract class TextRenderer<
498
498
  abstract renderQuads(
499
499
  state: StateT,
500
500
  transform: Matrix3d,
501
- clippingRect: Rect | null,
501
+ clippingRect: RectWithValid,
502
502
  alpha: number,
503
503
  ): void;
504
504
  }
@@ -19,6 +19,10 @@
19
19
 
20
20
  import type { CoreTextureManager } from '../CoreTextureManager.js';
21
21
  import { Texture, type TextureData } from './Texture.js';
22
+ import {
23
+ isCompressedTextureContainer,
24
+ loadCompressedTexture,
25
+ } from '../lib/textureCompression.js';
22
26
 
23
27
  /**
24
28
  * Properties of the {@link ImageTexture}
@@ -84,6 +88,11 @@ export class ImageTexture extends Texture {
84
88
  };
85
89
  }
86
90
 
91
+ // Handle compressed textures
92
+ if (isCompressedTextureContainer(src)) {
93
+ return loadCompressedTexture(src);
94
+ }
95
+
87
96
  if (this.txManager.imageWorkerManager.imageWorkersEnabled) {
88
97
  return await this.txManager.imageWorkerManager.getImage(
89
98
  src,
@@ -35,6 +35,38 @@ export type TextureLoadedEventHandler = (
35
35
  dimensions: Readonly<Dimensions>,
36
36
  ) => void;
37
37
 
38
+ /**
39
+ * Represents compressed texture data.
40
+ */
41
+ interface CompressedData {
42
+ /**
43
+ * GLenum spcifying compression format
44
+ */
45
+ glInternalFormat: number;
46
+
47
+ /**
48
+ * All mipmap levels
49
+ */
50
+ mipmaps?: ArrayBuffer[];
51
+
52
+ /**
53
+ * Supported container types ('pvr' or 'ktx').
54
+ */
55
+ type: 'pvr' | 'ktx';
56
+
57
+ /**
58
+ * The width of the compressed texture in pixels. Defaults to 0.
59
+ *
60
+ * @default 0
61
+ */
62
+ width: number;
63
+
64
+ /**
65
+ * The height of the compressed texture in pixels.
66
+ **/
67
+ height: number;
68
+ }
69
+
38
70
  /**
39
71
  * Event handler for when a Texture fails to load
40
72
  */
@@ -47,7 +79,7 @@ export interface TextureData {
47
79
  /**
48
80
  * The texture data
49
81
  */
50
- data: ImageBitmap | ImageData | SubTextureProps | null;
82
+ data: ImageBitmap | ImageData | SubTextureProps | CompressedData | null;
51
83
  /**
52
84
  * Premultiply alpha when uploading texture data to the GPU
53
85
  *