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

@qrcommunication/gigapdf-lib

v0.121.0

Published

TypeScript SDK for gigapdf-lib — a Rust→WASM document engine with no third-party PDF/Office/image library: read/edit/render, OCR, full-text search, AcroForm fields (read · fill · create), annotations, signing, encryption, PDF↔Office/HTML/RTF conversions,

Readme

@qrcommunication/gigapdf-lib

TypeScript SDK for gigapdf-lib — a zero-dependency Rust→WASM PDF engine. Read, edit, render, search, sign (self-signed · PKCS#12 · PAdES-B-T timestamped · PAdES-LTV), encrypt, fill & build forms, annotate, redact, and convert PDFs ↔ Office/HTML/RTF/Markdown/CSV — plus a native HTML/CSS→PDF renderer with a built-in JavaScript engine, and a unified editable model that lowers/raises every format. The engine .wasm is self-contained: no third-party runtime dependencies. (OCR is a separate host-side native crate, gigapdf-ocr-rten.)

Install

pnpm add @qrcommunication/gigapdf-lib
# or: npm i @qrcommunication/gigapdf-lib

Quick start

import { GigaPdfEngine } from "@qrcommunication/gigapdf-lib";

// Node: loads the bundled gigapdf.wasm from the package.
const giga = await GigaPdfEngine.loadDefault();

// Browser / explicit control: pass a URL, Response, or bytes.
// const giga = await GigaPdfEngine.load("/gigapdf.wasm");

const doc = giga.open(pdfBytes); // Uint8Array

// Read
const lines = doc.structuredText(1); // [{ text, x, y, w, h }]
const hits = doc.search("invoice"); // [{ page, text, x, y, w, h }]
const text = doc.toText(); // whole-document plain text (exact, no OCR)

// Edit (operates on the real content stream — no cosmetic overlay)
doc.replaceText(1, 0, "New value");
doc.redact(1, 72, 700, 180, 14); // physically remove content in a region
doc.addHighlight(1, 72, 690, 252, 704, 0xffff00);

// Convert
const docx = doc.toDocx(); // also: toPptx/toOdp/toOdt/toXlsx/toOds/toHtml/toText/toRtf/toPdfA
const png = doc.renderPage(1, 2); // rasterize a page

// Save
const out = doc.save(); // or doc.saveCompressed()
doc.close();

Office / HTML / RTF → PDF

const fromDocx = giga.officeToPdf(officeBytes); // docx/xlsx/pptx/odt/ods/odp auto-detected
const fromHtml = giga.htmlToPdf("<h1>Hello</h1>");
const fromRtf = giga.rtfToPdf(rtfString);

Build an interactive form (no pdf-lib)

// Coordinates are PDF user space (origin bottom-left): [x0, y0, x1, y1].
doc.addTextField(1, "fullname", [50, 700, 300, 720], "", { maxLen: 60 });
doc.addCheckbox(1, "subscribe", [50, 670, 64, 684], true, { export: "Yes" });
doc.addRadioGroup(1, "plan", [
  { export: "Basic", rect: [50, 640, 64, 654] },
  { export: "Pro", rect: [80, 640, 94, 654] },
], { selected: "Pro" });
doc.addComboBox(1, "country", [50, 610, 200, 626], ["FR", "US", "DE"], { selected: "FR" });
doc.addListBox(1, "langs", [50, 540, 200, 600], ["en", "fr", "de"], { multi: true });

// Optional per-field styling.
doc.addTextField(1, "vat", [50, 510, 200, 528], "", {
  style: { fontSize: 11, color: 0x102030, border: 0x888888, background: 0xf5f5f5 },
});

const filled = doc.fields(); // read them straight back: kind + value + options

Fonts — three sources, no host font files required

// 1. Base-14 standard fonts — no embedding, no network.
doc.addStandardText(1, 72, 720, 24, "Heading", "Helvetica-Bold", 0x111111);
doc.addStandardText(1, 72, 690, 12, "body in Times", "Times-Roman");

// 2. Any family / Google Font — the host fetches, the engine embeds. embedFont
//    accepts any outline file: glyf .ttf OR OpenType-CFF .otf (auto-detected).
const url = giga.fontRequestUrl("Roboto", 400); // Google Fonts CSS2 URL
const css = await (await fetch(url, { headers: { "User-Agent": "Mozilla/4.0" } })).text();
const ttf = new Uint8Array(await (await fetch(giga.parseCssFontUrl(css))).arrayBuffer());
const fontObj = doc.embedFont("Roboto", ttf);
doc.addText(1, 72, 660, 18, "Selectable embedded text", fontObj, 0x111111);
// Font-aware editing: replace the run's text — re-encoded through Roboto's cmap.
doc.replaceText(1, doc.textRuns(1).length - 1, "Edited in the same font");

// 3. Reuse a face the PDF already embeds: list → extract → re-embed → draw.
const face = doc.embeddedFonts().find((f) => f.format === "truetype");
if (face) {
  const prog = doc.extractFont(face.baseFont)!;        // { format, bytes }
  const reused = doc.embedFont("Reused", prog.bytes);
  doc.addText(1, 72, 630, 14, "drawn in the document's own font", reused);
}

Recipes

Task-oriented snippets using the high-level classes. Each assumes const giga = await GigaPdfEngine.loadDefault() (Node) or GigaPdfEngine.load(url) (browser), Uint8Array in/out, and a final close().

Merge several PDFs

const merged = giga.mergePdfs([first, second, third]); // one PDF, pages in order

Or, for finer control, append page-by-page yourself:

const doc = giga.open(first);                 // the base document
for (const extra of [second, third]) doc.appendPages(extra); // append every page
const merged = doc.saveCompressed();
doc.close();

Image → PDF

const pdf = giga.imageToPdf(imageBytes); // PNG/JPEG/GIF/WebP/AVIF → one A4 page
// (image centred, shrink-to-fit, never upscaled; empty array if not an image)

Split — extract a page range into a new PDF

const doc = giga.open(pdfBytes);
const partA = doc.extractPages([1, 2, 3]);    // a NEW, self-contained PDF…
const partB = doc.extractPages([4, 5, 6]);    // …links / fields / dests to dropped pages pruned
doc.close();

Encrypt (AES-256) and re-open

// `fileId` is the document /ID (any stable string). AES-256 auto-generates the
// 32-byte key via Web Crypto; pass `opts.keySeed` to supply your own.
const locked = doc.saveEncrypted("user-pw", "doc-001", {
  ownerPassword: "owner-pw",
  algorithm: "aes256",           // "rc4" | "aes128" | "aes256" (default)
  // permissions: -44,           // PDF permission bitmask (optional)
});
doc.close();

const reopened = giga.openEncrypted(locked, "user-pw"); // null on a wrong password
reopened?.close();
// Inspect without opening:
const info = giga.encryptionInfo(locked); // { encrypted, permissions, version, revision }

Digital signature

// (a) Self-signed — an ephemeral digital ID. `random` ≥ 256 host-entropy bytes.
//     fields = "name\treason\tdate\tnotBefore\tnotAfter" (PDF date strings).
const fields = "Jane Doe\tApproved\tD:20260618120000Z\t260618000000Z\t360618000000Z";
const random = crypto.getRandomValues(new Uint8Array(256));
const signed = doc.sign(fields, random);

// (b) PKCS#12 — your CA / eIDAS certificate + RSA key (.p12/.pfx), imported
//     natively. Throws on a wrong password / malformed file.
const signed2 = doc.signP12(p12Bytes, "p12-password", {
  name: "Jane Doe",
  reason: "I approve this document",
  date: "D:20260618120000Z",
  location: "Paris",
});

// (c) PAdES-B-T — add an RFC 3161 trusted timestamp (async: one TSA round trip).
//     Identity is the p12 above, or a self-signed id (random + notBefore/notAfter).
const timestamped = await doc.signTimestamped({
  p12: p12Bytes, password: "p12-password", name: "Jane Doe",
  tsaUrl: "https://freetsa.org/tsr",
  // tsaFetch: custom fetch (auth / proxy / SSRF allow-list — recommended in prod)
});

// (d) PAdES-LTV — B-T plus a /DSS (chain + OCSP/CRL the host fetches), so the
//     signature validates long-term. archiveTimestamp adds a B-LTA doc timestamp.
const ltv = await doc.signLtv({
  p12: p12Bytes, password: "p12-password",
  tsaUrl: "https://freetsa.org/tsr", archiveTimestamp: true,
  // revocationFetch / crlFetch: override the OCSP/CRL HTTP (SSRF allow-list)
});
doc.close();

Timestamping and LTV need network the WASM core can't perform, so the engine emits the request bytes and the SDK does the HTTP. The OCSP/CRL/TSA URLs are host-supplied (LTV URLs come from the certificates) — in production pass tsaFetch / revocationFetch / crlFetch to apply your own SSRF allow-list.

Annotate, then flatten

doc.addHighlight(1, 72, 690, 252, 704, 0xffff00);
doc.addTextNote(1, 300, 700, 0xff0000, { contents: "Check this clause", author: "Reviewer" });
doc.addSquare(1, 60, 680, 264, 712, 0xff0000, null);   // stroke red, no fill
const all = doc.annotations(1);                         // read back, with full metadata
doc.flattenAnnotations(1);                              // bake into page content (non-interactive)
const out = doc.save();
doc.close();

HTML + CSS → PDF with auto-fetched Google Fonts

import type { HtmlFontRequest, HtmlFont } from "@qrcommunication/gigapdf-lib";

// Phase 1: ask the engine which fonts the HTML needs; the HOST fetches them
// (the wasm has no network). Phase 2: render with those fonts embedded.
async function fetchFonts(reqs: HtmlFontRequest[]): Promise<HtmlFont[]> {
  return Promise.all(reqs.map(async (r) => {
    const css = await (await fetch(r.url, {
      headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0)" }, // → TTF, not WOFF2
    })).text();
    const ttf = new Uint8Array(await (await fetch(giga.parseCssFontUrl(css))).arrayBuffer());
    return { family: r.family, weight: r.weight, italic: r.italic, ttf };
  }));
}

