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

@timkozlov/html-to-pdf

v1.0.2

Published

Convert a live DOM element into a vector PDF in the browser — real text and shapes, not a screenshot.

Readme

html-to-pdf

Convert a live DOM element into a vector PDF in the browser — real selectable text and crisp shapes, not a screenshot.

Instead of rasterizing (html2canvas-style), this library clones your element, lets the browser lay it out at the page width, and transcribes that layout into PDF primitives: background colors become filled rects, borders become vector strokes and fills, text becomes real positioned PDF text (searchable, selectable, crisp at any zoom), and images are embedded at their exact rects. Output files are small and print sharp.

Quick start

<script src="dist/html-to-pdf.iife.min.js"></script>
<script>
  // the bundle is self-contained (jsPDF included)
  await htmlToPdf(document.querySelector("#invoice"), {
    output: "save",
    filename: "invoice.pdf",
  });
</script>

Or as a module (jsPDF is a regular dependency here):

import { htmlToPdf } from "@timkozlov/html-to-pdf";

const blob = await htmlToPdf(element);                        // Promise<Blob>
const bytes = await htmlToPdf(element, { output: "arraybuffer" });
const doc = await htmlToPdf(element, { output: "jspdf" });    // the jsPDF instance

The element you pass is never touched: a clone is laid out off-screen at the page's printable width and removed afterwards.

Options

| Option | Default | Meaning | | --- | --- | --- | | format | "a4" | "a4", "letter", or [widthPt, heightPt] | | orientation | — | "portrait" / "landscape"; swaps the page dimensions | | margins | 36 | pt; number or { top, right, bottom, left } | | scale | 1 | Content zoom. 1 maps CSS px→pt at the browser-print ratio (16px text = 12pt). >1 prints bigger, <1 fits more per page | | output | "blob" | "blob" | "save" | "arraybuffer" | "jspdf" | | filename | "document.pdf" | Used by output: "save" | | imageScale | 2 | Raster resolution multiplier vs. displayed CSS size | | imageQuality | 0.92 | JPEG re-encode quality | | colorFilter | — | (color, use) => color applied to every color drawn; see below |

Color correction (colorFilter)

Every color the renderer draws — backgrounds, borders, text, and each pixel of raster images — is passed through your function before it lands in the PDF. color is { r, g, b, a } (0–255 channels, alpha 0–1) and use is "fill" | "stroke" | "text" | "image". Return values are clamped; non-finite channels fall back to the original.

Useful for fax or low-quality printing, where subtle grays disappear and light tints turn to noise:

const grayscale = (c) => {
  const y = 0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b;
  return { r: y, g: y, b: y, a: c.a };
};

const faxContrast = (c) => {
  const y = 0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b;
  const boosted = 255 / (1 + Math.exp(-(y - 128) / 24));
  return { r: boosted, g: boosted, b: boosted, a: c.a };
};

await htmlToPdf(el, { colorFilter: faxContrast, output: "save" });

faxContrast pushes light colors toward white and dark colors toward black (a sigmoid around mid-gray) so dividers either disappear cleanly or print solid instead of dithering.

What renders (v1)

  • Backgrounds — background-color (incl. alpha), uniform border-radius
  • Borders — per-side widths/colors, solid (also used for double/groove/ridge/inset/outset), dashed, dotted, rounded uniform borders
  • Text — exact per-line placement from the browser's own layout: font size, weight (≥600 → bold), italic, color, letter-spacing, text-align incl. justify, text-transform, underline/overline/line-through, white-space: pre. Fonts map to the standard PDF families (Arial/Helvetica-likes → Helvetica, Georgia/Times-likes → Times, monospace → Courier)
  • Images — <img> (PNG/JPEG/SVG sources) and <canvas>, placed in the content box with object-fit/object-position (cover is clipped), JPEG stays JPEG, alpha preserved via PNG
  • Effects — opacity (multiplied down the tree), overflow: hidden clipping, visibility, multi-page splitting

Multi-page behavior

Content taller than one page is split geometrically at page boundaries: rectangles and vertical lines split exactly at the seam; images and rounded/dashed shapes are drawn clipped per page so their geometry is never distorted; text lines are never torn — each line goes wholly to the page containing its baseline (descenders may dip a few pt into the bottom margin).

Not yet supported

z-index/stacking-context reordering (paint order is document order) · background images & gradients · shadows · CSS transforms · custom font embedding (non-WinAnsi glyphs — Cyrillic/CJK/emoji — warn and render incorrectly) · ::before/::after/list markers · form control values (boxes render, text doesn't) · RTL text · rounded overflow clips · break-inside: avoid (page breaks are geometric) · position: fixed.

Cross-origin images need CORS (crossorigin="anonymous" + proper headers) or they are skipped with a warning.

Development

npm install
npx playwright install chromium   # for browser tests

npm run build        # dist/: IIFE bundle (jsPDF inlined) + ESM + .d.ts types
npm test             # node unit tests + headless-Chromium tests (incl. PDF-byte assertions)
npm run dev          # http://localhost:8000/examples/ — live side-by-side DOM vs PDF harness

The examples/ harness renders each fixture in fixtures/ next to its generated PDF for visual comparison, with page format/margins/scale controls.

How it works

snapshot  clone element → hidden sibling at page width → wait for fonts/images
walk      computed styles + client rects → draw ops (px, clone-root coords)
          text: per-character Range walk → line fragments → baseline from font metrics
paginate  pure splitting of ops into pages (never re-derives geometry)
render    jsPDF calls; single px→pt scale; text stretched (Tz) to match DOM width exactly

The op stream between stages is what makes the geometry testable: browser tests assert emitted ops directly against getBoundingClientRect, and e2e tests tokenize the uncompressed PDF content streams and check final page coordinates to fractions of a point.

Roadmap

Custom TTF font embedding (removes the WinAnsi limit) · background images · break-inside: avoid pagination hints · list markers · a pdf-lib renderer backend.