feat(wasm): port Ghostty's wasm32 build
Ports main_wasm.zig as crates/ghostty-wasm (cdylib exporting the Zig names: atlas_*, face_*, deferred_face_*, shaper_*, config_*), the web_canvas font face and shaper (backend selected on wasm32), os/wasm (logging, target, JS interop) and config/Wasm.zig. C-library dependencies (freetype, harfbuzz, fontconfig, onig, gettext, D-Bus, rustix) are target-gated. Like Zig's wasm build, link regex search (StringMap::search_iterator) is unavailable without Oniguruma; Collection/SharedGrid/harfbuzz are compiled out on wasm32. ghostty-os switches from forbid to deny unsafe_code for the JS import blocks. The JS import ABI is currently a documented handle-based interface with a reference host (js/ghostty_host.js); aligning it with zig-js's exact import names follows separately. Test Plan: - cargo build --target wasm32-unknown-unknown -p ghostty-font \ -p ghostty-config -p ghostty-terminal -p ghostty-vt -p ghostty-wasm - cargo test -p ghostty-wasm (node runs the module against a fake canvas)
This commit is contained in:
1 parent
0915f7b935
commit
d89691b0ce
33 files changed
+2866
-30
No files matched your search
@@ -0,0 +1,86 @@
|
||||
# WASM port (W1)
|
||||
|
||||
Port of Ghostty's wasm-specific Zig. Everything is behind
|
||||
`cfg(target_arch = "wasm32")` (or target-gated Cargo deps); native behaviour is
|
||||
unchanged.
|
||||
|
||||
## File map
|
||||
| Zig | Rust |
|
||||
|---|---|
|
||||
| `main_wasm.zig` | `crates/ghostty-wasm` (new, `cdylib`; empty on native) |
|
||||
| `config/Wasm.zig` | `ghostty-wasm/src/config.rs` |
|
||||
| `os/wasm.zig`, `os/wasm/target.zig`, `os/wasm/log.zig` | `ghostty-os/src/wasm/{mod,target,log}.rs` |
|
||||
| zig-js (`js.*` used by Atlas/web_canvas) | `ghostty-os/src/wasm/js.rs` + host `ghostty-wasm/js/ghostty_host.js` |
|
||||
| `font/face/web_canvas.zig` | `ghostty-font/src/face/web_canvas.rs` (+ exports in `ghostty-wasm/src/face.rs`) |
|
||||
| `font/shaper/web_canvas.zig` | `ghostty-font/src/shaper/web_canvas.rs` (+ `ghostty-wasm/src/shaper.rs`) |
|
||||
| `font/DeferredFace.zig` (`wc` variant, `Wasm`) | `ghostty-font/src/deferred_face_web_canvas.rs` |
|
||||
| `font/Atlas.zig` `Wasm` | `ghostty-wasm/src/atlas.rs` |
|
||||
| `font/backend.zig` | `ghostty-font/src/backend.rs` (`OPTIONS`/`default_for_target` = `WebCanvas` on wasm32) |
|
||||
| `renderer/WebGL.zig` (empty stub), `App.zig` `Wasm` (commented out upstream) | nothing to export; `ghostty-wasm/src/app.rs` documents it |
|
||||
| `os/passwd.zig` (`@compileError` on wasm) | `ghostty-os/src/unsupported.rs` shims |
|
||||
|
||||
## Build gating
|
||||
- `ghostty-os`: nix/rustix/zbus/gettext target-gated; POSIX modules (`env`,
|
||||
`file`, `flatpak`, ...) are not compiled on wasm32. `unsupported.rs` gives
|
||||
API-compatible "unavailable" stand-ins (`xdg`, `passwd`, `expand_home`,
|
||||
`resources_dir`, `is_flatpak`) so `ghostty-config` compiles unchanged
|
||||
(Zig never analyzes them on wasm). `unsafe` is `deny` (not `forbid`) for the
|
||||
wasm import blocks only.
|
||||
- `ghostty-config`: only `std::os::unix` bits changed (file-type names,
|
||||
`OsStrExt`).
|
||||
- `ghostty-terminal`: `onig`, `ghostty-onig`, `rustix` target-gated. Like Zig
|
||||
with `oniguruma = false`, `StringMap::search_iterator`/`SearchIterator` do
|
||||
not exist on wasm (match types stay). Kitty shared-memory transmission
|
||||
returns `InvalidData` on wasm; `OsStrExt` shim for the file medium.
|
||||
- `ghostty-font`: freetype/harfbuzz/fontconfig `-sys` target-gated; on wasm the
|
||||
`collection`, `codepoint_resolver`, `shared_grid(_set)`, `library`,
|
||||
`discovery`, `fontconfig`, `embedded`, `face::freetype`, harfbuzz shaper
|
||||
and `shaper::{run,cache,noop}` modules are compiled out.
|
||||
- Builds: `cargo build --target wasm32-unknown-unknown -p ghostty-font -p ghostty-config -p ghostty-terminal -p ghostty-vt -p ghostty-wasm` passes (debug + release); clippy `-D warnings` clean on both targets.
|
||||
|
||||
## JS protocol (deviation)
|
||||
`zig-js` (mitchellh/zig-js) is **not vendored** in the Ghostty tree, so its
|
||||
import names could not be mirrored. `js.rs` documents a small handle-based
|
||||
ABI in import module `ghostty_js` (`global/get/set/call/new/string/number/
|
||||
boolean/to_f64/string_len/string_copy/is_nullish/bytes/copy_bytes/release`)
|
||||
plus `env.log(ptr,len)` (same as Zig's log). The Rust-side API mirrors
|
||||
`js.Object`. `ghostty-wasm/js/ghostty_host.js` is a reference host.
|
||||
Allocation helpers `wasm_alloc/wasm_free` (host-owned buffers) replace the
|
||||
stale `toHostOwned` (does not exist in current `os/wasm.zig`).
|
||||
|
||||
## Exports
|
||||
Same names as Zig: `atlas_{new,free,reserve,set,grow,clear,debug_canvas}`,
|
||||
`face_{new,free,render_glyph,debug_canvas}`, `deferred_face_{new,free,load}`,
|
||||
`shaper_{new,free,test}`, `config_{new,free,load_string,finalize}`.
|
||||
Differences/extras: `region_free`/`glyph_free` (Zig: "global free"),
|
||||
`atlas_data/atlas_size`, `face_metrics`, `face_glyph_index`,
|
||||
`shaper_{prepare,add_codepoint,shape}`, `config_format`,
|
||||
`config_diagnostics_count`, `ghostty_wasm_init`, `wasm_alloc/free`.
|
||||
`shaper_test` takes a `Face` (Zig takes a stale `GroupCache`) and prints into a
|
||||
`ghostty-terminal` Terminal.
|
||||
|
||||
## Deviations from the (stale) Zig web_canvas code
|
||||
- Zig refers to `Atlas.Format.rgba` and a 7-field `Metrics` that no longer
|
||||
exist; here Bgra atlases get the canvas RGBA bitmap with R/B swapped,
|
||||
grayscale gets alpha, and `Metrics::init()` + face size = cell size.
|
||||
- The `RunIterator` hook API is tied to the FreeType `Collection` (not built on
|
||||
wasm), so `Shaper` exposes `prepare/add_codepoint/finalize/shape` directly and
|
||||
takes the grapheme-index callback (`Face::grapheme_glyph_index`).
|
||||
- `Collection`/`SharedGrid` are not generic over the face type in this port, so
|
||||
they are unavailable on wasm (Zig's wasm exports never use them).
|
||||
- `render_glyph` drops the unused `RenderOptions` parameter.
|
||||
|
||||
## Tests
|
||||
- Native: pure parts of web_canvas (grapheme table, metrics math, presentation
|
||||
detection, glyph sizing, bitmap/atlas conversion; 9 tests) and the shaper
|
||||
clustering (6 tests) run natively; `backend::web_canvas_capabilities`.
|
||||
- `crates/ghostty-wasm/tests/node.rs` builds the module (separate target dir
|
||||
`target/wasm-test`) and runs `tests/node_test.js` under node with a fake
|
||||
canvas: config parse/finalize/format, atlas, face metrics/glyph_index/
|
||||
render, emoji presentation, shaper clustering, deferred face, debug canvas.
|
||||
Skipped if node or the wasm32 target is missing.
|
||||
- Not ported: Zig tests in Atlas.Wasm (`toHostOwned`, stale).
|
||||
|
||||
## Known gaps
|
||||
- `ghostty-os/tests/common/flatpak_mock.rs` (another agent's, untracked) fails
|
||||
`clippy --all-targets -D warnings` (needless_pass_by_value); unrelated.
|
||||
Reference in new issue
Block a user