@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.
Files changed (129) hide show
  1. package/dist/index.cjs +2998 -114
  2. package/dist/index.cjs.map +4 -4
  3. package/dist/index.d.ts +11 -2
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +9 -1
  6. package/dist/index.js.map +1 -1
  7. package/dist/layers/animation-layer/animation-layer.d.ts +30 -0
  8. package/dist/layers/animation-layer/animation-layer.d.ts.map +1 -0
  9. package/dist/layers/animation-layer/animation-layer.js +53 -0
  10. package/dist/layers/animation-layer/animation-layer.js.map +1 -0
  11. package/dist/layers/animation-layer/animation.d.ts +86 -0
  12. package/dist/layers/animation-layer/animation.d.ts.map +1 -0
  13. package/dist/layers/animation-layer/animation.js +214 -0
  14. package/dist/layers/animation-layer/animation.js.map +1 -0
  15. package/dist/layers/block-layer/block-layer-fragment.glsl.d.ts +3 -0
  16. package/dist/layers/block-layer/block-layer-fragment.glsl.d.ts.map +1 -0
  17. package/dist/layers/block-layer/block-layer-fragment.glsl.js +41 -0
  18. package/dist/layers/block-layer/block-layer-fragment.glsl.js.map +1 -0
  19. package/dist/layers/block-layer/block-layer-uniforms.d.ts +27 -0
  20. package/dist/layers/block-layer/block-layer-uniforms.d.ts.map +1 -0
  21. package/dist/layers/block-layer/block-layer-uniforms.js +26 -0
  22. package/dist/layers/block-layer/block-layer-uniforms.js.map +1 -0
  23. package/dist/layers/block-layer/block-layer-vertex.glsl.d.ts +3 -0
  24. package/dist/layers/block-layer/block-layer-vertex.glsl.d.ts.map +1 -0
  25. package/dist/layers/block-layer/block-layer-vertex.glsl.js +67 -0
  26. package/dist/layers/block-layer/block-layer-vertex.glsl.js.map +1 -0
  27. package/dist/layers/block-layer/block-layer.d.ts +71 -0
  28. package/dist/layers/block-layer/block-layer.d.ts.map +1 -0
  29. package/dist/layers/block-layer/block-layer.js +108 -0
  30. package/dist/layers/block-layer/block-layer.js.map +1 -0
  31. package/dist/layers/fast-text-layer/fast-text-layer.d.ts +148 -0
  32. package/dist/layers/fast-text-layer/fast-text-layer.d.ts.map +1 -0
  33. package/dist/layers/fast-text-layer/fast-text-layer.js +862 -0
  34. package/dist/layers/fast-text-layer/fast-text-layer.js.map +1 -0
  35. package/dist/layers/fast-text-layer/fast-text-layer.test.d.ts +2 -0
  36. package/dist/layers/fast-text-layer/fast-text-layer.test.d.ts.map +1 -0
  37. package/dist/layers/fast-text-layer/fast-text-layer.test.js +300 -0
  38. package/dist/layers/fast-text-layer/fast-text-layer.test.js.map +1 -0
  39. package/dist/layers/fast-text-layer/fast-text-layout.d.ts +179 -0
  40. package/dist/layers/fast-text-layer/fast-text-layout.d.ts.map +1 -0
  41. package/dist/layers/fast-text-layer/fast-text-layout.js +665 -0
  42. package/dist/layers/fast-text-layer/fast-text-layout.js.map +1 -0
  43. package/dist/layers/fast-text-layer/font-atlas.d.ts +98 -0
  44. package/dist/layers/fast-text-layer/font-atlas.d.ts.map +1 -0
  45. package/dist/layers/fast-text-layer/font-atlas.js +337 -0
  46. package/dist/layers/fast-text-layer/font-atlas.js.map +1 -0
  47. package/dist/layers/fast-text-layer/index.d.ts +4 -0
  48. package/dist/layers/fast-text-layer/index.d.ts.map +1 -0
  49. package/dist/layers/fast-text-layer/index.js +4 -0
  50. package/dist/layers/fast-text-layer/index.js.map +1 -0
  51. package/dist/layers/time-delta-layer.d.ts +22 -60
  52. package/dist/layers/time-delta-layer.d.ts.map +1 -1
  53. package/dist/layers/time-delta-layer.js +31 -53
  54. package/dist/layers/time-delta-layer.js.map +1 -1
  55. package/dist/synchronized-views/synchronized-views.js +1 -1
  56. package/dist/synchronized-views/synchronized-views.js.map +1 -1
  57. package/dist/utils/format-utils.d.ts +27 -3
  58. package/dist/utils/format-utils.d.ts.map +1 -1
  59. package/dist/utils/format-utils.js +72 -40
  60. package/dist/utils/format-utils.js.map +1 -1
  61. package/dist/utils/utf8-string-view.d.ts +53 -0
  62. package/dist/utils/utf8-string-view.d.ts.map +1 -0
  63. package/dist/utils/utf8-string-view.js +169 -0
  64. package/dist/utils/utf8-string-view.js.map +1 -0
  65. package/dist/utils/utf8-string-view.test.d.ts +2 -0
  66. package/dist/utils/utf8-string-view.test.d.ts.map +1 -0
  67. package/dist/utils/utf8-string-view.test.js +88 -0
  68. package/dist/utils/utf8-string-view.test.js.map +1 -0
  69. package/dist/views/bounds-utils.d.ts +23 -0
  70. package/dist/views/bounds-utils.d.ts.map +1 -0
  71. package/dist/views/bounds-utils.js +65 -0
  72. package/dist/views/bounds-utils.js.map +1 -0
  73. package/dist/views/layer-bounds-filter.d.ts +48 -0
  74. package/dist/views/layer-bounds-filter.d.ts.map +1 -0
  75. package/dist/views/layer-bounds-filter.js +99 -0
  76. package/dist/views/layer-bounds-filter.js.map +1 -0
  77. package/dist/views/layer-filter.js +4 -4
  78. package/dist/views/layer-filter.js.map +1 -1
  79. package/dist/views/orthographic-utils.d.ts +30 -2
  80. package/dist/views/orthographic-utils.d.ts.map +1 -1
  81. package/dist/views/orthographic-utils.js +33 -14
  82. package/dist/views/orthographic-utils.js.map +1 -1
  83. package/dist/views/view-layout/build-views-from-view-layout.d.ts +37 -0
  84. package/dist/views/view-layout/build-views-from-view-layout.d.ts.map +1 -0
  85. package/dist/views/view-layout/build-views-from-view-layout.js +413 -0
  86. package/dist/views/view-layout/build-views-from-view-layout.js.map +1 -0
  87. package/dist/views/view-layout/index.d.ts +3 -0
  88. package/dist/views/view-layout/index.d.ts.map +1 -0
  89. package/dist/views/view-layout/index.js +3 -0
  90. package/dist/views/view-layout/index.js.map +1 -0
  91. package/dist/views/view-layout/view-layout-item.d.ts +74 -0
  92. package/dist/views/view-layout/view-layout-item.d.ts.map +1 -0
  93. package/dist/views/view-layout/view-layout-item.js +48 -0
  94. package/dist/views/view-layout/view-layout-item.js.map +1 -0
  95. package/dist/views/view-layout/view-layout.test.d.ts +2 -0
  96. package/dist/views/view-layout/view-layout.test.d.ts.map +1 -0
  97. package/dist/views/view-layout/view-layout.test.js +112 -0
  98. package/dist/views/view-layout/view-layout.test.js.map +1 -0
  99. package/dist/views/viewport-bounds-utils.d.ts +55 -0
  100. package/dist/views/viewport-bounds-utils.d.ts.map +1 -0
  101. package/dist/views/viewport-bounds-utils.js +70 -0
  102. package/dist/views/viewport-bounds-utils.js.map +1 -0
  103. package/package.json +15 -5
  104. package/src/index.ts +91 -2
  105. package/src/layers/animation-layer/animation-layer.ts +92 -0
  106. package/src/layers/animation-layer/animation.ts +328 -0
  107. package/src/layers/block-layer/block-layer-fragment.glsl.ts +41 -0
  108. package/src/layers/block-layer/block-layer-uniforms.ts +43 -0
  109. package/src/layers/block-layer/block-layer-vertex.glsl.ts +67 -0
  110. package/src/layers/block-layer/block-layer.ts +199 -0
  111. package/src/layers/fast-text-layer/fast-text-layer.test.ts +359 -0
  112. package/src/layers/fast-text-layer/fast-text-layer.ts +1208 -0
  113. package/src/layers/fast-text-layer/fast-text-layout.ts +1124 -0
  114. package/src/layers/fast-text-layer/font-atlas.ts +574 -0
  115. package/src/layers/fast-text-layer/index.ts +37 -0
  116. package/src/layers/time-delta-layer.ts +55 -70
  117. package/src/synchronized-views/synchronized-views.ts +1 -1
  118. package/src/utils/format-utils.ts +90 -40
  119. package/src/utils/utf8-string-view.test.ts +119 -0
  120. package/src/utils/utf8-string-view.ts +273 -0
  121. package/src/views/bounds-utils.ts +88 -0
  122. package/src/views/layer-bounds-filter.ts +160 -0
  123. package/src/views/layer-filter.ts +4 -4
  124. package/src/views/orthographic-utils.ts +65 -18
  125. package/src/views/view-layout/build-views-from-view-layout.ts +547 -0
  126. package/src/views/view-layout/index.ts +18 -0
  127. package/src/views/view-layout/view-layout-item.ts +125 -0
  128. package/src/views/view-layout/view-layout.test.ts +129 -0
  129. package/src/views/viewport-bounds-utils.ts +104 -0
