Files
pdf/wasm
Furqan-14andClaude Opus 4.8 ddbafbcd43 fix(build): build_wasm.ps1 builds the real wasm-pdfium engine + deploys pdfium-engine.{mjs,wasm}
build_wasm.ps1 built the `wasm` STUB preset (PDFENGINE_WITH_PDFIUM=OFF, reflow #ifdef'd out)
and deployed it to the UNUSED pdfengine.{mjs,wasm}. The frontend actually loads
pdfium-engine.{mjs,wasm} (the `wasm-pdfium` preset). So "rebuild the WASM" silently shipped a
reflow-less stub to the wrong file — engine changes never reached the browser.

- Build the `wasm-pdfium` preset (PDFium ON) into pdfeng-build/wasm-pdfium (outside OneDrive).
- Deploy bin/pdfengine.{mjs,wasm} -> public/pdfium-engine.{mjs,wasm} (the rename the frontend expects).
- Set EMSDK_QUIET before sourcing emsdk_env.ps1 (its stderr tripped $ErrorActionPreference='Stop').
- Run a new pdfium-engine.smoke.mjs (buildInfo + live-preview exports) instead of pdfengine.test.mjs,
  which targets the stub facade (mock render / engineHasSkia / getDocumentFonts) and can't pass here.
- Relax EAP around the smoke node call so a real failure reports cleanly, not as NativeCommandError.
- pdfengine.test.mjs: de-brittle the buildInfo assertion (was pinned to a stale exact phrase).

Verified: scripts/build_wasm.ps1 runs green end-to-end and deploys pdfium-engine.wasm (md5 53d1ec9f).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 19:24:45 +05:30
..

WASM (Emscripten)

Phase 0 task "WASM hello-world build (Emscripten)" lives here. The full engine-in-WASM build is Phase 2 work — see Phase 2 below.

Rule R5: WASM never blocks shipping. Server-side rendering is always the fallback. This Phase 0 deliverable is validation only — proves that the Emscripten toolchain end-to-end compiles a C++ function callable from JS. Do not gate Phase 1 on it.

What's here today

wasm/
  emsdk.pinned          # pinned Emscripten SDK version
  CMakeLists.txt        # builds the hello-world Emscripten target
  hello.cpp             # exports add() and hello_version() via EMSCRIPTEN_KEEPALIVE
  hello.test.mjs        # Node 20+ smoke test — loads the module, asserts return values
  package.json          # `npm test` shortcut for hello.test.mjs

Build output goes to out/build/wasm/bin/hello.{mjs,wasm}. The .mjs is an ES6 module that imports cleanly into Node 20+ and modern browsers.

Prerequisites

  1. Emscripten SDK at the version pinned in emsdk.pinned.

    git clone https://github.com/emscripten-core/emsdk
    cd emsdk
    ./emsdk install 3.1.74     # match emsdk.pinned
    ./emsdk activate 3.1.74
    source ./emsdk_env.sh      # Linux/macOS
    .\emsdk_env.bat            # Windows cmd.exe
    .\emsdk_env.ps1            # Windows PowerShell
    

    After emsdk_env runs in your shell, $EMSDK is set — that's what cmake/toolchains/wasm.cmake keys off. On Windows, install emsdk on a space-free path outside OneDrive (same rule as the PDFium build — see docs/phase0.md).

  2. Node 20+ for the smoke test (CI uses Node 20).

  3. CMake 3.25+ and Ninja — already required for the rest of the repo.

Build + run

cmake --preset wasm
cmake --build --preset wasm

node wasm/hello.test.mjs
# → [wasm-smoke] OK — add(2,3)=5, hello_version()=1, cwrap add(40,2)=42

CI does the same three commands on Ubuntu after installing the pinned emsdk.

Local build dir override (Windows / OneDrive)

The default binaryDir lives under the repo. On Windows the OneDrive + spaced-path constraint applies — drop a CMakeUserPresets.json next to this README's parent that overrides binaryDir to e.g. C:/Users/<you>/pdfeng-build/wasm, and set HELLO_MJS=C:/Users/<you>/pdfeng-build/wasm/bin/hello.mjs when running the smoke test.

What this proves (and what it does NOT)

Validated by Phase 0 hello-world Still TODO in Phase 2
emsdk install + version pin PDFium cross-compiled with Emscripten
cmake --preset wasm configures cleanly Skia / FreeType / HarfBuzz cross-compiled
C → JS export via ccall/cwrap Engine cross-compiled
ES6-module output usable from Node + web Web Worker + OffscreenCanvas integration
CI pipeline for the WASM artifact Page-render parity with the server path

Phase 2 — real engine-in-WASM

When that work begins, do not delete the hello-world target — keep it as a toolchain canary so a broken emsdk install fails fast and obviously. The real target will live alongside it (e.g. pdfengine_wasm) and consume the engine library via the standard CMake import path once the engine has Emscripten support in its vcpkg/build story.

Key flags to carry forward (engine blueprint §6): ALLOW_MEMORY_GROWTH=1, Web Worker + OffscreenCanvas for off-main-thread rendering, ES6 module output.