@deck.gl-community/infovis-layers 9.3.5 → 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 (126) 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 +23 -45
  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/orthographic-utils.d.ts +30 -2
  78. package/dist/views/orthographic-utils.d.ts.map +1 -1
  79. package/dist/views/orthographic-utils.js +33 -14
  80. package/dist/views/orthographic-utils.js.map +1 -1
  81. package/dist/views/view-layout/build-views-from-view-layout.d.ts +37 -0
  82. package/dist/views/view-layout/build-views-from-view-layout.d.ts.map +1 -0
  83. package/dist/views/view-layout/build-views-from-view-layout.js +413 -0
  84. package/dist/views/view-layout/build-views-from-view-layout.js.map +1 -0
  85. package/dist/views/view-layout/index.d.ts +3 -0
  86. package/dist/views/view-layout/index.d.ts.map +1 -0
  87. package/dist/views/view-layout/index.js +3 -0
  88. package/dist/views/view-layout/index.js.map +1 -0
  89. package/dist/views/view-layout/view-layout-item.d.ts +74 -0
  90. package/dist/views/view-layout/view-layout-item.d.ts.map +1 -0
  91. package/dist/views/view-layout/view-layout-item.js +48 -0
  92. package/dist/views/view-layout/view-layout-item.js.map +1 -0
  93. package/dist/views/view-layout/view-layout.test.d.ts +2 -0
  94. package/dist/views/view-layout/view-layout.test.d.ts.map +1 -0
  95. package/dist/views/view-layout/view-layout.test.js +112 -0
  96. package/dist/views/view-layout/view-layout.test.js.map +1 -0
  97. package/dist/views/viewport-bounds-utils.d.ts +55 -0
  98. package/dist/views/viewport-bounds-utils.d.ts.map +1 -0
  99. package/dist/views/viewport-bounds-utils.js +70 -0
  100. package/dist/views/viewport-bounds-utils.js.map +1 -0
  101. package/package.json +11 -4
  102. package/src/index.ts +91 -2
  103. package/src/layers/animation-layer/animation-layer.ts +92 -0
  104. package/src/layers/animation-layer/animation.ts +328 -0
  105. package/src/layers/block-layer/block-layer-fragment.glsl.ts +41 -0
  106. package/src/layers/block-layer/block-layer-uniforms.ts +43 -0
  107. package/src/layers/block-layer/block-layer-vertex.glsl.ts +67 -0
  108. package/src/layers/block-layer/block-layer.ts +199 -0
  109. package/src/layers/fast-text-layer/fast-text-layer.test.ts +359 -0
  110. package/src/layers/fast-text-layer/fast-text-layer.ts +1208 -0
  111. package/src/layers/fast-text-layer/fast-text-layout.ts +1124 -0
  112. package/src/layers/fast-text-layer/font-atlas.ts +574 -0
  113. package/src/layers/fast-text-layer/index.ts +37 -0
  114. package/src/layers/time-delta-layer.ts +47 -62
  115. package/src/synchronized-views/synchronized-views.ts +1 -1
  116. package/src/utils/format-utils.ts +90 -40
  117. package/src/utils/utf8-string-view.test.ts +119 -0
  118. package/src/utils/utf8-string-view.ts +273 -0
  119. package/src/views/bounds-utils.ts +88 -0
  120. package/src/views/layer-bounds-filter.ts +160 -0
  121. package/src/views/orthographic-utils.ts +65 -18
  122. package/src/views/view-layout/build-views-from-view-layout.ts +547 -0
  123. package/src/views/view-layout/index.ts +18 -0
  124. package/src/views/view-layout/view-layout-item.ts +125 -0
  125. package/src/views/view-layout/view-layout.test.ts +129 -0
  126. package/src/views/viewport-bounds-utils.ts +104 -0
