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

pages-to-pdf

v0.4.1

Published

Browser-only DOM-to-PDF emitter for CSS Paged Media output

Readme

pages-to-pdf

Browser-only DOM-to-PDF emitter for CSS Paged Media output.

pages-to-pdf takes a paginated HTML document (already rendered into page containers by a CSS Paged Media engine) and re-renders it as a real vector PDF using @pdfme/pdf-lib. No server, no print dialog. It is intentionally pagination-engine agnostic: it works with paginate-for-print by default and can be configured for other engines such as @vivliostyle/print.

See the demo for usage examples.

Install

npm install pages-to-pdf

Usage

The library exports one main function: emitPdfFromWindow(window, options?). You supply a Window (usually an iframe contentWindow) whose document has already been paginated into page containers, plus the optional EmitOptions object described below. Among other things, EmitOptions takes an optional onProgress: (message: string) => void callback for status feedback.

With paginate-for-print (default)

import {emitPdfFromWindow} from "pages-to-pdf"
import {printHTML, renderHTML, htmlToPDF} from "paginate-for-print"

// One-shot helper provided by paginate-for-print:
const bytes = await htmlToPDF(html, {title: "My document"})

// Or compose the steps manually:
const iframe = await printHTML(html, {keepIframe: true})
try {
    const bytes = await emitPdfFromWindow(iframe.contentWindow!, {
        sourceHtml: html,
        metadata: {title: "My document"}
    })
} finally {
    iframe.remove()
}

With @vivliostyle/print

The vivliostyle-pdf package wraps @vivliostyle/print and pins the backend configuration matching Vivliostyle's output, so switching from paginate-for-print is just a matter of switching packages:

import {
    emitPdfFromVivliostyleWindow,
    printHTML
} from "vivliostyle-pdf"

printHTML(html, {
    removeIframe: false,
    printCallback: iframeWindow => {
        void (async () => {
            const bytes = await emitPdfFromVivliostyleWindow(iframeWindow, {
                sourceHtml: html,
                metadata: {title: "My document"}
            })
            // download or upload the bytes
            iframeWindow.frameElement?.remove()
        })()
    }
})

For a fully custom Vivliostyle configuration you can also compose the steps yourself with emitPdfFromWindow and a backend configuration describing Vivliostyle's DOM output (see the demo/vivliostyle/ folder of this repository for a working example).

@vivliostyle/print does not await printCallback, so removeIframe: false keeps the iframe alive until the asynchronous emitter resolves.

Configuration

The backend option controls how the emitter discovers pages, links, margin boxes, and asset bases. It is a BackendConfig object. When omitted, the emitter defaults to PAGINATE_FOR_PRINT_BACKEND.

import {
    emitPdfFromWindow,
    PAGINATE_FOR_PRINT_BACKEND,
    type BackendConfig
} from "pages-to-pdf"

// Default backend (paginate-for-print)
await emitPdfFromWindow(win)

// Explicit default
await emitPdfFromWindow(win, {backend: PAGINATE_FOR_PRINT_BACKEND})

// Custom engine
await emitPdfFromWindow(win, {
    backend: {
        pageSelector: ".my-page",
        filterEmptyPages: false,
        scaleRunsToMeasuredWidth: true,
        handleHyphenation: true
        // ...
    }
})

BackendConfig fields

| Field | Description | |---|---| | pageSelector | CSS selector for page root elements. | | filterEmptyPages | Drop pages containing no visible text or images. | | unhidePagesSelector | Selector for hidden page containers to un-hide before measuring. | | marginBoxSelector | Selector for margin-box pseudo-content (used by paginate-for-print). | | internalLinkPrefix | String/RegExp prefix to strip from internal fragment IDs (Vivliostyle rewrites anchors). | | bundleBaseGlobal | Global property on window that holds the font/asset base URL. | | scaleRunsToMeasuredWidth | Scale each word run horizontally to its measured DOM width. | | handleHyphenation | Synthesize trailing hyphens for browser-inserted hyphenation. | | metricCompatibleFonts | Extra metric-compatible fallback font files. | | metricFamilyAliases | Family aliases that map to metric-compatible buckets. | | defaultCreator | Default PDF /Creator string. |

The default backend is PAGINATE_FOR_PRINT_BACKEND.

PDF/A and PDF/UA

Pass pdfOptions to enable archival and accessibility conformance:

