Differences And Restrictions
Differences fall into two groups: Wasm platform bounds that will not change, and Wasm gaps that could be closed. Gaps are tracked in #971.
Wasm bounds (by design)
WebAssembly intentionally excludes Node/V8 facilities. Public APIs do not
accept filesystem paths, Node streams, Node callbacks, InputFile, OutputFile,
plugin loaders, or synchronous CommonJS loading. Emscripten filesystem support,
where used internally, is not a public storage API.
Wasm recryptAsync() returns a promise like native's. Native recrypts on a
libuv pool thread; Wasm has no thread pool, so it recrypts in a worker it starts
itself: worker_threads in Node, Deno, and Bun, and a module Worker in
browsers. Inside a worker, where no worker can start, with module options a
worker cannot receive, or with the Wasm-only module option recryptWorker:
false, it recrypts on the calling thread, as fast and as blocking as
recrypt(). It also rejects for an unsupported source or option, where native
recryptAsync() throws synchronously for wrong arguments, as every Wasm
*Async method rejects. See
Change PDF Passwords.
Recipe.thirdPartyLicenses() is Wasm-only: it reads the third-party notices
embedded in a .wasm (see License Notices). The native
packages ship their notices as THIRD_PARTY_NOTICES.md and have no such
function.
Wasm-only Recipe.dispose() and Recipe.disposeAssets() release Emscripten
allocations that JavaScript garbage collection cannot reclaim. Native objects
use normal native lifetime management. See the Recipe topic
guides for examples that follow these boundaries.
split() returns named byte arrays rather than writing an output directory, and
structure("json") returns an in-memory summary rather than writing a
diagnostic file.
The Recipe HTML subset is DOM-free and Worker-safe. It supports URL links
through <a href> and visual nested lists through ul, ol, and li, but
does not provide arbitrary DOM, general CSS inheritance, semantic tagged-PDF
lists, or plugin HTML handlers. Wasm htmlToTextObjects() returns flat visual
runs with list prefixes and indent values, while native returns its nested
XML-derived layout tree.
Wasm gaps (tracked)
Existing byte-backed PDFs can be read, modified, and copied, but persistent-file continuation, password-protected Recipe source editing, and the path-based Recipe constructor are unavailable.
Appending or rebuilding an existing source page does not deep-copy that page's
/Annots graph, although annotations created in the output are written
normally.
Recipe bundles Roboto Regular as its default rather than native Recipe's Helvetica family. Bold and italic fall back to regular unless their byte fonts are registered, and the different metrics can change wrapping. The low-level writer does not load the bundled font.
Table header styling follows native precedence and is independent of body
text styles, except direction, which a header without its own takes from its
column, then the table. See Create Multi-Page Tables for header
overrides; font metrics remain the source of typography differences.
Wasm Recipe does not support native chroma("!load", path) color-file loading.
Register named colors individually, including Separation colors.
The Recipe HTML parser is more forgiving than the XML-strict native parser: an
omitted </li> ends that item at its next sibling or at the end of its list
rather than throwing.
Native Recipe HTML sizes h1–h3 and small text, which Wasm does not
recognize, and Wasm breaks lines around div, which native treats as inline.
Wasm loads bidi-js, which the direction option reorders right-to-left text
with, when createMuhammaraWasm() or createRecipe() runs, unless given
bidi: false; the Wasm-only loadBidi() loads it later. Without it, calls
that have to order text by direction throw. Native always has it. See
Browser Setup for loading it with or without a bundler.