@deck.gl-community/infovis-layers 9.3.2 → 9.3.7
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/index.cjs +2998 -114
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/layers/animation-layer/animation-layer.d.ts +30 -0
- package/dist/layers/animation-layer/animation-layer.d.ts.map +1 -0
- package/dist/layers/animation-layer/animation-layer.js +53 -0
- package/dist/layers/animation-layer/animation-layer.js.map +1 -0
- package/dist/layers/animation-layer/animation.d.ts +86 -0
- package/dist/layers/animation-layer/animation.d.ts.map +1 -0
- package/dist/layers/animation-layer/animation.js +214 -0
- package/dist/layers/animation-layer/animation.js.map +1 -0
- package/dist/layers/block-layer/block-layer-fragment.glsl.d.ts +3 -0
- package/dist/layers/block-layer/block-layer-fragment.glsl.d.ts.map +1 -0
- package/dist/layers/block-layer/block-layer-fragment.glsl.js +41 -0
- package/dist/layers/block-layer/block-layer-fragment.glsl.js.map +1 -0
- package/dist/layers/block-layer/block-layer-uniforms.d.ts +27 -0
- package/dist/layers/block-layer/block-layer-uniforms.d.ts.map +1 -0
- package/dist/layers/block-layer/block-layer-uniforms.js +26 -0
- package/dist/layers/block-layer/block-layer-uniforms.js.map +1 -0
- package/dist/layers/block-layer/block-layer-vertex.glsl.d.ts +3 -0
- package/dist/layers/block-layer/block-layer-vertex.glsl.d.ts.map +1 -0
- package/dist/layers/block-layer/block-layer-vertex.glsl.js +67 -0
- package/dist/layers/block-layer/block-layer-vertex.glsl.js.map +1 -0
- package/dist/layers/block-layer/block-layer.d.ts +71 -0
- package/dist/layers/block-layer/block-layer.d.ts.map +1 -0
- package/dist/layers/block-layer/block-layer.js +108 -0
- package/dist/layers/block-layer/block-layer.js.map +1 -0
- package/dist/layers/fast-text-layer/fast-text-layer.d.ts +148 -0
- package/dist/layers/fast-text-layer/fast-text-layer.d.ts.map +1 -0
- package/dist/layers/fast-text-layer/fast-text-layer.js +862 -0
- package/dist/layers/fast-text-layer/fast-text-layer.js.map +1 -0
- package/dist/layers/fast-text-layer/fast-text-layer.test.d.ts +2 -0
- package/dist/layers/fast-text-layer/fast-text-layer.test.d.ts.map +1 -0
- package/dist/layers/fast-text-layer/fast-text-layer.test.js +300 -0
- package/dist/layers/fast-text-layer/fast-text-layer.test.js.map +1 -0
- package/dist/layers/fast-text-layer/fast-text-layout.d.ts +179 -0
- package/dist/layers/fast-text-layer/fast-text-layout.d.ts.map +1 -0
- package/dist/layers/fast-text-layer/fast-text-layout.js +665 -0
- package/dist/layers/fast-text-layer/fast-text-layout.js.map +1 -0
- package/dist/layers/fast-text-layer/font-atlas.d.ts +98 -0
- package/dist/layers/fast-text-layer/font-atlas.d.ts.map +1 -0
- package/dist/layers/fast-text-layer/font-atlas.js +337 -0
- package/dist/layers/fast-text-layer/font-atlas.js.map +1 -0
- package/dist/layers/fast-text-layer/index.d.ts +4 -0
- package/dist/layers/fast-text-layer/index.d.ts.map +1 -0
- package/dist/layers/fast-text-layer/index.js +4 -0
- package/dist/layers/fast-text-layer/index.js.map +1 -0
- package/dist/layers/time-delta-layer.d.ts +22 -60
- package/dist/layers/time-delta-layer.d.ts.map +1 -1
- package/dist/layers/time-delta-layer.js +31 -53
- package/dist/layers/time-delta-layer.js.map +1 -1
- package/dist/synchronized-views/synchronized-views.js +1 -1
- package/dist/synchronized-views/synchronized-views.js.map +1 -1
- package/dist/utils/format-utils.d.ts +27 -3
- package/dist/utils/format-utils.d.ts.map +1 -1
- package/dist/utils/format-utils.js +72 -40
- package/dist/utils/format-utils.js.map +1 -1
- package/dist/utils/utf8-string-view.d.ts +53 -0
- package/dist/utils/utf8-string-view.d.ts.map +1 -0
- package/dist/utils/utf8-string-view.js +169 -0
- package/dist/utils/utf8-string-view.js.map +1 -0
- package/dist/utils/utf8-string-view.test.d.ts +2 -0
- package/dist/utils/utf8-string-view.test.d.ts.map +1 -0
- package/dist/utils/utf8-string-view.test.js +88 -0
- package/dist/utils/utf8-string-view.test.js.map +1 -0
- package/dist/views/bounds-utils.d.ts +23 -0
- package/dist/views/bounds-utils.d.ts.map +1 -0
- package/dist/views/bounds-utils.js +65 -0
- package/dist/views/bounds-utils.js.map +1 -0
- package/dist/views/layer-bounds-filter.d.ts +48 -0
- package/dist/views/layer-bounds-filter.d.ts.map +1 -0
- package/dist/views/layer-bounds-filter.js +99 -0
- package/dist/views/layer-bounds-filter.js.map +1 -0
- package/dist/views/layer-filter.js +4 -4
- package/dist/views/layer-filter.js.map +1 -1
- package/dist/views/orthographic-utils.d.ts +30 -2
- package/dist/views/orthographic-utils.d.ts.map +1 -1
- package/dist/views/orthographic-utils.js +33 -14
- package/dist/views/orthographic-utils.js.map +1 -1
- package/dist/views/view-layout/build-views-from-view-layout.d.ts +37 -0
- package/dist/views/view-layout/build-views-from-view-layout.d.ts.map +1 -0
- package/dist/views/view-layout/build-views-from-view-layout.js +413 -0
- package/dist/views/view-layout/build-views-from-view-layout.js.map +1 -0
- package/dist/views/view-layout/index.d.ts +3 -0
- package/dist/views/view-layout/index.d.ts.map +1 -0
- package/dist/views/view-layout/index.js +3 -0
- package/dist/views/view-layout/index.js.map +1 -0
- package/dist/views/view-layout/view-layout-item.d.ts +74 -0
- package/dist/views/view-layout/view-layout-item.d.ts.map +1 -0
- package/dist/views/view-layout/view-layout-item.js +48 -0
- package/dist/views/view-layout/view-layout-item.js.map +1 -0
- package/dist/views/view-layout/view-layout.test.d.ts +2 -0
- package/dist/views/view-layout/view-layout.test.d.ts.map +1 -0
- package/dist/views/view-layout/view-layout.test.js +112 -0
- package/dist/views/view-layout/view-layout.test.js.map +1 -0
- package/dist/views/viewport-bounds-utils.d.ts +55 -0
- package/dist/views/viewport-bounds-utils.d.ts.map +1 -0
- package/dist/views/viewport-bounds-utils.js +70 -0
- package/dist/views/viewport-bounds-utils.js.map +1 -0
- package/package.json +15 -5
- package/src/index.ts +91 -2
- package/src/layers/animation-layer/animation-layer.ts +92 -0
- package/src/layers/animation-layer/animation.ts +328 -0
- package/src/layers/block-layer/block-layer-fragment.glsl.ts +41 -0
- package/src/layers/block-layer/block-layer-uniforms.ts +43 -0
- package/src/layers/block-layer/block-layer-vertex.glsl.ts +67 -0
- package/src/layers/block-layer/block-layer.ts +199 -0
- package/src/layers/fast-text-layer/fast-text-layer.test.ts +359 -0
- package/src/layers/fast-text-layer/fast-text-layer.ts +1208 -0
- package/src/layers/fast-text-layer/fast-text-layout.ts +1124 -0
- package/src/layers/fast-text-layer/font-atlas.ts +574 -0
- package/src/layers/fast-text-layer/index.ts +37 -0
- package/src/layers/time-delta-layer.ts +55 -70
- package/src/synchronized-views/synchronized-views.ts +1 -1
- package/src/utils/format-utils.ts +90 -40
- package/src/utils/utf8-string-view.test.ts +119 -0
- package/src/utils/utf8-string-view.ts +273 -0
- package/src/views/bounds-utils.ts +88 -0
- package/src/views/layer-bounds-filter.ts +160 -0
- package/src/views/layer-filter.ts +4 -4
- package/src/views/orthographic-utils.ts +65 -18
- package/src/views/view-layout/build-views-from-view-layout.ts +547 -0
- package/src/views/view-layout/index.ts +18 -0
- package/src/views/view-layout/view-layout-item.ts +125 -0
- package/src/views/view-layout/view-layout.test.ts +129 -0
- package/src/views/viewport-bounds-utils.ts +104 -0
|
@@ -0,0 +1,1124 @@
|
|
|
1
|
+
// Minimal WebGL attribute text layout ported from WIP luma.gl text utilities.
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
import {createIterable} from '@deck.gl/core';
|
|
5
|
+
|
|
6
|
+
import {getArrowUtf8ColumnSource, getUtf8ColumnSourceRowView} from '../../utils/utf8-string-view';
|
|
7
|
+
|
|
8
|
+
import type {Utf8ColumnSource, Utf8StringView} from '../../utils/utf8-string-view';
|
|
9
|
+
import type {FastTextCharacter, FastTextCharacterMapping} from './font-atlas';
|
|
10
|
+
import type {Accessor, AccessorContext, Color, LayerDataSource, Position} from '@deck.gl/core';
|
|
11
|
+
import type * as arrow from 'apache-arrow';
|
|
12
|
+
|
|
13
|
+
/** Horizontal label anchor accepted by {@link FastTextLayer}. */
|
|
14
|
+
export type FastTextAnchor = 'start' | 'middle' | 'end';
|
|
15
|
+
|
|
16
|
+
/** Vertical label baseline accepted by {@link FastTextLayer}. */
|
|
17
|
+
export type FastTextAlignmentBaseline = 'top' | 'center' | 'bottom';
|
|
18
|
+
|
|
19
|
+
/** Content alignment mode accepted by {@link FastTextLayer}. */
|
|
20
|
+
export type FastTextContentAlign = 'none' | 'start' | 'center' | 'end';
|
|
21
|
+
|
|
22
|
+
/** Source-row clip rectangle in deck.gl text content coordinates. */
|
|
23
|
+
export type FastTextClipRect = readonly [x: number, y: number, width: number, height: number];
|
|
24
|
+
|
|
25
|
+
/** Direct-buffer UTF-8 column source consumed by {@link FastTextLayer}. */
|
|
26
|
+
export type FastTextUtf8ColumnSource = Utf8ColumnSource;
|
|
27
|
+
|
|
28
|
+
/** Arrow Utf8 vector or pre-normalized UTF-8 column source consumed by {@link FastTextLayer}. */
|
|
29
|
+
export type FastTextUtf8Column = arrow.Vector<arrow.Utf8> | FastTextUtf8ColumnSource;
|
|
30
|
+
|
|
31
|
+
/** Accessor that fills a caller-owned UTF-8 byte view for one source row. */
|
|
32
|
+
export type FastTextUtf8ViewAccessor<DataT> = (
|
|
33
|
+
object: DataT,
|
|
34
|
+
out: Utf8StringView,
|
|
35
|
+
objectInfo: AccessorContext<DataT>
|
|
36
|
+
) => boolean;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Bytes per generated glyph vertex record.
|
|
40
|
+
*
|
|
41
|
+
* The record stores `sint16x2 glyphOffsets`, `uint16x4 glyphFrames`, `sint16x4 clipRect`,
|
|
42
|
+
* and `unorm8x4 color`.
|
|
43
|
+
*/
|
|
44
|
+
export const FAST_TEXT_GLYPH_VERTEX_BYTE_STRIDE =
|
|
45
|
+
Int16Array.BYTES_PER_ELEMENT * 10 + Uint8Array.BYTES_PER_ELEMENT * 4;
|
|
46
|
+
|
|
47
|
+
/** Packed per-glyph typed arrays consumed by the fast text shader. */
|
|
48
|
+
export type FastTextGlyphAttributes = {
|
|
49
|
+
/**
|
|
50
|
+
* Interleaved generated glyph records.
|
|
51
|
+
*
|
|
52
|
+
* Each record stores `[offsetX, offsetY]` as signed 16-bit integers,
|
|
53
|
+
* `[atlasX, atlasY, atlasWidth, atlasHeight]` as unsigned 16-bit integers,
|
|
54
|
+
* `[clipX, clipY, clipWidth, clipHeight]` as signed 16-bit integers, and RGBA color as
|
|
55
|
+
* four normalized unsigned bytes.
|
|
56
|
+
*/
|
|
57
|
+
instanceGlyphData: Uint8Array;
|
|
58
|
+
/** Per-glyph 2D source anchor position, repeated from source label rows. */
|
|
59
|
+
instancePositions: Float32Array;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/** Timing counters captured while expanding source rows into FastText glyph attributes. */
|
|
63
|
+
export type FastTextGlyphBuildStats = {
|
|
64
|
+
/** Source text path used for this glyph build. */
|
|
65
|
+
sourceMode: 'string' | 'utf8-column' | 'utf8-view';
|
|
66
|
+
/** Layout path used for this glyph build. */
|
|
67
|
+
layoutMode: 'single-line' | 'multi-line';
|
|
68
|
+
/** Number of source rows visited. */
|
|
69
|
+
rowCount: number;
|
|
70
|
+
/** Number of generated glyph instances. */
|
|
71
|
+
glyphCount: number;
|
|
72
|
+
/** Number of bytes occupied by generated CPU attribute arrays. */
|
|
73
|
+
attributeByteLength: number;
|
|
74
|
+
/** Time spent normalizing the optional Arrow UTF-8 column source. */
|
|
75
|
+
columnNormalizeDurationMs: number;
|
|
76
|
+
/** Time spent counting rows and glyphs before fixed-size allocation. */
|
|
77
|
+
countDurationMs: number;
|
|
78
|
+
/** Time spent allocating generated CPU attribute arrays. */
|
|
79
|
+
allocateDurationMs: number;
|
|
80
|
+
/** Time spent writing generated CPU attribute arrays. */
|
|
81
|
+
writeDurationMs: number;
|
|
82
|
+
/** Write-phase time spent resolving label text or UTF-8 row views. */
|
|
83
|
+
textResolveDurationMs: number;
|
|
84
|
+
/** Write-phase time spent resolving style accessors for position, color, and clip rect. */
|
|
85
|
+
styleAccessorDurationMs: number;
|
|
86
|
+
/** Write-phase time spent computing label layout offsets and widths. */
|
|
87
|
+
layoutDurationMs: number;
|
|
88
|
+
/** Write-phase time spent packing per-glyph attribute records. */
|
|
89
|
+
glyphWriteDurationMs: number;
|
|
90
|
+
/** Total time spent in FastText glyph-data construction. */
|
|
91
|
+
totalDurationMs: number;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/** Result of expanding source text rows into one glyph instance stream. */
|
|
95
|
+
export type FastTextGlyphData = {
|
|
96
|
+
/** Number of rendered glyph instances. */
|
|
97
|
+
length: number;
|
|
98
|
+
/** Cumulative glyph starts, one entry per source row plus a terminal entry. */
|
|
99
|
+
startIndices: Uint32Array;
|
|
100
|
+
/** Packed per-glyph attributes ready for GPU upload. */
|
|
101
|
+
attributes: FastTextGlyphAttributes;
|
|
102
|
+
/** Characters observed while visiting source text rows. */
|
|
103
|
+
characterSet: Set<string>;
|
|
104
|
+
/** Approximate CPU typed-array bytes occupied by this glyph data. */
|
|
105
|
+
byteLength: number;
|
|
106
|
+
/** Timing counters for the CPU glyph attribute build. */
|
|
107
|
+
buildStats: FastTextGlyphBuildStats;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/** Inputs used to collect an automatic atlas character set. */
|
|
111
|
+
export type CollectFastTextCharacterSetProps<DataT> = {
|
|
112
|
+
/** Source label rows. */
|
|
113
|
+
data: LayerDataSource<DataT>;
|
|
114
|
+
/** Source-row text accessor. */
|
|
115
|
+
getText: Accessor<DataT, string>;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/** Inputs used to build a packed fast text glyph stream. */
|
|
119
|
+
export type BuildFastTextGlyphDataProps<DataT> = {
|
|
120
|
+
/** Source label rows. */
|
|
121
|
+
data: LayerDataSource<DataT>;
|
|
122
|
+
/** Source-row text accessor used by the string path. */
|
|
123
|
+
getText: Accessor<DataT, string>;
|
|
124
|
+
/** Optional UTF-8 text column used by the byte path. */
|
|
125
|
+
textUtf8Column?: FastTextUtf8Column | null;
|
|
126
|
+
/** Source-row UTF-8 column row accessor. Defaults to the source row index. */
|
|
127
|
+
getTextUtf8Row?: Accessor<DataT, number | null>;
|
|
128
|
+
/** Optional accessor that fills a reused UTF-8 byte view for the source row. */
|
|
129
|
+
getTextUtf8?: FastTextUtf8ViewAccessor<DataT> | null;
|
|
130
|
+
/** Whether source labels are expected to be single-line strings. */
|
|
131
|
+
singleLine: boolean;
|
|
132
|
+
/** Source-row position accessor. */
|
|
133
|
+
getPosition: Accessor<DataT, Position>;
|
|
134
|
+
/** Source-row color accessor. */
|
|
135
|
+
getColor: Accessor<DataT, Color>;
|
|
136
|
+
/** Layer-wide horizontal text anchor. */
|
|
137
|
+
textAnchor: FastTextAnchor;
|
|
138
|
+
/** Layer-wide vertical alignment baseline. */
|
|
139
|
+
alignmentBaseline: FastTextAlignmentBaseline;
|
|
140
|
+
/** Source-row content clip rectangle accessor. */
|
|
141
|
+
getClipRect: Accessor<DataT, FastTextClipRect>;
|
|
142
|
+
/** Glyph metric lookup keyed by rendered character. */
|
|
143
|
+
mapping: FastTextCharacterMapping;
|
|
144
|
+
/** Atlas baseline offset in pixels. */
|
|
145
|
+
baselineOffset: number;
|
|
146
|
+
/** Atlas font size in pixels. */
|
|
147
|
+
fontSize: number;
|
|
148
|
+
/** Unitless line-height multiplier. */
|
|
149
|
+
lineHeight: number;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/** Inputs used to rewrite dynamic per-row glyph attributes without rebuilding text layout. */
|
|
153
|
+
export type UpdateFastTextDynamicGlyphAttributesProps<DataT> = {
|
|
154
|
+
/** Source label rows. */
|
|
155
|
+
data: LayerDataSource<DataT>;
|
|
156
|
+
/** Existing glyph data whose dynamic attributes should be updated in place. */
|
|
157
|
+
glyphData: FastTextGlyphData;
|
|
158
|
+
/** Source-row position accessor. */
|
|
159
|
+
getPosition: Accessor<DataT, Position>;
|
|
160
|
+
/** Source-row color accessor. */
|
|
161
|
+
getColor: Accessor<DataT, Color>;
|
|
162
|
+
/** Source-row content clip rectangle accessor. */
|
|
163
|
+
getClipRect: Accessor<DataT, FastTextClipRect>;
|
|
164
|
+
/** Whether to rewrite repeated per-glyph anchor positions. */
|
|
165
|
+
updatePositions: boolean;
|
|
166
|
+
/** Whether to rewrite packed per-glyph colors. */
|
|
167
|
+
updateColors: boolean;
|
|
168
|
+
/** Whether to rewrite packed per-glyph clip rectangles. */
|
|
169
|
+
updateClipRects: boolean;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/** Timing counters captured while rewriting dynamic FastText glyph attributes. */
|
|
173
|
+
export type FastTextDynamicGlyphUpdateStats = {
|
|
174
|
+
/** Number of source rows visited. */
|
|
175
|
+
rowCount: number;
|
|
176
|
+
/** Number of generated glyph instances visited. */
|
|
177
|
+
glyphCount: number;
|
|
178
|
+
/** Number of bytes occupied by the existing CPU attribute arrays. */
|
|
179
|
+
attributeByteLength: number;
|
|
180
|
+
/** Time spent resolving position accessors. */
|
|
181
|
+
positionAccessorDurationMs: number;
|
|
182
|
+
/** Time spent resolving color accessors. */
|
|
183
|
+
colorAccessorDurationMs: number;
|
|
184
|
+
/** Time spent resolving clip-rectangle accessors. */
|
|
185
|
+
clipRectAccessorDurationMs: number;
|
|
186
|
+
/** Time spent writing dynamic glyph attributes. */
|
|
187
|
+
writeDurationMs: number;
|
|
188
|
+
/** Total time spent updating dynamic glyph attributes. */
|
|
189
|
+
totalDurationMs: number;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
/** Build a direct-buffer UTF-8 column source without copying Arrow string bytes. */
|
|
193
|
+
export function buildFastTextUtf8ColumnSource(
|
|
194
|
+
utf8Column: arrow.Vector<arrow.Utf8>
|
|
195
|
+
): FastTextUtf8ColumnSource | null {
|
|
196
|
+
return getArrowUtf8ColumnSource(utf8Column);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Collect all renderable characters reached by a text accessor. */
|
|
200
|
+
export function collectFastTextCharacterSet<DataT>(
|
|
201
|
+
props: CollectFastTextCharacterSetProps<DataT>
|
|
202
|
+
): Set<string> {
|
|
203
|
+
const characterSet = new Set<string>();
|
|
204
|
+
const {iterable, objectInfo} = createIterable(props.data);
|
|
205
|
+
|
|
206
|
+
for (const object of iterable) {
|
|
207
|
+
objectInfo.index++;
|
|
208
|
+
const text = resolveAccessor(props.getText, object, objectInfo) ?? '';
|
|
209
|
+
for (const character of Array.from(text)) {
|
|
210
|
+
if (character !== '\n') {
|
|
211
|
+
characterSet.add(character);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return characterSet;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Build packed per-glyph attributes for one fast text layer update. */
|
|
220
|
+
export function buildFastTextGlyphData<DataT>(
|
|
221
|
+
props: BuildFastTextGlyphDataProps<DataT>
|
|
222
|
+
): FastTextGlyphData {
|
|
223
|
+
const buildStartTime = performance.now();
|
|
224
|
+
const columnNormalizeStartTime = performance.now();
|
|
225
|
+
const textUtf8Column = resolveFastTextUtf8ColumnSource(props.textUtf8Column);
|
|
226
|
+
const columnNormalizeDurationMs = performance.now() - columnNormalizeStartTime;
|
|
227
|
+
if (props.textUtf8Column && !textUtf8Column) {
|
|
228
|
+
throw new Error('FastTextLayer textUtf8Column must expose Arrow Utf8 buffers');
|
|
229
|
+
}
|
|
230
|
+
if ((textUtf8Column || props.getTextUtf8) && !props.singleLine) {
|
|
231
|
+
throw new Error('FastTextLayer UTF-8 text sources require singleLine text layout');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const glyphData = props.singleLine
|
|
235
|
+
? buildSingleLineGlyphData({
|
|
236
|
+
...props,
|
|
237
|
+
textUtf8Column
|
|
238
|
+
})
|
|
239
|
+
: buildMultiLineGlyphData(props);
|
|
240
|
+
return withFastTextGlyphBuildStats(glyphData, {
|
|
241
|
+
columnNormalizeDurationMs,
|
|
242
|
+
totalDurationMs: performance.now() - buildStartTime
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Build packed per-glyph attributes for source rows that are expected to contain no newlines. */
|
|
247
|
+
export function buildSingleLineGlyphData<DataT>(
|
|
248
|
+
props: BuildFastTextGlyphDataProps<DataT> & {
|
|
249
|
+
/** Normalized UTF-8 column source, when the byte path is active. */
|
|
250
|
+
textUtf8Column?: FastTextUtf8ColumnSource | null;
|
|
251
|
+
}
|
|
252
|
+
): FastTextGlyphData {
|
|
253
|
+
const buildStartTime = performance.now();
|
|
254
|
+
const countStartTime = performance.now();
|
|
255
|
+
const countResult = countSingleLineFastTextGlyphs(props);
|
|
256
|
+
const countDurationMs = performance.now() - countStartTime;
|
|
257
|
+
const allocateStartTime = performance.now();
|
|
258
|
+
const attributes = allocateFastTextGlyphAttributes(countResult.glyphCount);
|
|
259
|
+
const startIndices = new Uint32Array(countResult.rowCount + 1);
|
|
260
|
+
const allocateDurationMs = performance.now() - allocateStartTime;
|
|
261
|
+
const writeState: FastTextGlyphWriteState = {
|
|
262
|
+
glyphIndex: 0,
|
|
263
|
+
startIndices,
|
|
264
|
+
attributes,
|
|
265
|
+
glyphRecordViews: createFastTextGlyphRecordViews(attributes.instanceGlyphData)
|
|
266
|
+
};
|
|
267
|
+
const writeTimings = createFastTextGlyphWriteTimings();
|
|
268
|
+
const byteLookup =
|
|
269
|
+
props.textUtf8Column || props.getTextUtf8 ? buildFastTextByteGlyphLookup(props.mapping) : null;
|
|
270
|
+
const scratchUtf8View: Utf8StringView = {data: EMPTY_BYTES, start: 0, end: 0};
|
|
271
|
+
const {iterable, objectInfo} = createIterable(props.data);
|
|
272
|
+
|
|
273
|
+
const writeStartTime = performance.now();
|
|
274
|
+
for (const object of iterable) {
|
|
275
|
+
objectInfo.index++;
|
|
276
|
+
startIndices[objectInfo.index] = writeState.glyphIndex;
|
|
277
|
+
if (props.textUtf8Column && byteLookup) {
|
|
278
|
+
writeSingleLineUtf8LabelGlyphs(
|
|
279
|
+
props,
|
|
280
|
+
props.textUtf8Column,
|
|
281
|
+
byteLookup,
|
|
282
|
+
scratchUtf8View,
|
|
283
|
+
object,
|
|
284
|
+
objectInfo,
|
|
285
|
+
writeState,
|
|
286
|
+
writeTimings
|
|
287
|
+
);
|
|
288
|
+
} else if (props.getTextUtf8 && byteLookup) {
|
|
289
|
+
writeSingleLineUtf8ViewLabelGlyphs(
|
|
290
|
+
props,
|
|
291
|
+
props.getTextUtf8,
|
|
292
|
+
byteLookup,
|
|
293
|
+
scratchUtf8View,
|
|
294
|
+
object,
|
|
295
|
+
objectInfo,
|
|
296
|
+
writeState,
|
|
297
|
+
writeTimings
|
|
298
|
+
);
|
|
299
|
+
} else {
|
|
300
|
+
writeSingleLineStringLabelGlyphs(props, object, objectInfo, writeState, writeTimings);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
startIndices[countResult.rowCount] = writeState.glyphIndex;
|
|
304
|
+
const writeDurationMs = performance.now() - writeStartTime;
|
|
305
|
+
|
|
306
|
+
return createFastTextGlyphData(countResult, startIndices, attributes, {
|
|
307
|
+
sourceMode: props.textUtf8Column ? 'utf8-column' : props.getTextUtf8 ? 'utf8-view' : 'string',
|
|
308
|
+
layoutMode: 'single-line',
|
|
309
|
+
columnNormalizeDurationMs: 0,
|
|
310
|
+
countDurationMs,
|
|
311
|
+
allocateDurationMs,
|
|
312
|
+
writeDurationMs,
|
|
313
|
+
...writeTimings,
|
|
314
|
+
totalDurationMs: performance.now() - buildStartTime
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** Build packed per-glyph attributes for source rows that may contain newlines. */
|
|
319
|
+
export function buildMultiLineGlyphData<DataT>(
|
|
320
|
+
props: BuildFastTextGlyphDataProps<DataT>
|
|
321
|
+
): FastTextGlyphData {
|
|
322
|
+
const buildStartTime = performance.now();
|
|
323
|
+
const countStartTime = performance.now();
|
|
324
|
+
const countResult = countMultiLineFastTextGlyphs(props);
|
|
325
|
+
const countDurationMs = performance.now() - countStartTime;
|
|
326
|
+
const allocateStartTime = performance.now();
|
|
327
|
+
const attributes = allocateFastTextGlyphAttributes(countResult.glyphCount);
|
|
328
|
+
const startIndices = new Uint32Array(countResult.rowCount + 1);
|
|
329
|
+
const allocateDurationMs = performance.now() - allocateStartTime;
|
|
330
|
+
const writeState: FastTextGlyphWriteState = {
|
|
331
|
+
glyphIndex: 0,
|
|
332
|
+
startIndices,
|
|
333
|
+
attributes,
|
|
334
|
+
glyphRecordViews: createFastTextGlyphRecordViews(attributes.instanceGlyphData)
|
|
335
|
+
};
|
|
336
|
+
const writeTimings = createFastTextGlyphWriteTimings();
|
|
337
|
+
const {iterable, objectInfo} = createIterable(props.data);
|
|
338
|
+
|
|
339
|
+
const writeStartTime = performance.now();
|
|
340
|
+
for (const object of iterable) {
|
|
341
|
+
objectInfo.index++;
|
|
342
|
+
startIndices[objectInfo.index] = writeState.glyphIndex;
|
|
343
|
+
writeMultiLineLabelGlyphs(props, object, objectInfo, writeState, writeTimings);
|
|
344
|
+
}
|
|
345
|
+
startIndices[countResult.rowCount] = writeState.glyphIndex;
|
|
346
|
+
const writeDurationMs = performance.now() - writeStartTime;
|
|
347
|
+
|
|
348
|
+
return createFastTextGlyphData(countResult, startIndices, attributes, {
|
|
349
|
+
sourceMode: 'string',
|
|
350
|
+
layoutMode: 'multi-line',
|
|
351
|
+
columnNormalizeDurationMs: 0,
|
|
352
|
+
countDurationMs,
|
|
353
|
+
allocateDurationMs,
|
|
354
|
+
writeDurationMs,
|
|
355
|
+
...writeTimings,
|
|
356
|
+
totalDurationMs: performance.now() - buildStartTime
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** Rewrite dynamic per-row glyph attributes while preserving generated text layout attributes. */
|
|
361
|
+
export function updateFastTextDynamicGlyphAttributes<DataT>(
|
|
362
|
+
props: UpdateFastTextDynamicGlyphAttributesProps<DataT>
|
|
363
|
+
): FastTextDynamicGlyphUpdateStats {
|
|
364
|
+
const updateStartTime = performance.now();
|
|
365
|
+
const glyphRecordViews = createFastTextGlyphRecordViews(
|
|
366
|
+
props.glyphData.attributes.instanceGlyphData
|
|
367
|
+
);
|
|
368
|
+
const {iterable, objectInfo} = createIterable(props.data);
|
|
369
|
+
let rowCount = 0;
|
|
370
|
+
let glyphCount = 0;
|
|
371
|
+
let positionAccessorDurationMs = 0;
|
|
372
|
+
let colorAccessorDurationMs = 0;
|
|
373
|
+
let clipRectAccessorDurationMs = 0;
|
|
374
|
+
let writeDurationMs = 0;
|
|
375
|
+
|
|
376
|
+
for (const object of iterable) {
|
|
377
|
+
objectInfo.index++;
|
|
378
|
+
rowCount++;
|
|
379
|
+
const rowGlyphStart = props.glyphData.startIndices[objectInfo.index] ?? 0;
|
|
380
|
+
const rowGlyphEnd = props.glyphData.startIndices[objectInfo.index + 1] ?? rowGlyphStart;
|
|
381
|
+
if (rowGlyphEnd <= rowGlyphStart) {
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
let position: Position = DEFAULT_POSITION;
|
|
386
|
+
let color: Color = DEFAULT_COLOR;
|
|
387
|
+
let clipRect: FastTextClipRect = DEFAULT_CLIP_RECT;
|
|
388
|
+
if (props.updatePositions) {
|
|
389
|
+
const accessorStartTime = performance.now();
|
|
390
|
+
position = resolveAccessor(props.getPosition, object, objectInfo) ?? DEFAULT_POSITION;
|
|
391
|
+
positionAccessorDurationMs += performance.now() - accessorStartTime;
|
|
392
|
+
}
|
|
393
|
+
if (props.updateColors) {
|
|
394
|
+
const accessorStartTime = performance.now();
|
|
395
|
+
color = resolveAccessor(props.getColor, object, objectInfo) ?? DEFAULT_COLOR;
|
|
396
|
+
colorAccessorDurationMs += performance.now() - accessorStartTime;
|
|
397
|
+
}
|
|
398
|
+
if (props.updateClipRects) {
|
|
399
|
+
const accessorStartTime = performance.now();
|
|
400
|
+
clipRect = resolveAccessor(props.getClipRect, object, objectInfo) ?? DEFAULT_CLIP_RECT;
|
|
401
|
+
clipRectAccessorDurationMs += performance.now() - accessorStartTime;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const writeStartTime = performance.now();
|
|
405
|
+
for (let glyphIndex = rowGlyphStart; glyphIndex < rowGlyphEnd; glyphIndex += 1) {
|
|
406
|
+
if (props.updatePositions) {
|
|
407
|
+
writeFastTextGlyphPosition(props.glyphData.attributes, glyphIndex, position);
|
|
408
|
+
}
|
|
409
|
+
if (props.updateClipRects) {
|
|
410
|
+
writeFastTextGlyphClipRect(glyphRecordViews, glyphIndex, clipRect);
|
|
411
|
+
}
|
|
412
|
+
if (props.updateColors) {
|
|
413
|
+
writeFastTextGlyphColor(glyphRecordViews, glyphIndex, color);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
writeDurationMs += performance.now() - writeStartTime;
|
|
417
|
+
glyphCount += rowGlyphEnd - rowGlyphStart;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return {
|
|
421
|
+
rowCount,
|
|
422
|
+
glyphCount,
|
|
423
|
+
attributeByteLength: props.glyphData.byteLength,
|
|
424
|
+
positionAccessorDurationMs,
|
|
425
|
+
colorAccessorDurationMs,
|
|
426
|
+
clipRectAccessorDurationMs,
|
|
427
|
+
writeDurationMs,
|
|
428
|
+
totalDurationMs: performance.now() - updateStartTime
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
type FastTextGlyphCount = {
|
|
433
|
+
/** Number of rendered glyph instances. */
|
|
434
|
+
readonly glyphCount: number;
|
|
435
|
+
/** Number of source text rows. */
|
|
436
|
+
readonly rowCount: number;
|
|
437
|
+
/** Characters observed while counting rows. */
|
|
438
|
+
readonly characterSet: Set<string>;
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
type FastTextGlyphWriteState = {
|
|
442
|
+
/** Current glyph write cursor. */
|
|
443
|
+
glyphIndex: number;
|
|
444
|
+
/** Cumulative glyph starts by source row. */
|
|
445
|
+
readonly startIndices: Uint32Array;
|
|
446
|
+
/** Typed arrays receiving per-glyph attributes. */
|
|
447
|
+
readonly attributes: FastTextGlyphAttributes;
|
|
448
|
+
/** Typed views over the interleaved generated glyph record buffer. */
|
|
449
|
+
readonly glyphRecordViews: FastTextGlyphRecordViews;
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
type FastTextGlyphRecordViews = {
|
|
453
|
+
/** Signed 16-bit view used for glyph offsets. */
|
|
454
|
+
readonly int16Values: Int16Array;
|
|
455
|
+
/** Unsigned 16-bit view used for atlas frames. */
|
|
456
|
+
readonly uint16Values: Uint16Array;
|
|
457
|
+
/** Unsigned 8-bit view used for packed colors. */
|
|
458
|
+
readonly uint8Values: Uint8Array;
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
type FastTextGlyphWriteTimings = {
|
|
462
|
+
/** Write-phase time spent resolving label text or UTF-8 row views. */
|
|
463
|
+
textResolveDurationMs: number;
|
|
464
|
+
/** Write-phase time spent resolving style accessors for position, color, and clip rect. */
|
|
465
|
+
styleAccessorDurationMs: number;
|
|
466
|
+
/** Write-phase time spent computing label layout offsets and widths. */
|
|
467
|
+
layoutDurationMs: number;
|
|
468
|
+
/** Write-phase time spent packing per-glyph attribute records. */
|
|
469
|
+
glyphWriteDurationMs: number;
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
type FastTextByteGlyphLookup = readonly (FastTextCharacter | undefined)[];
|
|
473
|
+
|
|
474
|
+
type FastTextLineLayout = {
|
|
475
|
+
/** Characters in this source text line. */
|
|
476
|
+
readonly characters: readonly string[];
|
|
477
|
+
/** Line width in atlas pixels, including missing-character advances. */
|
|
478
|
+
readonly width: number;
|
|
479
|
+
/** Renderable glyphs in this line. */
|
|
480
|
+
readonly glyphCount: number;
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
const MISSING_CHARACTER_ADVANCE = 32;
|
|
484
|
+
const FAST_TEXT_BYTE_LOOKUP_SIZE = 256;
|
|
485
|
+
const EMPTY_BYTES = new Uint8Array();
|
|
486
|
+
const DEFAULT_COLOR: readonly [number, number, number, number] = [0, 0, 0, 255];
|
|
487
|
+
const DEFAULT_POSITION: readonly [number, number, number] = [0, 0, 0];
|
|
488
|
+
const DEFAULT_CLIP_RECT: FastTextClipRect = [0, 0, -1, -1];
|
|
489
|
+
const ANCHOR_OFFSET_MULTIPLIER: Record<FastTextAnchor, number> = {
|
|
490
|
+
start: 0,
|
|
491
|
+
middle: -0.5,
|
|
492
|
+
end: -1
|
|
493
|
+
};
|
|
494
|
+
const BASELINE_OFFSET_MULTIPLIER: Record<FastTextAlignmentBaseline, number> = {
|
|
495
|
+
top: 0,
|
|
496
|
+
center: -0.5,
|
|
497
|
+
bottom: -1
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
/** Count single-line rendered glyphs and rows before allocating fixed-size typed arrays. */
|
|
501
|
+
function countSingleLineFastTextGlyphs<DataT>(
|
|
502
|
+
props: Pick<
|
|
503
|
+
BuildFastTextGlyphDataProps<DataT>,
|
|
504
|
+
'data' | 'getText' | 'getTextUtf8' | 'getTextUtf8Row' | 'mapping'
|
|
505
|
+
> & {
|
|
506
|
+
/** Normalized UTF-8 column source, when the byte path is active. */
|
|
507
|
+
readonly textUtf8Column?: FastTextUtf8ColumnSource | null;
|
|
508
|
+
}
|
|
509
|
+
): FastTextGlyphCount {
|
|
510
|
+
if (props.textUtf8Column) {
|
|
511
|
+
return countSingleLineUtf8FastTextGlyphs(props, props.textUtf8Column);
|
|
512
|
+
}
|
|
513
|
+
if (props.getTextUtf8) {
|
|
514
|
+
return countSingleLineUtf8ViewFastTextGlyphs(props, props.getTextUtf8);
|
|
515
|
+
}
|
|
516
|
+
return countSingleLineStringFastTextGlyphs(props);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/** Count single-line UTF-8 bytes as glyph records without decoding strings. */
|
|
520
|
+
function countSingleLineUtf8FastTextGlyphs<DataT>(
|
|
521
|
+
props: Pick<BuildFastTextGlyphDataProps<DataT>, 'data' | 'getTextUtf8Row'>,
|
|
522
|
+
textUtf8Column: FastTextUtf8ColumnSource
|
|
523
|
+
): FastTextGlyphCount {
|
|
524
|
+
let glyphCount = 0;
|
|
525
|
+
let rowCount = 0;
|
|
526
|
+
const characterSet = new Set<string>();
|
|
527
|
+
const textView: Utf8StringView = {data: EMPTY_BYTES, start: 0, end: 0};
|
|
528
|
+
const {iterable, objectInfo} = createIterable(props.data);
|
|
529
|
+
|
|
530
|
+
for (const object of iterable) {
|
|
531
|
+
objectInfo.index++;
|
|
532
|
+
rowCount++;
|
|
533
|
+
const rowIndex = resolveTextUtf8Row(props.getTextUtf8Row, object, objectInfo);
|
|
534
|
+
if (rowIndex == null || !getUtf8ColumnSourceRowView(textUtf8Column, rowIndex, textView)) {
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
glyphCount += textView.end - textView.start;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return {glyphCount, rowCount, characterSet};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/** Count single-line UTF-8 byte-view labels without decoding strings. */
|
|
544
|
+
function countSingleLineUtf8ViewFastTextGlyphs<DataT>(
|
|
545
|
+
props: Pick<BuildFastTextGlyphDataProps<DataT>, 'data'>,
|
|
546
|
+
getTextUtf8: FastTextUtf8ViewAccessor<DataT>
|
|
547
|
+
): FastTextGlyphCount {
|
|
548
|
+
let glyphCount = 0;
|
|
549
|
+
let rowCount = 0;
|
|
550
|
+
const characterSet = new Set<string>();
|
|
551
|
+
const textView: Utf8StringView = {data: EMPTY_BYTES, start: 0, end: 0};
|
|
552
|
+
const {iterable, objectInfo} = createIterable(props.data);
|
|
553
|
+
|
|
554
|
+
for (const object of iterable) {
|
|
555
|
+
objectInfo.index++;
|
|
556
|
+
rowCount++;
|
|
557
|
+
if (getTextUtf8(object, textView, objectInfo)) {
|
|
558
|
+
glyphCount += Math.max(0, textView.end - textView.start);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
return {glyphCount, rowCount, characterSet};
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/** Count single-line JavaScript string code units without scanning for newline separators. */
|
|
566
|
+
function countSingleLineStringFastTextGlyphs<DataT>(
|
|
567
|
+
props: Pick<BuildFastTextGlyphDataProps<DataT>, 'data' | 'getText'>
|
|
568
|
+
): FastTextGlyphCount {
|
|
569
|
+
let glyphCount = 0;
|
|
570
|
+
let rowCount = 0;
|
|
571
|
+
const characterSet = new Set<string>();
|
|
572
|
+
const {iterable, objectInfo} = createIterable(props.data);
|
|
573
|
+
|
|
574
|
+
for (const object of iterable) {
|
|
575
|
+
objectInfo.index++;
|
|
576
|
+
rowCount++;
|
|
577
|
+
const text = resolveAccessor(props.getText, object, objectInfo) ?? '';
|
|
578
|
+
glyphCount += text.length;
|
|
579
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
580
|
+
characterSet.add(text[index]!);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
return {glyphCount, rowCount, characterSet};
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/** Count multiline rendered glyphs and rows before allocating fixed-size typed arrays. */
|
|
588
|
+
function countMultiLineFastTextGlyphs<DataT>(
|
|
589
|
+
props: Pick<BuildFastTextGlyphDataProps<DataT>, 'data' | 'getText' | 'mapping'>
|
|
590
|
+
): FastTextGlyphCount {
|
|
591
|
+
let glyphCount = 0;
|
|
592
|
+
let rowCount = 0;
|
|
593
|
+
const characterSet = new Set<string>();
|
|
594
|
+
const {iterable, objectInfo} = createIterable(props.data);
|
|
595
|
+
|
|
596
|
+
for (const object of iterable) {
|
|
597
|
+
objectInfo.index++;
|
|
598
|
+
rowCount++;
|
|
599
|
+
const text = resolveAccessor(props.getText, object, objectInfo) ?? '';
|
|
600
|
+
for (const character of Array.from(text)) {
|
|
601
|
+
if (character === '\n') {
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
characterSet.add(character);
|
|
605
|
+
glyphCount++;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
return {glyphCount, rowCount, characterSet};
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/** Allocate all per-glyph typed arrays for one glyph count. */
|
|
613
|
+
function allocateFastTextGlyphAttributes(glyphCount: number): FastTextGlyphAttributes {
|
|
614
|
+
return {
|
|
615
|
+
instanceGlyphData: new Uint8Array(glyphCount * FAST_TEXT_GLYPH_VERTEX_BYTE_STRIDE),
|
|
616
|
+
instancePositions: new Float32Array(glyphCount * 2)
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/** Build typed record views over the packed generated glyph data. */
|
|
621
|
+
function createFastTextGlyphRecordViews(instanceGlyphData: Uint8Array): FastTextGlyphRecordViews {
|
|
622
|
+
return {
|
|
623
|
+
int16Values: new Int16Array(instanceGlyphData.buffer),
|
|
624
|
+
uint16Values: new Uint16Array(instanceGlyphData.buffer),
|
|
625
|
+
uint8Values: instanceGlyphData
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/** Create the public glyph data wrapper around generated typed arrays. */
|
|
630
|
+
function createFastTextGlyphData(
|
|
631
|
+
countResult: FastTextGlyphCount,
|
|
632
|
+
startIndices: Uint32Array,
|
|
633
|
+
attributes: FastTextGlyphAttributes,
|
|
634
|
+
stats: Omit<FastTextGlyphBuildStats, 'rowCount' | 'glyphCount' | 'attributeByteLength'>
|
|
635
|
+
): FastTextGlyphData {
|
|
636
|
+
const byteLength =
|
|
637
|
+
startIndices.byteLength +
|
|
638
|
+
attributes.instanceGlyphData.byteLength +
|
|
639
|
+
attributes.instancePositions.byteLength;
|
|
640
|
+
return {
|
|
641
|
+
length: countResult.glyphCount,
|
|
642
|
+
startIndices,
|
|
643
|
+
attributes,
|
|
644
|
+
characterSet: countResult.characterSet,
|
|
645
|
+
byteLength,
|
|
646
|
+
buildStats: {
|
|
647
|
+
...stats,
|
|
648
|
+
rowCount: countResult.rowCount,
|
|
649
|
+
glyphCount: countResult.glyphCount,
|
|
650
|
+
attributeByteLength: byteLength
|
|
651
|
+
}
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function createFastTextGlyphWriteTimings(): FastTextGlyphWriteTimings {
|
|
656
|
+
return {
|
|
657
|
+
textResolveDurationMs: 0,
|
|
658
|
+
styleAccessorDurationMs: 0,
|
|
659
|
+
layoutDurationMs: 0,
|
|
660
|
+
glyphWriteDurationMs: 0
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/** Return one glyph-data object with caller-supplied timing overrides. */
|
|
665
|
+
function withFastTextGlyphBuildStats(
|
|
666
|
+
glyphData: FastTextGlyphData,
|
|
667
|
+
stats: Pick<FastTextGlyphBuildStats, 'columnNormalizeDurationMs' | 'totalDurationMs'>
|
|
668
|
+
): FastTextGlyphData {
|
|
669
|
+
return {
|
|
670
|
+
...glyphData,
|
|
671
|
+
buildStats: {
|
|
672
|
+
...glyphData.buildStats,
|
|
673
|
+
...stats
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/** Write all generated glyph records for one single-line JavaScript string label row. */
|
|
679
|
+
function writeSingleLineStringLabelGlyphs<DataT>(
|
|
680
|
+
props: BuildFastTextGlyphDataProps<DataT>,
|
|
681
|
+
object: DataT,
|
|
682
|
+
objectInfo: AccessorContext<DataT>,
|
|
683
|
+
writeState: FastTextGlyphWriteState,
|
|
684
|
+
timings: FastTextGlyphWriteTimings
|
|
685
|
+
): void {
|
|
686
|
+
const textResolveStartTime = performance.now();
|
|
687
|
+
const text = resolveAccessor(props.getText, object, objectInfo) ?? '';
|
|
688
|
+
timings.textResolveDurationMs += performance.now() - textResolveStartTime;
|
|
689
|
+
if (!text) {
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const styleAccessorStartTime = performance.now();
|
|
694
|
+
const position = resolveAccessor(props.getPosition, object, objectInfo) ?? DEFAULT_POSITION;
|
|
695
|
+
const color = resolveAccessor(props.getColor, object, objectInfo) ?? DEFAULT_COLOR;
|
|
696
|
+
const clipRect = resolveAccessor(props.getClipRect, object, objectInfo) ?? DEFAULT_CLIP_RECT;
|
|
697
|
+
timings.styleAccessorDurationMs += performance.now() - styleAccessorStartTime;
|
|
698
|
+
|
|
699
|
+
const layoutStartTime = performance.now();
|
|
700
|
+
const width = getSingleLineStringWidth(text, props.mapping);
|
|
701
|
+
const lineOffsetX = ANCHOR_OFFSET_MULTIPLIER[props.textAnchor] * width;
|
|
702
|
+
const rowBaselineY = getSingleLineBaselineY(props);
|
|
703
|
+
timings.layoutDurationMs += performance.now() - layoutStartTime;
|
|
704
|
+
|
|
705
|
+
let cursorX = 0;
|
|
706
|
+
|
|
707
|
+
const glyphWriteStartTime = performance.now();
|
|
708
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
709
|
+
const character = text[index]!;
|
|
710
|
+
const frame = props.mapping[character];
|
|
711
|
+
writeFastTextGlyphRecord({
|
|
712
|
+
frame,
|
|
713
|
+
lineOffsetX,
|
|
714
|
+
cursorX,
|
|
715
|
+
rowBaselineY,
|
|
716
|
+
position,
|
|
717
|
+
color,
|
|
718
|
+
clipRect,
|
|
719
|
+
writeState
|
|
720
|
+
});
|
|
721
|
+
cursorX += frame?.advance ?? MISSING_CHARACTER_ADVANCE;
|
|
722
|
+
}
|
|
723
|
+
timings.glyphWriteDurationMs += performance.now() - glyphWriteStartTime;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
/** Write all generated glyph records for one single-line UTF-8 byte label row. */
|
|
727
|
+
function writeSingleLineUtf8LabelGlyphs<DataT>(
|
|
728
|
+
props: BuildFastTextGlyphDataProps<DataT>,
|
|
729
|
+
textUtf8Column: FastTextUtf8ColumnSource,
|
|
730
|
+
byteLookup: FastTextByteGlyphLookup,
|
|
731
|
+
textView: Utf8StringView,
|
|
732
|
+
object: DataT,
|
|
733
|
+
objectInfo: AccessorContext<DataT>,
|
|
734
|
+
writeState: FastTextGlyphWriteState,
|
|
735
|
+
timings: FastTextGlyphWriteTimings
|
|
736
|
+
): void {
|
|
737
|
+
const textResolveStartTime = performance.now();
|
|
738
|
+
const rowIndex = resolveTextUtf8Row(props.getTextUtf8Row, object, objectInfo);
|
|
739
|
+
const hasTextView =
|
|
740
|
+
rowIndex != null && getUtf8ColumnSourceRowView(textUtf8Column, rowIndex, textView);
|
|
741
|
+
timings.textResolveDurationMs += performance.now() - textResolveStartTime;
|
|
742
|
+
if (!hasTextView) {
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
if (textView.end <= textView.start) {
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
const styleAccessorStartTime = performance.now();
|
|
750
|
+
const position = resolveAccessor(props.getPosition, object, objectInfo) ?? DEFAULT_POSITION;
|
|
751
|
+
const color = resolveAccessor(props.getColor, object, objectInfo) ?? DEFAULT_COLOR;
|
|
752
|
+
const clipRect = resolveAccessor(props.getClipRect, object, objectInfo) ?? DEFAULT_CLIP_RECT;
|
|
753
|
+
timings.styleAccessorDurationMs += performance.now() - styleAccessorStartTime;
|
|
754
|
+
|
|
755
|
+
const layoutStartTime = performance.now();
|
|
756
|
+
const width = getSingleLineUtf8Width(textView, byteLookup);
|
|
757
|
+
const lineOffsetX = ANCHOR_OFFSET_MULTIPLIER[props.textAnchor] * width;
|
|
758
|
+
const rowBaselineY = getSingleLineBaselineY(props);
|
|
759
|
+
timings.layoutDurationMs += performance.now() - layoutStartTime;
|
|
760
|
+
|
|
761
|
+
let cursorX = 0;
|
|
762
|
+
|
|
763
|
+
const glyphWriteStartTime = performance.now();
|
|
764
|
+
for (let byteIndex = textView.start; byteIndex < textView.end; byteIndex += 1) {
|
|
765
|
+
const frame = byteLookup[textView.data[byteIndex] ?? -1];
|
|
766
|
+
writeFastTextGlyphRecord({
|
|
767
|
+
frame,
|
|
768
|
+
lineOffsetX,
|
|
769
|
+
cursorX,
|
|
770
|
+
rowBaselineY,
|
|
771
|
+
position,
|
|
772
|
+
color,
|
|
773
|
+
clipRect,
|
|
774
|
+
writeState
|
|
775
|
+
});
|
|
776
|
+
cursorX += frame?.advance ?? MISSING_CHARACTER_ADVANCE;
|
|
777
|
+
}
|
|
778
|
+
timings.glyphWriteDurationMs += performance.now() - glyphWriteStartTime;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/** Write all generated glyph records for one accessor-provided UTF-8 byte label row. */
|
|
782
|
+
function writeSingleLineUtf8ViewLabelGlyphs<DataT>(
|
|
783
|
+
props: BuildFastTextGlyphDataProps<DataT>,
|
|
784
|
+
getTextUtf8: FastTextUtf8ViewAccessor<DataT>,
|
|
785
|
+
byteLookup: FastTextByteGlyphLookup,
|
|
786
|
+
textView: Utf8StringView,
|
|
787
|
+
object: DataT,
|
|
788
|
+
objectInfo: AccessorContext<DataT>,
|
|
789
|
+
writeState: FastTextGlyphWriteState,
|
|
790
|
+
timings: FastTextGlyphWriteTimings
|
|
791
|
+
): void {
|
|
792
|
+
const textResolveStartTime = performance.now();
|
|
793
|
+
const hasTextView = getTextUtf8(object, textView, objectInfo);
|
|
794
|
+
timings.textResolveDurationMs += performance.now() - textResolveStartTime;
|
|
795
|
+
if (!hasTextView || textView.end <= textView.start) {
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
const styleAccessorStartTime = performance.now();
|
|
800
|
+
const position = resolveAccessor(props.getPosition, object, objectInfo) ?? DEFAULT_POSITION;
|
|
801
|
+
const color = resolveAccessor(props.getColor, object, objectInfo) ?? DEFAULT_COLOR;
|
|
802
|
+
const clipRect = resolveAccessor(props.getClipRect, object, objectInfo) ?? DEFAULT_CLIP_RECT;
|
|
803
|
+
timings.styleAccessorDurationMs += performance.now() - styleAccessorStartTime;
|
|
804
|
+
|
|
805
|
+
const layoutStartTime = performance.now();
|
|
806
|
+
const width = getSingleLineUtf8Width(textView, byteLookup);
|
|
807
|
+
const lineOffsetX = ANCHOR_OFFSET_MULTIPLIER[props.textAnchor] * width;
|
|
808
|
+
const rowBaselineY = getSingleLineBaselineY(props);
|
|
809
|
+
timings.layoutDurationMs += performance.now() - layoutStartTime;
|
|
810
|
+
|
|
811
|
+
let cursorX = 0;
|
|
812
|
+
|
|
813
|
+
const glyphWriteStartTime = performance.now();
|
|
814
|
+
for (let byteIndex = textView.start; byteIndex < textView.end; byteIndex += 1) {
|
|
815
|
+
const frame = byteLookup[textView.data[byteIndex] ?? -1];
|
|
816
|
+
writeFastTextGlyphRecord({
|
|
817
|
+
frame,
|
|
818
|
+
lineOffsetX,
|
|
819
|
+
cursorX,
|
|
820
|
+
rowBaselineY,
|
|
821
|
+
position,
|
|
822
|
+
color,
|
|
823
|
+
clipRect,
|
|
824
|
+
writeState
|
|
825
|
+
});
|
|
826
|
+
cursorX += frame?.advance ?? MISSING_CHARACTER_ADVANCE;
|
|
827
|
+
}
|
|
828
|
+
timings.glyphWriteDurationMs += performance.now() - glyphWriteStartTime;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/** Write all generated glyph records for one multiline source label row. */
|
|
832
|
+
function writeMultiLineLabelGlyphs<DataT>(
|
|
833
|
+
props: BuildFastTextGlyphDataProps<DataT>,
|
|
834
|
+
object: DataT,
|
|
835
|
+
objectInfo: AccessorContext<DataT>,
|
|
836
|
+
writeState: FastTextGlyphWriteState,
|
|
837
|
+
timings: FastTextGlyphWriteTimings
|
|
838
|
+
): void {
|
|
839
|
+
const textResolveStartTime = performance.now();
|
|
840
|
+
const text = resolveAccessor(props.getText, object, objectInfo) ?? '';
|
|
841
|
+
timings.textResolveDurationMs += performance.now() - textResolveStartTime;
|
|
842
|
+
if (!text) {
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
const styleAccessorStartTime = performance.now();
|
|
847
|
+
const position = resolveAccessor(props.getPosition, object, objectInfo) ?? DEFAULT_POSITION;
|
|
848
|
+
const color = resolveAccessor(props.getColor, object, objectInfo) ?? DEFAULT_COLOR;
|
|
849
|
+
const clipRect = resolveAccessor(props.getClipRect, object, objectInfo) ?? DEFAULT_CLIP_RECT;
|
|
850
|
+
timings.styleAccessorDurationMs += performance.now() - styleAccessorStartTime;
|
|
851
|
+
|
|
852
|
+
const layoutStartTime = performance.now();
|
|
853
|
+
const lines = buildFastTextLineLayouts(text, props.mapping);
|
|
854
|
+
const lineHeightPixels = props.lineHeight * props.fontSize;
|
|
855
|
+
const labelHeight = lines.length * lineHeightPixels;
|
|
856
|
+
const blockOffsetY = BASELINE_OFFSET_MULTIPLIER[props.alignmentBaseline] * labelHeight;
|
|
857
|
+
timings.layoutDurationMs += performance.now() - layoutStartTime;
|
|
858
|
+
|
|
859
|
+
const glyphWriteStartTime = performance.now();
|
|
860
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
861
|
+
const line = lines[lineIndex];
|
|
862
|
+
const lineOffsetX = ANCHOR_OFFSET_MULTIPLIER[props.textAnchor] * line.width;
|
|
863
|
+
const rowBaselineY =
|
|
864
|
+
blockOffsetY + props.baselineOffset + lineHeightPixels / 2 + lineIndex * lineHeightPixels;
|
|
865
|
+
let cursorX = 0;
|
|
866
|
+
|
|
867
|
+
for (const character of line.characters) {
|
|
868
|
+
const frame = props.mapping[character];
|
|
869
|
+
writeFastTextGlyphRecord({
|
|
870
|
+
frame,
|
|
871
|
+
lineOffsetX,
|
|
872
|
+
cursorX,
|
|
873
|
+
rowBaselineY,
|
|
874
|
+
position,
|
|
875
|
+
color,
|
|
876
|
+
clipRect,
|
|
877
|
+
writeState
|
|
878
|
+
});
|
|
879
|
+
cursorX += frame?.advance ?? MISSING_CHARACTER_ADVANCE;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
timings.glyphWriteDurationMs += performance.now() - glyphWriteStartTime;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/** Convert one source text into line widths and per-line characters. */
|
|
886
|
+
function buildFastTextLineLayouts(
|
|
887
|
+
text: string,
|
|
888
|
+
mapping: FastTextCharacterMapping
|
|
889
|
+
): FastTextLineLayout[] {
|
|
890
|
+
const lines: FastTextLineLayout[] = [];
|
|
891
|
+
let currentCharacters: string[] = [];
|
|
892
|
+
let currentWidth = 0;
|
|
893
|
+
let currentGlyphCount = 0;
|
|
894
|
+
|
|
895
|
+
for (const character of Array.from(text)) {
|
|
896
|
+
if (character === '\n') {
|
|
897
|
+
lines.push({
|
|
898
|
+
characters: currentCharacters,
|
|
899
|
+
width: currentWidth,
|
|
900
|
+
glyphCount: currentGlyphCount
|
|
901
|
+
});
|
|
902
|
+
currentCharacters = [];
|
|
903
|
+
currentWidth = 0;
|
|
904
|
+
currentGlyphCount = 0;
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
const frame = mapping[character];
|
|
909
|
+
currentCharacters.push(character);
|
|
910
|
+
currentWidth += frame?.advance ?? MISSING_CHARACTER_ADVANCE;
|
|
911
|
+
if (frame) {
|
|
912
|
+
currentGlyphCount++;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
lines.push({
|
|
917
|
+
characters: currentCharacters,
|
|
918
|
+
width: currentWidth,
|
|
919
|
+
glyphCount: currentGlyphCount
|
|
920
|
+
});
|
|
921
|
+
return lines;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
/** Resolve a caller-supplied Arrow vector or pre-normalized source into a direct UTF-8 source. */
|
|
925
|
+
function resolveFastTextUtf8ColumnSource(
|
|
926
|
+
textUtf8Column: FastTextUtf8Column | null | undefined
|
|
927
|
+
): FastTextUtf8ColumnSource | null {
|
|
928
|
+
if (!textUtf8Column) {
|
|
929
|
+
return null;
|
|
930
|
+
}
|
|
931
|
+
return isFastTextUtf8ColumnSource(textUtf8Column)
|
|
932
|
+
? textUtf8Column
|
|
933
|
+
: buildFastTextUtf8ColumnSource(textUtf8Column);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/** Return whether an object already has the normalized UTF-8 column-source shape. */
|
|
937
|
+
function isFastTextUtf8ColumnSource(
|
|
938
|
+
textUtf8Column: FastTextUtf8Column
|
|
939
|
+
): textUtf8Column is FastTextUtf8ColumnSource {
|
|
940
|
+
return 'rowCount' in textUtf8Column && 'chunks' in textUtf8Column;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
/** Build an ASCII byte-to-glyph lookup from the string-keyed atlas mapping. */
|
|
944
|
+
function buildFastTextByteGlyphLookup(mapping: FastTextCharacterMapping): FastTextByteGlyphLookup {
|
|
945
|
+
const lookup = new Array<FastTextCharacter | undefined>(FAST_TEXT_BYTE_LOOKUP_SIZE);
|
|
946
|
+
for (let byte = 0; byte < 128; byte += 1) {
|
|
947
|
+
lookup[byte] = mapping[String.fromCharCode(byte)];
|
|
948
|
+
}
|
|
949
|
+
return lookup;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/** Resolve the source-row index inside a UTF-8 column. */
|
|
953
|
+
function resolveTextUtf8Row<DataT>(
|
|
954
|
+
getTextUtf8Row: Accessor<DataT, number | null> | undefined,
|
|
955
|
+
object: DataT,
|
|
956
|
+
objectInfo: AccessorContext<DataT>
|
|
957
|
+
): number | null {
|
|
958
|
+
return getTextUtf8Row === undefined
|
|
959
|
+
? objectInfo.index
|
|
960
|
+
: resolveAccessor(getTextUtf8Row, object, objectInfo);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
/** Compute one single-line string label width without looking for newline separators. */
|
|
964
|
+
function getSingleLineStringWidth(text: string, mapping: FastTextCharacterMapping): number {
|
|
965
|
+
let width = 0;
|
|
966
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
967
|
+
width += mapping[text[index]!]?.advance ?? MISSING_CHARACTER_ADVANCE;
|
|
968
|
+
}
|
|
969
|
+
return width;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/** Compute one single-line UTF-8 byte label width without decoding bytes to characters. */
|
|
973
|
+
function getSingleLineUtf8Width(
|
|
974
|
+
textView: Utf8StringView,
|
|
975
|
+
byteLookup: FastTextByteGlyphLookup
|
|
976
|
+
): number {
|
|
977
|
+
let width = 0;
|
|
978
|
+
for (let byteIndex = textView.start; byteIndex < textView.end; byteIndex += 1) {
|
|
979
|
+
width += byteLookup[textView.data[byteIndex] ?? -1]?.advance ?? MISSING_CHARACTER_ADVANCE;
|
|
980
|
+
}
|
|
981
|
+
return width;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
/** Compute the baseline Y position for a one-line label. */
|
|
985
|
+
function getSingleLineBaselineY<DataT>(props: BuildFastTextGlyphDataProps<DataT>): number {
|
|
986
|
+
const lineHeightPixels = props.lineHeight * props.fontSize;
|
|
987
|
+
const blockOffsetY = BASELINE_OFFSET_MULTIPLIER[props.alignmentBaseline] * lineHeightPixels;
|
|
988
|
+
return blockOffsetY + props.baselineOffset + lineHeightPixels / 2;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
/** Write one packed glyph record into the generated attribute arrays. */
|
|
992
|
+
function writeFastTextGlyphRecord(params: {
|
|
993
|
+
/** Glyph atlas frame and metrics, or undefined for missing glyphs. */
|
|
994
|
+
readonly frame: FastTextCharacter | undefined;
|
|
995
|
+
/** Line-level horizontal offset in atlas pixels. */
|
|
996
|
+
readonly lineOffsetX: number;
|
|
997
|
+
/** Cursor X position before the glyph advance is applied. */
|
|
998
|
+
readonly cursorX: number;
|
|
999
|
+
/** Baseline Y position for this glyph row. */
|
|
1000
|
+
readonly rowBaselineY: number;
|
|
1001
|
+
/** Source label anchor position. */
|
|
1002
|
+
readonly position: Position;
|
|
1003
|
+
/** Source label color. */
|
|
1004
|
+
readonly color: Color;
|
|
1005
|
+
/** Source label content clip rectangle. */
|
|
1006
|
+
readonly clipRect: FastTextClipRect;
|
|
1007
|
+
/** Mutable glyph write cursor and typed-array views. */
|
|
1008
|
+
readonly writeState: FastTextGlyphWriteState;
|
|
1009
|
+
}): void {
|
|
1010
|
+
const frame = params.frame;
|
|
1011
|
+
const glyphIndex = params.writeState.glyphIndex;
|
|
1012
|
+
const recordInt16Index =
|
|
1013
|
+
(glyphIndex * FAST_TEXT_GLYPH_VERTEX_BYTE_STRIDE) / Int16Array.BYTES_PER_ELEMENT;
|
|
1014
|
+
const glyphX =
|
|
1015
|
+
params.lineOffsetX + params.cursorX + (frame?.anchorX ?? 0) - (frame?.width ?? 0) / 2;
|
|
1016
|
+
const glyphY = params.rowBaselineY - (frame?.anchorY ?? 0);
|
|
1017
|
+
const writeState = params.writeState;
|
|
1018
|
+
|
|
1019
|
+
writeFastTextGlyphPosition(writeState.attributes, glyphIndex, params.position);
|
|
1020
|
+
writeState.glyphRecordViews.int16Values[recordInt16Index] = toInt16(glyphX);
|
|
1021
|
+
writeState.glyphRecordViews.int16Values[recordInt16Index + 1] = toInt16(glyphY);
|
|
1022
|
+
writeState.glyphRecordViews.uint16Values[recordInt16Index + 2] = toUint16(frame?.x ?? 0);
|
|
1023
|
+
writeState.glyphRecordViews.uint16Values[recordInt16Index + 3] = toUint16(frame?.y ?? 0);
|
|
1024
|
+
writeState.glyphRecordViews.uint16Values[recordInt16Index + 4] = toUint16(frame?.width ?? 0);
|
|
1025
|
+
writeState.glyphRecordViews.uint16Values[recordInt16Index + 5] = toUint16(frame?.height ?? 0);
|
|
1026
|
+
writeFastTextGlyphClipRect(writeState.glyphRecordViews, glyphIndex, params.clipRect);
|
|
1027
|
+
writeFastTextGlyphColor(writeState.glyphRecordViews, glyphIndex, params.color);
|
|
1028
|
+
writeState.glyphIndex++;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
/** Write one source anchor position into the repeated per-glyph position array. */
|
|
1032
|
+
function writeFastTextGlyphPosition(
|
|
1033
|
+
attributes: FastTextGlyphAttributes,
|
|
1034
|
+
glyphIndex: number,
|
|
1035
|
+
position: Position
|
|
1036
|
+
): void {
|
|
1037
|
+
const positionOffset = glyphIndex * 2;
|
|
1038
|
+
attributes.instancePositions[positionOffset] = position[0] ?? 0;
|
|
1039
|
+
attributes.instancePositions[positionOffset + 1] = position[1] ?? 0;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
/** Write one source clip rectangle into the packed generated glyph record. */
|
|
1043
|
+
function writeFastTextGlyphClipRect(
|
|
1044
|
+
glyphRecordViews: FastTextGlyphRecordViews,
|
|
1045
|
+
glyphIndex: number,
|
|
1046
|
+
clipRect: FastTextClipRect
|
|
1047
|
+
): void {
|
|
1048
|
+
const recordInt16Index =
|
|
1049
|
+
(glyphIndex * FAST_TEXT_GLYPH_VERTEX_BYTE_STRIDE) / Int16Array.BYTES_PER_ELEMENT;
|
|
1050
|
+
glyphRecordViews.int16Values[recordInt16Index + 6] = toInt16(clipRect[0] ?? 0);
|
|
1051
|
+
glyphRecordViews.int16Values[recordInt16Index + 7] = toInt16(clipRect[1] ?? 0);
|
|
1052
|
+
glyphRecordViews.int16Values[recordInt16Index + 8] = toInt16ClipDimension(clipRect[2] ?? -1);
|
|
1053
|
+
glyphRecordViews.int16Values[recordInt16Index + 9] = toInt16ClipDimension(clipRect[3] ?? -1);
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
/** Write one source color into the packed generated glyph record. */
|
|
1057
|
+
function writeFastTextGlyphColor(
|
|
1058
|
+
glyphRecordViews: FastTextGlyphRecordViews,
|
|
1059
|
+
glyphIndex: number,
|
|
1060
|
+
color: Color
|
|
1061
|
+
): void {
|
|
1062
|
+
const recordByteIndex = glyphIndex * FAST_TEXT_GLYPH_VERTEX_BYTE_STRIDE;
|
|
1063
|
+
writeColor(
|
|
1064
|
+
glyphRecordViews.uint8Values,
|
|
1065
|
+
recordByteIndex + Int16Array.BYTES_PER_ELEMENT * 10,
|
|
1066
|
+
color
|
|
1067
|
+
);
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
/** Resolve deck accessors that may be constants or functions. */
|
|
1071
|
+
function resolveAccessor<DataT, ValueT>(
|
|
1072
|
+
accessor: Accessor<DataT, ValueT>,
|
|
1073
|
+
object: DataT,
|
|
1074
|
+
objectInfo: AccessorContext<DataT>
|
|
1075
|
+
): ValueT {
|
|
1076
|
+
return typeof accessor === 'function'
|
|
1077
|
+
? (accessor as (object: DataT, objectInfo: AccessorContext<DataT>) => ValueT)(
|
|
1078
|
+
object,
|
|
1079
|
+
objectInfo
|
|
1080
|
+
)
|
|
1081
|
+
: accessor;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
/** Write one deck color into a byte array with a default opaque alpha. */
|
|
1085
|
+
function writeColor(target: Uint8Array, offset: number, color: Color): void {
|
|
1086
|
+
target[offset] = clampByte(color[0] ?? DEFAULT_COLOR[0]);
|
|
1087
|
+
target[offset + 1] = clampByte(color[1] ?? DEFAULT_COLOR[1]);
|
|
1088
|
+
target[offset + 2] = clampByte(color[2] ?? DEFAULT_COLOR[2]);
|
|
1089
|
+
target[offset + 3] = clampByte(color[3] ?? DEFAULT_COLOR[3]);
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
/** Clamp one color channel into the unsigned byte range. */
|
|
1093
|
+
function clampByte(value: number): number {
|
|
1094
|
+
return Math.max(0, Math.min(255, Math.round(value)));
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/** Round and validate one value for the signed 16-bit generated attribute record. */
|
|
1098
|
+
function toInt16(value: number): number {
|
|
1099
|
+
const integerValue = Math.round(value);
|
|
1100
|
+
if (integerValue < -32768 || integerValue > 32767) {
|
|
1101
|
+
throw new Error(`FastTextLayer value ${value} is outside the signed 16-bit range`);
|
|
1102
|
+
}
|
|
1103
|
+
return integerValue;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
/** Round one clip dimension while preserving positive non-zero values. */
|
|
1107
|
+
function toInt16ClipDimension(value: number): number {
|
|
1108
|
+
const integerValue = value > 0 ? Math.max(1, Math.round(value)) : Math.round(value);
|
|
1109
|
+
if (integerValue < -32768 || integerValue > 32767) {
|
|
1110
|
+
throw new Error(`FastTextLayer value ${value} is outside the signed 16-bit range`);
|
|
1111
|
+
}
|
|
1112
|
+
return integerValue;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
/** Round and validate one atlas frame value for the unsigned 16-bit generated attribute record. */
|
|
1116
|
+
function toUint16(value: number): number {
|
|
1117
|
+
const integerValue = Math.round(value);
|
|
1118
|
+
if (integerValue < 0 || integerValue > 65535) {
|
|
1119
|
+
throw new Error(
|
|
1120
|
+
`FastTextLayer glyph frame value ${value} is outside the unsigned 16-bit range`
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
return integerValue;
|
|
1124
|
+
}
|