@lightningjs/renderer 0.8.4 → 0.9.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/dist/src/core/lib/Matrix3d.d.ts +38 -24
- package/dist/src/core/lib/Matrix3d.js +143 -166
- package/dist/src/core/lib/Matrix3d.js.map +1 -1
- package/dist/src/core/text-rendering/TextTextureRendererUtils.d.ts +19 -0
- package/dist/src/core/text-rendering/TextTextureRendererUtils.js +61 -0
- package/dist/src/core/text-rendering/TextTextureRendererUtils.js.map +1 -1
- package/dist/src/core/text-rendering/font-face-types/SdfTrFontFace/SdfTrFontFace.d.ts +8 -2
- package/dist/src/core/text-rendering/font-face-types/SdfTrFontFace/SdfTrFontFace.js +24 -3
- package/dist/src/core/text-rendering/font-face-types/SdfTrFontFace/SdfTrFontFace.js.map +1 -1
- package/dist/src/core/text-rendering/font-face-types/SdfTrFontFace/internal/SdfFontShaper.d.ts +2 -0
- package/dist/src/core/text-rendering/font-face-types/SdfTrFontFace/internal/SdfFontShaper.js.map +1 -1
- package/dist/src/core/text-rendering/font-face-types/TrFontFace.d.ts +72 -1
- package/dist/src/core/text-rendering/font-face-types/TrFontFace.js +11 -1
- package/dist/src/core/text-rendering/font-face-types/TrFontFace.js.map +1 -1
- package/dist/src/core/text-rendering/font-face-types/WebTrFontFace.d.ts +5 -2
- package/dist/src/core/text-rendering/font-face-types/WebTrFontFace.js +4 -3
- package/dist/src/core/text-rendering/font-face-types/WebTrFontFace.js.map +1 -1
- package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.d.ts +8 -0
- package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.js +42 -16
- package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.js.map +1 -1
- package/dist/src/core/text-rendering/renderers/LightningTextTextureRenderer.d.ts +7 -1
- package/dist/src/core/text-rendering/renderers/LightningTextTextureRenderer.js +42 -11
- package/dist/src/core/text-rendering/renderers/LightningTextTextureRenderer.js.map +1 -1
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.d.ts +4 -0
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js +26 -9
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js.map +1 -1
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/getStartConditions.d.ts +2 -1
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/getStartConditions.js +32 -5
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/getStartConditions.js.map +1 -1
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.d.ts +2 -1
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.js +2 -1
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.js.map +1 -1
- package/dist/src/core/text-rendering/renderers/TextRenderer.d.ts +8 -4
- package/dist/src/core/text-rendering/renderers/TextRenderer.js +6 -5
- package/dist/src/core/text-rendering/renderers/TextRenderer.js.map +1 -1
- package/dist/src/main-api/RendererMain.js +2 -2
- package/dist/src/main-api/RendererMain.js.map +1 -1
- package/dist/src/render-drivers/main/MainOnlyTextNode.js +1 -3
- package/dist/src/render-drivers/main/MainOnlyTextNode.js.map +1 -1
- package/dist/src/render-drivers/threadx/TextNodeStruct.js +3 -1
- package/dist/src/render-drivers/threadx/TextNodeStruct.js.map +1 -1
- package/dist/tsconfig.dist.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/core/lib/Matrix3d.ts +144 -190
- package/src/core/text-rendering/TextRenderingUtils.ts +36 -0
- package/src/core/text-rendering/TextTextureRendererUtils.ts +74 -0
- package/src/core/text-rendering/font-face-types/SdfTrFontFace/SdfTrFontFace.ts +41 -12
- package/src/core/text-rendering/font-face-types/SdfTrFontFace/internal/SdfFontShaper.ts +2 -0
- package/src/core/text-rendering/font-face-types/TrFontFace.ts +86 -1
- package/src/core/text-rendering/font-face-types/WebTrFontFace.ts +13 -7
- package/src/core/text-rendering/renderers/CanvasTextRenderer.ts +52 -20
- package/src/core/text-rendering/renderers/LightningTextTextureRenderer.ts +59 -13
- package/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.ts +30 -9
- package/src/core/text-rendering/renderers/SdfTextRenderer/internal/getStartConditions.ts +38 -5
- package/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.ts +5 -2
- package/src/core/text-rendering/renderers/TextRenderer.ts +14 -10
- package/src/main-api/RendererMain.ts +2 -2
- package/src/render-drivers/main/MainOnlyTextNode.ts +1 -3
- package/src/render-drivers/threadx/TextNodeStruct.ts +3 -1
|
@@ -64,13 +64,98 @@ export interface TrFontFaceDescriptors {
|
|
|
64
64
|
variant?: string;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
export interface FontMetrics {
|
|
68
|
+
/**
|
|
69
|
+
* The distance, in font units, from the baseline to the highest point of the font.
|
|
70
|
+
*/
|
|
71
|
+
ascender: number;
|
|
72
|
+
/**
|
|
73
|
+
* The distance, in font units, from the baseline to the lowest point of the font.
|
|
74
|
+
*/
|
|
75
|
+
descender: number;
|
|
76
|
+
/**
|
|
77
|
+
* The additional space used in the calculation of the default line height in font units.
|
|
78
|
+
*/
|
|
79
|
+
lineGap: number;
|
|
80
|
+
/**
|
|
81
|
+
* The number of font units per 1 EM.
|
|
82
|
+
*/
|
|
83
|
+
unitsPerEm: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface NormalizedFontMetrics {
|
|
87
|
+
/**
|
|
88
|
+
* The distance, as a fraction of 1 EM, from the baseline to the highest point of the font.
|
|
89
|
+
*
|
|
90
|
+
* @remarks
|
|
91
|
+
* This value should be positive.
|
|
92
|
+
*/
|
|
93
|
+
ascender: number;
|
|
94
|
+
/**
|
|
95
|
+
* The distance, as a fraction of 1 EM, from the baseline to the lowest point of the font.
|
|
96
|
+
*
|
|
97
|
+
* @remarks
|
|
98
|
+
* This value should be positive.
|
|
99
|
+
*/
|
|
100
|
+
descender: number;
|
|
101
|
+
/**
|
|
102
|
+
* The additional space used in the calculation of the default line height as a fraction of 1 EM
|
|
103
|
+
*
|
|
104
|
+
* @remarks
|
|
105
|
+
* This value should be positive.
|
|
106
|
+
*/
|
|
107
|
+
lineGap: number;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface TrFontFaceOptions {
|
|
111
|
+
fontFamily: string;
|
|
112
|
+
descriptors: Partial<TrFontFaceDescriptors>;
|
|
113
|
+
/**
|
|
114
|
+
* Font metrics used for layout and default line height calculations.
|
|
115
|
+
*
|
|
116
|
+
* @remarks
|
|
117
|
+
* Provides the font metrics in order to ensure consistent text layout across
|
|
118
|
+
* different font types (Canvas/Web, SDF, etc.).
|
|
119
|
+
*
|
|
120
|
+
* **Important**: We HIGHLY recommend providing these metrics for both SDF
|
|
121
|
+
* and Web/Canvas fonts. You can use the Lightning 3 [msdf-generator](https://github.com/lightning-js/msdf-generator/)
|
|
122
|
+
* tool to generate these metrics from a font file. The metrics generated by
|
|
123
|
+
* this tool can be used with both SDF and Web/Canvas fonts.
|
|
124
|
+
*
|
|
125
|
+
* If not provided, a warning will be logged and the text layout may not be
|
|
126
|
+
* consistent across different font types.
|
|
127
|
+
*
|
|
128
|
+
* If not provided, the metrics will be gathered depending on the font type.
|
|
129
|
+
* - For SDF fonts, the metrics will be gathered from the font atlas data.
|
|
130
|
+
* If the font is generated using Lightning 3's msdf-generator tool, the
|
|
131
|
+
* metrics will be the most accurate.
|
|
132
|
+
* - For Web/Canvas fonts, the metrics will be gathered from the
|
|
133
|
+
* CanvasRenderingContext2D.measureText() method. The accuracy/consistency
|
|
134
|
+
* of these metrics depends on the browser implementation. And will not
|
|
135
|
+
* guarantee the line-by-line alignment of text with SDF fonts.
|
|
136
|
+
*/
|
|
137
|
+
metrics?: FontMetrics;
|
|
138
|
+
}
|
|
139
|
+
|
|
67
140
|
export class TrFontFace extends EventEmitter {
|
|
68
141
|
public readonly fontFamily: string;
|
|
69
142
|
public readonly descriptors: TrFontFaceDescriptors;
|
|
70
143
|
public readonly loaded: boolean = false;
|
|
144
|
+
public readonly metrics: NormalizedFontMetrics | null = null;
|
|
71
145
|
|
|
72
|
-
constructor(
|
|
146
|
+
constructor(options: TrFontFaceOptions) {
|
|
73
147
|
super();
|
|
148
|
+
const { fontFamily, descriptors, metrics } = options;
|
|
149
|
+
|
|
150
|
+
if (metrics) {
|
|
151
|
+
// Normalize metrics to be in the range of 0 to 1
|
|
152
|
+
this.metrics = {
|
|
153
|
+
ascender: metrics.ascender / metrics.unitsPerEm,
|
|
154
|
+
descender: metrics.descender / metrics.unitsPerEm,
|
|
155
|
+
lineGap: metrics.lineGap / metrics.unitsPerEm,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
74
159
|
this.fontFamily = fontFamily;
|
|
75
160
|
this.descriptors = {
|
|
76
161
|
style: 'normal',
|
|
@@ -17,7 +17,11 @@
|
|
|
17
17
|
* limitations under the License.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
TrFontFace,
|
|
22
|
+
type TrFontFaceDescriptors,
|
|
23
|
+
type TrFontFaceOptions,
|
|
24
|
+
} from './TrFontFace.js';
|
|
21
25
|
|
|
22
26
|
declare module './TrFontFace.js' {
|
|
23
27
|
interface TrFontFaceMap {
|
|
@@ -25,16 +29,18 @@ declare module './TrFontFace.js' {
|
|
|
25
29
|
}
|
|
26
30
|
}
|
|
27
31
|
|
|
32
|
+
export interface WebTrFontFaceOptions extends TrFontFaceOptions {
|
|
33
|
+
fontUrl: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
28
36
|
export class WebTrFontFace extends TrFontFace {
|
|
29
37
|
public readonly fontFace: FontFace;
|
|
30
38
|
public readonly fontUrl: string;
|
|
31
39
|
|
|
32
|
-
constructor(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
fontUrl
|
|
36
|
-
) {
|
|
37
|
-
super(fontFamily, descriptors);
|
|
40
|
+
constructor(options: WebTrFontFaceOptions) {
|
|
41
|
+
super(options);
|
|
42
|
+
|
|
43
|
+
const { fontFamily, fontUrl } = options;
|
|
38
44
|
|
|
39
45
|
// Filter out parentheses from fontUrl
|
|
40
46
|
const fontUrlWithoutParentheses = fontUrl.replace(/\(|\)/g, '');
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
type RectWithValid,
|
|
34
34
|
} from '../../lib/utils.js';
|
|
35
35
|
import type { ImageTexture } from '../../textures/ImageTexture.js';
|
|
36
|
+
import { TrFontManager, type FontFamilyMap } from '../TrFontManager.js';
|
|
36
37
|
import type { TrFontFace } from '../font-face-types/TrFontFace.js';
|
|
37
38
|
import { WebTrFontFace } from '../font-face-types/WebTrFontFace.js';
|
|
38
39
|
import {
|
|
@@ -81,6 +82,7 @@ export interface CanvasTextRendererState extends TextRendererState {
|
|
|
81
82
|
fontFaceLoadedHandler: (() => void) | undefined;
|
|
82
83
|
fontInfo:
|
|
83
84
|
| {
|
|
85
|
+
fontFace: WebTrFontFace;
|
|
84
86
|
cssString: string;
|
|
85
87
|
loaded: boolean;
|
|
86
88
|
}
|
|
@@ -107,6 +109,12 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
|
|
|
107
109
|
| OffscreenCanvasRenderingContext2D
|
|
108
110
|
| CanvasRenderingContext2D;
|
|
109
111
|
private rendererBounds: Bound;
|
|
112
|
+
/**
|
|
113
|
+
* Font family map used to store web font faces that were added to the
|
|
114
|
+
* canvas text renderer.
|
|
115
|
+
*/
|
|
116
|
+
private fontFamilies: FontFamilyMap = {};
|
|
117
|
+
private fontFamilyArray: FontFamilyMap[] = [this.fontFamilies];
|
|
110
118
|
|
|
111
119
|
constructor(stage: Stage) {
|
|
112
120
|
super(stage);
|
|
@@ -135,6 +143,14 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
|
|
|
135
143
|
x2: this.stage.options.appWidth,
|
|
136
144
|
y2: this.stage.options.appHeight,
|
|
137
145
|
};
|
|
146
|
+
// Install the default 'san-serif' font face
|
|
147
|
+
this.addFontFace(
|
|
148
|
+
new WebTrFontFace({
|
|
149
|
+
fontFamily: 'sans-serif',
|
|
150
|
+
descriptors: {},
|
|
151
|
+
fontUrl: '',
|
|
152
|
+
}),
|
|
153
|
+
);
|
|
138
154
|
}
|
|
139
155
|
|
|
140
156
|
//#region Overrides
|
|
@@ -259,10 +275,25 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
|
|
|
259
275
|
// the `isFontFaceSupported` check)
|
|
260
276
|
assertTruthy(fontFace instanceof WebTrFontFace);
|
|
261
277
|
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
|
|
278
|
+
// Add the font face to the document
|
|
279
|
+
// Except for the 'sans-serif' font family, which the Renderer provides
|
|
280
|
+
// as a special default fallback.
|
|
281
|
+
if (fontFace.fontFamily !== 'sans-serif') {
|
|
282
|
+
// @ts-expect-error `add()` method should be available from a FontFaceSet
|
|
283
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
284
|
+
globalFontSet.add(fontFace.fontFace);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const { fontFamilies } = this;
|
|
288
|
+
const familyName = fontFace.fontFace.family;
|
|
289
|
+
|
|
290
|
+
let faceSet = fontFamilies[familyName];
|
|
291
|
+
if (!faceSet) {
|
|
292
|
+
faceSet = new Set();
|
|
293
|
+
fontFamilies[familyName] = faceSet;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
faceSet.add(fontFace);
|
|
266
297
|
}
|
|
267
298
|
|
|
268
299
|
override createState(props: TrProps): CanvasTextRendererState {
|
|
@@ -312,7 +343,13 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
|
|
|
312
343
|
// If fontInfo is invalid, we need to establish it
|
|
313
344
|
if (!state.fontInfo) {
|
|
314
345
|
const cssString = getFontCssString(state.props);
|
|
346
|
+
const trFontFace = TrFontManager.resolveFontFace(
|
|
347
|
+
this.fontFamilyArray,
|
|
348
|
+
state.props,
|
|
349
|
+
) as WebTrFontFace | undefined;
|
|
350
|
+
assertTruthy(trFontFace, `Could not resolve font face for ${cssString}`);
|
|
315
351
|
state.fontInfo = {
|
|
352
|
+
fontFace: trFontFace,
|
|
316
353
|
cssString: cssString,
|
|
317
354
|
// TODO: For efficiency we would use this here but it's not reliable on WPE -> document.fonts.check(cssString),
|
|
318
355
|
loaded: false,
|
|
@@ -333,22 +370,11 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
|
|
|
333
370
|
}
|
|
334
371
|
|
|
335
372
|
if (!state.renderInfo) {
|
|
336
|
-
const maxLines = state.props.maxLines;
|
|
337
|
-
const containedMaxLines =
|
|
338
|
-
state.props.contain === 'both'
|
|
339
|
-
? Math.floor(
|
|
340
|
-
(state.props.height - state.props.offsetY) /
|
|
341
|
-
state.props.lineHeight,
|
|
342
|
-
)
|
|
343
|
-
: 0;
|
|
344
|
-
const calcMaxLines =
|
|
345
|
-
containedMaxLines > 0 && maxLines > 0
|
|
346
|
-
? Math.min(containedMaxLines, maxLines)
|
|
347
|
-
: Math.max(containedMaxLines, maxLines);
|
|
348
373
|
state.lightning2TextRenderer.settings = {
|
|
349
374
|
text: state.props.text,
|
|
350
375
|
textAlign: state.props.textAlign,
|
|
351
|
-
|
|
376
|
+
fontFamily: state.props.fontFamily,
|
|
377
|
+
trFontFace: state.fontInfo.fontFace,
|
|
352
378
|
fontSize: state.props.fontSize,
|
|
353
379
|
fontStyle: [
|
|
354
380
|
state.props.fontStretch,
|
|
@@ -356,16 +382,21 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
|
|
|
356
382
|
state.props.fontWeight,
|
|
357
383
|
].join(' '),
|
|
358
384
|
textColor: getNormalizedRgbaComponents(state.props.color),
|
|
359
|
-
offsetY: state.props.
|
|
385
|
+
offsetY: state.props.offsetY,
|
|
360
386
|
wordWrap: state.props.contain !== 'none',
|
|
361
387
|
wordWrapWidth:
|
|
362
388
|
state.props.contain === 'none' ? undefined : state.props.width,
|
|
363
389
|
letterSpacing: state.props.letterSpacing,
|
|
364
|
-
lineHeight: state.props.lineHeight,
|
|
365
|
-
maxLines:
|
|
390
|
+
lineHeight: state.props.lineHeight ?? null,
|
|
391
|
+
maxLines: state.props.maxLines,
|
|
392
|
+
maxHeight:
|
|
393
|
+
state.props.contain === 'both'
|
|
394
|
+
? state.props.height - state.props.offsetY
|
|
395
|
+
: null,
|
|
366
396
|
textBaseline: state.props.textBaseline,
|
|
367
397
|
verticalAlign: state.props.verticalAlign,
|
|
368
398
|
overflowSuffix: state.props.overflowSuffix,
|
|
399
|
+
w: state.props.contain !== 'none' ? state.props.width : undefined,
|
|
369
400
|
};
|
|
370
401
|
// const renderInfoCalculateTime = performance.now();
|
|
371
402
|
state.renderInfo = state.lightning2TextRenderer.calculateRenderInfo();
|
|
@@ -712,6 +743,7 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
|
|
|
712
743
|
}
|
|
713
744
|
|
|
714
745
|
override destroyState(state: CanvasTextRendererState): void {
|
|
746
|
+
super.destroyState(state);
|
|
715
747
|
// Remove state object owner from any canvas page textures
|
|
716
748
|
state.canvasPages?.forEach((pageInfo) => {
|
|
717
749
|
pageInfo.texture?.setRenderableOwner(state, false);
|
|
@@ -19,7 +19,12 @@
|
|
|
19
19
|
|
|
20
20
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
21
21
|
|
|
22
|
+
import { assertTruthy } from '../../../utils.js';
|
|
22
23
|
import { getRgbaString, type RGBA } from '../../lib/utils.js';
|
|
24
|
+
import { calcDefaultLineHeight } from '../TextRenderingUtils.js';
|
|
25
|
+
import { getWebFontMetrics } from '../TextTextureRendererUtils.js';
|
|
26
|
+
import type { NormalizedFontMetrics } from '../font-face-types/TrFontFace.js';
|
|
27
|
+
import type { WebTrFontFace } from '../font-face-types/WebTrFontFace.js';
|
|
23
28
|
|
|
24
29
|
const MAX_TEXTURE_DIMENSION = 2048;
|
|
25
30
|
|
|
@@ -62,7 +67,8 @@ export interface Settings {
|
|
|
62
67
|
fontStyle: string;
|
|
63
68
|
fontSize: number;
|
|
64
69
|
fontBaselineRatio: number;
|
|
65
|
-
|
|
70
|
+
fontFamily: string | null;
|
|
71
|
+
trFontFace: WebTrFontFace | null;
|
|
66
72
|
wordWrap: boolean;
|
|
67
73
|
wordWrapWidth: number;
|
|
68
74
|
wordBreak: boolean;
|
|
@@ -73,6 +79,7 @@ export interface Settings {
|
|
|
73
79
|
verticalAlign: TextVerticalAlign;
|
|
74
80
|
offsetY: number | null;
|
|
75
81
|
maxLines: number;
|
|
82
|
+
maxHeight: number | null;
|
|
76
83
|
overflowSuffix: string;
|
|
77
84
|
precision: number;
|
|
78
85
|
textColor: RGBA;
|
|
@@ -117,12 +124,14 @@ export interface RenderInfo {
|
|
|
117
124
|
cutEx: number;
|
|
118
125
|
cutEy: number;
|
|
119
126
|
lineHeight: number;
|
|
127
|
+
defLineHeight: number;
|
|
120
128
|
lineWidths: number[];
|
|
121
129
|
offsetY: number;
|
|
122
130
|
paddingLeft: number;
|
|
123
131
|
paddingRight: number;
|
|
124
132
|
letterSpacing: number;
|
|
125
133
|
textIndent: number;
|
|
134
|
+
metrics: NormalizedFontMetrics;
|
|
126
135
|
}
|
|
127
136
|
|
|
128
137
|
/**
|
|
@@ -186,7 +195,7 @@ export class LightningTextTextureRenderer {
|
|
|
186
195
|
}
|
|
187
196
|
|
|
188
197
|
_getFontSetting() {
|
|
189
|
-
const ff = [this._settings.
|
|
198
|
+
const ff = [this._settings.fontFamily];
|
|
190
199
|
|
|
191
200
|
const ffs = [];
|
|
192
201
|
for (let i = 0, n = ff.length; i < n; i++) {
|
|
@@ -238,7 +247,6 @@ export class LightningTextTextureRenderer {
|
|
|
238
247
|
this._settings.offsetY === null
|
|
239
248
|
? null
|
|
240
249
|
: this._settings.offsetY * precision;
|
|
241
|
-
let lineHeight = (this._settings.lineHeight || fontSize) * precision;
|
|
242
250
|
const w = this._settings.w * precision;
|
|
243
251
|
const h = this._settings.h * precision;
|
|
244
252
|
let wordWrapWidth = this._settings.wordWrapWidth * precision;
|
|
@@ -248,10 +256,31 @@ export class LightningTextTextureRenderer {
|
|
|
248
256
|
const cutEy = this._settings.cutEy * precision;
|
|
249
257
|
const letterSpacing = (this._settings.letterSpacing || 0) * precision;
|
|
250
258
|
const textIndent = this._settings.textIndent * precision;
|
|
259
|
+
const trFontFace = this._settings.trFontFace;
|
|
251
260
|
|
|
252
261
|
// Set font properties.
|
|
253
262
|
this.setFontProperties();
|
|
254
263
|
|
|
264
|
+
assertTruthy(trFontFace);
|
|
265
|
+
const metrics = getWebFontMetrics(this._context, trFontFace, fontSize);
|
|
266
|
+
const defLineHeight = calcDefaultLineHeight(metrics, fontSize) * precision;
|
|
267
|
+
const lineHeight =
|
|
268
|
+
this._settings.lineHeight !== null
|
|
269
|
+
? this._settings.lineHeight * precision
|
|
270
|
+
: defLineHeight;
|
|
271
|
+
|
|
272
|
+
const maxHeight = this._settings.maxHeight;
|
|
273
|
+
const containedMaxLines =
|
|
274
|
+
maxHeight !== null && lineHeight > 0
|
|
275
|
+
? Math.floor(maxHeight / lineHeight)
|
|
276
|
+
: 0;
|
|
277
|
+
|
|
278
|
+
const setMaxLines = this._settings.maxLines;
|
|
279
|
+
const calcMaxLines =
|
|
280
|
+
containedMaxLines > 0 && setMaxLines > 0
|
|
281
|
+
? Math.min(containedMaxLines, setMaxLines)
|
|
282
|
+
: Math.max(containedMaxLines, setMaxLines);
|
|
283
|
+
|
|
255
284
|
// Total width.
|
|
256
285
|
let width = w || 2048 / this.getPrecision();
|
|
257
286
|
|
|
@@ -305,8 +334,8 @@ export class LightningTextTextureRenderer {
|
|
|
305
334
|
}
|
|
306
335
|
let lines = linesInfo.l;
|
|
307
336
|
|
|
308
|
-
if (
|
|
309
|
-
const usedLines = lines.slice(0,
|
|
337
|
+
if (calcMaxLines && lines.length > calcMaxLines) {
|
|
338
|
+
const usedLines = lines.slice(0, calcMaxLines);
|
|
310
339
|
|
|
311
340
|
let otherLines = null;
|
|
312
341
|
if (this._settings.overflowSuffix) {
|
|
@@ -333,7 +362,7 @@ export class LightningTextTextureRenderer {
|
|
|
333
362
|
const n = lines.length;
|
|
334
363
|
let j = 0;
|
|
335
364
|
const m = linesInfo.n.length;
|
|
336
|
-
for (i =
|
|
365
|
+
for (i = calcMaxLines; i < n; i++) {
|
|
337
366
|
otherLines[j] += `${otherLines[j] ? ' ' : ''}${lines[i]!}`;
|
|
338
367
|
if (i + 1 < m && linesInfo.n[i + 1]) {
|
|
339
368
|
j++;
|
|
@@ -368,9 +397,6 @@ export class LightningTextTextureRenderer {
|
|
|
368
397
|
innerWidth = maxLineWidth;
|
|
369
398
|
}
|
|
370
399
|
|
|
371
|
-
// calculate text height
|
|
372
|
-
lineHeight = lineHeight || fontSize;
|
|
373
|
-
|
|
374
400
|
let height;
|
|
375
401
|
if (h) {
|
|
376
402
|
height = h;
|
|
@@ -420,12 +446,14 @@ export class LightningTextTextureRenderer {
|
|
|
420
446
|
renderInfo.cutEx = cutEx;
|
|
421
447
|
renderInfo.cutEy = cutEy;
|
|
422
448
|
renderInfo.lineHeight = lineHeight;
|
|
449
|
+
renderInfo.defLineHeight = defLineHeight;
|
|
423
450
|
renderInfo.lineWidths = lineWidths;
|
|
424
451
|
renderInfo.offsetY = offsetY;
|
|
425
452
|
renderInfo.paddingLeft = paddingLeft;
|
|
426
453
|
renderInfo.paddingRight = paddingRight;
|
|
427
454
|
renderInfo.letterSpacing = letterSpacing;
|
|
428
455
|
renderInfo.textIndent = textIndent;
|
|
456
|
+
renderInfo.metrics = metrics;
|
|
429
457
|
|
|
430
458
|
return renderInfo as RenderInfo;
|
|
431
459
|
}
|
|
@@ -477,17 +505,33 @@ export class LightningTextTextureRenderer {
|
|
|
477
505
|
|
|
478
506
|
const drawLines = [];
|
|
479
507
|
|
|
508
|
+
const { metrics } = renderInfo;
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Ascender (in pixels)
|
|
512
|
+
*/
|
|
513
|
+
const ascenderPx = metrics
|
|
514
|
+
? metrics.ascender * renderInfo.fontSize
|
|
515
|
+
: renderInfo.fontSize;
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* Bare line height is the distance between the ascender and descender of the font.
|
|
519
|
+
* without the line gap metric.
|
|
520
|
+
*/
|
|
521
|
+
const bareLineHeightPx =
|
|
522
|
+
(metrics.ascender - metrics.descender) * renderInfo.fontSize;
|
|
523
|
+
|
|
480
524
|
// Draw lines line by line.
|
|
481
525
|
for (let i = 0, n = lines.length; i < n; i++) {
|
|
482
526
|
linePositionX = i === 0 ? renderInfo.textIndent : 0;
|
|
483
527
|
|
|
484
528
|
// By default, text is aligned to top
|
|
485
|
-
linePositionY = i * renderInfo.lineHeight +
|
|
529
|
+
linePositionY = i * renderInfo.lineHeight + ascenderPx;
|
|
486
530
|
|
|
487
531
|
if (this._settings.verticalAlign == 'middle') {
|
|
488
|
-
linePositionY += (renderInfo.lineHeight -
|
|
532
|
+
linePositionY += (renderInfo.lineHeight - bareLineHeightPx) / 2;
|
|
489
533
|
} else if (this._settings.verticalAlign == 'bottom') {
|
|
490
|
-
linePositionY += renderInfo.lineHeight -
|
|
534
|
+
linePositionY += renderInfo.lineHeight - bareLineHeightPx;
|
|
491
535
|
}
|
|
492
536
|
|
|
493
537
|
if (this._settings.textAlign === 'right') {
|
|
@@ -699,7 +743,8 @@ export class LightningTextTextureRenderer {
|
|
|
699
743
|
h: 0,
|
|
700
744
|
fontStyle: 'normal',
|
|
701
745
|
fontSize: 40,
|
|
702
|
-
|
|
746
|
+
fontFamily: null,
|
|
747
|
+
trFontFace: null,
|
|
703
748
|
wordWrap: true,
|
|
704
749
|
wordWrapWidth: 0,
|
|
705
750
|
wordBreak: false,
|
|
@@ -710,6 +755,7 @@ export class LightningTextTextureRenderer {
|
|
|
710
755
|
verticalAlign: 'top',
|
|
711
756
|
offsetY: null,
|
|
712
757
|
maxLines: 0,
|
|
758
|
+
maxHeight: null,
|
|
713
759
|
overflowSuffix: '...',
|
|
714
760
|
textColor: [1.0, 1.0, 1.0, 1.0],
|
|
715
761
|
paddingLeft: 0,
|
|
@@ -58,6 +58,7 @@ import { EventEmitter } from '../../../../common/EventEmitter.js';
|
|
|
58
58
|
import type { Matrix3d } from '../../../lib/Matrix3d.js';
|
|
59
59
|
import type { Dimensions } from '../../../../common/CommonTypes.js';
|
|
60
60
|
import { WebGlCoreRenderer } from '../../../renderers/webgl/WebGlCoreRenderer.js';
|
|
61
|
+
import { calcDefaultLineHeight } from '../../TextRenderingUtils.js';
|
|
61
62
|
|
|
62
63
|
declare module '../TextRenderer.js' {
|
|
63
64
|
interface TextRendererMap {
|
|
@@ -105,6 +106,11 @@ export interface SdfTextRendererState extends TextRendererState {
|
|
|
105
106
|
distanceRange: number;
|
|
106
107
|
|
|
107
108
|
trFontFace: SdfTrFontFace | undefined;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Resolved line height in logical screen pixel units
|
|
112
|
+
*/
|
|
113
|
+
resLineHeight: number | undefined;
|
|
108
114
|
}
|
|
109
115
|
|
|
110
116
|
/**
|
|
@@ -251,6 +257,7 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
|
|
|
251
257
|
},
|
|
252
258
|
lineHeight: (state, value) => {
|
|
253
259
|
state.props.lineHeight = value;
|
|
260
|
+
state.resLineHeight = undefined;
|
|
254
261
|
this.invalidateLayoutCache(state);
|
|
255
262
|
},
|
|
256
263
|
maxLines: (state, value) => {
|
|
@@ -363,6 +370,7 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
|
|
|
363
370
|
distanceRange: 0,
|
|
364
371
|
trFontFace: undefined,
|
|
365
372
|
isRenderable: false,
|
|
373
|
+
resLineHeight: undefined,
|
|
366
374
|
debugData: {
|
|
367
375
|
updateCount: 0,
|
|
368
376
|
layoutCount: 0,
|
|
@@ -409,6 +417,7 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
|
|
|
409
417
|
|
|
410
418
|
// If the font is loaded then so should the data
|
|
411
419
|
assertTruthy(trFontFace.data, 'Font face data should be loaded');
|
|
420
|
+
assertTruthy(trFontFace.metrics, 'Font face metrics should be loaded');
|
|
412
421
|
|
|
413
422
|
const {
|
|
414
423
|
text,
|
|
@@ -418,7 +427,6 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
|
|
|
418
427
|
contain,
|
|
419
428
|
width,
|
|
420
429
|
height,
|
|
421
|
-
lineHeight,
|
|
422
430
|
verticalAlign,
|
|
423
431
|
scrollable,
|
|
424
432
|
overflowSuffix,
|
|
@@ -441,8 +449,21 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
|
|
|
441
449
|
*/
|
|
442
450
|
const fontSizeRatio = fontSize / sdfFontSize;
|
|
443
451
|
|
|
452
|
+
// If not already resolved, resolve the line height and store it in the state
|
|
453
|
+
let resLineHeight = state.resLineHeight;
|
|
454
|
+
if (resLineHeight === undefined) {
|
|
455
|
+
const lineHeight = state.props.lineHeight;
|
|
456
|
+
// If lineHeight is undefined, use the maxCharHeight from the font face
|
|
457
|
+
if (lineHeight === undefined) {
|
|
458
|
+
resLineHeight = calcDefaultLineHeight(trFontFace.metrics, fontSize);
|
|
459
|
+
} else {
|
|
460
|
+
resLineHeight = lineHeight;
|
|
461
|
+
}
|
|
462
|
+
state.resLineHeight = resLineHeight;
|
|
463
|
+
}
|
|
464
|
+
|
|
444
465
|
// Needed in renderWindow calculation
|
|
445
|
-
const sdfLineHeight =
|
|
466
|
+
const sdfLineHeight = resLineHeight / fontSizeRatio;
|
|
446
467
|
|
|
447
468
|
state.distanceRange =
|
|
448
469
|
fontSizeRatio * trFontFace.data.distanceField.distanceRange;
|
|
@@ -499,7 +520,7 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
|
|
|
499
520
|
x,
|
|
500
521
|
y,
|
|
501
522
|
scrollY,
|
|
502
|
-
|
|
523
|
+
resLineHeight,
|
|
503
524
|
contain === 'both' ? elementBounds.y2 - elementBounds.y1 : 0,
|
|
504
525
|
elementBounds,
|
|
505
526
|
fontSizeRatio,
|
|
@@ -510,7 +531,7 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
|
|
|
510
531
|
const start = getStartConditions(
|
|
511
532
|
sdfFontSize,
|
|
512
533
|
sdfLineHeight,
|
|
513
|
-
|
|
534
|
+
trFontFace,
|
|
514
535
|
verticalAlign,
|
|
515
536
|
offsetY,
|
|
516
537
|
fontSizeRatio,
|
|
@@ -537,7 +558,7 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
|
|
|
537
558
|
width,
|
|
538
559
|
height,
|
|
539
560
|
fontSize,
|
|
540
|
-
|
|
561
|
+
resLineHeight,
|
|
541
562
|
letterSpacing,
|
|
542
563
|
vertexBuffer,
|
|
543
564
|
contain,
|
|
@@ -561,7 +582,7 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
|
|
|
561
582
|
// If we didn't exit early, we know we have completely computed w/h
|
|
562
583
|
if (out2.fullyProcessed) {
|
|
563
584
|
state.textW = out2.maxX * fontSizeRatio;
|
|
564
|
-
state.textH = out2.
|
|
585
|
+
state.textH = out2.numLines * sdfLineHeight * fontSizeRatio;
|
|
565
586
|
}
|
|
566
587
|
|
|
567
588
|
// if (state.props.debug.printLayoutTime) {
|
|
@@ -584,8 +605,7 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
|
|
|
584
605
|
return;
|
|
585
606
|
}
|
|
586
607
|
|
|
587
|
-
const renderer
|
|
588
|
-
.renderer as WebGlCoreRenderer;
|
|
608
|
+
const renderer = this.stage.renderer;
|
|
589
609
|
assertTruthy(renderer instanceof WebGlCoreRenderer);
|
|
590
610
|
|
|
591
611
|
const { fontSize, color, contain, scrollable, zIndex, debug } = state.props;
|
|
@@ -670,7 +690,7 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
|
|
|
670
690
|
webGlBuffers,
|
|
671
691
|
this.sdfShader,
|
|
672
692
|
{
|
|
673
|
-
transform: transform.
|
|
693
|
+
transform: transform.getFloatArr(),
|
|
674
694
|
// IMPORTANT: The SDF Shader expects the color NOT to be premultiplied
|
|
675
695
|
// for the best blending results. Which is why we use `mergeColorAlpha`
|
|
676
696
|
// instead of `mergeColorAlphaPremultiplied` here.
|
|
@@ -780,6 +800,7 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
|
|
|
780
800
|
* @param state
|
|
781
801
|
*/
|
|
782
802
|
protected releaseFontFace(state: SdfTextRendererState) {
|
|
803
|
+
state.resLineHeight = undefined;
|
|
783
804
|
if (state.trFontFace) {
|
|
784
805
|
state.trFontFace.texture.setRenderableOwner(state, false);
|
|
785
806
|
state.trFontFace = undefined;
|
|
@@ -17,7 +17,9 @@
|
|
|
17
17
|
* limitations under the License.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
+
import { assertTruthy } from '../../../../../utils.js';
|
|
20
21
|
import type { Bound } from '../../../../lib/utils.js';
|
|
22
|
+
import type { SdfTrFontFace } from '../../../font-face-types/SdfTrFontFace/SdfTrFontFace.js';
|
|
21
23
|
import type { TrProps, TextRendererState } from '../../TextRenderer.js';
|
|
22
24
|
import type { SdfTextRendererState } from '../SdfTextRenderer.js';
|
|
23
25
|
import type { SdfRenderWindow } from './setRenderWindow.js';
|
|
@@ -39,7 +41,7 @@ import type { SdfRenderWindow } from './setRenderWindow.js';
|
|
|
39
41
|
export function getStartConditions(
|
|
40
42
|
sdfFontSize: number,
|
|
41
43
|
sdfLineHeight: number,
|
|
42
|
-
|
|
44
|
+
fontFace: SdfTrFontFace,
|
|
43
45
|
verticalAlign: TrProps['verticalAlign'],
|
|
44
46
|
offsetY: TrProps['offsetY'],
|
|
45
47
|
fontSizeRatio: number,
|
|
@@ -59,17 +61,48 @@ export function getStartConditions(
|
|
|
59
61
|
lineCache.length,
|
|
60
62
|
);
|
|
61
63
|
|
|
62
|
-
// TODO: (fontSize / 6.4286 / fontSizeRatio) Adding this to the startY helps the text line up better with Canvas rendered text
|
|
63
64
|
const sdfStartX = 0;
|
|
65
|
+
const { metrics } = fontFace;
|
|
66
|
+
assertTruthy(metrics, 'Font metrics not loaded');
|
|
67
|
+
assertTruthy(fontFace.data, 'Font data not loaded');
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Bare line height is the distance between the ascender and descender of the font.
|
|
71
|
+
* without the line gap metric.
|
|
72
|
+
*/
|
|
73
|
+
const sdfBareLineHeight =
|
|
74
|
+
(metrics.ascender - metrics.descender) * sdfFontSize;
|
|
64
75
|
let sdfVerticalAlignYOffset = 0;
|
|
65
76
|
if (verticalAlign === 'middle') {
|
|
66
|
-
sdfVerticalAlignYOffset = (sdfLineHeight -
|
|
77
|
+
sdfVerticalAlignYOffset = (sdfLineHeight - sdfBareLineHeight) / 2;
|
|
67
78
|
} else if (verticalAlign === 'bottom') {
|
|
68
|
-
sdfVerticalAlignYOffset = sdfLineHeight -
|
|
79
|
+
sdfVerticalAlignYOffset = sdfLineHeight - sdfBareLineHeight;
|
|
69
80
|
}
|
|
81
|
+
|
|
70
82
|
const sdfOffsetY = offsetY / fontSizeRatio;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* This is the position from the top of the text drawing line to where the
|
|
86
|
+
* baseline of the text will be according to the encoded positioning data for
|
|
87
|
+
* each glyph in the SDF data. This also happens to be the ascender value
|
|
88
|
+
* that is encoded into the font data.
|
|
89
|
+
*/
|
|
90
|
+
const sdfEncodedAscender = fontFace.data.common.base;
|
|
91
|
+
/**
|
|
92
|
+
* This is the ascender that is configured and overridable in the font face.
|
|
93
|
+
*/
|
|
94
|
+
const sdfConfiguredAscender = metrics.ascender * sdfFontSize;
|
|
95
|
+
/**
|
|
96
|
+
* If the configured ascender is different from the SDF data's encoded
|
|
97
|
+
* ascender, the offset of the text will be adjusted by the difference.
|
|
98
|
+
*/
|
|
99
|
+
const sdfAscenderAdjOffset = sdfConfiguredAscender - sdfEncodedAscender;
|
|
100
|
+
|
|
71
101
|
const sdfStartY =
|
|
72
|
-
sdfOffsetY +
|
|
102
|
+
sdfOffsetY +
|
|
103
|
+
sdfAscenderAdjOffset +
|
|
104
|
+
startLineIndex * sdfLineHeight +
|
|
105
|
+
sdfVerticalAlignYOffset; // TODO: Figure out what determines the initial y offset of text.
|
|
73
106
|
|
|
74
107
|
// Don't attempt to render anything if we know we're starting past the established end of the text
|
|
75
108
|
if (textH && sdfStartY >= textH / fontSizeRatio) {
|