@@ -0,0 +1,862 @@
1
+ // Minimal WebGL attribute text layer ported from WIP luma.gl text utilities.
2
+ // SPDX-License-Identifier: MIT
3
+ import { color, createIterable, Layer, project32, UNIT } from '@deck.gl/core';
4
+ import { Buffer } from '@luma.gl/core';
5
+ import { Geometry, Model } from '@luma.gl/engine';
6
+ import { Log } from '@probe.gl/log';
7
+ import { buildFastTextGlyphData, buildFastTextUtf8ColumnSource, collectFastTextCharacterSet, FAST_TEXT_GLYPH_VERTEX_BYTE_STRIDE, updateFastTextDynamicGlyphAttributes } from "./fast-text-layout.js";
8
+ import { DEFAULT_FAST_TEXT_FONT_SETTINGS, FastTextFontAtlasManager } from "./font-atlas.js";
9
+ const defaultProps = {
10
+ billboard: true,
11
+ sizeScale: { type: 'number', value: 1, min: 0 },
12
+ sizeUnits: 'pixels',
13
+ sizeMinPixels: { type: 'number', value: 0, min: 0 },
14
+ sizeMaxPixels: { type: 'number', value: Number.MAX_SAFE_INTEGER, min: 0 },
15
+ alphaCutoff: { type: 'number', value: 0.001, min: 0, max: 1 },
16
+ characterSet: { type: 'object', value: DEFAULT_FAST_TEXT_FONT_SETTINGS.characterSet },
17
+ fontFamily: DEFAULT_FAST_TEXT_FONT_SETTINGS.fontFamily,
18
+ fontWeight: DEFAULT_FAST_TEXT_FONT_SETTINGS.fontWeight,
19
+ lineHeight: { type: 'number', value: 1, min: 0 },
20
+ size: { type: 'number', value: 32, min: 0 },
21
+ pixelOffset: { type: 'array', value: [0, 0] },
22
+ getPixelOffset: { type: 'array', value: [0, 0] },
23
+ fontSettings: { type: 'object', value: {}, compare: 1 },
24
+ characterMapping: { type: 'object', value: null, optional: true },
25
+ fontAtlas: { type: 'object', value: null, optional: true },
26
+ getText: { type: 'accessor', value: (datum) => datum.text },
27
+ textUtf8Column: { type: 'object', value: null, optional: true },
28
+ getTextUtf8Row: {
29
+ type: 'accessor',
30
+ value: (_datum, objectInfo) => objectInfo.index
31
+ },
32
+ getTextUtf8: { type: 'accessor', value: null },
33
+ singleLine: true,
34
+ getPosition: { type: 'accessor', value: (datum) => datum.position },
35
+ getColor: { type: 'accessor', value: [0, 0, 0, 255] },
36
+ textAnchor: 'middle',
37
+ alignmentBaseline: 'center',
38
+ getClipRect: { type: 'accessor', value: [0, 0, -1, -1] },
39
+ getContentBox: { type: 'accessor', value: [0, 0, -1, -1] },
40
+ contentCutoffPixels: { type: 'array', value: [0, 0] },
41
+ contentAlignHorizontal: 'none',
42
+ contentAlignVertical: 'none'
43
+ };
44
+ /** Lightweight bitmap text layer backed by one typed-array glyph instance stream. */
45
+ export class FastTextLayer extends Layer {
46
+ static defaultProps = defaultProps;
47
+ static layerName = 'FastTextLayer';
48
+ /** Return the shader set used by the direct glyph instance model. */
49
+ getShaders() {
50
+ return super.getShaders({
51
+ vs: FAST_TEXT_VS,
52
+ fs: FAST_TEXT_FS,
53
+ modules: [project32, color, fastTextUniforms]
54
+ });
55
+ }
56
+ /** Initialize the model and atlas manager. */
57
+ initializeState() {
58
+ this.state = {
59
+ fontAtlasManager: new FastTextFontAtlasManager()
60
+ };
61
+ this.state.model = this.getModel();
62
+ }
63
+ /** Rebuild text layout or update dynamic glyph attributes when inputs change. */
64
+ updateState(params) {
65
+ super.updateState(params);
66
+ if (params.changeFlags.extensionsChanged) {
67
+ this.state.model?.destroy();
68
+ this.state.model = this.getModel();
69
+ if (this.state.buffers) {
70
+ this.state.model.setAttributes(this.state.buffers);
71
+ this.state.model.setInstanceCount(this.state.glyphData?.length ?? 0);
72
+ }
73
+ }
74
+ if (shouldRebuildFastText(params) || this.hasMutableLayoutSourceChanged()) {
75
+ this.rebuildGlyphState();
76
+ }
77
+ else {
78
+ const dynamicUpdate = getFastTextDynamicUpdate(params);
79
+ if (dynamicUpdate) {
80
+ this.updateDynamicGlyphState(dynamicUpdate);
81
+ }
82
+ }
83
+ }
84
+ /** Draw the glyph model when atlas texture and glyph buffers are ready. */
85
+ draw() {
86
+ const { atlasTexture, glyphData, model, fontSize } = this.state;
87
+ if (!model || !atlasTexture || !glyphData || glyphData.length === 0 || !fontSize) {
88
+ return;
89
+ }
90
+ model.shaderInputs.setProps({
91
+ fastText: {
92
+ fontAtlasTexture: atlasTexture,
93
+ fontAtlasSize: [atlasTexture.width, atlasTexture.height],
94
+ fontSize,
95
+ size: this.props.size,
96
+ sizeScale: this.props.sizeScale,
97
+ sizeMinPixels: this.props.sizeMinPixels,
98
+ sizeMaxPixels: this.props.sizeMaxPixels,
99
+ pixelOffset: this.props.pixelOffset,
100
+ billboard: this.props.billboard,
101
+ sizeUnits: UNIT[this.props.sizeUnits],
102
+ alphaCutoff: this.props.alphaCutoff,
103
+ sdfEnabled: this.resolveSdfEnabled(),
104
+ sdfBuffer: FAST_TEXT_SDF_BUFFER,
105
+ sdfGamma: this.resolveSdfSmoothing(),
106
+ contentCutoffPixels: this.props.contentCutoffPixels,
107
+ contentAlign: [
108
+ getFastTextContentAlignUniform(this.props.contentAlignHorizontal),
109
+ getFastTextContentAlignUniform(this.props.contentAlignVertical)
110
+ ],
111
+ flipY: Boolean(this.context.viewport.flipY)
112
+ }
113
+ });
114
+ model.draw(this.context.renderPass);
115
+ }
116
+ /** Release GPU buffers, atlas texture, and the luma model. */
117
+ finalizeState(context) {
118
+ destroyFastTextBuffers(this.state.buffers);
119
+ this.state.atlasTexture?.destroy();
120
+ this.state.model?.destroy();
121
+ super.finalizeState(context);
122
+ }
123
+ /** Build a luma model for instanced glyph quads. */
124
+ getModel() {
125
+ return new Model(this.context.device, {
126
+ ...this.getShaders(),
127
+ id: this.props.id,
128
+ bufferLayout: FAST_TEXT_BUFFER_LAYOUT,
129
+ geometry: new Geometry({
130
+ topology: 'triangle-list',
131
+ attributes: {
132
+ positions: {
133
+ size: 2,
134
+ value: new Float32Array([0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1])
135
+ }
136
+ }
137
+ }),
138
+ isInstanced: true
139
+ });
140
+ }
141
+ /** Rebuild CPU glyph data, GPU vertex buffers, and the font atlas texture. */
142
+ rebuildGlyphState() {
143
+ const rebuildStartTime = performance.now();
144
+ const atlasStartTime = performance.now();
145
+ const atlas = this.resolveFontAtlas();
146
+ const atlasDurationMs = performance.now() - atlasStartTime;
147
+ const mapping = this.props.characterMapping ?? atlas.mapping;
148
+ const glyphDataStartTime = performance.now();
149
+ const glyphData = buildFastTextGlyphData({
150
+ data: this.props.data,
151
+ getText: this.props.getText,
152
+ textUtf8Column: this.props.textUtf8Column,
153
+ getTextUtf8Row: this.props.getTextUtf8Row,
154
+ getTextUtf8: this.props.getTextUtf8,
155
+ singleLine: this.props.singleLine,
156
+ getPosition: this.props.getPosition,
157
+ getColor: this.props.getColor,
158
+ textAnchor: this.props.textAnchor,
159
+ alignmentBaseline: this.props.alignmentBaseline,
160
+ getClipRect: this.props.getClipRect,
161
+ mapping,
162
+ baselineOffset: atlas.baselineOffset,
163
+ fontSize: this.resolveFontSize(),
164
+ lineHeight: this.props.lineHeight
165
+ });
166
+ const glyphDataDurationMs = performance.now() - glyphDataStartTime;
167
+ const bufferStartTime = performance.now();
168
+ const buffers = createFastTextBuffers(this.context.device, glyphData);
169
+ const bufferCreateDurationMs = performance.now() - bufferStartTime;
170
+ destroyFastTextBuffers(this.state.buffers);
171
+ this.state.model?.setAttributes(buffers);
172
+ this.state.model?.setInstanceCount(glyphData.length);
173
+ this.setState({
174
+ glyphData,
175
+ buffers,
176
+ layoutSourceSignature: getFastTextLayoutSourceSignature(this.props)
177
+ });
178
+ logFastTextAttributeBuildProbe({
179
+ atlasDurationMs,
180
+ bufferCreateDurationMs,
181
+ glyphDataDurationMs,
182
+ glyphStats: glyphData.buildStats,
183
+ layerId: this.props.id,
184
+ totalDurationMs: performance.now() - rebuildStartTime
185
+ });
186
+ this.setNeedsRedraw();
187
+ }
188
+ /** Rewrite dynamic glyph attributes in place without rebuilding text layout. */
189
+ updateDynamicGlyphState(update) {
190
+ const { glyphData, buffers } = this.state;
191
+ if (!glyphData || !buffers) {
192
+ this.rebuildGlyphState();
193
+ return;
194
+ }
195
+ const updateStartTime = performance.now();
196
+ const dynamicStats = updateFastTextDynamicGlyphAttributes({
197
+ data: this.props.data,
198
+ glyphData,
199
+ getPosition: this.props.getPosition,
200
+ getColor: this.props.getColor,
201
+ getClipRect: this.props.getClipRect,
202
+ updatePositions: update.positions,
203
+ updateColors: update.colors,
204
+ updateClipRects: update.clipRects
205
+ });
206
+ if (dynamicStats.rowCount !== glyphData.startIndices.length - 1) {
207
+ this.rebuildGlyphState();
208
+ return;
209
+ }
210
+ const bufferUploadStartTime = performance.now();
211
+ if (update.positions) {
212
+ buffers.instancePositions.write(glyphData.attributes.instancePositions);
213
+ }
214
+ if (update.colors || update.clipRects) {
215
+ buffers.instanceGlyphData.write(glyphData.attributes.instanceGlyphData);
216
+ }
217
+ const bufferUploadDurationMs = performance.now() - bufferUploadStartTime;
218
+ logFastTextDynamicAttributeUpdateProbe({
219
+ bufferUploadDurationMs,
220
+ dynamicStats,
221
+ layerId: this.props.id,
222
+ totalDurationMs: performance.now() - updateStartTime,
223
+ update
224
+ });
225
+ this.setNeedsRedraw();
226
+ }
227
+ /** Return whether source data or UTF-8 column growth invalidated the current glyph layout. */
228
+ hasMutableLayoutSourceChanged() {
229
+ const { glyphData, layoutSourceSignature } = this.state;
230
+ if (!glyphData || !layoutSourceSignature) {
231
+ return true;
232
+ }
233
+ return !areFastTextLayoutSourceSignaturesEqual(getFastTextLayoutSourceSignature(this.props), layoutSourceSignature);
234
+ }
235
+ /** Resolve a caller-supplied atlas or rebuild the local generated atlas. */
236
+ resolveFontAtlas() {
237
+ const { characterMapping, fontAtlas } = this.props;
238
+ if ((this.props.textUtf8Column || this.props.getTextUtf8) &&
239
+ this.props.characterSet === 'auto') {
240
+ throw new Error('FastTextLayer characterSet="auto" is not supported with UTF-8 text sources');
241
+ }
242
+ if (characterMapping || fontAtlas) {
243
+ if (!characterMapping || !fontAtlas) {
244
+ throw new Error('FastTextLayer requires characterMapping and fontAtlas to be supplied together');
245
+ }
246
+ this.syncAtlasTexture(fontAtlas);
247
+ return fontAtlas;
248
+ }
249
+ const characterSet = this.props.characterSet === 'auto'
250
+ ? collectFastTextCharacterSet({
251
+ data: this.props.data,
252
+ getText: this.props.getText
253
+ })
254
+ : this.props.characterSet;
255
+ this.state.fontAtlasManager.setProps({
256
+ ...this.props.fontSettings,
257
+ fontFamily: this.props.fontFamily,
258
+ fontWeight: this.props.fontWeight,
259
+ characterSet
260
+ });
261
+ const atlas = this.state.fontAtlasManager.atlas;
262
+ if (!atlas) {
263
+ throw new Error('FastTextLayer failed to build a font atlas');
264
+ }
265
+ this.syncAtlasTexture(atlas);
266
+ return atlas;
267
+ }
268
+ /** Resolve the atlas pixel size used by glyph offsets. */
269
+ resolveFontSize() {
270
+ return this.props.fontAtlas
271
+ ? (this.props.fontSettings.fontSize ?? DEFAULT_FAST_TEXT_FONT_SETTINGS.fontSize)
272
+ : this.state.fontAtlasManager.props.fontSize;
273
+ }
274
+ /** Resolve whether the current atlas should be sampled as SDF data. */
275
+ resolveSdfEnabled() {
276
+ return this.props.fontAtlas
277
+ ? (this.props.fontSettings.sdf ?? DEFAULT_FAST_TEXT_FONT_SETTINGS.sdf)
278
+ : this.state.fontAtlasManager.props.sdf;
279
+ }
280
+ /** Resolve the SDF smoothing width for the current atlas. */
281
+ resolveSdfSmoothing() {
282
+ return this.props.fontAtlas
283
+ ? (this.props.fontSettings.smoothing ?? DEFAULT_FAST_TEXT_FONT_SETTINGS.smoothing)
284
+ : this.state.fontAtlasManager.props.smoothing;
285
+ }
286
+ /** Upload a new atlas texture when the atlas object changes. */
287
+ syncAtlasTexture(atlas) {
288
+ if (this.state.textureAtlas === atlas && this.state.atlasTexture) {
289
+ return;
290
+ }
291
+ this.state.atlasTexture?.destroy();
292
+ const texture = createFastTextAtlasTexture(this.context.device, atlas);
293
+ this.setState({
294
+ textureAtlas: atlas,
295
+ atlasTexture: texture,
296
+ fontSize: this.resolveFontSize()
297
+ });
298
+ }
299
+ }
300
+ const FAST_TEXT_CONTENT_ALIGN = {
301
+ none: 0,
302
+ start: 1,
303
+ center: 2,
304
+ end: 3
305
+ };
306
+ const FAST_TEXT_ALIGN_START = FAST_TEXT_CONTENT_ALIGN.start;
307
+ const FAST_TEXT_ALIGN_CENTER = FAST_TEXT_CONTENT_ALIGN.center;
308
+ const FAST_TEXT_ALIGN_END = FAST_TEXT_CONTENT_ALIGN.end;
309
+ const FAST_TEXT_SDF_BUFFER = 192.0 / 256.0;
310
+ const fastTextLog = new Log({ id: 'trace-layers' });
311
+ const FAST_TEXT_PROBE_LABEL_STYLE = 'background:#f59e0b;color:#111827;font-weight:700;padding:2px 8px;border-radius:6px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;';
312
+ const fastTextUniforms = {
313
+ name: 'fastText',
314
+ vs: `\
315
+ layout(std140) uniform fastTextUniforms {
316
+ vec2 fontAtlasSize;
317
+ float fontSize;
318
+ float size;
319
+ float sizeScale;
320
+ float sizeMinPixels;
321
+ float sizeMaxPixels;
322
+ vec2 pixelOffset;
323
+ bool billboard;
324
+ highp int sizeUnits;
325
+ float alphaCutoff;
326
+ bool sdfEnabled;
327
+ float sdfBuffer;
328
+ float sdfGamma;
329
+ vec2 contentCutoffPixels;
330
+ highp ivec2 contentAlign;
331
+ bool flipY;
332
+ } fastText;
333
+
334
+ #define FAST_TEXT_ALIGN_MODE_START ${FAST_TEXT_ALIGN_START}
335
+ #define FAST_TEXT_ALIGN_MODE_CENTER ${FAST_TEXT_ALIGN_CENTER}
336
+ #define FAST_TEXT_ALIGN_MODE_END ${FAST_TEXT_ALIGN_END}
337
+ `,
338
+ fs: `\
339
+ layout(std140) uniform fastTextUniforms {
340
+ vec2 fontAtlasSize;
341
+ float fontSize;
342
+ float size;
343
+ float sizeScale;
344
+ float sizeMinPixels;
345
+ float sizeMaxPixels;
346
+ vec2 pixelOffset;
347
+ bool billboard;
348
+ highp int sizeUnits;
349
+ float alphaCutoff;
350
+ bool sdfEnabled;
351
+ float sdfBuffer;
352
+ float sdfGamma;
353
+ vec2 contentCutoffPixels;
354
+ highp ivec2 contentAlign;
355
+ bool flipY;
356
+ } fastText;
357
+ `,
358
+ uniformTypes: {
359
+ fontAtlasSize: 'vec2<f32>',
360
+ fontSize: 'f32',
361
+ size: 'f32',
362
+ sizeScale: 'f32',
363
+ sizeMinPixels: 'f32',
364
+ sizeMaxPixels: 'f32',
365
+ pixelOffset: 'vec2<f32>',
366
+ billboard: 'f32',
367
+ sizeUnits: 'i32',
368
+ alphaCutoff: 'f32',
369
+ sdfEnabled: 'f32',
370
+ sdfBuffer: 'f32',
371
+ sdfGamma: 'f32',
372
+ contentCutoffPixels: 'vec2<f32>',
373
+ contentAlign: 'vec2<i32>',
374
+ flipY: 'f32'
375
+ }
376
+ };
377
+ const FAST_TEXT_BUFFER_LAYOUT = [
378
+ { name: 'instancePositions', stepMode: 'instance', format: 'float32x2' },
379
+ {
380
+ name: 'instanceGlyphData',
381
+ stepMode: 'instance',
382
+ byteStride: FAST_TEXT_GLYPH_VERTEX_BYTE_STRIDE,
383
+ attributes: [
384
+ { attribute: 'instanceGlyphOffsets', format: 'sint16x2', byteOffset: 0 },
385
+ {
386
+ attribute: 'instanceGlyphFrames',
387
+ format: 'uint16x4',
388
+ byteOffset: Int16Array.BYTES_PER_ELEMENT * 2
389
+ },
390
+ {
391
+ attribute: 'instanceClipRects',
392
+ format: 'sint16x4',
393
+ byteOffset: Int16Array.BYTES_PER_ELEMENT * 6
394
+ },
395
+ {
396
+ attribute: 'instanceColors',
397
+ format: 'unorm8x4',
398
+ byteOffset: Int16Array.BYTES_PER_ELEMENT * 10
399
+ }
400
+ ]
401
+ }
402
+ ];
403
+ const DEFAULT_SAMPLER_PARAMETERS = {
404
+ minFilter: 'linear',
405
+ mipmapFilter: 'linear',
406
+ magFilter: 'linear',
407
+ addressModeU: 'clamp-to-edge',
408
+ addressModeV: 'clamp-to-edge'
409
+ };
410
+ const FAST_TEXT_VS = `\
411
+ #version 300 es
412
+ #define SHADER_NAME fast-text-layer-vertex-shader
413
+
414
+ in vec2 positions;
415
+ in vec2 instancePositions;
416
+ in ivec2 instanceGlyphOffsets;
417
+ in uvec4 instanceGlyphFrames;
418
+ in ivec4 instanceClipRects;
419
+ in vec4 instanceColors;
420
+
421
+ out vec4 vColor;
422
+ out vec2 vTextureCoords;
423
+ out vec2 uv;
424
+
425
+ float fastTextGetAlignmentPixelOffset(float anchor, float extent, float clipStart, float clipEnd, int mode) {
426
+ if (clipEnd < clipStart) {
427
+ return 0.0;
428
+ }
429
+ if (mode == FAST_TEXT_ALIGN_MODE_START) {
430
+ return max(-(anchor + clipStart), 0.0);
431
+ }
432
+ if (mode == FAST_TEXT_ALIGN_MODE_CENTER) {
433
+ float visibleMin = max(0.0, anchor + clipStart);
434
+ float visibleMax = min(extent, anchor + clipEnd);
435
+ return visibleMin < visibleMax ? (visibleMin + visibleMax) / 2.0 - anchor : 0.0;
436
+ }
437
+ if (mode == FAST_TEXT_ALIGN_MODE_END) {
438
+ return min(extent - (anchor + clipEnd), 0.0);
439
+ }
440
+ return 0.0;
441
+ }
442
+
443
+ void fastTextClipGlyphVertex(vec2 pixelOffset, vec2 anchorPosScreen) {
444
+ vec4 clipRect = vec4(instanceClipRects);
445
+ vec2 clipXY = project_size_to_pixel(clipRect.xy);
446
+ vec2 clipWH = project_size_to_pixel(clipRect.zw);
447
+ if (fastText.flipY) {
448
+ clipXY.y = -clipXY.y - clipWH.y;
449
+ }
450
+
451
+ if (fastText.contentAlign.x > 0 || fastText.contentAlign.y > 0) {
452
+ vec2 viewportPixels = project.viewportSize / project.devicePixelRatio;
453
+ vec2 scrollPixels = vec2(
454
+ fastTextGetAlignmentPixelOffset(
455
+ anchorPosScreen.x,
456
+ viewportPixels.x,
457
+ clipXY.x,
458
+ clipXY.x + clipWH.x,
459
+ fastText.contentAlign.x
460
+ ),
461
+ -fastTextGetAlignmentPixelOffset(
462
+ anchorPosScreen.y,
463
+ viewportPixels.y,
464
+ -clipXY.y - clipWH.y,
465
+ -clipXY.y,
466
+ fastText.contentAlign.y
467
+ )
468
+ );
469
+ pixelOffset += scrollPixels;
470
+ gl_Position.xy += project_pixel_size_to_clipspace(scrollPixels);
471
+ }
472
+
473
+ if (clipRect.z >= 0.0) {
474
+ if (pixelOffset.x < clipXY.x || pixelOffset.x > clipXY.x + clipWH.x) {
475
+ gl_Position = vec4(0.0);
476
+ } else if (fastText.contentCutoffPixels.x > 0.0) {
477
+ float viewportWidth = project.viewportSize.x / project.devicePixelRatio;
478
+ float left = max(anchorPosScreen.x + clipXY.x, 0.0);
479
+ float right = min(anchorPosScreen.x + clipXY.x + clipWH.x, viewportWidth);
480
+ if (right - left < fastText.contentCutoffPixels.x) {
481
+ gl_Position = vec4(0.0);
482
+ }
483
+ }
484
+ }
485
+ if (clipRect.w >= 0.0) {
486
+ if (pixelOffset.y < clipXY.y || pixelOffset.y > clipXY.y + clipWH.y) {
487
+ gl_Position = vec4(0.0);
488
+ } else if (fastText.contentCutoffPixels.y > 0.0) {
489
+ float viewportHeight = project.viewportSize.y / project.devicePixelRatio;
490
+ float top = max(anchorPosScreen.y - clipXY.y - clipWH.y, 0.0);
491
+ float bottom = min(anchorPosScreen.y - clipXY.y, viewportHeight);
492
+ if (bottom - top < fastText.contentCutoffPixels.y) {
493
+ gl_Position = vec4(0.0);
494
+ }
495
+ }
496
+ }
497
+ }
498
+
499
+ void main(void) {
500
+ vec3 worldPosition = vec3(instancePositions, 0.0);
501
+ geometry.worldPosition = worldPosition;
502
+ geometry.uv = positions;
503
+ uv = positions;
504
+
505
+ vec4 glyphFrame = vec4(instanceGlyphFrames);
506
+ vec2 glyphSize = glyphFrame.zw;
507
+ float sizePixels = clamp(
508
+ project_size_to_pixel(fastText.size * fastText.sizeScale, fastText.sizeUnits),
509
+ fastText.sizeMinPixels,
510
+ fastText.sizeMaxPixels
511
+ );
512
+ float instanceScale = fastText.fontSize == 0.0 ? 0.0 : sizePixels / fastText.fontSize;
513
+ vec2 pixelOffset = vec2(instanceGlyphOffsets) + positions * glyphSize;
514
+ pixelOffset = pixelOffset * instanceScale + fastText.pixelOffset;
515
+ pixelOffset.y *= -1.0;
516
+
517
+ vec2 anchorPosScreen;
518
+ if (fastText.billboard) {
519
+ gl_Position = project_position_to_clipspace(
520
+ worldPosition,
521
+ vec3(0.0),
522
+ vec3(0.0),
523
+ geometry.position
524
+ );
525
+ anchorPosScreen = gl_Position.xy / gl_Position.w;
526
+ DECKGL_FILTER_GL_POSITION(gl_Position, geometry);
527
+ vec3 offset = vec3(pixelOffset, 0.0);
528
+ DECKGL_FILTER_SIZE(offset, geometry);
529
+ gl_Position.xy += project_pixel_size_to_clipspace(offset.xy);
530
+ } else {
531
+ vec3 offsetCommon = vec3(project_pixel_size(pixelOffset), 0.0);
532
+ if (fastText.flipY) {
533
+ offsetCommon.y *= -1.0;
534
+ }
535
+ DECKGL_FILTER_SIZE(offsetCommon, geometry);
536
+ vec4 anchorPosition = project_position_to_clipspace(worldPosition, vec3(0.0), vec3(0.0));
537
+ anchorPosScreen = anchorPosition.xy / anchorPosition.w;
538
+ gl_Position = project_position_to_clipspace(
539
+ worldPosition,
540
+ vec3(0.0),
541
+ offsetCommon,
542
+ geometry.position
543
+ );
544
+ DECKGL_FILTER_GL_POSITION(gl_Position, geometry);
545
+ }
546
+
547
+ anchorPosScreen = vec2(anchorPosScreen.x + 1.0, 1.0 - anchorPosScreen.y) / 2.0 *
548
+ project.viewportSize / project.devicePixelRatio;
549
+ fastTextClipGlyphVertex(pixelOffset, anchorPosScreen);
550
+
551
+ vTextureCoords = (glyphFrame.xy + positions * glyphSize) / fastText.fontAtlasSize;
552
+ vColor = instanceColors;
553
+ DECKGL_FILTER_COLOR(vColor, geometry);
554
+ }
555
+ `;
556
+ const FAST_TEXT_FS = `\
557
+ #version 300 es
558
+ #define SHADER_NAME fast-text-layer-fragment-shader
559
+
560
+ precision highp float;
561
+
562
+ uniform sampler2D fontAtlasTexture;
563
+
564
+ in vec4 vColor;
565
+ in vec2 vTextureCoords;
566
+ in vec2 uv;
567
+
568
+ out vec4 fragColor;
569
+
570
+ void main(void) {
571
+ geometry.uv = uv;
572
+
573
+ float alpha = texture(fontAtlasTexture, vTextureCoords).a;
574
+ if (fastText.sdfEnabled) {
575
+ alpha = smoothstep(fastText.sdfBuffer - fastText.sdfGamma, fastText.sdfBuffer + fastText.sdfGamma, alpha);
576
+ }
577
+ float outputAlpha = alpha * vColor.a;
578
+
579
+ if (outputAlpha < fastText.alphaCutoff) {
580
+ discard;
581
+ }
582
+
583
+ fragColor = vec4(vColor.rgb, outputAlpha * layer.opacity);
584
+
585
+ DECKGL_FILTER_COLOR(fragColor, geometry);
586
+ }
587
+ `;
588
+ /** Capture primitive source-shape values that can change without prop identity changes. */
589
+ function getFastTextLayoutSourceSignature(props) {
590
+ const textUtf8ColumnSignature = getFastTextUtf8ColumnSourceSignature(props.textUtf8Column);
591
+ return {
592
+ dataRowCount: getFastTextDataRowCount(props.data),
593
+ hasTextUtf8Accessor: Boolean(props.getTextUtf8),
594
+ textUtf8ColumnRowCount: textUtf8ColumnSignature.rowCount,
595
+ textUtf8ColumnChunkCount: textUtf8ColumnSignature.chunkCount,
596
+ textUtf8ColumnOffsetCount: textUtf8ColumnSignature.offsetCount,
597
+ textUtf8ColumnValueByteLength: textUtf8ColumnSignature.valueByteLength
598
+ };
599
+ }
600
+ /** Return whether two captured source-shape signatures match. */
601
+ function areFastTextLayoutSourceSignaturesEqual(signature, oldSignature) {
602
+ return (signature.dataRowCount === oldSignature.dataRowCount &&
603
+ signature.hasTextUtf8Accessor === oldSignature.hasTextUtf8Accessor &&
604
+ signature.textUtf8ColumnRowCount === oldSignature.textUtf8ColumnRowCount &&
605
+ signature.textUtf8ColumnChunkCount === oldSignature.textUtf8ColumnChunkCount &&
606
+ signature.textUtf8ColumnOffsetCount === oldSignature.textUtf8ColumnOffsetCount &&
607
+ signature.textUtf8ColumnValueByteLength === oldSignature.textUtf8ColumnValueByteLength);
608
+ }
609
+ /** Count source data rows without relying on deck.gl's identity-based data change flag. */
610
+ function getFastTextDataRowCount(data) {
611
+ const arrayLikeLength = data.length;
612
+ if (typeof arrayLikeLength === 'number') {
613
+ return arrayLikeLength;
614
+ }
615
+ const tableLikeNumRows = data.numRows;
616
+ if (typeof tableLikeNumRows === 'number') {
617
+ return tableLikeNumRows;
618
+ }
619
+ const { iterable } = createIterable(data);
620
+ let rowCount = 0;
621
+ for (const _object of iterable) {
622
+ rowCount++;
623
+ }
624
+ return rowCount;
625
+ }
626
+ /** Capture primitive shape values for the optional direct UTF-8 text source. */
627
+ function getFastTextUtf8ColumnSourceSignature(textUtf8Column) {
628
+ if (!textUtf8Column) {
629
+ return { rowCount: 0, chunkCount: 0, offsetCount: 0, valueByteLength: 0 };
630
+ }
631
+ const source = normalizeFastTextUtf8ColumnForComparison(textUtf8Column);
632
+ if (!source) {
633
+ return { rowCount: -1, chunkCount: -1, offsetCount: -1, valueByteLength: -1 };
634
+ }
635
+ let offsetCount = 0;
636
+ let valueByteLength = 0;
637
+ for (const chunk of source.chunks) {
638
+ offsetCount += chunk.valueOffsets.length;
639
+ valueByteLength += chunk.values.byteLength;
640
+ }
641
+ return {
642
+ rowCount: source.rowCount,
643
+ chunkCount: source.chunks.length,
644
+ offsetCount,
645
+ valueByteLength
646
+ };
647
+ }
648
+ /** Decide whether a deck update requires glyph data regeneration. */
649
+ function shouldRebuildFastText(layerParams) {
650
+ const { props, oldProps, changeFlags } = layerParams;
651
+ const updateTriggersChanged = changeFlags.updateTriggersChanged;
652
+ return (Boolean(changeFlags.dataChanged) ||
653
+ didFastTextAccessorPropChange(props.getText, oldProps.getText) ||
654
+ !areFastTextUtf8ColumnsEqual(props.textUtf8Column, oldProps.textUtf8Column) ||
655
+ didFastTextAccessorPropChange(props.getTextUtf8Row, oldProps.getTextUtf8Row) ||
656
+ didFastTextAccessorPropChange(props.getTextUtf8, oldProps.getTextUtf8) ||
657
+ props.singleLine !== oldProps.singleLine ||
658
+ props.textAnchor !== oldProps.textAnchor ||
659
+ props.alignmentBaseline !== oldProps.alignmentBaseline ||
660
+ props.characterSet !== oldProps.characterSet ||
661
+ props.fontFamily !== oldProps.fontFamily ||
662
+ props.fontWeight !== oldProps.fontWeight ||
663
+ props.lineHeight !== oldProps.lineHeight ||
664
+ props.fontSettings !== oldProps.fontSettings ||
665
+ props.characterMapping !== oldProps.characterMapping ||
666
+ props.fontAtlas !== oldProps.fontAtlas ||
667
+ Boolean(updateTriggersChanged &&
668
+ (updateTriggersChanged.all ||
669
+ updateTriggersChanged.getText ||
670
+ updateTriggersChanged.getTextUtf8 ||
671
+ updateTriggersChanged.getTextUtf8Row)));
672
+ }
673
+ /** Return which dynamic FastText attributes can be updated without rebuilding text layout. */
674
+ function getFastTextDynamicUpdate(layerParams) {
675
+ const { props, oldProps, changeFlags } = layerParams;
676
+ const updateTriggersChanged = changeFlags.updateTriggersChanged;
677
+ const positions = didFastTextAccessorPropChange(props.getPosition, oldProps.getPosition) ||
678
+ Boolean(updateTriggersChanged && updateTriggersChanged.getPosition);
679
+ const colors = didFastTextAccessorPropChange(props.getColor, oldProps.getColor) ||
680
+ Boolean(updateTriggersChanged && updateTriggersChanged.getColor);
681
+ const clipRects = didFastTextAccessorPropChange(props.getClipRect, oldProps.getClipRect) ||
682
+ Boolean(updateTriggersChanged && updateTriggersChanged.getClipRect);
683
+ if (!positions && !colors && !clipRects) {
684
+ return null;
685
+ }
686
+ return { positions, colors, clipRects };
687
+ }
688
+ /** Return whether two UTF-8 column props read from the same underlying Arrow buffers. */
689
+ function areFastTextUtf8ColumnsEqual(textUtf8Column, oldTextUtf8Column) {
690
+ if (textUtf8Column === oldTextUtf8Column) {
691
+ return true;
692
+ }
693
+ if (!textUtf8Column || !oldTextUtf8Column) {
694
+ return false;
695
+ }
696
+ const source = normalizeFastTextUtf8ColumnForComparison(textUtf8Column);
697
+ const oldSource = normalizeFastTextUtf8ColumnForComparison(oldTextUtf8Column);
698
+ if (!source || !oldSource || source.rowCount !== oldSource.rowCount) {
699
+ return false;
700
+ }
701
+ if (source.chunks.length !== oldSource.chunks.length) {
702
+ return false;
703
+ }
704
+ for (let index = 0; index < source.chunks.length; index += 1) {
705
+ const chunk = source.chunks[index];
706
+ const oldChunk = oldSource.chunks[index];
707
+ if (chunk.rowOffset !== oldChunk.rowOffset ||
708
+ chunk.rowCount !== oldChunk.rowCount ||
709
+ chunk.valueOffsetIndex !== oldChunk.valueOffsetIndex ||
710
+ chunk.valueOffsets !== oldChunk.valueOffsets ||
711
+ chunk.values !== oldChunk.values) {
712
+ return false;
713
+ }
714
+ }
715
+ return true;
716
+ }
717
+ /** Normalize a UTF-8 column prop for same-buffer comparison. */
718
+ function normalizeFastTextUtf8ColumnForComparison(textUtf8Column) {
719
+ return isFastTextUtf8ColumnSource(textUtf8Column)
720
+ ? textUtf8Column
721
+ : buildFastTextUtf8ColumnSource(textUtf8Column);
722
+ }
723
+ /** Return whether an object has the normalized FastText UTF-8 source shape. */
724
+ function isFastTextUtf8ColumnSource(textUtf8Column) {
725
+ return 'rowCount' in textUtf8Column && 'chunks' in textUtf8Column;
726
+ }
727
+ /** Return whether an accessor prop changed in a way deck.gl expects attributes to observe. */
728
+ function didFastTextAccessorPropChange(accessor, oldAccessor) {
729
+ if (typeof oldAccessor === 'function') {
730
+ return false;
731
+ }
732
+ if (typeof accessor === 'function') {
733
+ return true;
734
+ }
735
+ return !areFastTextAccessorConstantsEqual(accessor, oldAccessor);
736
+ }
737
+ /** Return whether two non-function accessor constants are shallowly equivalent. */
738
+ function areFastTextAccessorConstantsEqual(accessor, oldAccessor) {
739
+ if (accessor === oldAccessor) {
740
+ return true;
741
+ }
742
+ if (!Array.isArray(accessor) || !Array.isArray(oldAccessor)) {
743
+ return false;
744
+ }
745
+ if (accessor.length !== oldAccessor.length) {
746
+ return false;
747
+ }
748
+ for (let index = 0; index < accessor.length; index += 1) {
749
+ if (accessor[index] !== oldAccessor[index]) {
750
+ return false;
751
+ }
752
+ }
753
+ return true;
754
+ }
755
+ /** Emit one compact timing probe for a completed FastText attribute rebuild. */
756
+ function logFastTextAttributeBuildProbe(params) {
757
+ getFastTextProbeLog().probe(0, `%cFastTextLayer%c ${params.glyphStats.rowCount} rows in ${formatFastTextDurationMs(params.totalDurationMs)} ${params.layerId}`, FAST_TEXT_PROBE_LABEL_STYLE, '', {
758
+ layerId: params.layerId,
759
+ updateMode: 'full',
760
+ sourceMode: params.glyphStats.sourceMode,
761
+ layoutMode: params.glyphStats.layoutMode,
762
+ rowCount: params.glyphStats.rowCount,
763
+ glyphCount: params.glyphStats.glyphCount,
764
+ attributeByteLength: params.glyphStats.attributeByteLength,
765
+ atlasDurationMs: params.atlasDurationMs,
766
+ glyphDataDurationMs: params.glyphDataDurationMs,
767
+ columnNormalizeDurationMs: params.glyphStats.columnNormalizeDurationMs,
768
+ countDurationMs: params.glyphStats.countDurationMs,
769
+ allocateDurationMs: params.glyphStats.allocateDurationMs,
770
+ writeDurationMs: params.glyphStats.writeDurationMs,
771
+ textResolveDurationMs: params.glyphStats.textResolveDurationMs,
772
+ styleAccessorDurationMs: params.glyphStats.styleAccessorDurationMs,
773
+ layoutDurationMs: params.glyphStats.layoutDurationMs,
774
+ glyphWriteDurationMs: params.glyphStats.glyphWriteDurationMs,
775
+ bufferCreateDurationMs: params.bufferCreateDurationMs,
776
+ totalDurationMs: params.totalDurationMs
777
+ })();
778
+ }
779
+ /** Emit one compact timing probe for a dynamic FastText attribute update. */
780
+ function logFastTextDynamicAttributeUpdateProbe(params) {
781
+ getFastTextProbeLog().probe(0, `%cFastTextLayer%c dynamic ${params.dynamicStats.rowCount} rows in ${formatFastTextDurationMs(params.totalDurationMs)} ${params.layerId}`, FAST_TEXT_PROBE_LABEL_STYLE, '', {
782
+ layerId: params.layerId,
783
+ updateMode: 'dynamic',
784
+ updatePositions: params.update.positions,
785
+ updateColors: params.update.colors,
786
+ updateClipRects: params.update.clipRects,
787
+ rowCount: params.dynamicStats.rowCount,
788
+ glyphCount: params.dynamicStats.glyphCount,
789
+ attributeByteLength: params.dynamicStats.attributeByteLength,
790
+ positionAccessorDurationMs: params.dynamicStats.positionAccessorDurationMs,
791
+ colorAccessorDurationMs: params.dynamicStats.colorAccessorDurationMs,
792
+ clipRectAccessorDurationMs: params.dynamicStats.clipRectAccessorDurationMs,
793
+ writeDurationMs: params.dynamicStats.writeDurationMs,
794
+ bufferUploadDurationMs: params.bufferUploadDurationMs,
795
+ totalDurationMs: params.totalDurationMs
796
+ })();
797
+ }
798
+ /** Format a duration for compact inline probe messages. */
799
+ function formatFastTextDurationMs(durationMs) {
800
+ return `${durationMs.toFixed(durationMs < 10 ? 2 : 1)}ms`;
801
+ }
802
+ /** Return the shared Tracevis debug log when it is available in the page. */
803
+ function getFastTextProbeLog() {
804
+ const globalLog = globalThis.traceLayers?.log;
805
+ return globalLog ?? fastTextLog;
806
+ }
807
+ /** Create luma buffers for every per-glyph typed array. */
808
+ function createFastTextBuffers(device, glyphData) {
809
+ const { attributes } = glyphData;
810
+ return {
811
+ instanceGlyphData: createVertexBuffer(device, attributes.instanceGlyphData, new Uint8Array(FAST_TEXT_GLYPH_VERTEX_BYTE_STRIDE)),
812
+ instancePositions: createVertexBuffer(device, attributes.instancePositions, new Float32Array(2))
813
+ };
814
+ }
815
+ /** Create one non-empty luma vertex buffer for a typed array. */
816
+ function createVertexBuffer(device, data, fallback) {
817
+ return device.createBuffer({
818
+ data: data.byteLength > 0 ? data : fallback,
819
+ usage: Buffer.VERTEX | Buffer.COPY_DST
820
+ });
821
+ }
822
+ /** Destroy all owned glyph vertex buffers. */
823
+ function destroyFastTextBuffers(buffers) {
824
+ if (!buffers) {
825
+ return;
826
+ }
827
+ for (const buffer of Object.values(buffers)) {
828
+ buffer.destroy();
829
+ }
830
+ }
831
+ /** Upload one canvas-backed font atlas into a luma texture. */
832
+ function createFastTextAtlasTexture(device, atlas) {
833
+ const texture = device.createTexture({
834
+ format: 'rgba8unorm',
835
+ data: null,
836
+ width: atlas.width,
837
+ height: atlas.height,
838
+ sampler: DEFAULT_SAMPLER_PARAMETERS,
839
+ mipLevels: device.getMipLevelCount(atlas.width, atlas.height)
840
+ });
841
+ texture.copyExternalImage({
842
+ image: atlas.data,
843
+ width: atlas.width,
844
+ height: atlas.height
845
+ });
846
+ regenerateMipmaps(texture);
847
+ return texture;
848
+ }
849
+ /** Regenerate texture mipmaps for WebGL or WebGPU devices. */
850
+ function regenerateMipmaps(texture) {
851
+ if (texture.device.type === 'webgl') {
852
+ texture.generateMipmapsWebGL();
853
+ }
854
+ else if (texture.device.type === 'webgpu') {
855
+ texture.device.generateMipmapsWebGPU(texture);
856
+ }
857
+ }
858
+ /** Return the shader enum value for one fast-text content alignment mode. */
859
+ function getFastTextContentAlignUniform(mode) {
860
+ return FAST_TEXT_CONTENT_ALIGN[mode];
861
+ }
862
+ //# sourceMappingURL=fast-text-layer.js.map