npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@pdfluent/sdk-wasm

v1.0.0

Published

PDFluent browser SDK — find and replace PDF text programmatically while preserving fonts and layout, plus edit, annotate, redact, sign and validate PDFs (including XFA), entirely client-side via WASM.

Readme

PDFluent browser SDK (WASM)

Source on GitHub · Documentation · Report an issue

WebAssembly distribution of the PDFluent PDF engine. Read, edit, annotate, redact, sign, and validate PDFs (including XFA) entirely in the browser — zero bytes go to a server.

Published as @pdfluent/sdk-wasm on npm. (The crate directory is named xfa-wasm for historical XFA roots — the published npm package covers the full SDK surface needed for an in-browser PDF editor. Previously known as @pdfluent/xfa-wasm; renamed to @pdfluent/sdk-wasm.)

Features

  • XFA Forms: Parse, calculate, import/export XFA form data
  • PDF Analysis: Metadata, signatures, PDF/A compliance validation
  • Page Rendering (feature render): Render pages to RGBA pixels or Canvas2D
  • Page Manipulation: delete, rotate, reorder, extract, split via re-extract, merge
  • Forms write-back: set AcroForm field values, save modified PDF bytes
  • Annotations (feature annotate): highlight, sticky note, free text
  • Text watermark: diagonal text watermark with configurable opacity
  • Redaction: by region (rectangle) or by search query (permanent removal: the content is gone, not covered)
  • Stream compression: re-deflate content streams for smaller file size
  • PdfDocMut: stateful editing handle — open once, mutate in place, save once. One parse and one serialise for a whole editing session instead of one per operation, with identical output bytes.

Building

# Install wasm-pack
cargo install wasm-pack

# Build the WASM package
wasm-pack build crates/xfa-wasm --target web

# Without rendering (smaller bundle)
wasm-pack build crates/xfa-wasm --target web -- --no-default-features

Quick Start

XFA Forms

import init, { XfaEngine } from './pkg/xfa_wasm';

await init();

const engine = XfaEngine.fromFields(JSON.stringify([
  { name: "Name", value: "Alice" },
  { name: "Total", value: "", calculate: "100 + 21" },
]));

engine.runCalculations();
console.log(engine.getFieldValue("form1.Total")); // "121"

const json = engine.exportJson();
engine.importJson('{"fields": {"form1.Name": "Bob"}}');

PDF Analysis

import init, { PdfDoc } from './pkg/xfa_wasm';

await init();

const response = await fetch('document.pdf');
const data = new Uint8Array(await response.arrayBuffer());
const doc = PdfDoc.open(data);

console.log(`Pages: ${doc.pageCount()}`);

// Metadata
const meta = JSON.parse(doc.metadata());
console.log(`Title: ${meta.title}`);

// Signatures
if (doc.hasSignatures()) {
  const sigs = JSON.parse(doc.verifySignatures());
  for (const sig of sigs) {
    console.log(`${sig.signer}: integrity ${sig.structural_integrity}`);
  }
}

// PDF/A validation
const report = JSON.parse(doc.validatePdfA("pdfa2b"));
console.log(`Compliant: ${report.compliant}`);

Page Rendering

const raw = doc.renderPage(0, 1.5); // scale factor
const view = new DataView(raw.buffer);
const w = view.getUint32(0, true);  // little-endian
const h = view.getUint32(4, true);
const pixels = raw.slice(8);
const imageData = new ImageData(new Uint8ClampedArray(pixels), w, h);
ctx.putImageData(imageData, 0, 0);

Annotations

// Read existing annotations
const annots = JSON.parse(doc.getAnnotations(0));
for (const a of annots) {
  console.log(`${a.subtype} at (${a.rect?.x0}, ${a.rect?.y0})`);
}

// Add a highlight (returns new PDF bytes)
const newPdf = PdfDoc.addHighlight(pdfBytes, 0,
  100, 700, 400, 720,   // rect: x0, y0, x1, y1
  1.0, 1.0, 0.0);       // color: yellow RGB

// Add a sticky note
const withNote = PdfDoc.addStickyNote(pdfBytes, 0,
  50, 750, "Review this section");

// Add free text
const withText = PdfDoc.addFreeText(pdfBytes, 0,
  100, 600, 300, 620, "Important!", 12.0);

TypeScript Support

Typed wrappers are provided in ts/index.ts:

import { XfaForms, PdfDocument, FieldDef } from './ts/index';

const fields: FieldDef[] = [
  { name: "Amount", value: "100" },
];
const forms = XfaForms.fromFields(fields);
forms.runCalculations();
const data = forms.exportJson();

Features

| Feature | Default | Description | |---------|---------|-------------| | render | Yes | Page rendering via pdf-render | | annotate | Yes | Annotation read/write via pdf-annot + lopdf |

Build without optional features for a smaller WASM binary:

wasm-pack build crates/xfa-wasm --target web -- --no-default-features

API Reference

XfaEngine

| Method | Description | |--------|-------------| | XfaEngine.fromFields(json) | Create from JSON field definitions | | XfaEngine.fromJson(json) | Create from exported JSON | | runCalculations() | Execute FormCalc calculate scripts | | exportJson() | Export field values as JSON | | exportSchema() | Export form schema as JSON | | importJson(json) | Import field values from JSON | | getFieldValue(path) | Get field value by SOM path | | setFieldValue(path, value) | Set field value by SOM path | | nodeCount() | Number of form nodes | | version() | Engine version string |

PdfDoc