await emitPdfFromWindow(win, {
    pdfOptions: {
        pdfA: true,   // `true`/`"4"` → PDF/A-4, `"4f"` → PDF/A-4f
        pdfUa: true   // PDF/UA-2 (tagged PDF with a structure tree)
    }
})
  • Enabling either option produces a PDF 2.0 file with an sRGB output intent (the sRGB2014.icc profile is fetched from <baseUrl>icc/, see below) and XMP metadata declaring the conformance claims. Both flags can be combined for a dual-conformant PDF/A-4 + PDF/UA-2 file.
  • pdfA: "4f" is required instead of true/"4" whenever embedded files (the attachments option) are present, since PDF/A-4 forbids embedded files.
  • PDF/UA-2 tags the content: logical structure, artifacts (page margin boxes, repeated headers), footnote structure, and annotation flags are derived from the paginated DOM via the backend's tagging configuration (artifactSelectors, wrapperSelectors, footnoteSelectors, logicalElementKey, repeatedHeaderSelector).

Architecture

HTML + CSS Paged Media
        │
        ▼  pagination engine (paginate-for-print, @vivliostyle/print, ...)
   paginated DOM (hidden iframe, one container per page)
        │
        ▼  emitPdfFromWindow()
   DOM→PDF emitter (src/pdf-emitter.ts)
     • measures each page container (px → pt, 1 px = 0.75 pt, Y flipped)
     • paints background-color rects and solid/dashed/dotted/double borders
     • embeds PNG/JPEG images; draws SVG as vector or rasterizes it
     • positions every word from Range.getClientRects() so browser line
       breaking and justification are preserved
     • synthesizes small caps, list markers, and margin-box content
     • attaches link annotations (URI and GoTo actions)
     • writes metadata, outlines (h1–h6 bookmarks), viewer preferences,
       and optional file attachments
     • optionally writes PDF/A-4(-4f) and PDF/UA-2 conformance: output
       intent, XMP metadata, tagged structure tree, artifact handling
     • resolves @font-face fonts via CSS font matching, embeds them
       subsetted, and falls back to bundled fonts when needed
        │
        ▼
   Uint8Array → Blob → PDF file

Develop

pnpm install
pnpm run build        # TypeScript compile → lib/
pnpm run demo         # start the local demo server (http://localhost:5173/pages-to-pdf/)
pnpm run test:e2e     # Playwright end-to-end tests

Features

  • Word-precise text positioning preserving browser layout.
  • Embedded subsetted fonts from @font-face rules with CSS-style matching.
  • Per-glyph fallback to bundled fallback fonts.
  • Metric-compatible fallback fonts for common system font substitutions.
  • PNG/JPEG images; SVG as vector or rasterized.
  • Synthesized ::marker and @page margin-box content.
  • Internal/external link annotations.
  • PDF metadata, outline/bookmarks, viewer preferences, attachments.
  • PDF/A-4 / PDF/A-4f and PDF/UA-2 output (tagged structure tree, sRGB output intent, XMP conformance metadata).

Limitations

  • Baselines are approximated from font metrics.
  • Backgrounds and borders are painted in document order — a simplification of the CSS stacking model.
  • Only a subset of border styles and background fills are supported.
  • SVG support has some gaps (see svg4pdf-lib limitations).
  • Browser-inserted hyphenation is only synthesized when handleHyphenation: true is enabled.

Bundled assets

The npm package ships public/woff2/, public/fonts/, and public/icc/. At runtime these are resolved relative to EmitOptions.baseUrl:

WOFF2

WOFF2 fonts are decoded with fonteditor-core's WASM build of Google's woff2 decoder. The decoder loads lazily and degrades gracefully. The WASM is resolved in this order:

  1. EmitOptions.woff2WasmUrl (string URL or raw ArrayBuffer).
  2. <EmitOptions.baseUrl>woff2/woff2.wasm.
  3. In Node, fonteditor-core resolves its own packaged woff2.wasm.

ICC profile

When PDF/A or PDF/UA is enabled, the sRGB output intent profile is fetched from <baseUrl>icc/sRGB2014.icc. If it cannot be loaded, the output intent is skipped and the file is written without it.

Fallback fonts

Per-glyph fallback fonts are fetched from <baseUrl>fonts/…. See the "Licenses" section below.

Licenses

  • Code: LGPL-3.0 (see LICENSE).
  • Bundled fonts: see public/fonts/OFL.txt and the individual NOTICE-*.txt files.
  • public/icc/sRGB2014.icc: the ICC's sRGB2014 profile, redistributed under the ICC license — see public/icc/NOTICE.txt.