@lightningjs/renderer 0.5.0 → 0.6.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.
- package/README.md +40 -8
- package/dist/exports/main-api.d.ts +3 -3
- package/dist/exports/main-api.js +3 -3
- package/dist/exports/main-api.js.map +1 -1
- package/dist/src/core/CoreNode.d.ts +24 -10
- package/dist/src/core/CoreNode.js +92 -20
- package/dist/src/core/CoreNode.js.map +1 -1
- package/dist/src/core/CoreShaderManager.d.ts +4 -0
- package/dist/src/core/CoreShaderManager.js +8 -0
- package/dist/src/core/CoreShaderManager.js.map +1 -1
- package/dist/src/core/CoreTextNode.d.ts +2 -4
- package/dist/src/core/CoreTextNode.js +5 -53
- package/dist/src/core/CoreTextNode.js.map +1 -1
- package/dist/src/core/Stage.d.ts +14 -3
- package/dist/src/core/Stage.js +52 -32
- package/dist/src/core/Stage.js.map +1 -1
- package/dist/src/core/animations/CoreAnimation.d.ts +1 -0
- package/dist/src/core/animations/CoreAnimation.js +25 -15
- package/dist/src/core/animations/CoreAnimation.js.map +1 -1
- package/dist/src/core/lib/utils.d.ts +5 -1
- package/dist/src/core/lib/utils.js +17 -12
- package/dist/src/core/lib/utils.js.map +1 -1
- package/dist/src/core/platform.js +4 -4
- package/dist/src/core/platform.js.map +1 -1
- package/dist/src/core/renderers/CoreRenderer.d.ts +1 -3
- package/dist/src/core/renderers/CoreRenderer.js.map +1 -1
- package/dist/src/core/renderers/webgl/WebGlCoreRenderer.d.ts +11 -17
- package/dist/src/core/renderers/webgl/WebGlCoreRenderer.js +13 -30
- package/dist/src/core/renderers/webgl/WebGlCoreRenderer.js.map +1 -1
- package/dist/src/core/renderers/webgl/shaders/DefaultShader.js +3 -0
- package/dist/src/core/renderers/webgl/shaders/DefaultShader.js.map +1 -1
- package/dist/src/core/renderers/webgl/shaders/DynamicShader.js +5 -28
- package/dist/src/core/renderers/webgl/shaders/DynamicShader.js.map +1 -1
- package/dist/src/core/renderers/webgl/shaders/effects/RadialProgressEffect.d.ts +61 -0
- package/dist/src/core/renderers/webgl/shaders/effects/RadialProgressEffect.js +126 -0
- package/dist/src/core/renderers/webgl/shaders/effects/RadialProgressEffect.js.map +1 -0
- package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.d.ts +20 -2
- package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.js +98 -45
- package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.js.map +1 -1
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.d.ts +16 -4
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js +80 -42
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js.map +1 -1
- package/dist/src/core/text-rendering/renderers/TextRenderer.d.ts +16 -8
- package/dist/src/core/text-rendering/renderers/TextRenderer.js +34 -8
- package/dist/src/core/text-rendering/renderers/TextRenderer.js.map +1 -1
- package/dist/src/main-api/{IRenderDriver.d.ts → ICoreDriver.d.ts} +3 -2
- package/dist/src/main-api/{IRenderDriver.js → ICoreDriver.js} +1 -1
- package/dist/src/main-api/ICoreDriver.js.map +1 -0
- package/dist/src/main-api/INode.d.ts +2 -2
- package/dist/src/main-api/RendererMain.d.ts +16 -6
- package/dist/src/main-api/RendererMain.js +9 -3
- package/dist/src/main-api/RendererMain.js.map +1 -1
- package/dist/src/render-drivers/main/{MainRenderDriver.d.ts → MainCoreDriver.d.ts} +3 -2
- package/dist/src/render-drivers/main/{MainRenderDriver.js → MainCoreDriver.js} +14 -4
- package/dist/src/render-drivers/main/MainCoreDriver.js.map +1 -0
- package/dist/src/render-drivers/threadx/{ThreadXRenderDriver.d.ts → ThreadXCoreDriver.d.ts} +4 -2
- package/dist/src/render-drivers/threadx/{ThreadXRenderDriver.js → ThreadXCoreDriver.js} +18 -4
- package/dist/src/render-drivers/threadx/ThreadXCoreDriver.js.map +1 -0
- package/dist/src/render-drivers/threadx/ThreadXRendererMessage.d.ts +9 -0
- package/dist/src/render-drivers/threadx/ThreadXRendererMessage.js.map +1 -1
- package/dist/src/render-drivers/threadx/worker/renderer.js +8 -0
- package/dist/src/render-drivers/threadx/worker/renderer.js.map +1 -1
- package/dist/src/utils.js +2 -1
- package/dist/src/utils.js.map +1 -1
- package/dist/tsconfig.dist.tsbuildinfo +1 -1
- package/exports/main-api.ts +3 -3
- package/package.json +8 -3
- package/src/core/CoreNode.ts +107 -21
- package/src/core/CoreShaderManager.ts +11 -0
- package/src/core/CoreTextNode.ts +5 -58
- package/src/core/Stage.ts +65 -34
- package/src/core/animations/CoreAnimation.ts +47 -27
- package/src/core/lib/utils.ts +39 -13
- package/src/core/platform.ts +5 -4
- package/src/core/renderers/CoreRenderer.ts +1 -2
- package/src/core/renderers/webgl/WebGlCoreRenderer.ts +14 -35
- package/src/core/renderers/webgl/shaders/DefaultShader.ts +4 -0
- package/src/core/renderers/webgl/shaders/DynamicShader.ts +5 -29
- package/src/core/renderers/webgl/shaders/effects/RadialProgressEffect.ts +186 -0
- package/src/core/text-rendering/renderers/CanvasTextRenderer.ts +107 -50
- package/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.ts +95 -44
- package/src/core/text-rendering/renderers/TextRenderer.ts +44 -16
- package/src/main-api/{IRenderDriver.ts → ICoreDriver.ts} +4 -2
- package/src/main-api/INode.ts +2 -2
- package/src/main-api/RendererMain.ts +23 -6
- package/src/render-drivers/main/{MainRenderDriver.ts → MainCoreDriver.ts} +17 -4
- package/src/render-drivers/threadx/{ThreadXRenderDriver.ts → ThreadXCoreDriver.ts} +23 -7
- package/src/render-drivers/threadx/ThreadXRendererMessage.ts +11 -0
- package/src/render-drivers/threadx/worker/renderer.ts +10 -0
- package/src/utils.ts +2 -1
- package/dist/src/main-api/IRenderDriver.js.map +0 -1
- package/dist/src/render-drivers/main/MainRenderDriver.js.map +0 -1
- package/dist/src/render-drivers/threadx/ThreadXRenderDriver.js.map +0 -1
|
@@ -28,6 +28,8 @@ import {
|
|
|
28
28
|
getNormalizedRgbaComponents,
|
|
29
29
|
type Rect,
|
|
30
30
|
getNormalizedAlphaComponent,
|
|
31
|
+
type BoundWithValid,
|
|
32
|
+
createBound,
|
|
31
33
|
} from '../../lib/utils.js';
|
|
32
34
|
import type { ImageTexture } from '../../textures/ImageTexture.js';
|
|
33
35
|
import type { TrFontFace } from '../font-face-types/TrFontFace.js';
|
|
@@ -86,13 +88,24 @@ export interface CanvasTextRendererState extends TextRendererState {
|
|
|
86
88
|
lightning2TextRenderer: LightningTextTextureRenderer;
|
|
87
89
|
renderInfo: RenderInfo | undefined;
|
|
88
90
|
renderWindow: Bound | undefined;
|
|
91
|
+
visibleWindow: BoundWithValid;
|
|
89
92
|
}
|
|
90
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Ephemeral bounds object used for intersection calculations
|
|
96
|
+
*
|
|
97
|
+
* @remarks
|
|
98
|
+
* Used to avoid creating a new object every time we need to intersect
|
|
99
|
+
* element bounds.
|
|
100
|
+
*/
|
|
101
|
+
const tmpElementBounds = createBound(0, 0, 0, 0);
|
|
102
|
+
|
|
91
103
|
export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
|
|
92
104
|
protected canvas: OffscreenCanvas | HTMLCanvasElement;
|
|
93
105
|
protected context:
|
|
94
106
|
| OffscreenCanvasRenderingContext2D
|
|
95
107
|
| CanvasRenderingContext2D;
|
|
108
|
+
private rendererBounds: Bound;
|
|
96
109
|
|
|
97
110
|
constructor(stage: Stage) {
|
|
98
111
|
super(stage);
|
|
@@ -111,6 +124,12 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
|
|
|
111
124
|
}
|
|
112
125
|
assertTruthy(context);
|
|
113
126
|
this.context = context;
|
|
127
|
+
this.rendererBounds = {
|
|
128
|
+
x1: 0,
|
|
129
|
+
y1: 0,
|
|
130
|
+
x2: this.stage.options.appWidth,
|
|
131
|
+
y2: this.stage.options.appHeight,
|
|
132
|
+
};
|
|
114
133
|
}
|
|
115
134
|
|
|
116
135
|
//#region Overrides
|
|
@@ -121,68 +140,70 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
|
|
|
121
140
|
fontFamily: (state, value) => {
|
|
122
141
|
state.props.fontFamily = value;
|
|
123
142
|
state.fontInfo = undefined;
|
|
124
|
-
this.
|
|
143
|
+
this.invalidateLayoutCache(state);
|
|
125
144
|
},
|
|
126
145
|
fontWeight: (state, value) => {
|
|
127
146
|
state.props.fontWeight = value;
|
|
128
147
|
state.fontInfo = undefined;
|
|
129
|
-
this.
|
|
148
|
+
this.invalidateLayoutCache(state);
|
|
130
149
|
},
|
|
131
150
|
fontStyle: (state, value) => {
|
|
132
151
|
state.props.fontStyle = value;
|
|
133
152
|
state.fontInfo = undefined;
|
|
134
|
-
this.
|
|
153
|
+
this.invalidateLayoutCache(state);
|
|
135
154
|
},
|
|
136
155
|
fontStretch: (state, value) => {
|
|
137
156
|
state.props.fontStretch = value;
|
|
138
157
|
state.fontInfo = undefined;
|
|
139
|
-
this.
|
|
158
|
+
this.invalidateLayoutCache(state);
|
|
140
159
|
},
|
|
141
160
|
fontSize: (state, value) => {
|
|
142
161
|
state.props.fontSize = value;
|
|
143
162
|
state.fontInfo = undefined;
|
|
144
|
-
this.
|
|
163
|
+
this.invalidateLayoutCache(state);
|
|
145
164
|
},
|
|
146
165
|
text: (state, value) => {
|
|
147
166
|
state.props.text = value;
|
|
148
|
-
this.
|
|
167
|
+
this.invalidateLayoutCache(state);
|
|
149
168
|
},
|
|
150
169
|
textAlign: (state, value) => {
|
|
151
170
|
state.props.textAlign = value;
|
|
152
|
-
this.
|
|
171
|
+
this.invalidateLayoutCache(state);
|
|
153
172
|
},
|
|
154
173
|
color: (state, value) => {
|
|
155
174
|
state.props.color = value;
|
|
156
|
-
this.
|
|
175
|
+
this.invalidateLayoutCache(state);
|
|
157
176
|
},
|
|
158
177
|
x: (state, value) => {
|
|
159
178
|
state.props.x = value;
|
|
179
|
+
this.invalidateVisibleWindowCache(state);
|
|
160
180
|
},
|
|
161
181
|
y: (state, value) => {
|
|
162
182
|
state.props.y = value;
|
|
183
|
+
this.invalidateVisibleWindowCache(state);
|
|
163
184
|
},
|
|
164
185
|
contain: (state, value) => {
|
|
165
186
|
state.props.contain = value;
|
|
166
|
-
this.
|
|
187
|
+
this.invalidateLayoutCache(state);
|
|
167
188
|
},
|
|
168
189
|
width: (state, value) => {
|
|
169
190
|
state.props.width = value;
|
|
170
|
-
this.
|
|
191
|
+
this.invalidateLayoutCache(state);
|
|
171
192
|
},
|
|
172
193
|
height: (state, value) => {
|
|
173
194
|
state.props.height = value;
|
|
174
|
-
this.
|
|
195
|
+
this.invalidateLayoutCache(state);
|
|
175
196
|
},
|
|
176
197
|
offsetY: (state, value) => {
|
|
177
198
|
state.props.offsetY = value;
|
|
178
|
-
this.
|
|
199
|
+
this.invalidateLayoutCache(state);
|
|
179
200
|
},
|
|
180
201
|
scrollY: (state, value) => {
|
|
181
202
|
state.props.scrollY = value;
|
|
182
203
|
},
|
|
183
204
|
letterSpacing: (state, value) => {
|
|
184
205
|
state.props.letterSpacing = value;
|
|
185
|
-
this.
|
|
206
|
+
this.invalidateLayoutCache(state);
|
|
186
207
|
},
|
|
187
208
|
// debug: (state, value) => {
|
|
188
209
|
// state.props.debug = value;
|
|
@@ -217,6 +238,7 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
|
|
|
217
238
|
return {
|
|
218
239
|
props,
|
|
219
240
|
status: 'initialState',
|
|
241
|
+
updateScheduled: false,
|
|
220
242
|
emitter: new EventEmitter(),
|
|
221
243
|
canvasPages: undefined,
|
|
222
244
|
lightning2TextRenderer: new LightningTextTextureRenderer(
|
|
@@ -224,6 +246,13 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
|
|
|
224
246
|
this.context,
|
|
225
247
|
),
|
|
226
248
|
renderWindow: undefined,
|
|
249
|
+
visibleWindow: {
|
|
250
|
+
x1: 0,
|
|
251
|
+
y1: 0,
|
|
252
|
+
x2: 0,
|
|
253
|
+
y2: 0,
|
|
254
|
+
valid: false,
|
|
255
|
+
},
|
|
227
256
|
renderInfo: undefined,
|
|
228
257
|
forceFullLayoutCalc: false,
|
|
229
258
|
textW: 0,
|
|
@@ -289,13 +318,13 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
|
|
|
289
318
|
state.props.contain === 'none' ? undefined : state.props.width,
|
|
290
319
|
letterSpacing: state.props.letterSpacing,
|
|
291
320
|
};
|
|
292
|
-
const renderInfoCalculateTime = performance.now();
|
|
321
|
+
// const renderInfoCalculateTime = performance.now();
|
|
293
322
|
state.renderInfo = state.lightning2TextRenderer.calculateRenderInfo();
|
|
294
|
-
console.log(
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
);
|
|
323
|
+
// console.log(
|
|
324
|
+
// 'Render info calculated in',
|
|
325
|
+
// performance.now() - renderInfoCalculateTime,
|
|
326
|
+
// 'ms',
|
|
327
|
+
// );
|
|
299
328
|
state.textH = state.renderInfo.lineHeight * state.renderInfo.lines.length;
|
|
300
329
|
state.textW = state.renderInfo.width;
|
|
301
330
|
|
|
@@ -304,26 +333,24 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
|
|
|
304
333
|
}
|
|
305
334
|
|
|
306
335
|
const { x, y, width, height, scrollY, contain } = state.props;
|
|
307
|
-
|
|
336
|
+
const { visibleWindow } = state;
|
|
308
337
|
let { renderWindow, canvasPages } = state;
|
|
309
338
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
*/
|
|
326
|
-
const visibleWindow: Bound = intersectBound(rendererBounds, elementBounds);
|
|
339
|
+
if (!visibleWindow.valid) {
|
|
340
|
+
// Figure out whats actually in the bounds of the renderer/canvas (visibleWindow)
|
|
341
|
+
const elementBounds = createBound(
|
|
342
|
+
x,
|
|
343
|
+
y,
|
|
344
|
+
contain !== 'none' ? x + width : Infinity,
|
|
345
|
+
contain === 'both' ? y + height : Infinity,
|
|
346
|
+
tmpElementBounds,
|
|
347
|
+
);
|
|
348
|
+
/**
|
|
349
|
+
* Area that is visible on the screen.
|
|
350
|
+
*/
|
|
351
|
+
intersectBound(this.rendererBounds, elementBounds, visibleWindow);
|
|
352
|
+
visibleWindow.valid = true;
|
|
353
|
+
}
|
|
327
354
|
|
|
328
355
|
const visibleWindowHeight = visibleWindow.y2 - visibleWindow.y1;
|
|
329
356
|
|
|
@@ -331,9 +358,16 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
|
|
|
331
358
|
visibleWindowHeight / state.renderInfo.lineHeight,
|
|
332
359
|
);
|
|
333
360
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
361
|
+
if (visibleWindowHeight === 0) {
|
|
362
|
+
// Nothing to render. Clear any canvasPages and existing renderWindow
|
|
363
|
+
// Return early.
|
|
364
|
+
canvasPages = undefined;
|
|
365
|
+
renderWindow = undefined;
|
|
366
|
+
this.setStatus(state, 'loaded');
|
|
367
|
+
return;
|
|
368
|
+
} else if (renderWindow && canvasPages) {
|
|
369
|
+
// Return early if we're still viewing inside the established render window
|
|
370
|
+
// No need to re-render what we've already rendered
|
|
337
371
|
const renderWindowScreenX1 = x + renderWindow.x1;
|
|
338
372
|
const renderWindowScreenY1 = y - scrollY + renderWindow.y1;
|
|
339
373
|
const renderWindowScreenX2 = x + renderWindow.x2;
|
|
@@ -344,8 +378,10 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
|
|
|
344
378
|
renderWindowScreenX2 >= visibleWindow.x2 &&
|
|
345
379
|
renderWindowScreenY1 <= visibleWindow.y1 &&
|
|
346
380
|
renderWindowScreenY2 >= visibleWindow.y2
|
|
347
|
-
)
|
|
381
|
+
) {
|
|
382
|
+
this.setStatus(state, 'loaded');
|
|
348
383
|
return;
|
|
384
|
+
}
|
|
349
385
|
if (renderWindowScreenY2 < visibleWindow.y2) {
|
|
350
386
|
// We've scrolled up, so we need to render the next page
|
|
351
387
|
renderWindow.y1 += maxLinesPerCanvasPage * state.renderInfo.lineHeight;
|
|
@@ -400,7 +436,7 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
|
|
|
400
436
|
];
|
|
401
437
|
state.canvasPages = canvasPages;
|
|
402
438
|
|
|
403
|
-
const scrollYNearestPage =
|
|
439
|
+
const scrollYNearestPage = page1Block * pageHeight;
|
|
404
440
|
|
|
405
441
|
renderWindow = {
|
|
406
442
|
x1: 0,
|
|
@@ -450,7 +486,7 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
|
|
|
450
486
|
}
|
|
451
487
|
pageInfo.valid = true;
|
|
452
488
|
}
|
|
453
|
-
console.log('pageDrawTime', performance.now() - pageDrawTime, 'ms');
|
|
489
|
+
// console.log('pageDrawTime', performance.now() - pageDrawTime, 'ms');
|
|
454
490
|
|
|
455
491
|
// Report final status
|
|
456
492
|
this.setStatus(state, 'loaded');
|
|
@@ -509,7 +545,7 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
|
|
|
509
545
|
const combinedAlpha = alpha * getNormalizedAlphaComponent(color);
|
|
510
546
|
|
|
511
547
|
if (canvasPages[0].valid) {
|
|
512
|
-
this.stage.renderer.
|
|
548
|
+
this.stage.renderer.addQuad({
|
|
513
549
|
alpha: combinedAlpha,
|
|
514
550
|
clippingRect,
|
|
515
551
|
colorBl: 0xffffffff,
|
|
@@ -532,7 +568,7 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
|
|
|
532
568
|
});
|
|
533
569
|
}
|
|
534
570
|
if (canvasPages[1].valid) {
|
|
535
|
-
this.stage.renderer.
|
|
571
|
+
this.stage.renderer.addQuad({
|
|
536
572
|
alpha: combinedAlpha,
|
|
537
573
|
clippingRect,
|
|
538
574
|
colorBl: 0xffffffff,
|
|
@@ -555,7 +591,7 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
|
|
|
555
591
|
});
|
|
556
592
|
}
|
|
557
593
|
if (canvasPages[2].valid) {
|
|
558
|
-
this.stage.renderer.
|
|
594
|
+
this.stage.renderer.addQuad({
|
|
559
595
|
alpha: combinedAlpha,
|
|
560
596
|
clippingRect,
|
|
561
597
|
colorBl: 0xffffffff,
|
|
@@ -612,9 +648,31 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
|
|
|
612
648
|
}
|
|
613
649
|
//#endregion Overrides
|
|
614
650
|
|
|
615
|
-
|
|
651
|
+
/**
|
|
652
|
+
* Invalidate the visible window stored in the state. This will cause a new
|
|
653
|
+
* visible window to be calculated on the next update.
|
|
654
|
+
*
|
|
655
|
+
* @param state
|
|
656
|
+
*/
|
|
657
|
+
protected invalidateVisibleWindowCache(state: CanvasTextRendererState): void {
|
|
658
|
+
state.visibleWindow.valid = false;
|
|
659
|
+
this.setStatus(state, 'loading');
|
|
660
|
+
this.scheduleUpdateState(state);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Invalidate the layout cache stored in the state. This will cause the text
|
|
665
|
+
* to be re-layed out on the next update.
|
|
666
|
+
*
|
|
667
|
+
* @remarks
|
|
668
|
+
* This also invalidates the visible window cache.
|
|
669
|
+
*
|
|
670
|
+
* @param state
|
|
671
|
+
*/
|
|
672
|
+
private invalidateLayoutCache(state: CanvasTextRendererState): void {
|
|
616
673
|
state.renderInfo = undefined;
|
|
617
674
|
this.setStatus(state, 'loading');
|
|
675
|
+
this.scheduleUpdateState(state);
|
|
618
676
|
}
|
|
619
677
|
|
|
620
678
|
private onFontLoaded(
|
|
@@ -625,7 +683,7 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
|
|
|
625
683
|
return;
|
|
626
684
|
}
|
|
627
685
|
state.fontInfo.loaded = true;
|
|
628
|
-
this.
|
|
686
|
+
this.scheduleUpdateState(state);
|
|
629
687
|
}
|
|
630
688
|
|
|
631
689
|
private onFontLoadError(
|
|
@@ -645,7 +703,6 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
|
|
|
645
703
|
`CanvasTextRenderer: Error loading font '${state.fontInfo.cssString}'`,
|
|
646
704
|
error,
|
|
647
705
|
);
|
|
648
|
-
|
|
649
|
-
this.updateState(state);
|
|
706
|
+
this.scheduleUpdateState(state);
|
|
650
707
|
}
|
|
651
708
|
}
|
|
@@ -17,7 +17,13 @@
|
|
|
17
17
|
* limitations under the License.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
intersectBound,
|
|
22
|
+
type Bound,
|
|
23
|
+
type Rect,
|
|
24
|
+
createBound,
|
|
25
|
+
type BoundWithValid,
|
|
26
|
+
} from '../../../lib/utils.js';
|
|
21
27
|
import {
|
|
22
28
|
TextRenderer,
|
|
23
29
|
type TrProps,
|
|
@@ -73,6 +79,8 @@ export interface SdfTextRendererState extends TextRendererState {
|
|
|
73
79
|
|
|
74
80
|
renderWindow: Bound | undefined;
|
|
75
81
|
|
|
82
|
+
visibleWindow: BoundWithValid;
|
|
83
|
+
|
|
76
84
|
bufferNumFloats: number;
|
|
77
85
|
|
|
78
86
|
bufferNumQuads: number;
|
|
@@ -90,6 +98,15 @@ export interface SdfTextRendererState extends TextRendererState {
|
|
|
90
98
|
trFontFace: SdfTrFontFace | undefined;
|
|
91
99
|
}
|
|
92
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Ephemeral bounds object used for intersection calculations
|
|
103
|
+
*
|
|
104
|
+
* @remarks
|
|
105
|
+
* Used to avoid creating a new object every time we need to intersect
|
|
106
|
+
* element bounds.
|
|
107
|
+
*/
|
|
108
|
+
const tmpElementBounds = createBound(0, 0, 0, 0);
|
|
109
|
+
|
|
93
110
|
/**
|
|
94
111
|
* Singleton class for rendering text using signed distance fields.
|
|
95
112
|
*
|
|
@@ -103,10 +120,17 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
|
|
|
103
120
|
private ssdfFontFamilies: FontFamilyMap = {};
|
|
104
121
|
private msdfFontFamilies: FontFamilyMap = {};
|
|
105
122
|
private sdfShader: SdfShader;
|
|
123
|
+
private rendererBounds: Bound;
|
|
106
124
|
|
|
107
125
|
constructor(stage: Stage) {
|
|
108
126
|
super(stage);
|
|
109
127
|
this.sdfShader = this.stage.shManager.loadShader('SdfShader').shader;
|
|
128
|
+
this.rendererBounds = {
|
|
129
|
+
x1: 0,
|
|
130
|
+
y1: 0,
|
|
131
|
+
x2: this.stage.options.appWidth,
|
|
132
|
+
y2: this.stage.options.appHeight,
|
|
133
|
+
};
|
|
110
134
|
}
|
|
111
135
|
|
|
112
136
|
//#region Overrides
|
|
@@ -115,70 +139,75 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
|
|
|
115
139
|
fontFamily: (state, value) => {
|
|
116
140
|
state.props.fontFamily = value;
|
|
117
141
|
state.trFontFace = undefined;
|
|
118
|
-
this.
|
|
142
|
+
this.invalidateLayoutCache(state);
|
|
119
143
|
},
|
|
120
144
|
fontWeight: (state, value) => {
|
|
121
145
|
state.props.fontWeight = value;
|
|
122
146
|
state.trFontFace = undefined;
|
|
123
|
-
this.
|
|
147
|
+
this.invalidateLayoutCache(state);
|
|
124
148
|
},
|
|
125
149
|
fontStyle: (state, value) => {
|
|
126
150
|
state.props.fontStyle = value;
|
|
127
151
|
state.trFontFace = undefined;
|
|
128
|
-
this.
|
|
152
|
+
this.invalidateLayoutCache(state);
|
|
129
153
|
},
|
|
130
154
|
fontStretch: (state, value) => {
|
|
131
155
|
state.props.fontStretch = value;
|
|
132
156
|
state.trFontFace = undefined;
|
|
133
|
-
this.
|
|
157
|
+
this.invalidateLayoutCache(state);
|
|
134
158
|
},
|
|
135
159
|
fontSize: (state, value) => {
|
|
136
160
|
state.props.fontSize = value;
|
|
137
|
-
this.
|
|
161
|
+
this.invalidateLayoutCache(state);
|
|
138
162
|
},
|
|
139
163
|
text: (state, value) => {
|
|
140
164
|
state.props.text = value;
|
|
141
|
-
this.
|
|
165
|
+
this.invalidateLayoutCache(state);
|
|
142
166
|
},
|
|
143
167
|
textAlign: (state, value) => {
|
|
144
168
|
state.props.textAlign = value;
|
|
145
|
-
this.
|
|
169
|
+
this.invalidateLayoutCache(state);
|
|
146
170
|
},
|
|
147
171
|
color: (state, value) => {
|
|
148
172
|
state.props.color = value;
|
|
149
173
|
},
|
|
150
174
|
x: (state, value) => {
|
|
151
175
|
state.props.x = value;
|
|
176
|
+
this.invalidateVisibleWindowCache(state);
|
|
152
177
|
},
|
|
153
178
|
y: (state, value) => {
|
|
154
179
|
state.props.y = value;
|
|
180
|
+
this.invalidateVisibleWindowCache(state);
|
|
155
181
|
},
|
|
156
182
|
contain: (state, value) => {
|
|
157
183
|
state.props.contain = value;
|
|
158
|
-
this.
|
|
184
|
+
this.invalidateLayoutCache(state);
|
|
159
185
|
},
|
|
160
186
|
width: (state, value) => {
|
|
161
187
|
state.props.width = value;
|
|
162
|
-
this.
|
|
188
|
+
this.invalidateLayoutCache(state);
|
|
163
189
|
},
|
|
164
190
|
height: (state, value) => {
|
|
165
191
|
state.props.height = value;
|
|
166
|
-
this.
|
|
192
|
+
this.invalidateLayoutCache(state);
|
|
167
193
|
},
|
|
168
194
|
offsetY: (state, value) => {
|
|
169
195
|
state.props.offsetY = value;
|
|
170
|
-
this.
|
|
196
|
+
this.invalidateLayoutCache(state);
|
|
171
197
|
},
|
|
172
198
|
scrollable: (state, value) => {
|
|
173
199
|
state.props.scrollable = value;
|
|
174
|
-
this.
|
|
200
|
+
this.invalidateLayoutCache(state);
|
|
175
201
|
},
|
|
176
202
|
scrollY: (state, value) => {
|
|
177
203
|
state.props.scrollY = value;
|
|
204
|
+
// Scrolling doesn't need to invalidate any caches, but it does need to
|
|
205
|
+
// schedule an update
|
|
206
|
+
this.scheduleUpdateState(state);
|
|
178
207
|
},
|
|
179
208
|
letterSpacing: (state, value) => {
|
|
180
209
|
state.props.letterSpacing = value;
|
|
181
|
-
this.
|
|
210
|
+
this.invalidateLayoutCache(state);
|
|
182
211
|
},
|
|
183
212
|
debug: (state, value) => {
|
|
184
213
|
state.props.debug = value;
|
|
@@ -229,10 +258,18 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
|
|
|
229
258
|
return {
|
|
230
259
|
props,
|
|
231
260
|
status: 'initialState',
|
|
261
|
+
updateScheduled: false,
|
|
232
262
|
emitter: new EventEmitter(),
|
|
233
263
|
lineCache: [],
|
|
234
264
|
forceFullLayoutCalc: false,
|
|
235
265
|
renderWindow: undefined,
|
|
266
|
+
visibleWindow: {
|
|
267
|
+
x1: 0,
|
|
268
|
+
y1: 0,
|
|
269
|
+
x2: 0,
|
|
270
|
+
y2: 0,
|
|
271
|
+
valid: false,
|
|
272
|
+
},
|
|
236
273
|
bufferNumFloats: 0,
|
|
237
274
|
bufferNumQuads: 0,
|
|
238
275
|
vertexBuffer: undefined,
|
|
@@ -255,7 +292,6 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
|
|
|
255
292
|
}
|
|
256
293
|
|
|
257
294
|
override updateState(state: SdfTextRendererState): void {
|
|
258
|
-
const updateStartTime = performance.now();
|
|
259
295
|
let { trFontFace } = state;
|
|
260
296
|
const { textH, lineCache, debugData, forceFullLayoutCalc } = state;
|
|
261
297
|
debugData.updateCount++;
|
|
@@ -280,9 +316,8 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
|
|
|
280
316
|
// If the font hasn't been loaded yet, stop here.
|
|
281
317
|
// Listen for the 'loaded' event and forward fontLoaded event
|
|
282
318
|
if (!trFontFace.loaded) {
|
|
283
|
-
trFontFace.
|
|
284
|
-
|
|
285
|
-
trFontFace?.off('fontLoaded', loadedHandler);
|
|
319
|
+
trFontFace.once('loaded', () => {
|
|
320
|
+
this.scheduleUpdateState(state);
|
|
286
321
|
});
|
|
287
322
|
return;
|
|
288
323
|
}
|
|
@@ -317,23 +352,23 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
|
|
|
317
352
|
vertexBuffer = new Float32Array(neededLength * 2);
|
|
318
353
|
}
|
|
319
354
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
355
|
+
const visibleWindow = state.visibleWindow;
|
|
356
|
+
// If the visibleWindow is not valid, calculate it
|
|
357
|
+
if (!visibleWindow.valid) {
|
|
358
|
+
// Figure out whats actually in the bounds of the renderer/canvas (visibleWindow)
|
|
359
|
+
const elementBounds = createBound(
|
|
360
|
+
x,
|
|
361
|
+
y,
|
|
362
|
+
contain !== 'none' ? x + width : Infinity,
|
|
363
|
+
contain === 'both' ? y + height : Infinity,
|
|
364
|
+
tmpElementBounds, // Prevent allocation by using this temp object
|
|
365
|
+
);
|
|
366
|
+
/**
|
|
367
|
+
* Area that is visible on the screen.
|
|
368
|
+
*/
|
|
369
|
+
intersectBound(this.rendererBounds, elementBounds, state.visibleWindow);
|
|
370
|
+
visibleWindow.valid = true;
|
|
371
|
+
}
|
|
337
372
|
|
|
338
373
|
// Return early if we're still viewing inside the established render window
|
|
339
374
|
// No need to re-render what we've already rendered
|
|
@@ -344,10 +379,13 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
|
|
|
344
379
|
x + renderWindow.x2 >= visibleWindow.x2 &&
|
|
345
380
|
y - scrollY + renderWindow.y1 <= visibleWindow.y1 &&
|
|
346
381
|
y - scrollY + renderWindow.y2 >= visibleWindow.y2
|
|
347
|
-
)
|
|
382
|
+
) {
|
|
383
|
+
this.setStatus(state, 'loaded');
|
|
348
384
|
return;
|
|
385
|
+
}
|
|
349
386
|
// Otherwise clear the renderWindow so it can be redone
|
|
350
|
-
|
|
387
|
+
renderWindow = state.renderWindow = undefined;
|
|
388
|
+
this.setStatus(state, 'loading');
|
|
351
389
|
}
|
|
352
390
|
|
|
353
391
|
const { offsetY, textAlign } = state.props;
|
|
@@ -456,10 +494,7 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
|
|
|
456
494
|
textH = 0,
|
|
457
495
|
distanceRange,
|
|
458
496
|
vertexBuffer,
|
|
459
|
-
bufferNumFloats,
|
|
460
497
|
bufferUploaded,
|
|
461
|
-
renderWindow,
|
|
462
|
-
debugData,
|
|
463
498
|
trFontFace,
|
|
464
499
|
} = state;
|
|
465
500
|
|
|
@@ -540,7 +575,7 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
|
|
|
540
575
|
renderOp.length = state.bufferNumFloats;
|
|
541
576
|
renderOp.numQuads = state.bufferNumQuads;
|
|
542
577
|
|
|
543
|
-
renderer.
|
|
578
|
+
renderer.addRenderOp(renderOp);
|
|
544
579
|
|
|
545
580
|
// const elementRect = {
|
|
546
581
|
// x: x,
|
|
@@ -621,16 +656,32 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
|
|
|
621
656
|
}
|
|
622
657
|
|
|
623
658
|
/**
|
|
624
|
-
* Invalidate the
|
|
625
|
-
*
|
|
659
|
+
* Invalidate the visible window stored in the state. This will cause a new
|
|
660
|
+
* visible window to be calculated on the next update.
|
|
661
|
+
*
|
|
662
|
+
* @param state
|
|
663
|
+
*/
|
|
664
|
+
protected invalidateVisibleWindowCache(state: SdfTextRendererState): void {
|
|
665
|
+
state.visibleWindow.valid = false;
|
|
666
|
+
this.scheduleUpdateState(state);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Invalidate the layout cache stored in the state. This will cause the text
|
|
671
|
+
* to be re-layed out on the next update.
|
|
672
|
+
*
|
|
673
|
+
* @remarks
|
|
674
|
+
* This also invalidates the visible window cache.
|
|
626
675
|
*
|
|
627
676
|
* @param state
|
|
628
677
|
*/
|
|
629
|
-
protected
|
|
678
|
+
protected invalidateLayoutCache(state: SdfTextRendererState): void {
|
|
679
|
+
state.visibleWindow.valid = false;
|
|
630
680
|
state.renderWindow = undefined;
|
|
631
681
|
state.textH = undefined;
|
|
632
682
|
state.textW = undefined;
|
|
633
683
|
state.lineCache = [];
|
|
634
684
|
this.setStatus(state, 'loading');
|
|
685
|
+
this.scheduleUpdateState(state);
|
|
635
686
|
}
|
|
636
687
|
}
|