| Method | Description | |--------|-------------| | PdfDoc.open(data) | Open PDF from Uint8Array | | pageCount() | Number of pages | | pageWidth(index) | Page width in points | | pageHeight(index) | Page height in points | | metadata() | Document metadata as JSON | | signatures() | Signature info as JSON array | | hasSignatures() | Whether document has signatures | | verifySignatures() | Verify signatures, returns JSON | | validatePdfA(level) | PDF/A compliance check | | dssInfo() | Document Security Store info | | renderPage(index, scale) | Render page to RGBA (feature: render) | | renderThumbnail(index, maxDim) | Render thumbnail (feature: render) | | renderPageToCanvas(canvas, index, scale) | Render page directly to a Canvas2D (feature: render) | | getAnnotations(index) | Read annotations as JSON (feature: annotate) | | getTextPositions(index) | Per-glyph text positions as JSON | | merge(other) | Append another PDF, returns merged bytes | | flattenXfa() | Flatten XFA form fields into static PDF content | | convertToPdfa(level) | Convert to PDF/A 1b/2b/3b, returns bytes |

Page manipulation

| Method | Description | |--------|-------------| | deletePages(pages: Uint32Array) | Remove the listed 0-based pages; returns new bytes | | rotatePage(pageIndex, degrees) | Rotate one page by 90/180/270 (or negative); returns new bytes | | reorderPages(newOrder: Uint32Array) | Permute pages; returns new bytes | | extractPages(pages: Uint32Array) | Extract the listed pages into a new PDF (use for split) |

Edit, annotate, redact, optimise

| Method | Description | |--------|-------------| | setFormField(path, value) | Fill a single AcroForm text field; updates /V, /AS, and /AP; returns new bytes. For checkbox/radio/choice use PdfDocMut. | | setFormFields(jsonObject) | Bulk-fill text fields from a JSON {path: value} map; returns new bytes | | addHighlight(pageIndex, x, y, w, h, colorHex?) | Highlight annotation (feature: annotate) | | addStickyNote(pageIndex, x, y, contents) | Sticky note annotation (feature: annotate) | | addFreeText(pageIndex, x, y, w, h, contents) | Free-text annotation (feature: annotate) | | addTextWatermark(text, opacity) | Diagonal text watermark on all pages | | redactRegion(pageIndex, x, y, w, h) | Permanently remove content in a rectangle | | redactSearch(query) | Find all literal matches of query and redact each | | compress() | Re-deflate content streams; returns optimised bytes |

PdfDocMut — recommended for editor workflows

PdfDocMut is a stateful editing handle. Open once, mutate in place, save once. The PdfDoc class above is great for inspection and single-shot transformations, but for an editor that applies multiple operations to the same document, PdfDocMut does the work once: a session of many steps is one parse and one serialise instead of a parse and a serialise per step.

| Method | Description | |--------|-------------| | PdfDocMut.open(bytes) | Open for editing | | pageCount() | Live page count after any mutations so far | | save() | Serialise current state to Uint8Array. Non-consuming — keep editing after | | free() | Release the WASM-side memory deterministically | | deletePages(pages) | In place | | rotatePage(pageIndex, degrees) | In place | | reorderPages(newOrder) | In place | | extractPages(pages) | Returns bytes for a NEW subdocument; current editor unchanged | | setFormField(path, value) | In place — text, checkbox, radio, and choice; updates /V, /AS, and /AP | | setFormFields(jsonObject) | In place, bulk — same full chain for each entry | | addHighlight(pageIndex, x, y, w, h, colorHex?) | In place (feature: annotate) | | addStickyNote(pageIndex, x, y, contents) | In place (feature: annotate) | | addFreeText(pageIndex, x, y, w, h, contents) | In place (feature: annotate) | | addTextWatermark(text, opacity) | In place | | redactRegion(pageIndex, x, y, w, h) | In place (permanent removal: the content is gone, not covered) | | redactSearch(query) | In place | | compress() | In place |

Editor flow with PdfDocMut

import init, { PdfDocMut } from '@pdfluent/sdk-wasm';

await init();
const bytes  = new Uint8Array(await (await fetch('/document.pdf')).arrayBuffer());
const editor = PdfDocMut.open(bytes);

// All edits operate on the same internal lopdf::Document — no re-parse.
editor.reorderPages(new Uint32Array([1, 0, 2]));
editor.addTextWatermark('CONCEPT', 0.3);
editor.addHighlight(0, 100, 700, 200, 20, '#ffeb3b');
editor.redactSearch('John Doe');
editor.compress();

// Save once at the end.
const out = editor.save();
editor.free();
const blob = new Blob([out], { type: 'application/pdf' });

One-shot helpers on PdfDoc (kept for compatibility)

For a workflow with a single mutation, the stateless PdfDoc methods are equally fine. They are kept for backward compatibility but the recommended path for any multi-step edit is PdfDocMut.

import init, { PdfDoc } from '@pdfluent/sdk-wasm';
await init();
const doc = PdfDoc.open(bytes);
const merged = doc.merge(otherBytes);   // single op, one-shot

Licence

AGPL-3.0 or a commercial licence, at your option. The AGPL is the default and the complete product: there is no licence key, no activation call and no tier, and every feature works in every build. If you cannot accept the copyleft obligation, the commercial licence is sold yearly and self-service at pdfluent.com/sdk/pricing, in four options: Commercial (per organisation), OEM Startup and OEM (per product), and Priority support (an add-on). The two texts are LICENSE and LICENSE-COMMERCIAL in this package.

Links