Run PDF processing entirely client-side. No server round-trip, no file upload, no privacy risk.
// src/lib.rs
use wasm_bindgen::prelude::*;
use pdfluent::PdfDocument;
#[wasm_bindgen]
pub fn page_count(bytes: &[u8]) -> Result<u32, JsValue> {
let doc = PdfDocument::from_bytes(bytes)
.map_err(|e| JsValue::from_str(&e.to_string()))?;
Ok(doc.page_count())
}
#[wasm_bindgen]
pub fn extract_text(bytes: &[u8]) -> Result<String, JsValue> {
let doc = PdfDocument::from_bytes(bytes)
.map_err(|e| JsValue::from_str(&e.to_string()))?;
doc.extract_text()
.map_err(|e| JsValue::from_str(&e.to_string()))
}PDFluent has a wasm feature that disables std-only dependencies. wasm-bindgen generates the JS glue code.
# Cargo.toml
[package]
name = "pdf-wasm"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
pdfluent = { version = "0.9", features = ["wasm"] }
wasm-bindgen = "0.2"Mark each function with #[wasm_bindgen]. Functions that receive PDF bytes take &[u8]. Return JsValue errors so JavaScript can catch them.
use wasm_bindgen::prelude::*;
use pdfluent::PdfDocument;
#[wasm_bindgen]
pub fn page_count(bytes: &[u8]) -> Result<u32, JsValue> {
let doc = PdfDocument::from_bytes(bytes)
.map_err(|e| JsValue::from_str(&e.to_string()))?;
Ok(doc.page_count())
}Install wasm-pack and run the build command. This produces a pkg/ directory with .wasm and JS binding files.
# Install wasm-pack
cargo install wasm-pack
# Build for the browser (ES module output)
wasm-pack build --target web --out-dir pkg
# Or build for Node.js
wasm-pack build --target nodejs --out-dir pkgLoad the WASM module asynchronously, then pass ArrayBuffer data from a file input or fetch.
import init, { page_count, extract_text } from './pkg/pdf_wasm.js';
await init();
const input = document.getElementById('file-input');
input.addEventListener('change', async (e) => {
const file = e.target.files[0];
const buffer = await file.arrayBuffer();
const bytes = new Uint8Array(buffer);
const pages = page_count(bytes);
const text = extract_text(bytes);
console.log(`Pages: ${pages}`);
console.log(`Text: ${text.slice(0, 200)}`);
});Browsers require .wasm files to be served with Content-Type: application/wasm. Most dev servers handle this automatically. In production, verify your CDN or server config.
# Vite, webpack, and Parcel all handle WASM automatically.
# For a plain nginx config:
# types {
# application/wasm wasm;
# }
# Quick local test with wasm-pack's built-in server:
npx serve .No JVM, no runtime, no DLL dependencies. Ships as a single native binary or WASM module.
Rust's ownership model prevents buffer overflows and use-after-free. No segfaults in PDF parsing.
Same code runs server-side, in Docker, on AWS Lambda, on Cloudflare Workers, or in the browser via WASM.