const html = `<body style="font-family: Roboto"><h1>Invoice</h1><p>Net 30.</p></body>`;
const fonts = await fetchFonts(giga.htmlNeededFonts(html));
const pdf = giga.htmlRender(html, fonts, 595, 842, 36);  // A4 in points, 36pt margin
// Named sizes, per-side margins and a header/footer with {{page}}/{{pages}} tokens:
// giga.htmlRenderWith(html, fonts, { pageSize: "A4", header, footer });

Make a scanned PDF searchable (host OCR → invisible text layer)

OCR itself runs host-side — it lives in the separate native crate gigapdf-ocr-rten (PaddleOCR PP-OCR on a pure-Rust runtime), whose model weights are far heavier than the lean WASM core. This SDK provides the text-layer side: addTextLayer stamps recognized words back onto the page so the scan becomes selectable & searchable. (For PDFs that already carry text, use toText/structuredText/search — exact, no OCR needed.)

const doc = giga.open(scannedPdf);
const scale = 2;                                  // rasterise at 2× = 144 dpi for OCR
for (let page = 1; page <= doc.pageCount(); page++) {
  const { height } = doc.pageInfo(page);
  const png = doc.renderPage(page, scale);        // raster the page for your OCR service…
  const words = await ocrService(png);            // … your host OCR → [{ x, y, w, h, text }]
                                                  //    boxes in raster pixels (top-left)
  // Map each word box back to PDF user space (bottom-left, Y up) and stamp an
  // invisible (render-mode 3) text layer — selectable & searchable, pixel-aligned.
  doc.addTextLayer(page, words.map((w) => ({
    x: w.x / scale,
    y: height - (w.y + w.h) / scale,
    size: w.h / scale,
    text: w.text,
  })));
}
const searchable = doc.save();
doc.close();

