@solidtv/renderer 1.0.4 → 1.0.6
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/dist/src/core/CoreNode.d.ts +34 -1
- package/dist/src/core/CoreNode.js +48 -56
- package/dist/src/core/CoreNode.js.map +1 -1
- package/dist/src/core/CoreTextNode.d.ts +2 -0
- package/dist/src/core/CoreTextNode.js +35 -18
- package/dist/src/core/CoreTextNode.js.map +1 -1
- package/dist/src/core/CoreTextureManager.js +0 -1
- package/dist/src/core/CoreTextureManager.js.map +1 -1
- package/dist/src/core/Stage.js +3 -0
- package/dist/src/core/Stage.js.map +1 -1
- package/dist/src/core/TextureMemoryManager.d.ts +0 -13
- package/dist/src/core/TextureMemoryManager.js +0 -24
- package/dist/src/core/TextureMemoryManager.js.map +1 -1
- package/dist/src/core/animations/AnimationManager.d.ts +1 -0
- package/dist/src/core/animations/AnimationManager.js.map +1 -1
- package/dist/src/core/animations/CoreAnimation.d.ts +1 -0
- package/dist/src/core/animations/CoreAnimation.js +1 -0
- package/dist/src/core/animations/CoreAnimation.js.map +1 -1
- package/dist/src/core/renderers/webgl/WebGlRenderer.js +4 -6
- package/dist/src/core/renderers/webgl/WebGlRenderer.js.map +1 -1
- package/dist/src/core/text-rendering/CanvasTextRenderer.js +12 -1
- package/dist/src/core/text-rendering/CanvasTextRenderer.js.map +1 -1
- package/dist/src/core/text-rendering/SdfFontHandler.js +5 -4
- package/dist/src/core/text-rendering/SdfFontHandler.js.map +1 -1
- package/dist/src/core/text-rendering/SdfTextRenderer.js +16 -6
- package/dist/src/core/text-rendering/SdfTextRenderer.js.map +1 -1
- package/dist/src/core/text-rendering/TextRenderer.d.ts +0 -31
- package/dist/src/main-api/Inspector.js +78 -5
- package/dist/src/main-api/Inspector.js.map +1 -1
- package/dist/src/main-api/Renderer.js +4 -2
- package/dist/src/main-api/Renderer.js.map +1 -1
- package/dist/tsconfig.dist.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +1 -1
- package/src/core/CoreNode.test.ts +47 -46
- package/src/core/CoreNode.ts +86 -55
- package/src/core/CoreTextNode.ts +38 -23
- package/src/core/CoreTextureManager.ts +0 -2
- package/src/core/Stage.ts +3 -0
- package/src/core/TextureMemoryManager.ts +0 -27
- package/src/core/animations/AnimationManager.ts +1 -0
- package/src/core/animations/CoreAnimation.ts +2 -1
- package/src/core/renderers/webgl/WebGlRenderer.ts +4 -6
- package/src/core/text-rendering/CanvasTextRenderer.ts +14 -1
- package/src/core/text-rendering/SdfFontHandler.ts +6 -5
- package/src/core/text-rendering/SdfTextRenderer.ts +19 -6
- package/src/core/text-rendering/TextRenderer.ts +0 -31
- package/src/main-api/Inspector.ts +87 -5
- package/src/main-api/Renderer.ts +4 -2
package/src/core/CoreNode.ts
CHANGED
|
@@ -13,7 +13,8 @@ import type { BufferCollection } from './renderers/webgl/internal/BufferCollecti
|
|
|
13
13
|
import type { CoreRenderer } from './renderers/CoreRenderer.js';
|
|
14
14
|
import type { Stage } from './Stage.js';
|
|
15
15
|
import {
|
|
16
|
-
|
|
16
|
+
Texture,
|
|
17
|
+
TextureType,
|
|
17
18
|
type TextureCoords,
|
|
18
19
|
type TextureFailedEventHandler,
|
|
19
20
|
type TextureFreedEventHandler,
|
|
@@ -695,6 +696,38 @@ export interface CoreNodeProps {
|
|
|
695
696
|
* @default false
|
|
696
697
|
*/
|
|
697
698
|
preventDestroy?: boolean;
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* The name of the framework component that created this node.
|
|
702
|
+
*
|
|
703
|
+
* @remarks
|
|
704
|
+
* When set, the Inspector will create a custom HTML element with this name
|
|
705
|
+
* so that the Chrome DevTools Elements panel displays the component name
|
|
706
|
+
* instead of a generic `<div>`. The value should match the JSX component
|
|
707
|
+
* name as written in source (e.g. `"MyButton"`).
|
|
708
|
+
*
|
|
709
|
+
* This is typically injected by a Babel plugin (e.g. `jsx-locator`) and is
|
|
710
|
+
* only meaningful when the Inspector is enabled.
|
|
711
|
+
*
|
|
712
|
+
* @default `undefined`
|
|
713
|
+
*/
|
|
714
|
+
componentName?: string;
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* The source file path of the framework component that created this node.
|
|
718
|
+
*
|
|
719
|
+
* @remarks
|
|
720
|
+
* When set alongside {@link componentName}, the Inspector will attach a
|
|
721
|
+
* `data-location` attribute to the DOM element, enabling click-to-source
|
|
722
|
+
* navigation from Chrome DevTools.
|
|
723
|
+
*
|
|
724
|
+
* The value should be an absolute or project-relative path to the component
|
|
725
|
+
* source file, optionally followed by `:line:column`
|
|
726
|
+
* (e.g. `"/src/components/MyButton.tsx:12:3"`).
|
|
727
|
+
*
|
|
728
|
+
* @default `undefined`
|
|
729
|
+
*/
|
|
730
|
+
componentLocation?: string;
|
|
698
731
|
}
|
|
699
732
|
|
|
700
733
|
/**
|
|
@@ -806,51 +839,19 @@ export class CoreNode extends EventEmitter {
|
|
|
806
839
|
let initialUpdateType =
|
|
807
840
|
UpdateType.Local | UpdateType.RenderBounds | UpdateType.RenderState;
|
|
808
841
|
|
|
809
|
-
//
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
colorRight: props.colorRight,
|
|
823
|
-
colorTl: props.colorTl,
|
|
824
|
-
colorTr: props.colorTr,
|
|
825
|
-
colorBl: props.colorBl,
|
|
826
|
-
colorBr: props.colorBr,
|
|
827
|
-
scaleX: props.scaleX,
|
|
828
|
-
scaleY: props.scaleY,
|
|
829
|
-
rotation: props.rotation,
|
|
830
|
-
pivotX: props.pivotX,
|
|
831
|
-
pivotY: props.pivotY,
|
|
832
|
-
mountX: props.mountX,
|
|
833
|
-
mountY: props.mountY,
|
|
834
|
-
mount: props.mount,
|
|
835
|
-
pivot: props.pivot,
|
|
836
|
-
zIndex: props.zIndex,
|
|
837
|
-
textureOptions: props.textureOptions,
|
|
838
|
-
data: props.data,
|
|
839
|
-
imageType: props.imageType,
|
|
840
|
-
srcX: props.srcX,
|
|
841
|
-
srcY: props.srcY,
|
|
842
|
-
srcWidth: props.srcWidth,
|
|
843
|
-
srcHeight: props.srcHeight,
|
|
844
|
-
parent: props.parent,
|
|
845
|
-
texture: null,
|
|
846
|
-
shader: null,
|
|
847
|
-
src: null,
|
|
848
|
-
rtt: false,
|
|
849
|
-
boundsMargin: null,
|
|
850
|
-
scale: null,
|
|
851
|
-
interactive: props.interactive,
|
|
852
|
-
preventDestroy: props.preventDestroy,
|
|
853
|
-
});
|
|
842
|
+
// Use the incoming props object directly — resolveNodeDefaults already
|
|
843
|
+
// creates a fresh object with a consistent shape. Save fields that are
|
|
844
|
+
// re-applied through setters, then null them on props so the setters
|
|
845
|
+
// detect the change.
|
|
846
|
+
const { texture, shader, src, rtt, boundsMargin, interactive, parent } =
|
|
847
|
+
props;
|
|
848
|
+
const p = (this.props = props);
|
|
849
|
+
p.texture = null;
|
|
850
|
+
p.shader = null;
|
|
851
|
+
p.src = null;
|
|
852
|
+
p.rtt = false;
|
|
853
|
+
p.boundsMargin = null;
|
|
854
|
+
p.scale = null;
|
|
854
855
|
|
|
855
856
|
//check if any color props are set for premultiplied color updates
|
|
856
857
|
if (
|
|
@@ -873,17 +874,17 @@ export class CoreNode extends EventEmitter {
|
|
|
873
874
|
this.zIndex = p.zIndex;
|
|
874
875
|
}
|
|
875
876
|
|
|
876
|
-
if (
|
|
877
|
-
|
|
877
|
+
if (parent !== null) {
|
|
878
|
+
parent.addChild(this);
|
|
878
879
|
}
|
|
879
880
|
|
|
880
|
-
// Assign
|
|
881
|
-
this.texture =
|
|
882
|
-
this.shader =
|
|
883
|
-
this.src =
|
|
884
|
-
this.rtt =
|
|
885
|
-
this.boundsMargin =
|
|
886
|
-
this.interactive =
|
|
881
|
+
// Assign saved values through setters
|
|
882
|
+
this.texture = texture;
|
|
883
|
+
this.shader = shader;
|
|
884
|
+
this.src = src;
|
|
885
|
+
this.rtt = rtt;
|
|
886
|
+
this.boundsMargin = boundsMargin;
|
|
887
|
+
this.interactive = interactive;
|
|
887
888
|
|
|
888
889
|
// Initialize autosize if enabled
|
|
889
890
|
if (p.autosize === true) {
|
|
@@ -2740,6 +2741,11 @@ export class CoreNode extends EventEmitter {
|
|
|
2740
2741
|
this.loadTexture();
|
|
2741
2742
|
}
|
|
2742
2743
|
|
|
2744
|
+
if (this.texture?.type === TextureType.subTexture && this.textureLoaded) {
|
|
2745
|
+
// When setting the texture value of a subtexture but the atlas is already loaded,
|
|
2746
|
+
// requestRenderListUpdate is not triggered, but we still need to update the quad
|
|
2747
|
+
this.isQuadDirty = true;
|
|
2748
|
+
}
|
|
2743
2749
|
this.setUpdateType(UpdateType.IsRenderable);
|
|
2744
2750
|
this.updateIsSimple();
|
|
2745
2751
|
}
|
|
@@ -2768,6 +2774,14 @@ export class CoreNode extends EventEmitter {
|
|
|
2768
2774
|
return this.props.interactive;
|
|
2769
2775
|
}
|
|
2770
2776
|
|
|
2777
|
+
get componentName(): string | undefined {
|
|
2778
|
+
return this.props.componentName;
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
get componentLocation(): string | undefined {
|
|
2782
|
+
return this.props.componentLocation;
|
|
2783
|
+
}
|
|
2784
|
+
|
|
2771
2785
|
setRTTUpdates(type: number) {
|
|
2772
2786
|
this.hasRTTupdates = true;
|
|
2773
2787
|
this.parent?.setRTTUpdates(type);
|
|
@@ -2795,6 +2809,15 @@ export class CoreNode extends EventEmitter {
|
|
|
2795
2809
|
values.start = (this as any)[name] ?? 0;
|
|
2796
2810
|
values.target = value;
|
|
2797
2811
|
controller.progress = 0;
|
|
2812
|
+
|
|
2813
|
+
if (settings.adaptiveDuration === true) {
|
|
2814
|
+
const now = performance.now();
|
|
2815
|
+
const elapsed = now - controller.lastRunTime;
|
|
2816
|
+
controller.lastRunTime = now;
|
|
2817
|
+
const duration = settings.duration ?? controller.duration;
|
|
2818
|
+
controller.duration = elapsed < duration ? elapsed : duration;
|
|
2819
|
+
}
|
|
2820
|
+
|
|
2798
2821
|
return controller.start();
|
|
2799
2822
|
}
|
|
2800
2823
|
}
|
|
@@ -2810,6 +2833,14 @@ export class CoreNode extends EventEmitter {
|
|
|
2810
2833
|
return controller.start();
|
|
2811
2834
|
}
|
|
2812
2835
|
|
|
2836
|
+
animateToTarget(prop: string): number | undefined {
|
|
2837
|
+
const animation = this._animations[prop];
|
|
2838
|
+
if (!animation) {
|
|
2839
|
+
return undefined;
|
|
2840
|
+
}
|
|
2841
|
+
return animation.controller.props?.[prop]?.target;
|
|
2842
|
+
}
|
|
2843
|
+
|
|
2813
2844
|
flush() {
|
|
2814
2845
|
// no-op
|
|
2815
2846
|
}
|
package/src/core/CoreTextNode.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type {
|
|
|
6
6
|
TextRenderInfo,
|
|
7
7
|
SdfVertexCache,
|
|
8
8
|
} from './text-rendering/TextRenderer.js';
|
|
9
|
-
import { USE_RTT } from '../utils.js';
|
|
9
|
+
import { USE_RTT, premultiplyColorABGR } from '../utils.js';
|
|
10
10
|
import {
|
|
11
11
|
CoreNode,
|
|
12
12
|
CoreNodeRenderState,
|
|
@@ -239,27 +239,23 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
|
|
|
239
239
|
// Handle Canvas renderer (uses ImageData)
|
|
240
240
|
if (textRendererType === 'canvas') {
|
|
241
241
|
if (result.imageData === undefined) {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if (this.renderState > CoreNodeRenderState.OutOfBounds) {
|
|
261
|
-
// We do want the texture to load immediately
|
|
262
|
-
this.texture.setRenderableOwner(this._id, true);
|
|
242
|
+
// Empty text returns no imageData — mark not renderable and continue
|
|
243
|
+
// to update dimensions (w=0, h=0) rather than emitting a failure.
|
|
244
|
+
this.setRenderable(false);
|
|
245
|
+
} else {
|
|
246
|
+
this.texture = this.stage.txManager.createTexture('ImageTexture', {
|
|
247
|
+
premultiplyAlpha: true,
|
|
248
|
+
src: result.imageData as ImageData,
|
|
249
|
+
});
|
|
250
|
+
// It isn't renderable until the texture is loaded we have to set it to false here to avoid it
|
|
251
|
+
// being detected as a renderable default color node in the next frame
|
|
252
|
+
// it will be corrected once the texture is loaded
|
|
253
|
+
this.setRenderable(false);
|
|
254
|
+
|
|
255
|
+
if (this.renderState > CoreNodeRenderState.OutOfBounds) {
|
|
256
|
+
// We do want the texture to load immediately
|
|
257
|
+
this.texture.setRenderableOwner(this._id, true);
|
|
258
|
+
}
|
|
263
259
|
}
|
|
264
260
|
}
|
|
265
261
|
|
|
@@ -300,8 +296,15 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
|
|
|
300
296
|
return;
|
|
301
297
|
}
|
|
302
298
|
|
|
303
|
-
// Canvas renderer:
|
|
299
|
+
// Canvas renderer: color is baked into the texture to preserve
|
|
300
|
+
// native emoji colors. Use white tint with worldAlpha only.
|
|
304
301
|
if (this._type === 'canvas') {
|
|
302
|
+
const white = premultiplyColorABGR(0xffffffff, this.worldAlpha);
|
|
303
|
+
this.premultipliedColorTl =
|
|
304
|
+
this.premultipliedColorTr =
|
|
305
|
+
this.premultipliedColorBl =
|
|
306
|
+
this.premultipliedColorBr =
|
|
307
|
+
white;
|
|
305
308
|
super.renderQuads(renderer);
|
|
306
309
|
return;
|
|
307
310
|
}
|
|
@@ -558,6 +561,18 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
|
|
|
558
561
|
}
|
|
559
562
|
}
|
|
560
563
|
|
|
564
|
+
override get color(): number {
|
|
565
|
+
return this.props.color;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
override set color(value: number) {
|
|
569
|
+
super.color = value;
|
|
570
|
+
if (this._type === 'canvas') {
|
|
571
|
+
this._layoutGenerated = false;
|
|
572
|
+
this.setUpdateType(UpdateType.Local);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
561
576
|
get forceLoad() {
|
|
562
577
|
return this.textProps.forceLoad;
|
|
563
578
|
}
|
|
@@ -340,8 +340,6 @@ export class CoreTextureManager extends EventEmitter {
|
|
|
340
340
|
* @param immediate - Whether to prioritize the texture for immediate loading
|
|
341
341
|
*/
|
|
342
342
|
async loadTexture(texture: Texture, priority?: boolean): Promise<void> {
|
|
343
|
-
this.stage.txMemManager.removeFromOrphanedTextures(texture);
|
|
344
|
-
|
|
345
343
|
if (texture.type === TextureType.subTexture) {
|
|
346
344
|
// ignore subtextures - they get loaded through their parent
|
|
347
345
|
return;
|
package/src/core/Stage.ts
CHANGED
|
@@ -979,6 +979,9 @@ export class Stage {
|
|
|
979
979
|
data,
|
|
980
980
|
imageType: props.imageType,
|
|
981
981
|
interactive: props.interactive ?? false,
|
|
982
|
+
preventDestroy: props.preventDestroy,
|
|
983
|
+
componentName: props.componentName,
|
|
984
|
+
componentLocation: props.componentLocation,
|
|
982
985
|
};
|
|
983
986
|
}
|
|
984
987
|
|
|
@@ -98,7 +98,6 @@ export interface MemoryInfo {
|
|
|
98
98
|
export class TextureMemoryManager {
|
|
99
99
|
private memUsed = 0;
|
|
100
100
|
private loadedTextures: Set<Texture> = new Set();
|
|
101
|
-
private orphanedTextures: Set<Texture> = new Set();
|
|
102
101
|
private criticalThreshold: number = 124e6;
|
|
103
102
|
private targetThreshold: number = 0.5;
|
|
104
103
|
private cleanupInterval: number = 5000;
|
|
@@ -127,32 +126,6 @@ export class TextureMemoryManager {
|
|
|
127
126
|
this.updateSettings(settings);
|
|
128
127
|
}
|
|
129
128
|
|
|
130
|
-
/**
|
|
131
|
-
* Add a texture to the orphaned textures list
|
|
132
|
-
*
|
|
133
|
-
* @param texture - The texture to add to the orphaned textures list
|
|
134
|
-
*/
|
|
135
|
-
addToOrphanedTextures(texture: Texture) {
|
|
136
|
-
// if the texture is already in the orphaned textures list add it at the end
|
|
137
|
-
if (this.orphanedTextures.has(texture)) {
|
|
138
|
-
this.orphanedTextures.delete(texture);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// If the texture can be cleaned up, add it to the orphaned textures list
|
|
142
|
-
if (texture.preventCleanup === false) {
|
|
143
|
-
this.orphanedTextures.add(texture);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Remove a texture from the orphaned textures list
|
|
149
|
-
*
|
|
150
|
-
* @param texture - The texture to remove from the orphaned textures list
|
|
151
|
-
*/
|
|
152
|
-
removeFromOrphanedTextures(texture: Texture) {
|
|
153
|
-
this.orphanedTextures.delete(texture);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
129
|
/**
|
|
157
130
|
* Set the memory usage of a texture
|
|
158
131
|
*
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
|
|
9
9
|
export interface AnimationSettings {
|
|
10
10
|
duration: number;
|
|
11
|
+
adaptiveDuration: boolean;
|
|
11
12
|
delay: number;
|
|
12
13
|
easing: string | TimingFunction;
|
|
13
14
|
loop: boolean;
|
|
@@ -23,7 +24,6 @@ export function createAnimation(
|
|
|
23
24
|
): AnimationConfig {
|
|
24
25
|
const easing = settings.easing || 'linear';
|
|
25
26
|
const delay = settings.delay ?? 0;
|
|
26
|
-
|
|
27
27
|
let propValues: Record<string, PropValues> | null = null;
|
|
28
28
|
let shaderPropValues: Record<string, PropValues> | null = null;
|
|
29
29
|
|
|
@@ -73,6 +73,7 @@ export function createAnimation(
|
|
|
73
73
|
shaderProps: shaderPropValues,
|
|
74
74
|
stoppedResolve: null,
|
|
75
75
|
stoppedPromise: null,
|
|
76
|
+
lastRunTime: settings.adaptiveDuration ? performance.now() : 0,
|
|
76
77
|
|
|
77
78
|
start() {
|
|
78
79
|
if (this.state !== 'running' && this.state !== 'scheduled') {
|
|
@@ -600,18 +600,16 @@ export class WebGlRenderer extends CoreRenderer {
|
|
|
600
600
|
|
|
601
601
|
// Pre-compute the merged color (with alpha) packed as ABGR for
|
|
602
602
|
// UNSIGNED_BYTE normalized attribute.
|
|
603
|
+
// NOTE: Do NOT premultiply RGB by alpha here — the SDF fragment shader
|
|
604
|
+
// already multiplies v_color.rgb by the computed opacity (which includes
|
|
605
|
+
// v_color.a).
|
|
603
606
|
const mergedColor = mergeColorAlpha(color, worldAlpha);
|
|
604
607
|
const r = mergedColor >>> 24;
|
|
605
608
|
const g = (mergedColor >>> 16) & 0xff;
|
|
606
609
|
const b = (mergedColor >>> 8) & 0xff;
|
|
607
610
|
const a = mergedColor & 0xff;
|
|
608
|
-
// Premultiply alpha into RGB for correct blending
|
|
609
|
-
const na = a / 255;
|
|
610
|
-
const pr = (r * na) | 0;
|
|
611
|
-
const pg = (g * na) | 0;
|
|
612
|
-
const pb = (b * na) | 0;
|
|
613
611
|
// Pack as ABGR uint32 (little-endian read as vec4(r,g,b,a) normalized)
|
|
614
|
-
const packedColor = ((a << 24) | (
|
|
612
|
+
const packedColor = ((a << 24) | (b << 16) | (g << 8) | r) >>> 0;
|
|
615
613
|
|
|
616
614
|
// Transform matrix components (column-major 3x3)
|
|
617
615
|
// Pre-multiply fontScale here to save 4 multiplications per glyph in the hot loop
|
|
@@ -77,6 +77,14 @@ const renderText = (props: CoreTextNodeProps): TextRenderInfo => {
|
|
|
77
77
|
assertTruthy(canvas, 'Canvas is not initialized');
|
|
78
78
|
assertTruthy(context, 'Canvas context is not available');
|
|
79
79
|
assertTruthy(measureContext, 'Canvas measureContext is not available');
|
|
80
|
+
|
|
81
|
+
if (props.text.length === 0) {
|
|
82
|
+
return {
|
|
83
|
+
width: 0,
|
|
84
|
+
height: 0,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
80
88
|
// Extract already normalized properties
|
|
81
89
|
const {
|
|
82
90
|
text,
|
|
@@ -130,7 +138,12 @@ const renderText = (props: CoreTextNodeProps): TextRenderInfo => {
|
|
|
130
138
|
|
|
131
139
|
canvas.width = canvasW;
|
|
132
140
|
canvas.height = canvasH;
|
|
133
|
-
|
|
141
|
+
const color = props.color ?? 0xffffffff;
|
|
142
|
+
const r = (color >>> 24) & 0xff;
|
|
143
|
+
const g = (color >>> 16) & 0xff;
|
|
144
|
+
const b = (color >>> 8) & 0xff;
|
|
145
|
+
const a = color & 0xff;
|
|
146
|
+
context.fillStyle = `rgba(${r},${g},${b},${a / 255})`;
|
|
134
147
|
context.font = font;
|
|
135
148
|
context.textBaseline = 'hanging';
|
|
136
149
|
|
|
@@ -417,12 +417,13 @@ export const isFontLoaded = (fontFamily: string): boolean => {
|
|
|
417
417
|
*/
|
|
418
418
|
export const getFontMetrics = (
|
|
419
419
|
fontFamily: string,
|
|
420
|
-
|
|
421
420
|
fontSize: number,
|
|
422
421
|
): NormalizedFontMetrics => {
|
|
423
|
-
const
|
|
424
|
-
|
|
425
|
-
|
|
422
|
+
const label = fontFamily + '_' + fontSize;
|
|
423
|
+
const metricsCache = normalizedMetrics.get(label);
|
|
424
|
+
|
|
425
|
+
if (metricsCache !== undefined) {
|
|
426
|
+
return metricsCache;
|
|
426
427
|
}
|
|
427
428
|
let metrics = fontCache.get(fontFamily)!.metrics;
|
|
428
429
|
return processFontMetrics(fontFamily, fontSize, metrics);
|
|
@@ -433,7 +434,7 @@ export const processFontMetrics = (
|
|
|
433
434
|
fontSize: number,
|
|
434
435
|
metrics: FontMetrics,
|
|
435
436
|
): NormalizedFontMetrics => {
|
|
436
|
-
const label = fontFamily + fontSize;
|
|
437
|
+
const label = fontFamily + '_' + fontSize;
|
|
437
438
|
const normalized = normalizeFontMetrics(metrics, fontSize);
|
|
438
439
|
normalizedMetrics.set(label, normalized);
|
|
439
440
|
return normalized;
|
|
@@ -32,6 +32,10 @@ const init = (stage: Stage): void => {
|
|
|
32
32
|
};
|
|
33
33
|
|
|
34
34
|
const font: FontHandler = SdfFontHandler;
|
|
35
|
+
const layoutCache = new Map<string, TextLayout>();
|
|
36
|
+
|
|
37
|
+
const getLayoutCacheKey = (props: CoreTextNodeProps): string =>
|
|
38
|
+
`${props.fontFamily}-${props.fontSize}-${props.letterSpacing}-${props.lineHeight}-${props.maxHeight}-${props.maxWidth}-${props.textAlign}-${props.text}`;
|
|
35
39
|
|
|
36
40
|
/**
|
|
37
41
|
* SDF text renderer using MSDF/SDF fonts with WebGL
|
|
@@ -49,6 +53,18 @@ const renderText = (props: CoreTextNodeProps): TextRenderInfo => {
|
|
|
49
53
|
};
|
|
50
54
|
}
|
|
51
55
|
|
|
56
|
+
const cacheKey = getLayoutCacheKey(props);
|
|
57
|
+
let layout = layoutCache.get(cacheKey);
|
|
58
|
+
if (layout !== undefined) {
|
|
59
|
+
return {
|
|
60
|
+
remainingLines: 0,
|
|
61
|
+
hasRemainingText: false,
|
|
62
|
+
width: layout.width,
|
|
63
|
+
height: layout.height,
|
|
64
|
+
layout,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
52
68
|
// Get font cache for this font family
|
|
53
69
|
const fontData = SdfFontHandler.getFontData(props.fontFamily);
|
|
54
70
|
if (fontData === undefined) {
|
|
@@ -60,7 +76,8 @@ const renderText = (props: CoreTextNodeProps): TextRenderInfo => {
|
|
|
60
76
|
}
|
|
61
77
|
|
|
62
78
|
// Calculate text layout and generate glyph data for caching
|
|
63
|
-
|
|
79
|
+
layout = generateTextLayout(props, fontData);
|
|
80
|
+
layoutCache.set(cacheKey, layout);
|
|
64
81
|
|
|
65
82
|
// For SDF renderer, ImageData is null since we render via WebGL
|
|
66
83
|
return {
|
|
@@ -68,7 +85,7 @@ const renderText = (props: CoreTextNodeProps): TextRenderInfo => {
|
|
|
68
85
|
hasRemainingText: false,
|
|
69
86
|
width: layout.width,
|
|
70
87
|
height: layout.height,
|
|
71
|
-
layout,
|
|
88
|
+
layout,
|
|
72
89
|
};
|
|
73
90
|
};
|
|
74
91
|
|
|
@@ -302,14 +319,10 @@ const generateTextLayout = (
|
|
|
302
319
|
|
|
303
320
|
// Calculate glyph position and atlas coordinates (in design units)
|
|
304
321
|
const glyphLayout: GlyphLayout = {
|
|
305
|
-
codepoint,
|
|
306
|
-
glyphId: glyph.id,
|
|
307
322
|
x: currentX + glyph.xoffset,
|
|
308
323
|
y: currentY + glyph.yoffset,
|
|
309
324
|
width: glyph.width,
|
|
310
325
|
height: glyph.height,
|
|
311
|
-
xOffset: glyph.xoffset,
|
|
312
|
-
yOffset: glyph.yoffset,
|
|
313
326
|
atlasX: glyph.x * invAtlasWidth,
|
|
314
327
|
atlasY: glyph.y * invAtlasHeight,
|
|
315
328
|
atlasWidth: glyph.width * invAtlasWidth,
|
|
@@ -236,41 +236,10 @@ export interface TrProps extends TrFontProps {
|
|
|
236
236
|
* Glyph layout information for WebGL rendering
|
|
237
237
|
*/
|
|
238
238
|
export interface GlyphLayout {
|
|
239
|
-
/**
|
|
240
|
-
* Unicode codepoint
|
|
241
|
-
*/
|
|
242
|
-
codepoint: number;
|
|
243
|
-
/**
|
|
244
|
-
* Glyph ID in the font atlas
|
|
245
|
-
*/
|
|
246
|
-
glyphId: number;
|
|
247
|
-
/**
|
|
248
|
-
* X position relative to text origin
|
|
249
|
-
*/
|
|
250
239
|
x: number;
|
|
251
|
-
/**
|
|
252
|
-
* Y position relative to text origin
|
|
253
|
-
*/
|
|
254
240
|
y: number;
|
|
255
|
-
/**
|
|
256
|
-
* Width of glyph in font units
|
|
257
|
-
*/
|
|
258
241
|
width: number;
|
|
259
|
-
/**
|
|
260
|
-
* Height of glyph in font units
|
|
261
|
-
*/
|
|
262
242
|
height: number;
|
|
263
|
-
/**
|
|
264
|
-
* X offset for glyph positioning
|
|
265
|
-
*/
|
|
266
|
-
xOffset: number;
|
|
267
|
-
/**
|
|
268
|
-
* Y offset for glyph positioning
|
|
269
|
-
*/
|
|
270
|
-
yOffset: number;
|
|
271
|
-
/**
|
|
272
|
-
* Atlas texture coordinates (normalized 0-1)
|
|
273
|
-
*/
|
|
274
243
|
atlasX: number;
|
|
275
244
|
atlasY: number;
|
|
276
245
|
atlasWidth: number;
|