@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.
@@ -0,0 +1,115 @@
1
+ # Vendored HarfBuzz GPU encoder
2
+
3
+ A **vendored third-party binary**, not a build output. `dist/` is the tsdown TypeScript build and
4
+ stays gitignored; this directory is committed, because a wasm that only exists after a docker pull
5
+ and an emscripten run is a package a fresh checkout cannot use at all.
6
+
7
+ Everything here is reproducible from `../build.sh`, and every digest below is asserted by
8
+ `../test/vendor.test.ts` — so a rebuild that moves a byte turns the suite red until this file is
9
+ updated to match.
10
+
11
+ ## What is here
12
+
13
+ | file | bytes | sha256 |
14
+ | -------------------- | ------: | ------------------------------------------------------------------ |
15
+ | `hb-gpu.wasm` | 417 263 | `bfca55c32e788d5bb523ca7ae70cb895fe0c062173e2a35c4a7fb617c5b49e19` |
16
+ | `hb-gpu.mjs` | 9 909 | `2a3626057472a1158f10dda3f1b119f10f6f711ca40a1d88e2780360d6c88a41` |
17
+ | `LICENSE-harfbuzz` | 1 971 | verbatim `harfbuzz-14.4.0/COPYING` |
18
+ | `LICENSE-emscripten` | 5 093 | verbatim `/emsdk/upstream/emscripten/LICENSE` from the image below |
19
+ | `hb-gpu.d.mts` | — | hand-written by this repo, not vendored |
20
+
21
+ `hb-gpu.wasm` is HarfBuzz's Slug GPU glyph encoder, its shader-source getters **and its OpenType
22
+ shaper**, compiled from the single translation unit `src/harfbuzz-world.cc`. `hb-gpu.mjs` is
23
+ emscripten's ES6 glue for it.
24
+
25
+ **It grew 227 049 -> 417 263 bytes (+190 KB, 1.84x) on 2026-09-01, and that was the point.** The
26
+ shaping engine was always in the translation unit; `../hb-gpu.symbols` named none of it, so `-flto`
27
+ and `--gc-sections` threw it away, and every consumer therefore loaded npm `harfbuzzjs` as a SECOND
28
+ HarfBuzz and held every font face in BOTH wasm heaps — 4.19 MiB across the pair on the phone. The
29
+ symbol list now names `hb_shape` and the `hb_buffer_*` API behind it. One binary is bigger; one
30
+ face is resident once. `../test/shape.test.ts` grades the result against npm `harfbuzzjs`, which
31
+ stays a devDependency precisely because being a different build is what makes it a check.
32
+
33
+ Both are reachable as package subpaths — `@godot-scene-web/hb-gpu/vendor/hb-gpu.wasm` and
34
+ `.../vendor/hb-gpu.mjs` — so a consumer never has to reach into the package by relative path.
35
+
36
+ **The glue is browser-first, and node works only if you hand it the bytes.** It is built with
37
+ `-sENVIRONMENT=web,worker`, so the node branch is compiled out and an import that lets the glue go
38
+ looking for its own `.wasm` aborts with "both async and sync fetching of the wasm failed". Pass
39
+ `{ wasmBinary }` — the one key `-sINCOMING_MODULE_JS_API` admits — and that path is never reached,
40
+ which is how `../test/shape.test.ts` shapes under node. `../test/vendor.test.ts` still asserts
41
+ against the file on disk, because what it checks is the file and not a runtime.
42
+
43
+ ## Provenance
44
+
45
+ | what | value |
46
+ | ---------------- | ---------------------------------------------------------------------------------------------------------- |
47
+ | HarfBuzz version | 14.4.0 |
48
+ | tarball | <https://github.com/harfbuzz/harfbuzz/archive/refs/tags/14.4.0.tar.gz> |
49
+ | tarball sha256 | `46dc4f3b6aefc4d8256b10017186f5ebe50ea086714ab8948cdac4695a7a80a8` |
50
+ | toolchain image | `emscripten/emsdk@sha256:96617f27fe16421588241def73908fd348a7f9d260440ed0d00b36dcf7a063cc` (manifest list) |
51
+ | — linux/amd64 | `sha256:3ba391c5b1554e06f9af0a69652ff20919dff14e771619339dba988bd62574b5` |
52
+ | — emcc in it | 6.0.9 (`4e4223852a0835923411059a3929907d7df1232e`) |
53
+
54
+ A tag is not a pin: GitHub builds those archives on demand and a tag can be moved, so `build.sh`
55
+ checksums the tarball and refuses to compile different bytes under the same version. The image is
56
+ pinned by digest for the same reason — it used to be `emscripten/emsdk:latest`, which made the
57
+ toolchain a moving target while the source was nailed down.
58
+
59
+ To re-resolve the image digest after a deliberate toolchain bump:
60
+
61
+ ```bash
62
+ docker buildx imagetools inspect emscripten/emsdk:latest # -> Digest: sha256:...
63
+ ```
64
+
65
+ ## Reproducing
66
+
67
+ ```bash
68
+ packages/hb-gpu/build.sh
69
+ ```
70
+
71
+ Docker is the only prerequisite; the script fetches and checksums the tarball, copies upstream's own
72
+ `util/gpu/web/config.h` and `config-override.h`, compiles inside the pinned image as the invoking
73
+ uid, and prints the sizes and digests above. If nothing in `build.sh` or the pinned inputs changed,
74
+ the rebuild is byte-identical and `git status` stays clean.
75
+
76
+ **Measured, not assumed.** Rebuilt on 2026-09-01 on linux/amd64, on a different checkout of the same
77
+ commit, and both outputs came out byte-for-byte equal to the ones committed here — including across
78
+ the removal of the dead `-DHB_GPU_ATLAS_2D` flag, which reaches only `util/gpu/demo-atlas.cc` and is
79
+ not in this translation unit.
80
+
81
+ **Still byte-reproducible across the shaper change.** The digests above were produced by one build
82
+ and then reproduced by a second, from the same pinned tarball and the same pinned image, after only
83
+ comment edits to `../hb-gpu.symbols` — which the emscripten response-file parser drops, so the
84
+ export list, and therefore the binary, is unchanged by them.
85
+
86
+ ## Change-frequency policy
87
+
88
+ **This binary changes only on a deliberate HarfBuzz version bump, build-flag change, or edit to
89
+ `../hb-gpu.symbols`.** That third trigger is the one that produced the change of 2026-09-01, and it
90
+ is the least obvious: the symbol list is what `-flto` and `--gc-sections` prune against, so adding
91
+ one name can pull in a whole subsystem — `hb_shape` brought +190 KB of OpenType shaper with it.
92
+
93
+ It is not regenerated by `pnpm build`, it has no watch mode, and nothing in CI writes to it. A bump
94
+ is a conscious act, and it must land as one commit that updates, together:
95
+
96
+ - the rebuilt `hb-gpu.wasm` / `hb-gpu.mjs`,
97
+ - `HB_VERSION` / `HB_SHA256` / `IMAGE` in `../build.sh`,
98
+ - every digest and size in this file,
99
+ - the exported-function floor in `../test/vendor.test.ts`, if the symbol list changed,
100
+ - `LICENSE-harfbuzz` and `LICENSE-emscripten`, if the upstream texts moved.
101
+
102
+ A diff that touches the binary and not this file is the failure mode this policy exists to prevent;
103
+ `../test/vendor.test.ts` fails on exactly that.
104
+
105
+ ## Licensing
106
+
107
+ Both are permissive and compatible with this repo's MIT license. The obligation is attribution, and
108
+ shipping the texts here is how it is discharged — they must travel with the binaries.
109
+
110
+ - **HarfBuzz** — "Old MIT" (`LICENSE-harfbuzz`, verbatim `harfbuzz-14.4.0/COPYING`): the copyright
111
+ notice and the two disclaimer paragraphs must appear in all copies. The only other `COPYING` in
112
+ the tarball is `test/COPYING`, and `test/` is not compiled, so every source that reaches this wasm
113
+ is under the text shipped here.
114
+ - **Emscripten** — MIT / University of Illinois NCSA, dual (`LICENSE-emscripten`). This covers the
115
+ ~8 KB of JS glue and the emscripten-supplied runtime pieces linked into the wasm.
@@ -0,0 +1,29 @@
1
+ // TYPES FOR `hb-gpu.mjs`, HAND-WRITTEN AND NOT VENDORED. Everything else in this directory came out
2
+ // of emscripten or out of an upstream tarball; this file is ours, and it exists because `.mjs` next
3
+ // to a `.wasm` has no type surface of its own and TypeScript will not import it without one.
4
+ //
5
+ // DELIBERATELY THIN. The factory's resolved value is the emscripten `Module` object, whose real
6
+ // shape — the `_hb_*` exports, `HEAPU8`, `HEAP32`, `UTF8ToString` — is already stated once, as
7
+ // `HbGpuWasmExports` in `../src/index.ts`, and restating it here would be a second copy free to
8
+ // drift from the `-sEXPORTED_FUNCTIONS` list that actually determines it. `createHbGpu` is the
9
+ // supported way in, and it takes the factory and narrows the result.
10
+ //
11
+ // `wasmBinary` IS THE ONLY OPTION, because `-sINCOMING_MODULE_JS_API=wasmBinary` is the only key
12
+ // the glue was compiled to pick up. And the caller SHOULD pass it: this module was built with
13
+ // `-sENVIRONMENT=web,worker`, so the node branch is compiled out and a node import that lets the
14
+ // glue go looking for `hb-gpu.wasm` itself aborts with "both async and sync fetching of the wasm
15
+ // failed".
16
+
17
+ export interface HbGpuModuleOptions {
18
+ /** The bytes of `hb-gpu.wasm`. Without it the glue fetches the file relative to its own
19
+ * `import.meta.url`, which only works in a browser served over HTTP. */
20
+ wasmBinary?: ArrayBuffer | ArrayBufferView;
21
+ }
22
+
23
+ /** Emscripten's MODULARIZE=1 / EXPORT_ES6=1 factory. Resolves to the `Module` object; pass it to
24
+ * `createHbGpu` from the package root, which is what gives it a checked type. */
25
+ declare const createHbGpuModule: (
26
+ options?: HbGpuModuleOptions,
27
+ ) => Promise<unknown>;
28
+
29
+ export default createHbGpuModule;
@@ -0,0 +1,2 @@
1
+ async function Module(moduleArg={}){var Module=moduleArg;var ENVIRONMENT_IS_WEB=!!globalThis.window;var ENVIRONMENT_IS_WORKER=!!globalThis.WorkerGlobalScope;var ENVIRONMENT_IS_NODE=globalThis.process?.versions?.node&&globalThis.process?.type!="renderer";var quit_=(status,toThrow)=>{throw toThrow};var _scriptName=import.meta.url;var scriptDirectory="";function locateFile(path){return scriptDirectory+path}var readAsync,readBinary;if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){try{scriptDirectory=new URL(".",_scriptName).href}catch{}{if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=async url=>{var response=await fetch(url,{credentials:"same-origin"});if(response.ok){return response.arrayBuffer()}throw new Error(response.status+" : "+response.url)}}}else{}var out=console.log.bind(console);var err=console.error.bind(console);var wasmBinary;var ABORT=false;var EXITSTATUS;class EmscriptenEH{}class EmscriptenSjLj extends EmscriptenEH{}var runtimeInitialized=false;function getMemoryBuffer(){return wasmMemory.buffer}function updateMemoryViews(){if(HEAP8?.buffer?.resizable)return;var b=getMemoryBuffer();HEAP8=new Int8Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b)}function preRun(){}function initRuntime(){runtimeInitialized=true;wasmExports["g"]()}function postRun(){}function abort(what){what=`Aborted(${what})`;err(what);ABORT=true;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);throw e}var wasmBinaryFile;function findWasmBinary(){if(Module["locateFile"]){return locateFile("hb-gpu.wasm")}return new URL("hb-gpu.wasm",import.meta.url).href}function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}async function getWasmBinary(binaryFile){if(!wasmBinary){try{var response=await readAsync(binaryFile);return new Uint8Array(response)}catch{}}return getBinarySync(binaryFile)}async function instantiateArrayBuffer(binaryFile,imports){try{var binary=await getWasmBinary(binaryFile);var instance=await WebAssembly.instantiate(binary,imports);return instance}catch(reason){err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)}}async function instantiateAsync(binary,binaryFile,imports){if(!binary){try{var response=fetch(binaryFile,{credentials:"same-origin"});var instantiationResult=await WebAssembly.instantiateStreaming(response,imports);return instantiationResult}catch(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation")}}return instantiateArrayBuffer(binaryFile,imports)}function getWasmImports(){var imports={a:wasmImports};return imports}async function createWasm(){function receiveInstance(instance){wasmExports=instance.exports;assignWasmExports(wasmExports);updateMemoryViews();return wasmExports}function receiveInstantiationResult(result){return receiveInstance(result["instance"])}var info=getWasmImports();wasmBinaryFile??=findWasmBinary();var result=await instantiateAsync(wasmBinary,wasmBinaryFile,info);var exports=receiveInstantiationResult(result);return exports}class ExitStatus{name="ExitStatus";constructor(status){this.message=`Program terminated with exit(${status})`;this.status=status}}var HEAP8;var __abort_js=()=>abort("");var runtimeKeepaliveCounter=0;var __emscripten_runtime_keepalive_clear=()=>{runtimeKeepaliveCounter=0};var timers={};var handleException=e=>{if(e instanceof ExitStatus||e=="unwind"){return EXITSTATUS}quit_(1,e)};var keepRuntimeAlive=()=>true;var _proc_exit=code=>{EXITSTATUS=code;if(!keepRuntimeAlive()){ABORT=true}quit_(code,new ExitStatus(code))};var exitJS=(status,implicit)=>{EXITSTATUS=status;_proc_exit(status)};var _exit=exitJS;var maybeExit=()=>{if(!keepRuntimeAlive()){try{_exit(EXITSTATUS)}catch(e){handleException(e)}}};var callUserCallback=func=>{if(ABORT){return}try{return func()}catch(e){handleException(e)}finally{maybeExit()}};var _emscripten_get_now=()=>performance.now();var __setitimer_js=(which,timeout_ms)=>{if(timers[which]){clearTimeout(timers[which].id);delete timers[which]}if(!timeout_ms)return 0;var id=setTimeout(()=>{delete timers[which];callUserCallback(()=>__emscripten_timeout(which,_emscripten_get_now()))},timeout_ms);timers[which]={id,timeout_ms};return 0};var getHeapMax=()=>2147483648;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;var growMemory=size=>{var oldHeapSize=wasmMemory.buffer.byteLength;var pages=(size-oldHeapSize+65535)/65536|0;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};var HEAPU8;var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignMemory(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};var UTF8Decoder=new TextDecoder;var findStringEnd=(heapOrArray,idx,maxBytesToRead,ignoreNul)=>{var maxIdx=idx+maxBytesToRead;if(ignoreNul)return maxIdx;while(heapOrArray[idx]&&!(idx>=maxIdx))++idx;return idx};var UTF8ToString=(ptr,maxBytesToRead,ignoreNul)=>{if(!ptr)return"";var end=findStringEnd(HEAPU8,ptr,maxBytesToRead,ignoreNul);return UTF8Decoder.decode(HEAPU8.subarray(ptr,end))};var HEAP32;var HEAPU16;{if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"]}Module["UTF8ToString"]=UTF8ToString;var _hb_blob_create,_hb_blob_destroy,_hb_blob_get_length,_hb_blob_get_data,_hb_buffer_create,_hb_buffer_destroy,_hb_buffer_set_direction,_hb_buffer_set_script,_hb_buffer_set_language,_hb_buffer_clear_contents,_hb_buffer_allocation_successful,_hb_buffer_get_length,_hb_buffer_get_glyph_infos,_hb_buffer_get_glyph_positions,_hb_buffer_guess_segment_properties,_hb_buffer_add_utf16,_hb_language_from_string,_hb_script_from_string,_hb_feature_from_string,_malloc,_free,_hb_face_create,_hb_face_destroy,_hb_face_get_upem,_hb_font_get_nominal_glyph,_hb_font_create,_hb_font_destroy,_hb_font_set_scale,_hb_shape,_hb_gpu_draw_encode,_hb_gpu_draw_create_or_fail,_hb_gpu_draw_destroy,_hb_gpu_draw_set_scale,_hb_gpu_draw_glyph_or_fail,_hb_gpu_draw_clear,_hb_gpu_draw_reset,_hb_gpu_draw_recycle_blob,_hb_gpu_draw_shader_source,_hb_gpu_shader_source,__emscripten_timeout,memory,__indirect_function_table,wasmMemory;function assignWasmExports(wasmExports){_hb_blob_create=Module["_hb_blob_create"]=wasmExports["h"];_hb_blob_destroy=Module["_hb_blob_destroy"]=wasmExports["i"];_hb_blob_get_length=Module["_hb_blob_get_length"]=wasmExports["j"];_hb_blob_get_data=Module["_hb_blob_get_data"]=wasmExports["k"];_hb_buffer_create=Module["_hb_buffer_create"]=wasmExports["l"];_hb_buffer_destroy=Module["_hb_buffer_destroy"]=wasmExports["m"];_hb_buffer_set_direction=Module["_hb_buffer_set_direction"]=wasmExports["n"];_hb_buffer_set_script=Module["_hb_buffer_set_script"]=wasmExports["o"];_hb_buffer_set_language=Module["_hb_buffer_set_language"]=wasmExports["p"];_hb_buffer_clear_contents=Module["_hb_buffer_clear_contents"]=wasmExports["q"];_hb_buffer_allocation_successful=Module["_hb_buffer_allocation_successful"]=wasmExports["r"];_hb_buffer_get_length=Module["_hb_buffer_get_length"]=wasmExports["s"];_hb_buffer_get_glyph_infos=Module["_hb_buffer_get_glyph_infos"]=wasmExports["t"];_hb_buffer_get_glyph_positions=Module["_hb_buffer_get_glyph_positions"]=wasmExports["u"];_hb_buffer_guess_segment_properties=Module["_hb_buffer_guess_segment_properties"]=wasmExports["v"];_hb_buffer_add_utf16=Module["_hb_buffer_add_utf16"]=wasmExports["w"];_hb_language_from_string=Module["_hb_language_from_string"]=wasmExports["x"];_hb_script_from_string=Module["_hb_script_from_string"]=wasmExports["y"];_hb_feature_from_string=Module["_hb_feature_from_string"]=wasmExports["z"];_malloc=Module["_malloc"]=wasmExports["A"];_free=Module["_free"]=wasmExports["B"];_hb_face_create=Module["_hb_face_create"]=wasmExports["C"];_hb_face_destroy=Module["_hb_face_destroy"]=wasmExports["D"];_hb_face_get_upem=Module["_hb_face_get_upem"]=wasmExports["E"];_hb_font_get_nominal_glyph=Module["_hb_font_get_nominal_glyph"]=wasmExports["F"];_hb_font_create=Module["_hb_font_create"]=wasmExports["G"];_hb_font_destroy=Module["_hb_font_destroy"]=wasmExports["H"];_hb_font_set_scale=Module["_hb_font_set_scale"]=wasmExports["I"];_hb_shape=Module["_hb_shape"]=wasmExports["J"];_hb_gpu_draw_encode=Module["_hb_gpu_draw_encode"]=wasmExports["K"];_hb_gpu_draw_create_or_fail=Module["_hb_gpu_draw_create_or_fail"]=wasmExports["L"];_hb_gpu_draw_destroy=Module["_hb_gpu_draw_destroy"]=wasmExports["M"];_hb_gpu_draw_set_scale=Module["_hb_gpu_draw_set_scale"]=wasmExports["N"];_hb_gpu_draw_glyph_or_fail=Module["_hb_gpu_draw_glyph_or_fail"]=wasmExports["O"];_hb_gpu_draw_clear=Module["_hb_gpu_draw_clear"]=wasmExports["P"];_hb_gpu_draw_reset=Module["_hb_gpu_draw_reset"]=wasmExports["Q"];_hb_gpu_draw_recycle_blob=Module["_hb_gpu_draw_recycle_blob"]=wasmExports["R"];_hb_gpu_draw_shader_source=Module["_hb_gpu_draw_shader_source"]=wasmExports["S"];_hb_gpu_shader_source=Module["_hb_gpu_shader_source"]=wasmExports["T"];__emscripten_timeout=wasmExports["U"];memory=wasmMemory=wasmExports["f"];__indirect_function_table=wasmExports["__indirect_function_table"]}var wasmImports={e:__abort_js,d:__emscripten_runtime_keepalive_clear,a:__setitimer_js,b:_emscripten_resize_heap,c:_proc_exit};async function run(){preRun();if(ABORT)return;initRuntime();postRun()}var wasmExports;wasmExports=await createWasm();await run();
2
+ ;return Module}export default Module;
Binary file