@godot-scene-web/hb-gpu 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.d.ts +305 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +243 -0
- package/dist/index.js.map +1 -0
- package/dist/webgl.d.ts +523 -0
- package/dist/webgl.d.ts.map +1 -0
- package/dist/webgl.js +1624 -0
- package/dist/webgl.js.map +1 -0
- package/package.json +60 -0
- package/vendor/LICENSE-emscripten +102 -0
- package/vendor/LICENSE-harfbuzz +42 -0
- package/vendor/VENDOR.md +115 -0
- package/vendor/hb-gpu.d.mts +29 -0
- package/vendor/hb-gpu.mjs +2 -0
- package/vendor/hb-gpu.wasm +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tomás Fox
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
//#region src/index.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Every way this package can decline, as a stable string.
|
|
4
|
+
*
|
|
5
|
+
* A REASON CHANNEL EXISTS BECAUSE THE FUNCTIONS RETURN `null`. Construct-or-null is the repo's
|
|
6
|
+
* idiom (`createCanvasStage`) and the right one for a shipped renderer — a consumer that cannot
|
|
7
|
+
* have the GPU glyph path falls back to its DOM one. But a component that declined silently
|
|
8
|
+
* reports as a CHEAP one, in a perf table literally so, so every `null` is paired with one of
|
|
9
|
+
* these plus a sentence naming the failure mode.
|
|
10
|
+
*
|
|
11
|
+
* String union rather than an enum so a report can carry it verbatim and a `switch` over it is
|
|
12
|
+
* exhaustively checked.
|
|
13
|
+
*/
|
|
14
|
+
type HbGpuFailureReason = /** `_malloc` returned 0. The heap could not grow; the write would have gone to the null page. */"out-of-memory" /** Zero-length face bytes — usually a detached ArrayBuffer that was transferred elsewhere. */ | "empty-face" /** HarfBuzz would not take the bytes as a face, or the face's `upem` is unusable. */ | "face-rejected" /** `hb_gpu_draw_create_or_fail` returned null. */ | "encoder-unavailable" /** A `upem` that would make every glyph scale `Infinity` or `NaN`. */ | "degenerate-upem"
|
|
15
|
+
/**
|
|
16
|
+
* A `contrast.gamma` that is not a finite positive number. Not fatal — the exponent falls back
|
|
17
|
+
* to 1 and the renderer builds.
|
|
18
|
+
*
|
|
19
|
+
* Refused rather than passed through because `pow (cov, gamma)` with a NaN or an infinity is a
|
|
20
|
+
* NaN alpha, and a NaN alpha through a premultiplied MIX blend is an undefined framebuffer over
|
|
21
|
+
* the glyph's whole quad on some drivers and a black box on others. Neither reads as "somebody
|
|
22
|
+
* typed a bad number into a renderer option".
|
|
23
|
+
*/
|
|
24
|
+
| "degenerate-contrast" /** The WebGL2 context was lost, or was already lost when handed over. */ | "context-lost" /** `gl.create*` returned null. */ | "gl-object" | "shader-compile" | "program-link" /** `MAX_TEXTURE_SIZE` is too small for an atlas at all. */ | "texture-size" /** `MAX_TEXTURE_SIZE` forced a narrower or shorter atlas than asked for. Not fatal. */ | "atlas-clamped" /** A blob whose length is not a whole number of texels. */ | "blob-malformed"
|
|
25
|
+
/**
|
|
26
|
+
* `hb_feature_from_string` would not parse one of the feature strings handed to `shape`.
|
|
27
|
+
*
|
|
28
|
+
* Its own failure mode is the reason this is a refusal and not a warning: HarfBuzz zeroes the
|
|
29
|
+
* struct and returns false, and a zeroed `hb_feature_t` is tag 0 with value 0 over the whole
|
|
30
|
+
* run — which shapes, and shapes WITHOUT the feature the caller asked for. A typo in `"-liga"`
|
|
31
|
+
* would otherwise be a ligature quietly still applied.
|
|
32
|
+
*/
|
|
33
|
+
| "feature-malformed" /** One glyph larger than the entire atlas. */ | "blob-too-large" /** A face handle that this renderer never issued. */ | "face-unregistered" /** A context restore that could not put every resident glyph back. Not fatal. */ | "rebuild-incomplete";
|
|
34
|
+
/** A refusal, with the reason machine-readable and the sentence written for a human. */
|
|
35
|
+
interface HbGpuFailure {
|
|
36
|
+
reason: HbGpuFailureReason;
|
|
37
|
+
/** Always prefixed `hb-gpu: `, and always says what the failure LOOKS like, not just its name. */
|
|
38
|
+
message: string;
|
|
39
|
+
}
|
|
40
|
+
interface HbGpuOptions {
|
|
41
|
+
/** Where a refusal goes. See {@link HbGpuFailureReason}. */
|
|
42
|
+
onError?(failure: HbGpuFailure): void;
|
|
43
|
+
}
|
|
44
|
+
/** Values of `hb_gpu_shader_stage_t`. */
|
|
45
|
+
declare const HB_GPU_SHADER_STAGE_VERTEX = 0;
|
|
46
|
+
declare const HB_GPU_SHADER_STAGE_FRAGMENT = 1;
|
|
47
|
+
/** `HB_GPU_SHADER_LANG_GLSL`. WebGL2 is the only backend this package has a pipeline for. */
|
|
48
|
+
declare const HB_GPU_SHADER_LANG_GLSL = 1;
|
|
49
|
+
/** Bytes per encoded texel: RGBA16I, and the unit the atlas allocator counts in. */
|
|
50
|
+
declare const HB_GPU_TEXEL_BYTES = 8;
|
|
51
|
+
/**
|
|
52
|
+
* A glyph's ink box, in FONT UNITS at the font's scale, y-UP — HarfBuzz's convention, unaltered.
|
|
53
|
+
*
|
|
54
|
+
* `height` IS NEGATIVE and `yBearing` is the ink's TOP. Restated here because it is the single most
|
|
55
|
+
* common way to get a glyph quad upside down, and because `hb_gpu_draw_encode` floors/ceils these
|
|
56
|
+
* to whole font units, so the box is always at least the ink and never less.
|
|
57
|
+
*/
|
|
58
|
+
interface HbGpuGlyphExtents {
|
|
59
|
+
xBearing: number;
|
|
60
|
+
yBearing: number;
|
|
61
|
+
width: number;
|
|
62
|
+
height: number;
|
|
63
|
+
}
|
|
64
|
+
/** One encoded glyph: the texel stream to upload, and the box to draw it in. */
|
|
65
|
+
interface EncodedGlyph {
|
|
66
|
+
/** RGBA16I texels, little-endian, exactly as `hb_gpu_draw_encode` produced them. */
|
|
67
|
+
texels: Uint8Array;
|
|
68
|
+
extents: HbGpuGlyphExtents;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* `hb_direction_t`, as the four names rather than the four integers.
|
|
72
|
+
*
|
|
73
|
+
* A STRING UNION AND NOT A NUMBER, because `hb_buffer_set_direction` has no error channel: hand it
|
|
74
|
+
* anything outside 4..7 and the buffer's direction is INVALID, `guess_segment_properties` then
|
|
75
|
+
* fills in whatever it likes, and the run comes out laid the wrong way with nothing said. The
|
|
76
|
+
* mapping is an ABI constant, kept here the way `HB_MEMORY_MODE_READONLY` is.
|
|
77
|
+
*/
|
|
78
|
+
type HbGpuDirection = "ltr" | "rtl" | "ttb" | "btt";
|
|
79
|
+
/**
|
|
80
|
+
* What a caller can tell the shaper about a run.
|
|
81
|
+
*
|
|
82
|
+
* EVERY FIELD IS OPTIONAL AND THE DEFAULT IS `hb_buffer_guess_segment_properties`, which infers
|
|
83
|
+
* script from the code points and direction from the script. That is the right default and a poor
|
|
84
|
+
* guarantee: it cannot know that a Latin quotation inside an Arabic paragraph is still RTL, and it
|
|
85
|
+
* has no opinion at all about language. Anything set here is set BEFORE the guess, and the guess
|
|
86
|
+
* only fills what is still unset — so an explicit value always wins.
|
|
87
|
+
*/
|
|
88
|
+
interface HbGpuShapeOptions {
|
|
89
|
+
/** Overrides the direction the script implies. */
|
|
90
|
+
direction?: HbGpuDirection;
|
|
91
|
+
/** An ISO 15924 tag — `"Hans"`, `"Latn"`, `"Arab"`. Case is canonicalised by HarfBuzz. */
|
|
92
|
+
script?: string;
|
|
93
|
+
/**
|
|
94
|
+
* A BCP 47 tag — `"zh-Hans"`, `"en"`, `"tr"`.
|
|
95
|
+
*
|
|
96
|
+
* Left unset it stays unset: this build has `HB_NO_SETLOCALE`, so there is no ambient locale to
|
|
97
|
+
* fall back to and the font's `dflt` language system is used. Set it when the face has a
|
|
98
|
+
* language-specific feature the run needs (Turkish dotless i, Serbian Cyrillic italics).
|
|
99
|
+
*/
|
|
100
|
+
language?: string;
|
|
101
|
+
/**
|
|
102
|
+
* OpenType features in `hb-shape`'s own syntax: `"kern"`, `"-liga"`, `"ss01"`, `"aalt[3:5]=2"`.
|
|
103
|
+
*
|
|
104
|
+
* Parsed by `hb_feature_from_string`, and a string it refuses fails the whole call with
|
|
105
|
+
* `"feature-malformed"` rather than being dropped — see that reason.
|
|
106
|
+
*/
|
|
107
|
+
features?: readonly string[];
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* One glyph of a shaped run, in the font's own units, y-UP — HarfBuzz's output, unconverted.
|
|
111
|
+
*
|
|
112
|
+
* See {@link HbGpuFont.shape} for the pen arithmetic these five numbers go into; getting the
|
|
113
|
+
* offset/advance split or the y sign wrong produces text that looks plausible and is mis-spaced.
|
|
114
|
+
*/
|
|
115
|
+
interface HbGpuShapedGlyph {
|
|
116
|
+
/** Glyph id in this face. The same id `encode` takes. */
|
|
117
|
+
glyphId: number;
|
|
118
|
+
/**
|
|
119
|
+
* Where this glyph came from in `text`, as a UTF-16 code-unit index — i.e. an index you can
|
|
120
|
+
* hand straight to `String.prototype.slice`, because the text goes in through
|
|
121
|
+
* `hb_buffer_add_utf16` and a JS string already is UTF-16.
|
|
122
|
+
*
|
|
123
|
+
* Not one per glyph and not monotonic in general: several glyphs share a cluster when one
|
|
124
|
+
* character became many, and many characters share one when a ligature ate them.
|
|
125
|
+
*/
|
|
126
|
+
cluster: number;
|
|
127
|
+
/** How far the pen moves AFTER this glyph. */
|
|
128
|
+
xAdvance: number;
|
|
129
|
+
yAdvance: number;
|
|
130
|
+
/** Added to the pen for THIS glyph only, and never accumulated. */
|
|
131
|
+
xOffset: number;
|
|
132
|
+
yOffset: number;
|
|
133
|
+
}
|
|
134
|
+
interface HbGpuFont {
|
|
135
|
+
/** Units per em of the face — the scale outlines and extents are expressed in. */
|
|
136
|
+
readonly upem: number;
|
|
137
|
+
/** Glyph id for a code point, or 0 (`.notdef`) when the face has no cmap entry. */
|
|
138
|
+
glyphFor(codepoint: number): number;
|
|
139
|
+
/**
|
|
140
|
+
* Shape a run: text in, positioned glyph ids out.
|
|
141
|
+
*
|
|
142
|
+
* UNITS ARE THE FONT'S OWN AND NOT PIXELS, which is the sentence to read twice. `createFont`
|
|
143
|
+
* calls `hb_font_set_scale(font, upem, upem)` — see there for why — so every number that comes
|
|
144
|
+
* back is in font units at that scale, y is UP, and nothing has been converted. Same contract as
|
|
145
|
+
* {@link HbGpuGlyphExtents}, for the same reason: this package cannot know the caller's pixel
|
|
146
|
+
* size, and a half-applied convention is worse than none.
|
|
147
|
+
*
|
|
148
|
+
* THE PEN ARITHMETIC, WHICH IS THE PART THAT GOES SUBTLY WRONG. With `toPx = fontSizePx / upem`,
|
|
149
|
+
* a horizontal run is laid out:
|
|
150
|
+
*
|
|
151
|
+
* ```ts
|
|
152
|
+
* let pen = 0;
|
|
153
|
+
* for (const glyph of run) {
|
|
154
|
+
* const xPx = (pen + glyph.xOffset) * toPx; // offset positions THIS glyph...
|
|
155
|
+
* const yPx = -glyph.yOffset * toPx; // ...and y flips, because canvases are y-DOWN
|
|
156
|
+
* pen += glyph.xAdvance; // ...and only the advance moves the pen
|
|
157
|
+
* }
|
|
158
|
+
* ```
|
|
159
|
+
*
|
|
160
|
+
* The offset does not accumulate and the advance is applied AFTER the glyph. That is
|
|
161
|
+
* byte-for-byte what `perf-harness/src/scenarios/text-hb.ts` runs over npm `harfbuzzjs`'s
|
|
162
|
+
* output, whose pen positions are in turn proven identical to the independently written
|
|
163
|
+
* `hb-atlas` arm's — so matching it is what makes a run shaped here land exactly where every
|
|
164
|
+
* other arm in that round puts it. `test/shape.test.ts` asserts the two agree, glyph for glyph.
|
|
165
|
+
*
|
|
166
|
+
* `[]` FOR EMPTY TEXT AND `null` FOR A FAILURE, which are different answers to different
|
|
167
|
+
* questions. An empty run is legal; a run that could not be shaped is not, and it would
|
|
168
|
+
* otherwise render as a page with some of its text quietly missing.
|
|
169
|
+
*
|
|
170
|
+
* An array of objects rather than a flat typed array: shaping is a startup cost in every
|
|
171
|
+
* consumer here (each run is shaped once and baked), each field is read exactly once on the way
|
|
172
|
+
* into a pen position, and a per-glyph object is field-for-field what npm `harfbuzzjs` hands
|
|
173
|
+
* back — which is what makes the cross-check, and any port off it, a direct comparison rather
|
|
174
|
+
* than a re-derivation.
|
|
175
|
+
*/
|
|
176
|
+
shape(text: string, options?: HbGpuShapeOptions): HbGpuShapedGlyph[] | null;
|
|
177
|
+
/**
|
|
178
|
+
* Encode one glyph. `null` when the encoder failed; a zero-length `texels` when the glyph has no
|
|
179
|
+
* ink (a space), which is a different thing and must not be uploaded.
|
|
180
|
+
*/
|
|
181
|
+
encode(glyphId: number): EncodedGlyph | null;
|
|
182
|
+
destroy(): void;
|
|
183
|
+
}
|
|
184
|
+
interface HbGpu {
|
|
185
|
+
/**
|
|
186
|
+
* Bytes of wasm heap this module currently holds.
|
|
187
|
+
*
|
|
188
|
+
* `HEAPU8.byteLength` rather than an instrumented allocator: it reports what the heap has
|
|
189
|
+
* actually grown to, font copy and encoder scratch included, which is the number a consumer
|
|
190
|
+
* weighs against the arm's other costs.
|
|
191
|
+
*
|
|
192
|
+
* IT IS A CEILING, NOT A HIGH-WATER MARK, AND `build.sh` HAD TO BE CHANGED TO MAKE IT USEFUL.
|
|
193
|
+
* A wasm heap is only ever as small as its initial reservation, so this reads whatever
|
|
194
|
+
* `-sINITIAL_MEMORY` asked for until something exceeds it. At emscripten's default it read
|
|
195
|
+
* 16.19 MiB for an 826 KB face — about 8x the truth. The build asks for 2 MiB, re-measured when
|
|
196
|
+
* shaping landed: both S9 fixture faces, 2080 shaped runs and 340 encoded outlines high-water at
|
|
197
|
+
* 1.125 MiB and never reach the reservation. See `build.sh` for the whole ladder.
|
|
198
|
+
*
|
|
199
|
+
* AND IT IS NOW ONE NUMBER RATHER THAN TWO. A caller that shapes here has no second wasm heap to
|
|
200
|
+
* add to it; the arrangement this replaces held every face in both.
|
|
201
|
+
*/
|
|
202
|
+
readonly heapBytes: number;
|
|
203
|
+
/**
|
|
204
|
+
* HarfBuzz's own GLSL for one stage: the shared library half plus the draw-renderer half,
|
|
205
|
+
* concatenated in the order `demo-shader.cc` uses. No `main()` — see this file's header.
|
|
206
|
+
*/
|
|
207
|
+
shaderLibrary(stage: number): string;
|
|
208
|
+
/**
|
|
209
|
+
* Copy a face into the wasm heap and open an encoder over it, or `null`.
|
|
210
|
+
*
|
|
211
|
+
* `null` RATHER THAN A THROW, and the throw it replaces leaked: the old code allocated the face
|
|
212
|
+
* copy, created a blob, a face and a font, and then threw when the encoder came back null —
|
|
213
|
+
* losing all four for the life of the module. Every failure path below unwinds in the reverse
|
|
214
|
+
* order `destroy` uses. The reason reaches {@link HbGpuOptions.onError}.
|
|
215
|
+
*/
|
|
216
|
+
createFont(bytes: Uint8Array): HbGpuFont | null;
|
|
217
|
+
destroy(): void;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* The subset of emscripten's module object this package uses.
|
|
221
|
+
*
|
|
222
|
+
* Hand-written rather than generated: `-sEXPORTED_FUNCTIONS=@hb-gpu.symbols` already fixes the
|
|
223
|
+
* list, and a type that restates it is a second place the two can disagree — loudly, at the call
|
|
224
|
+
* site, which is where a missing export should be noticed.
|
|
225
|
+
*/
|
|
226
|
+
interface HbGpuWasmExports {
|
|
227
|
+
HEAPU8: Uint8Array;
|
|
228
|
+
HEAP32: Int32Array;
|
|
229
|
+
/** The view `hb_buffer_add_utf16`'s text is written through. See `build.sh`. */
|
|
230
|
+
HEAPU16: Uint16Array;
|
|
231
|
+
UTF8ToString(pointer: number): string;
|
|
232
|
+
_malloc(bytes: number): number;
|
|
233
|
+
_free(pointer: number): void;
|
|
234
|
+
_hb_blob_create(data: number, length: number, mode: number, userData: number, destroy: number): number;
|
|
235
|
+
_hb_blob_get_data(blob: number, lengthOut: number): number;
|
|
236
|
+
_hb_blob_get_length(blob: number): number;
|
|
237
|
+
_hb_blob_destroy(blob: number): void;
|
|
238
|
+
_hb_face_create(blob: number, index: number): number;
|
|
239
|
+
_hb_face_destroy(face: number): void;
|
|
240
|
+
_hb_face_get_upem(face: number): number;
|
|
241
|
+
_hb_font_create(face: number): number;
|
|
242
|
+
_hb_font_destroy(font: number): void;
|
|
243
|
+
_hb_font_set_scale(font: number, xScale: number, yScale: number): void;
|
|
244
|
+
_hb_font_get_nominal_glyph(font: number, codepoint: number, glyphOut: number): number;
|
|
245
|
+
_hb_shape(font: number, buffer: number, features: number, featureCount: number): void;
|
|
246
|
+
_hb_buffer_create(): number;
|
|
247
|
+
_hb_buffer_destroy(buffer: number): void;
|
|
248
|
+
_hb_buffer_allocation_successful(buffer: number): number;
|
|
249
|
+
_hb_buffer_clear_contents(buffer: number): void;
|
|
250
|
+
_hb_buffer_add_utf16(buffer: number, text: number, textLength: number, itemOffset: number, itemLength: number): void;
|
|
251
|
+
_hb_buffer_guess_segment_properties(buffer: number): void;
|
|
252
|
+
_hb_buffer_set_direction(buffer: number, direction: number): void;
|
|
253
|
+
_hb_buffer_set_script(buffer: number, script: number): void;
|
|
254
|
+
_hb_buffer_set_language(buffer: number, language: number): void;
|
|
255
|
+
_hb_language_from_string(text: number, length: number): number;
|
|
256
|
+
_hb_script_from_string(text: number, length: number): number;
|
|
257
|
+
_hb_feature_from_string(text: number, length: number, featureOut: number): number;
|
|
258
|
+
_hb_buffer_get_length(buffer: number): number;
|
|
259
|
+
_hb_buffer_get_glyph_infos(buffer: number, lengthOut: number): number;
|
|
260
|
+
_hb_buffer_get_glyph_positions(buffer: number, lengthOut: number): number;
|
|
261
|
+
_hb_gpu_draw_create_or_fail(): number;
|
|
262
|
+
_hb_gpu_draw_destroy(draw: number): void;
|
|
263
|
+
_hb_gpu_draw_set_scale(draw: number, xScale: number, yScale: number): void;
|
|
264
|
+
_hb_gpu_draw_glyph_or_fail(draw: number, font: number, glyph: number): number;
|
|
265
|
+
_hb_gpu_draw_encode(draw: number, extentsOut: number): number;
|
|
266
|
+
_hb_gpu_draw_clear(draw: number): void;
|
|
267
|
+
_hb_gpu_draw_reset(draw: number): void;
|
|
268
|
+
_hb_gpu_draw_recycle_blob(draw: number, blob: number): void;
|
|
269
|
+
_hb_gpu_shader_source(stage: number, lang: number): number;
|
|
270
|
+
_hb_gpu_draw_shader_source(stage: number, lang: number): number;
|
|
271
|
+
}
|
|
272
|
+
/** The default export of `dist/hb-gpu.mjs`, under `-sMODULARIZE=1 -sEXPORT_ES6=1`. */
|
|
273
|
+
type HbGpuModuleFactory = (options: {
|
|
274
|
+
wasmBinary: ArrayBuffer | Uint8Array;
|
|
275
|
+
}) => Promise<HbGpuWasmExports>;
|
|
276
|
+
/**
|
|
277
|
+
* Instantiate the module.
|
|
278
|
+
*
|
|
279
|
+
* The FACTORY is a parameter rather than an import, and deliberately: `dist/hb-gpu.mjs` is a build
|
|
280
|
+
* output of `build.sh` (docker, emscripten, minutes) that `dist/` gitignores, so a static import
|
|
281
|
+
* here would make this package fail to typecheck on a fresh checkout and fail to build in CI. The
|
|
282
|
+
* caller imports the glue — it is the caller that knows whether the build has been run.
|
|
283
|
+
*/
|
|
284
|
+
declare function createHbGpu(factory: HbGpuModuleFactory, wasmBinary: ArrayBuffer | Uint8Array, options?: HbGpuOptions): Promise<HbGpu>;
|
|
285
|
+
/**
|
|
286
|
+
* Total and per-glyph encoded bytes for a set of glyphs.
|
|
287
|
+
*
|
|
288
|
+
* THE PREDICTION UNDER TEST, made directly measurable. `docs/text-rendering.md` records it as
|
|
289
|
+
* "~5.4 KB per Han glyph against ~1.4 KB for a 38x38 R8 atlas cell", and that comparison decides
|
|
290
|
+
* the VRAM half of whether this arm is worth shipping — so it should be one call, on the real
|
|
291
|
+
* fixture face, rather than something reconstructed from a frame counter.
|
|
292
|
+
*
|
|
293
|
+
* DISTINCT glyph ids, because that is what the atlas pays for too: a Han run draws 12 glyphs and an
|
|
294
|
+
* atlas stores as many outlines as the pool has distinct members.
|
|
295
|
+
*/
|
|
296
|
+
declare function measureBlobBytes(font: HbGpuFont, glyphIds: Iterable<number>): {
|
|
297
|
+
glyphs: number;
|
|
298
|
+
totalBytes: number;
|
|
299
|
+
bytesPerGlyph: number;
|
|
300
|
+
minBytes: number;
|
|
301
|
+
maxBytes: number;
|
|
302
|
+
};
|
|
303
|
+
//#endregion
|
|
304
|
+
export { EncodedGlyph, HB_GPU_SHADER_LANG_GLSL, HB_GPU_SHADER_STAGE_FRAGMENT, HB_GPU_SHADER_STAGE_VERTEX, HB_GPU_TEXEL_BYTES, HbGpu, HbGpuDirection, HbGpuFailure, HbGpuFailureReason, HbGpuFont, HbGpuGlyphExtents, HbGpuModuleFactory, HbGpuOptions, HbGpuShapeOptions, HbGpuShapedGlyph, HbGpuWasmExports, createHbGpu, measureBlobBytes };
|
|
305
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
//#region src/index.ts
|
|
2
|
+
/** Values of `hb_gpu_shader_stage_t`. */
|
|
3
|
+
const HB_GPU_SHADER_STAGE_VERTEX = 0;
|
|
4
|
+
const HB_GPU_SHADER_STAGE_FRAGMENT = 1;
|
|
5
|
+
/** `HB_GPU_SHADER_LANG_GLSL`. WebGL2 is the only backend this package has a pipeline for. */
|
|
6
|
+
const HB_GPU_SHADER_LANG_GLSL = 1;
|
|
7
|
+
/** `HB_MEMORY_MODE_READONLY`: the blob points at our allocation and never writes to it. */
|
|
8
|
+
const HB_MEMORY_MODE_READONLY = 1;
|
|
9
|
+
/** Bytes per encoded texel: RGBA16I, and the unit the atlas allocator counts in. */
|
|
10
|
+
const HB_GPU_TEXEL_BYTES = 8;
|
|
11
|
+
/** `hb_direction_t`: HB_DIRECTION_LTR is 4 and the rest follow. */
|
|
12
|
+
const HB_DIRECTION = {
|
|
13
|
+
ltr: 4,
|
|
14
|
+
rtl: 5,
|
|
15
|
+
ttb: 6,
|
|
16
|
+
btt: 7
|
|
17
|
+
};
|
|
18
|
+
/** `sizeof (hb_glyph_extents_t)`: four `hb_position_t`, which is `int32_t`. */
|
|
19
|
+
const EXTENTS_BYTES = 16;
|
|
20
|
+
/**
|
|
21
|
+
* `sizeof (hb_glyph_info_t)` and `sizeof (hb_glyph_position_t)` on wasm32. Both 20, and the fact
|
|
22
|
+
* that they are EQUAL is a coincidence of layout, not a rule — they are named separately so that a
|
|
23
|
+
* future HarfBuzz that grows one of them cannot silently shift the other's reads.
|
|
24
|
+
*
|
|
25
|
+
* `hb_glyph_info_t` = codepoint, mask, cluster, var1, var2 (5 x uint32)
|
|
26
|
+
* `hb_glyph_position_t` = x_advance, y_advance, x_offset, y_offset, var (5 x int32)
|
|
27
|
+
*/
|
|
28
|
+
const GLYPH_INFO_BYTES = 20;
|
|
29
|
+
const GLYPH_POSITION_BYTES = 20;
|
|
30
|
+
/** `sizeof (hb_feature_t)`: tag, value, start, end. */
|
|
31
|
+
const FEATURE_BYTES = 16;
|
|
32
|
+
/** UTF-8 for the tag and feature strings. ASCII in practice; this is the correct encoder anyway. */
|
|
33
|
+
const utf8 = new TextEncoder();
|
|
34
|
+
/**
|
|
35
|
+
* Instantiate the module.
|
|
36
|
+
*
|
|
37
|
+
* The FACTORY is a parameter rather than an import, and deliberately: `dist/hb-gpu.mjs` is a build
|
|
38
|
+
* output of `build.sh` (docker, emscripten, minutes) that `dist/` gitignores, so a static import
|
|
39
|
+
* here would make this package fail to typecheck on a fresh checkout and fail to build in CI. The
|
|
40
|
+
* caller imports the glue — it is the caller that knows whether the build has been run.
|
|
41
|
+
*/
|
|
42
|
+
async function createHbGpu(factory, wasmBinary, options = {}) {
|
|
43
|
+
const wasm = await factory({ wasmBinary });
|
|
44
|
+
const fonts = /* @__PURE__ */ new Set();
|
|
45
|
+
const fail = (reason, message) => {
|
|
46
|
+
options.onError?.({
|
|
47
|
+
reason,
|
|
48
|
+
message: `hb-gpu: ${message}`
|
|
49
|
+
});
|
|
50
|
+
return null;
|
|
51
|
+
};
|
|
52
|
+
return {
|
|
53
|
+
get heapBytes() {
|
|
54
|
+
return wasm.HEAPU8.byteLength;
|
|
55
|
+
},
|
|
56
|
+
shaderLibrary(stage) {
|
|
57
|
+
const shared = wasm._hb_gpu_shader_source(stage, 1);
|
|
58
|
+
const draw = wasm._hb_gpu_draw_shader_source(stage, 1);
|
|
59
|
+
return (shared ? wasm.UTF8ToString(shared) : "") + (draw ? wasm.UTF8ToString(draw) : "");
|
|
60
|
+
},
|
|
61
|
+
createFont(bytes) {
|
|
62
|
+
if (bytes.byteLength === 0) return fail("empty-face", "createFont was given zero face bytes — a transferred or detached ArrayBuffer looks exactly like this, and the face would encode every glyph to nothing");
|
|
63
|
+
const dataPointer = wasm._malloc(bytes.byteLength);
|
|
64
|
+
if (!dataPointer) return fail("out-of-memory", `_malloc(${bytes.byteLength}) returned 0 — the wasm heap could not grow, and writing the face at address 0 would have corrupted the null page`);
|
|
65
|
+
wasm.HEAPU8.set(bytes, dataPointer);
|
|
66
|
+
let blob = 0;
|
|
67
|
+
let face = 0;
|
|
68
|
+
let font = 0;
|
|
69
|
+
let draw = 0;
|
|
70
|
+
let scratch = 0;
|
|
71
|
+
let buffer = 0;
|
|
72
|
+
const unwind = (reason, message) => {
|
|
73
|
+
if (buffer) wasm._hb_buffer_destroy(buffer);
|
|
74
|
+
if (draw) wasm._hb_gpu_draw_destroy(draw);
|
|
75
|
+
if (scratch) wasm._free(scratch);
|
|
76
|
+
if (font) wasm._hb_font_destroy(font);
|
|
77
|
+
if (face) wasm._hb_face_destroy(face);
|
|
78
|
+
if (blob) wasm._hb_blob_destroy(blob);
|
|
79
|
+
wasm._free(dataPointer);
|
|
80
|
+
return fail(reason, message);
|
|
81
|
+
};
|
|
82
|
+
blob = wasm._hb_blob_create(dataPointer, bytes.byteLength, HB_MEMORY_MODE_READONLY, 0, 0);
|
|
83
|
+
if (!blob || wasm._hb_blob_get_length(blob) !== bytes.byteLength) return unwind("face-rejected", `hb_blob_create returned a ${blob ? wasm._hb_blob_get_length(blob) : 0}-byte blob for ${bytes.byteLength} bytes of face — HarfBuzz would not take the allocation`);
|
|
84
|
+
face = wasm._hb_face_create(blob, 0);
|
|
85
|
+
if (!face) return unwind("face-rejected", "hb_face_create returned null");
|
|
86
|
+
const upem = wasm._hb_face_get_upem(face);
|
|
87
|
+
if (!Number.isInteger(upem) || upem <= 0 || upem > 16384) return unwind("face-rejected", `the face reports upem ${upem} — every glyph scaled by that is Infinity or NaN, which draws nothing and reports no error`);
|
|
88
|
+
font = wasm._hb_font_create(face);
|
|
89
|
+
if (!font) return unwind("face-rejected", "hb_font_create returned null");
|
|
90
|
+
wasm._hb_font_set_scale(font, upem, upem);
|
|
91
|
+
draw = wasm._hb_gpu_draw_create_or_fail();
|
|
92
|
+
if (!draw) return unwind("encoder-unavailable", "hb_gpu_draw_create_or_fail returned null — this face has no encoder, and every glyph of it would be missing from the frame");
|
|
93
|
+
wasm._hb_gpu_draw_set_scale(draw, upem, upem);
|
|
94
|
+
scratch = wasm._malloc(EXTENTS_BYTES);
|
|
95
|
+
if (!scratch) return unwind("out-of-memory", `_malloc(${EXTENTS_BYTES}) for the extents scratch returned 0 — reading glyph ids out of address 0 would hand back whatever the null page holds`);
|
|
96
|
+
buffer = wasm._hb_buffer_create();
|
|
97
|
+
if (!buffer || !wasm._hb_buffer_allocation_successful(buffer)) return unwind("out-of-memory", "hb_buffer_create handed back the immortal empty buffer — the heap could not allocate one, and every run shaped into it would come back with no glyphs at all");
|
|
98
|
+
let alive = true;
|
|
99
|
+
const self = {
|
|
100
|
+
upem,
|
|
101
|
+
glyphFor(codepoint) {
|
|
102
|
+
return wasm._hb_font_get_nominal_glyph(font, codepoint, scratch) ? wasm.HEAP32[scratch >> 2] >>> 0 : 0;
|
|
103
|
+
},
|
|
104
|
+
shape(text, options = {}) {
|
|
105
|
+
wasm._hb_buffer_clear_contents(buffer);
|
|
106
|
+
if (text.length === 0) return [];
|
|
107
|
+
const featureStrings = options.features ?? [];
|
|
108
|
+
const featuresAt = text.length * 2 + 3 & -4;
|
|
109
|
+
const strings = [];
|
|
110
|
+
let end = featuresAt + featureStrings.length * FEATURE_BYTES;
|
|
111
|
+
const place = (value) => {
|
|
112
|
+
const bytes = utf8.encode(value);
|
|
113
|
+
strings.push({
|
|
114
|
+
bytes,
|
|
115
|
+
at: end
|
|
116
|
+
});
|
|
117
|
+
const placed = {
|
|
118
|
+
at: end,
|
|
119
|
+
length: bytes.length
|
|
120
|
+
};
|
|
121
|
+
end += bytes.length;
|
|
122
|
+
return placed;
|
|
123
|
+
};
|
|
124
|
+
const script = options.script ? place(options.script) : null;
|
|
125
|
+
const language = options.language ? place(options.language) : null;
|
|
126
|
+
const features = featureStrings.map(place);
|
|
127
|
+
const block = wasm._malloc(end);
|
|
128
|
+
if (!block) return fail("out-of-memory", `_malloc(${end}) for a ${text.length}-code-unit run returned 0 — the wasm heap could not grow, and shaping out of address 0 would read the null page as text`);
|
|
129
|
+
const units = new Uint16Array(text.length);
|
|
130
|
+
for (let i = 0; i < text.length; i += 1) units[i] = text.charCodeAt(i);
|
|
131
|
+
wasm.HEAPU16.set(units, block >> 1);
|
|
132
|
+
for (const string of strings) wasm.HEAPU8.set(string.bytes, block + string.at);
|
|
133
|
+
for (let i = 0; i < features.length; i += 1) {
|
|
134
|
+
const at = block + featuresAt + i * FEATURE_BYTES;
|
|
135
|
+
if (!wasm._hb_feature_from_string(block + features[i].at, features[i].length, at)) {
|
|
136
|
+
wasm._free(block);
|
|
137
|
+
return fail("feature-malformed", `hb_feature_from_string refused "${featureStrings[i]}" — shaping on would silently lay the run out without the feature, so the run is refused instead`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
wasm._hb_buffer_add_utf16(buffer, block, text.length, 0, text.length);
|
|
141
|
+
if (options.direction) wasm._hb_buffer_set_direction(buffer, HB_DIRECTION[options.direction]);
|
|
142
|
+
if (script) wasm._hb_buffer_set_script(buffer, wasm._hb_script_from_string(block + script.at, script.length));
|
|
143
|
+
if (language) wasm._hb_buffer_set_language(buffer, wasm._hb_language_from_string(block + language.at, language.length));
|
|
144
|
+
wasm._hb_buffer_guess_segment_properties(buffer);
|
|
145
|
+
wasm._hb_shape(font, buffer, features.length ? block + featuresAt : 0, features.length);
|
|
146
|
+
wasm._free(block);
|
|
147
|
+
if (!wasm._hb_buffer_allocation_successful(buffer)) return fail("out-of-memory", `shaping a ${text.length}-code-unit run exhausted the wasm heap — the buffer came back empty, which is indistinguishable from a run with no glyphs and would render as missing text`);
|
|
148
|
+
const count = wasm._hb_buffer_get_length(buffer);
|
|
149
|
+
const infos = wasm._hb_buffer_get_glyph_infos(buffer, 0);
|
|
150
|
+
const positions = wasm._hb_buffer_get_glyph_positions(buffer, 0);
|
|
151
|
+
const heap = wasm.HEAP32;
|
|
152
|
+
const run = [];
|
|
153
|
+
for (let i = 0; i < count; i += 1) {
|
|
154
|
+
const info = infos + i * GLYPH_INFO_BYTES >> 2;
|
|
155
|
+
const position = positions + i * GLYPH_POSITION_BYTES >> 2;
|
|
156
|
+
run.push({
|
|
157
|
+
glyphId: heap[info] >>> 0,
|
|
158
|
+
cluster: heap[info + 2] >>> 0,
|
|
159
|
+
xAdvance: heap[position],
|
|
160
|
+
yAdvance: heap[position + 1],
|
|
161
|
+
xOffset: heap[position + 2],
|
|
162
|
+
yOffset: heap[position + 3]
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
return run;
|
|
166
|
+
},
|
|
167
|
+
encode(glyphId) {
|
|
168
|
+
wasm._hb_gpu_draw_clear(draw);
|
|
169
|
+
if (!wasm._hb_gpu_draw_glyph_or_fail(draw, font, glyphId)) return null;
|
|
170
|
+
const blobPointer = wasm._hb_gpu_draw_encode(draw, scratch);
|
|
171
|
+
if (!blobPointer) return null;
|
|
172
|
+
const extents = {
|
|
173
|
+
xBearing: wasm.HEAP32[scratch >> 2],
|
|
174
|
+
yBearing: wasm.HEAP32[(scratch >> 2) + 1],
|
|
175
|
+
width: wasm.HEAP32[(scratch >> 2) + 2],
|
|
176
|
+
height: wasm.HEAP32[(scratch >> 2) + 3]
|
|
177
|
+
};
|
|
178
|
+
const length = wasm._hb_blob_get_length(blobPointer);
|
|
179
|
+
const data = wasm._hb_blob_get_data(blobPointer, 0);
|
|
180
|
+
const texels = wasm.HEAPU8.slice(data, data + length);
|
|
181
|
+
wasm._hb_gpu_draw_recycle_blob(draw, blobPointer);
|
|
182
|
+
return {
|
|
183
|
+
texels,
|
|
184
|
+
extents
|
|
185
|
+
};
|
|
186
|
+
},
|
|
187
|
+
destroy() {
|
|
188
|
+
if (!alive) return;
|
|
189
|
+
alive = false;
|
|
190
|
+
fonts.delete(self);
|
|
191
|
+
wasm._hb_buffer_destroy(buffer);
|
|
192
|
+
wasm._hb_gpu_draw_destroy(draw);
|
|
193
|
+
wasm._free(scratch);
|
|
194
|
+
wasm._hb_font_destroy(font);
|
|
195
|
+
wasm._hb_face_destroy(face);
|
|
196
|
+
wasm._hb_blob_destroy(blob);
|
|
197
|
+
wasm._free(dataPointer);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
fonts.add(self);
|
|
201
|
+
return self;
|
|
202
|
+
},
|
|
203
|
+
destroy() {
|
|
204
|
+
for (const font of [...fonts]) font.destroy();
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Total and per-glyph encoded bytes for a set of glyphs.
|
|
210
|
+
*
|
|
211
|
+
* THE PREDICTION UNDER TEST, made directly measurable. `docs/text-rendering.md` records it as
|
|
212
|
+
* "~5.4 KB per Han glyph against ~1.4 KB for a 38x38 R8 atlas cell", and that comparison decides
|
|
213
|
+
* the VRAM half of whether this arm is worth shipping — so it should be one call, on the real
|
|
214
|
+
* fixture face, rather than something reconstructed from a frame counter.
|
|
215
|
+
*
|
|
216
|
+
* DISTINCT glyph ids, because that is what the atlas pays for too: a Han run draws 12 glyphs and an
|
|
217
|
+
* atlas stores as many outlines as the pool has distinct members.
|
|
218
|
+
*/
|
|
219
|
+
function measureBlobBytes(font, glyphIds) {
|
|
220
|
+
let glyphs = 0;
|
|
221
|
+
let totalBytes = 0;
|
|
222
|
+
let minBytes = Number.POSITIVE_INFINITY;
|
|
223
|
+
let maxBytes = 0;
|
|
224
|
+
for (const id of new Set(glyphIds)) {
|
|
225
|
+
const encoded = font.encode(id);
|
|
226
|
+
if (!encoded || encoded.texels.length === 0) continue;
|
|
227
|
+
glyphs += 1;
|
|
228
|
+
totalBytes += encoded.texels.length;
|
|
229
|
+
minBytes = Math.min(minBytes, encoded.texels.length);
|
|
230
|
+
maxBytes = Math.max(maxBytes, encoded.texels.length);
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
glyphs,
|
|
234
|
+
totalBytes,
|
|
235
|
+
bytesPerGlyph: glyphs > 0 ? totalBytes / glyphs : 0,
|
|
236
|
+
minBytes: glyphs > 0 ? minBytes : 0,
|
|
237
|
+
maxBytes
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
//#endregion
|
|
241
|
+
export { HB_GPU_SHADER_LANG_GLSL, HB_GPU_SHADER_STAGE_FRAGMENT, HB_GPU_SHADER_STAGE_VERTEX, HB_GPU_TEXEL_BYTES, createHbGpu, measureBlobBytes };
|
|
242
|
+
|
|
243
|
+
//# sourceMappingURL=index.js.map
|