@@ -0,0 +1,574 @@
1
+ // deck.gl and luma.gl inspired text atlas utilities.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ /* global document */
5
+
6
+ /** One rasterized font glyph entry in atlas pixel coordinates. */
7
+ export type FastTextCharacter = {
8
+ /** Left atlas pixel coordinate. */
9
+ x: number;
10
+ /** Top atlas pixel coordinate. */
11
+ y: number;
12
+ /** Glyph bitmap width in atlas pixels. */
13
+ width: number;
14
+ /** Glyph bitmap height in atlas pixels. */
15
+ height: number;
16
+ /** Horizontal anchor in glyph bitmap pixels. */
17
+ anchorX: number;
18
+ /** Vertical anchor in glyph bitmap pixels. */
19
+ anchorY: number;
20
+ /** Horizontal cursor advance in atlas pixels. */
21
+ advance: number;
22
+ };
23
+
24
+ /** Lookup from a rendered character to its atlas frame and metrics. */
25
+ export type FastTextCharacterMapping = Record<string, FastTextCharacter | undefined>;
26
+
27
+ /** Font rasterization inputs used to build a generated atlas. */
28
+ export type FastTextFontSettings = {
29
+ /** CSS font family used when rasterizing glyphs. */
30
+ fontFamily?: string;
31
+ /** CSS font weight used when rasterizing glyphs. */
32
+ fontWeight?: string | number;
33
+ /** Characters to rasterize into the atlas. */
34
+ characterSet?: Set<string> | readonly string[] | string;
35
+ /** Atlas rasterization size in CSS pixels. */
36
+ fontSize?: number;
37
+ /** Transparent pixel padding around every glyph. */
38
+ buffer?: number;
39
+ /** Whether generated atlases encode signed distance fields. */
40
+ sdf?: boolean;
41
+ /** SDF inside-glyph cutoff. Larger values make glyphs thinner. */
42
+ cutoff?: number;
43
+ /** SDF distance radius in atlas pixels. */
44
+ radius?: number;
45
+ /** SDF edge smoothing used by the shader. */
46
+ smoothing?: number;
47
+ };
48
+
49
+ /** Font atlas consumed by {@link FastTextLayer}. */
50
+ export type FastTextFontAtlas = {
51
+ /** Distance from a label baseline to its visual center in atlas pixels. */
52
+ baselineOffset: number;
53
+ /** Glyph metric lookup keyed by rendered character. */
54
+ mapping: FastTextCharacterMapping;
55
+ /** Canvas containing the packed glyph bitmaps. */
56
+ data: HTMLCanvasElement;
57
+ /** Atlas width in pixels. */
58
+ width: number;
59
+ /** Atlas height in pixels. */
60
+ height: number;
61
+ };
62
+
63
+ /** Default font settings used by {@link FastTextLayer}. */
64
+ export const DEFAULT_FAST_TEXT_FONT_SETTINGS: Required<FastTextFontSettings> = {
65
+ fontFamily: 'Monaco, monospace',
66
+ fontWeight: 'normal',
67
+ characterSet: getDefaultCharacterSet(),
68
+ fontSize: 64,
69
+ buffer: 4,
70
+ sdf: true,
71
+ cutoff: 0.25,
72
+ radius: 12,
73
+ smoothing: 0.1
74
+ };
75
+
76
+ /** Build a font atlas for a fixed character set without retaining a process cache. */
77
+ export function createFastTextFontAtlas(settings: FastTextFontSettings = {}): FastTextFontAtlas {
78
+ if (typeof document === 'undefined') {
79
+ throw new Error('FastTextLayer font atlas generation requires document.createElement');
80
+ }
81
+
82
+ const resolvedSettings = {
83
+ ...DEFAULT_FAST_TEXT_FONT_SETTINGS,
84
+ ...settings,
85
+ characterSet: normalizeCharacterSet(
86
+ settings.characterSet ?? DEFAULT_FAST_TEXT_FONT_SETTINGS.characterSet
87
+ )
88
+ };
89
+ const canvas = document.createElement('canvas');
90
+ canvas.width = MAX_CANVAS_WIDTH;
91
+ const context = canvas.getContext('2d', {willReadFrequently: true});
92
+ if (!context) {
93
+ throw new Error('FastTextLayer font atlas generation requires a 2D canvas context');
94
+ }
95
+
96
+ setTextStyle(context, resolvedSettings);
97
+ const sdfRenderer = resolvedSettings.sdf
98
+ ? createFastTextSdfRenderer(resolvedSettings)
99
+ : undefined;
100
+ const mappingResult = buildFastTextCharacterMapping({
101
+ characterSet: resolvedSettings.characterSet,
102
+ measureText: character => measureFastText(context, resolvedSettings.fontSize, character),
103
+ buffer: resolvedSettings.buffer,
104
+ maxCanvasWidth: MAX_CANVAS_WIDTH
105
+ });
106
+
107
+ canvas.height = mappingResult.canvasHeight;
108
+ setTextStyle(context, resolvedSettings);
109
+
110
+ for (const character of resolvedSettings.characterSet) {
111
+ const frame = mappingResult.mapping[character];
112
+ if (frame) {
113
+ if (sdfRenderer) {
114
+ drawFastTextSdfCharacter(context, sdfRenderer, character, frame, resolvedSettings.buffer);
115
+ } else {
116
+ context.fillText(character, frame.x, frame.y + frame.anchorY);
117
+ }
118
+ }
119
+ }
120
+
121
+ const fontMetrics = measureFastText(context, resolvedSettings.fontSize);
122
+ return {
123
+ baselineOffset: (fontMetrics.ascent - fontMetrics.descent) / 2,
124
+ mapping: mappingResult.mapping,
125
+ data: canvas,
126
+ width: canvas.width,
127
+ height: canvas.height
128
+ };
129
+ }
130
+
131
+ /** Build glyph atlas frames from text measurements without drawing the atlas canvas. */
132
+ export function buildFastTextCharacterMapping({
133
+ characterSet,
134
+ measureText,
135
+ buffer,
136
+ maxCanvasWidth
137
+ }: {
138
+ /** Characters that need atlas entries. */
139
+ characterSet: Set<string>;
140
+ /** Glyph measurement callback for one character. */
141
+ measureText: (character: string) => FastTextMeasuredCharacter;
142
+ /** Transparent pixel padding around every glyph. */
143
+ buffer: number;
144
+ /** Maximum atlas row width in pixels. */
145
+ maxCanvasWidth: number;
146
+ }): {
147
+ /** Glyph metric lookup keyed by rendered character. */
148
+ mapping: FastTextCharacterMapping;
149
+ /** Power-of-two atlas height in pixels. */
150
+ canvasHeight: number;
151
+ } {
152
+ const mapping: FastTextCharacterMapping = {};
153
+ let x = 0;
154
+ let yMin = 0;
155
+ let yMax = 0;
156
+
157
+ for (const character of characterSet) {
158
+ const {advance, width, ascent, descent} = measureText(character);
159
+ const height = ascent + descent;
160
+ if (x + width + buffer * 2 > maxCanvasWidth) {
161
+ x = 0;
162
+ yMin = yMax;
163
+ }
164
+ mapping[character] = {
165
+ x: x + buffer,
166
+ y: yMin + buffer,
167
+ width,
168
+ height,
169
+ advance,
170
+ anchorX: width / 2,
171
+ anchorY: ascent
172
+ };
173
+ x += width + buffer * 2;
174
+ yMax = Math.max(yMax, yMin + height + buffer * 2);
175
+ }
176
+
177
+ return {
178
+ mapping,
179
+ canvasHeight: Math.max(1, nextPowerOfTwo(yMax))
180
+ };
181
+ }
182
+
183
+ /** Minimal atlas manager that rebuilds only when rasterization inputs change. */
184
+ export class FastTextFontAtlasManager {
185
+ /** Current resolved font settings. */
186
+ props: Required<FastTextFontSettings> = {...DEFAULT_FAST_TEXT_FONT_SETTINGS};
187
+ private currentAtlas?: FastTextFontAtlas;
188
+ private currentKey?: string;
189
+
190
+ /** Current atlas, if one has been built. */
191
+ get atlas(): Readonly<FastTextFontAtlas> | undefined {
192
+ return this.currentAtlas;
193
+ }
194
+
195
+ /** Current character mapping, if an atlas has been built. */
196
+ get mapping(): FastTextCharacterMapping | undefined {
197
+ return this.currentAtlas?.mapping;
198
+ }
199
+
200
+ /** Update font settings and return whether the atlas was rebuilt. */
201
+ setProps(props: FastTextFontSettings = {}): boolean {
202
+ this.props = {
203
+ ...this.props,
204
+ ...props,
205
+ characterSet: normalizeCharacterSet(props.characterSet ?? this.props.characterSet)
206
+ };
207
+ const nextKey = getFontAtlasKey(this.props);
208
+ if (this.currentAtlas && this.currentKey === nextKey) {
209
+ return false;
210
+ }
211
+
212
+ this.currentAtlas = createFastTextFontAtlas(this.props);
213
+ this.currentKey = nextKey;
214
+ return true;
215
+ }
216
+ }
217
+
218
+ type FastTextMeasuredCharacter = {
219
+ /** Horizontal cursor advance in atlas pixels. */
220
+ readonly advance: number;
221
+ /** Glyph bitmap width in atlas pixels. */
222
+ readonly width: number;
223
+ /** Distance from baseline to glyph top in atlas pixels. */
224
+ readonly ascent: number;
225
+ /** Distance from baseline to glyph bottom in atlas pixels. */
226
+ readonly descent: number;
227
+ };
228
+
229
+ const MAX_CANVAS_WIDTH = 1024;
230
+ const DEFAULT_ASCENT = 0.9;
231
+ const DEFAULT_DESCENT = 0.3;
232
+
233
+ /** Return the default printable ASCII atlas character set. */
234
+ function getDefaultCharacterSet(): string[] {
235
+ const characterSet: string[] = [];
236
+ for (let code = 32; code < 128; code++) {
237
+ characterSet.push(String.fromCharCode(code));
238
+ }
239
+ return characterSet;
240
+ }
241
+
242
+ /** Normalize caller-supplied character sets into a Set for deterministic use. */
243
+ function normalizeCharacterSet(
244
+ characterSet: Set<string> | readonly string[] | string
245
+ ): Set<string> {
246
+ return typeof characterSet === 'string'
247
+ ? new Set(Array.from(characterSet))
248
+ : new Set(characterSet);
249
+ }
250
+
251
+ /** Generate a stable atlas key from font settings and sorted character set contents. */
252
+ function getFontAtlasKey(settings: Required<FastTextFontSettings>): string {
253
+ const characters = Array.from(settings.characterSet).sort().join('');
254
+ return [
255
+ settings.fontFamily,
256
+ settings.fontWeight,
257
+ settings.fontSize,
258
+ settings.buffer,
259
+ settings.sdf,
260
+ settings.cutoff,
261
+ settings.radius,
262
+ characters
263
+ ].join('\n');
264
+ }
265
+
266
+ /** Apply font rendering settings to a 2D canvas context. */
267
+ function setTextStyle(
268
+ context: CanvasRenderingContext2D,
269
+ settings: Pick<Required<FastTextFontSettings>, 'fontFamily' | 'fontSize' | 'fontWeight'>
270
+ ): void {
271
+ context.font = `${settings.fontWeight} ${settings.fontSize}px ${settings.fontFamily}`;
272
+ context.fillStyle = '#000';
273
+ context.textBaseline = 'alphabetic';
274
+ context.textAlign = 'left';
275
+ }
276
+
277
+ /** Measure one character or the fallback font metrics for the current canvas font. */
278
+ function measureFastText(
279
+ context: CanvasRenderingContext2D,
280
+ fontSize: number,
281
+ character?: string
282
+ ): FastTextMeasuredCharacter {
283
+ if (character === undefined) {
284
+ const fontMetrics = context.measureText('A');
285
+ if (fontMetrics.fontBoundingBoxAscent) {
286
+ return {
287
+ advance: 0,
288
+ width: 0,
289
+ ascent: Math.ceil(fontMetrics.fontBoundingBoxAscent),
290
+ descent: Math.ceil(fontMetrics.fontBoundingBoxDescent)
291
+ };
292
+ }
293
+ return {
294
+ advance: 0,
295
+ width: 0,
296
+ ascent: fontSize * DEFAULT_ASCENT,
297
+ descent: fontSize * DEFAULT_DESCENT
298
+ };
299
+ }
300
+
301
+ const metrics = context.measureText(character);
302
+ if (!metrics.actualBoundingBoxAscent) {
303
+ return {
304
+ advance: metrics.width,
305
+ width: metrics.width,
306
+ ascent: fontSize * DEFAULT_ASCENT,
307
+ descent: fontSize * DEFAULT_DESCENT
308
+ };
309
+ }
310
+ return {
311
+ advance: metrics.width,
312
+ width: Math.ceil(metrics.actualBoundingBoxRight - metrics.actualBoundingBoxLeft),
313
+ ascent: Math.ceil(metrics.actualBoundingBoxAscent),
314
+ descent: Math.ceil(metrics.actualBoundingBoxDescent)
315
+ };
316
+ }
317
+
318
+ /** Create a local TinySDF-style renderer for one resolved font. */
319
+ function createFastTextSdfRenderer(
320
+ settings: Pick<
321
+ Required<FastTextFontSettings>,
322
+ 'buffer' | 'cutoff' | 'fontFamily' | 'fontSize' | 'fontWeight' | 'radius'
323
+ >
324
+ ): FastTextSdfRenderer {
325
+ const buffer = Math.ceil(settings.buffer);
326
+ const size = Math.ceil(settings.fontSize + buffer * 4);
327
+ const scratchSize = size + buffer;
328
+ const canvas = document.createElement('canvas');
329
+ canvas.width = size;
330
+ canvas.height = size;
331
+ const context = canvas.getContext('2d', {willReadFrequently: true});
332
+ if (!context) {
333
+ throw new Error('FastTextLayer SDF generation requires a 2D canvas context');
334
+ }
335
+
336
+ setTextStyle(context, settings);
337
+ const gridOuter = new Float64Array(scratchSize * scratchSize);
338
+ const gridInner = new Float64Array(scratchSize * scratchSize);
339
+ const f = new Float64Array(scratchSize);
340
+ const z = new Float64Array(scratchSize + 1);
341
+ const v = new Uint16Array(scratchSize);
342
+ return {
343
+ draw: character =>
344
+ drawFastTextSdfGlyph({
345
+ character,
346
+ context,
347
+ fontSize: settings.fontSize,
348
+ buffer,
349
+ cutoff: settings.cutoff,
350
+ radius: settings.radius,
351
+ size,
352
+ gridOuter,
353
+ gridInner,
354
+ f,
355
+ z,
356
+ v
357
+ })
358
+ };
359
+ }
360
+
361
+ /** Draw one SDF glyph into the target atlas canvas. */
362
+ function drawFastTextSdfCharacter(
363
+ context: CanvasRenderingContext2D,
364
+ sdfRenderer: FastTextSdfRenderer,
365
+ character: string,
366
+ frame: FastTextCharacter,
367
+ buffer: number
368
+ ): void {
369
+ const {data, width, height} = sdfRenderer.draw(character);
370
+ const imageData = context.createImageData(width, height);
371
+ populateAlphaChannel(data, imageData);
372
+
373
+ const x = frame.x - buffer;
374
+ const y = frame.y - buffer;
375
+ const x0 = Math.max(0, Math.round(x));
376
+ const y0 = Math.max(0, Math.round(y));
377
+ const w = Math.min(width, context.canvas.width - x0);
378
+ const h = Math.min(height, context.canvas.height - y0);
379
+ context.putImageData(imageData, x0, y0, 0, 0, w, h);
380
+
381
+ frame.x += x0 - x;
382
+ frame.y += y0 - y;
383
+ }
384
+
385
+ /** Copy grayscale SDF values into an ImageData alpha channel. */
386
+ function populateAlphaChannel(
387
+ alphaChannel: Uint8ClampedArray | Uint8Array,
388
+ imageData: ImageData
389
+ ): void {
390
+ for (let index = 0; index < alphaChannel.length; index++) {
391
+ imageData.data[4 * index + 3] = alphaChannel[index];
392
+ }
393
+ }
394
+
395
+ type FastTextSdfRenderer = {
396
+ /** Draw one character into a grayscale SDF glyph buffer. */
397
+ draw: (character: string) => FastTextSdfGlyph;
398
+ };
399
+
400
+ type FastTextSdfGlyph = {
401
+ /** Grayscale SDF values for the glyph image. */
402
+ readonly data: Uint8ClampedArray;
403
+ /** Width of the returned SDF image in pixels. */
404
+ readonly width: number;
405
+ /** Height of the returned SDF image in pixels. */
406
+ readonly height: number;
407
+ };
408
+
409
+ type DrawFastTextSdfGlyphProps = {
410
+ /** Character to rasterize. */
411
+ readonly character: string;
412
+ /** Canvas context configured with the target font. */
413
+ readonly context: CanvasRenderingContext2D;
414
+ /** Font size used for metric fallbacks. */
415
+ readonly fontSize: number;
416
+ /** SDF padding around the glyph. */
417
+ readonly buffer: number;
418
+ /** SDF inside-glyph cutoff. */
419
+ readonly cutoff: number;
420
+ /** SDF distance radius. */
421
+ readonly radius: number;
422
+ /** Square scratch canvas dimension. */
423
+ readonly size: number;
424
+ /** Scratch grid for distances outside the glyph. */
425
+ readonly gridOuter: Float64Array;
426
+ /** Scratch grid for distances inside the glyph. */
427
+ readonly gridInner: Float64Array;
428
+ /** Scratch values for the 1D distance transform. */
429
+ readonly f: Float64Array;
430
+ /** Scratch boundary locations for the 1D distance transform. */
431
+ readonly z: Float64Array;
432
+ /** Scratch source indices for the 1D distance transform. */
433
+ readonly v: Uint16Array;
434
+ };
435
+
436
+ const FAST_TEXT_SDF_INF = 1e20;
437
+
438
+ /** Rasterize one glyph and convert its alpha mask into signed distance values. */
439
+ function drawFastTextSdfGlyph({
440
+ character,
441
+ context,
442
+ fontSize,
443
+ buffer,
444
+ cutoff,
445
+ radius,
446
+ size,
447
+ gridOuter,
448
+ gridInner,
449
+ f,
450
+ z,
451
+ v
452
+ }: DrawFastTextSdfGlyphProps): FastTextSdfGlyph {
453
+ const metrics = context.measureText(character);
454
+ const glyphTop = Math.ceil(metrics.actualBoundingBoxAscent ?? fontSize * DEFAULT_ASCENT);
455
+ const glyphWidth = Math.max(
456
+ 0,
457
+ Math.min(
458
+ size - buffer,
459
+ Math.ceil(
460
+ (metrics.actualBoundingBoxRight ?? metrics.width) - (metrics.actualBoundingBoxLeft ?? 0)
461
+ )
462
+ )
463
+ );
464
+ const glyphHeight = Math.min(
465
+ size - buffer,
466
+ glyphTop + Math.ceil(metrics.actualBoundingBoxDescent ?? fontSize * DEFAULT_DESCENT)
467
+ );
468
+ const width = glyphWidth + 2 * buffer;
469
+ const height = glyphHeight + 2 * buffer;
470
+ const data = new Uint8ClampedArray(Math.max(width * height, 0));
471
+ if (glyphWidth === 0 || glyphHeight === 0) {
472
+ return {data, width, height};
473
+ }
474
+
475
+ context.clearRect(buffer, buffer, glyphWidth, glyphHeight);
476
+ context.fillText(character, buffer, buffer + glyphTop);
477
+ const imageData = context.getImageData(buffer, buffer, glyphWidth, glyphHeight);
478
+
479
+ gridOuter.fill(FAST_TEXT_SDF_INF, 0, data.length);
480
+ gridInner.fill(0, 0, data.length);
481
+
482
+ for (let y = 0; y < glyphHeight; y++) {
483
+ for (let x = 0; x < glyphWidth; x++) {
484
+ const alpha = imageData.data[4 * (y * glyphWidth + x) + 3] / 255;
485
+ if (alpha === 0) {
486
+ continue;
487
+ }
488
+
489
+ const gridIndex = (y + buffer) * width + x + buffer;
490
+ if (alpha === 1) {
491
+ gridOuter[gridIndex] = 0;
492
+ gridInner[gridIndex] = FAST_TEXT_SDF_INF;
493
+ } else {
494
+ const distance = 0.5 - alpha;
495
+ gridOuter[gridIndex] = distance > 0 ? distance * distance : 0;
496
+ gridInner[gridIndex] = distance < 0 ? distance * distance : 0;
497
+ }
498
+ }
499
+ }
500
+
501
+ transformFastTextSdfGrid(gridOuter, 0, 0, width, height, width, f, v, z);
502
+ transformFastTextSdfGrid(gridInner, buffer, buffer, glyphWidth, glyphHeight, width, f, v, z);
503
+
504
+ for (let index = 0; index < data.length; index++) {
505
+ const distance = Math.sqrt(gridOuter[index]) - Math.sqrt(gridInner[index]);
506
+ data[index] = Math.round(255 - 255 * (distance / radius + cutoff));
507
+ }
508
+ return {data, width, height};
509
+ }
510
+
511
+ /** Run a 2D squared Euclidean distance transform over one SDF grid. */
512
+ function transformFastTextSdfGrid(
513
+ data: Float64Array,
514
+ x0: number,
515
+ y0: number,
516
+ width: number,
517
+ height: number,
518
+ gridSize: number,
519
+ f: Float64Array,
520
+ v: Uint16Array,
521
+ z: Float64Array
522
+ ): void {
523
+ for (let x = x0; x < x0 + width; x++) {
524
+ transformFastTextSdfGrid1d(data, y0 * gridSize + x, gridSize, height, f, v, z);
525
+ }
526
+ for (let y = y0; y < y0 + height; y++) {
527
+ transformFastTextSdfGrid1d(data, y * gridSize + x0, 1, width, f, v, z);
528
+ }
529
+ }
530
+
531
+ /** Run the 1D pass for the squared Euclidean distance transform. */
532
+ function transformFastTextSdfGrid1d(
533
+ grid: Float64Array,
534
+ offset: number,
535
+ stride: number,
536
+ length: number,
537
+ f: Float64Array,
538
+ v: Uint16Array,
539
+ z: Float64Array
540
+ ): void {
541
+ v[0] = 0;
542
+ z[0] = -FAST_TEXT_SDF_INF;
543
+ z[1] = FAST_TEXT_SDF_INF;
544
+ f[0] = grid[offset];
545
+
546
+ for (let q = 1, k = 0; q < length; q++) {
547
+ f[q] = grid[offset + q * stride];
548
+ const q2 = q * q;
549
+ let s = 0;
550
+ do {
551
+ const r = v[k];
552
+ s = (f[q] - f[r] + q2 - r * r) / (q - r) / 2;
553
+ } while (s <= z[k] && --k > -1);
554
+
555
+ k++;
556
+ v[k] = q;
557
+ z[k] = s;
558
+ z[k + 1] = FAST_TEXT_SDF_INF;
559
+ }
560
+
561
+ for (let q = 0, k = 0; q < length; q++) {
562
+ while (z[k + 1] < q) {
563
+ k++;
564
+ }
565
+ const r = v[k];
566
+ const qr = q - r;
567
+ grid[offset + q * stride] = f[r] + qr * qr;
568
+ }
569
+ }
570
+
571
+ /** Return the next power-of-two canvas dimension for one positive number. */
572
+ function nextPowerOfTwo(value: number): number {
573
+ return Math.pow(2, Math.ceil(Math.log2(Math.max(1, value))));
574
+ }
@@ -0,0 +1,37 @@
1
+ export {
2
+ FastTextLayer,
3
+ type FastTextLayerProps,
4
+ type _FastTextLayerProps
5
+ } from './fast-text-layer';
6
+ export {
7
+ buildFastTextUtf8ColumnSource,
8
+ buildFastTextGlyphData,
9
+ buildMultiLineGlyphData,
10
+ buildSingleLineGlyphData,
11
+ collectFastTextCharacterSet,
12
+ updateFastTextDynamicGlyphAttributes,
13
+ type BuildFastTextGlyphDataProps,
14
+ type CollectFastTextCharacterSetProps,
15
+ type FastTextAlignmentBaseline,
16
+ type FastTextAnchor,
17
+ type FastTextClipRect,
18
+ type FastTextContentAlign,
19
+ type FastTextDynamicGlyphUpdateStats,
20
+ type FastTextGlyphBuildStats,
21
+ type FastTextGlyphAttributes,
22
+ type FastTextGlyphData,
23
+ type FastTextUtf8Column,
24
+ type FastTextUtf8ColumnSource,
25
+ type FastTextUtf8ViewAccessor,
26
+ type UpdateFastTextDynamicGlyphAttributesProps
27
+ } from './fast-text-layout';
28
+ export {
29
+ buildFastTextCharacterMapping,
30
+ createFastTextFontAtlas,
31
+ DEFAULT_FAST_TEXT_FONT_SETTINGS,
32
+ FastTextFontAtlasManager,
33
+ type FastTextCharacter,
34
+ type FastTextCharacterMapping,
35
+ type FastTextFontAtlas,
36
+ type FastTextFontSettings
37
+ } from './font-atlas';