Metadata & bookmarks (outline)

doc.setMetadata("Title", "Quarterly report");
doc.setMetadata("Author", "Finance");
doc.setOutline([
  { title: "Summary", level: 0, page: 1 },
  { title: "Details", level: 0, page: 2 },
  { title: "Appendix", level: 1, page: 5 },
]);
const out = doc.save();
doc.close();

Next.js (output: "standalone")

loadDefault() reads gigapdf.wasm from the package directory. In standalone output, add it to the route's outputFileTracingIncludes so it is copied into the bundle:

// next.config.ts
outputFileTracingIncludes: {
  "/api/pdf/*": ["./node_modules/@qrcommunication/gigapdf-lib/gigapdf.wasm"],
}

Or call GigaPdfEngine.load(bytes) with bytes you read yourself.

API surface

Full, per-method reference: docs/SDK.md documents every method (parameters, return, notes) grouped by domain. Exact signatures and defaults also ship in the bundled .d.ts.

  • GigaPdfEngine — load/loadDefault, open/openEncrypted, txtToPdf/htmlToPdf/rtfToPdf/officeToPdf/imageToPdf (PNG/JPEG/GIF/WebP/AVIF → A4 page), mergePdfs (concatenate many PDFs), fontCatalog/fontRequestUrl/parseCssFontUrl.
  • GigaPdfDoc — text intelligence (textRuns, structuredText, pageBlocks, search, toText, elements, elementAt, documentLanguage), editing (replaceText, setTextRunStyle (per-character restyle), removeElement, moveElement, transformElement (full affine — move + resize
    • rotate in place), reorderElement (native z-order — bring to front / send to back), setPathStyle (in-place vector restyle: fill/stroke/width/dash + real opacity), setElementOpacity (constant opacity on any element — text/image/shape), duplicateElement, redact, redactPii (irreversible — erases image pixels)), vector drawing (addRectangle, drawLine, addEllipse, addPolygon, addPath (SVG path), addImage (PNG/JPEG, alpha + opacity), addImageWatermark (stamp an image across pages), addSvg (native vector)), pages (rotatePage, deletePage, movePage, appendPages, extractPages, resizePage, addPage, copyPage, pageInfo), renderPage (and renderPageNoText — a text-free background for editors that overlay real editable text; vectors/gradients/images/annotations still rendered — plus renderPageExcluding — a background omitting specific elements for live-overlay editing), fonts (base-14 addStandardText, embed any TrueType/OpenType via embedFont/addText, font-aware editing replaceText, the document's own faces embeddedFonts/extractFont, neededFonts), conversions (toText/Html/Docx/Pptx/Odp/Odt/Xlsx/Ods/Rtf/PdfA, toModel for the unified editable model, plus engine-level gridsToXlsx/gridsToOds to emit Office output from a host-built table grid), security (saveEncrypted (AES-256 R6) + permissionsToP/getPermissions; digital signatures: self-signed sign, PKCS#12 signP12, PAdES-B-T timestamped signTimestamped (RFC 3161 TSA), PAdES-LTV signLtv (DSS + OCSP/CRL, B-LT/B-LTA)), metadata (getMetadata, setMetadata), embedded files (attachments — extract every /EmbeddedFiles entry with its decoded bytes), annotations (addSquare, addHighlight, addLineAnnotation, addFreeText, addUnderline, addStrikeOut, addInk, addStamp, annotations, removeAnnotation, flattenAnnotations), links (links, addUriLink, addGotoLink, named destinations addNamedDest/namedDests/addGotoLinkNamed), outline (outline, setOutline), forms — read/fill (fields, setTextField, setCheckbox, setRadio, setChoice) and create (addTextField, addCheckbox, addRadioGroup, addComboBox, addListBox, each with an optional FieldStyle) plus flattenForm / flattenFormXObjects, margins & running header/footer (pageMargins/setPageMargins, setHeader/setFooter/headerFooter), and optional-content layers (layers, addLayer, setLayerVisibility, setLayerLocked, removeLayer).
  • HTML rendering engine (on GigaPdfEngine) — htmlNeededFonts(html) returns the Google fonts to download (phase 1); htmlRender(html, fonts, pageW?, pageH?, margin?) renders HTML + CSS to PDF with those fonts embedded (phase 2). No headless browser. Block / inline / table / flex (flex-direction, justify-content, flex-grow) / grid (grid-template-columns) layout, selector cascade, pagination, and forced page breaks via CSS page-break-before|after: always / break-*: page or a <pagebreak> tag. See the exhaustive list of supported HTML elements, CSS properties, units, colours and selectors in docs/HTML-CSS.md.
  • JavaScript — a document's inline <script>s run before layout through a built-in zero-dependency JS engine (no Chromium/Playwright), so script-driven content is rendered. It covers classes + super, closures, destructuring, RegExp, Map/Set, Symbol, eval/Function, and DOM bindings (document.getElementById, querySelector(All) with >/+/~/[attr], textContent, innerHTML, createElement/appendChild, classList, style). function*/async bodies compile to a suspendable bytecode VM, so generators are truly lazy (infinite generators, bidirectional .next(v), yield*) and await yields with spec microtask ordering. This happens automatically inside htmlRender/htmlNeededFonts — no extra call needed.

Every method is fully typed. Always close() a document when done to free the WASM handle.

License

PolyForm Noncommercial License 1.0.0 © Rony Licha / QR Communication. Free for noncommercial use; a commercial license is required otherwise. See LICENSE.