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

@urvis/pdf

v0.1.3

Published

Vector-preserving PDF export for urvis scenes. No external PDF library; deflate compression uses `CompressionStream("deflate")` only.

Readme

@urvis/pdf

Vector-preserving PDF export for urvis scenes. No external PDF library; deflate compression uses CompressionStream("deflate") only.

toPDF(pages, opts)

import { toPDF } from "@urvis/pdf";

const bytes = await toPDF([sceneRoot], { pageSize: "a4", dpi: 96 });
  • One physical page per input scene root.
  • pageSize: a PAPER_SIZES key ("a4", "a5", "letter", "legal", "tabloid") or an explicit { width, height } PhysicalLength pair. MediaBox is [0, 0, toPt(width), toPt(height)].
  • dpi (default 96): scene pixels per inch. Each page opens with a global s 0 0 -s 0 Hpt cm flip (s = 72/dpi), so all geometry is emitted in the scene's own pixel coordinates, y-down — the scene maps straight through.

What is preserved as vectors

  • Shapesrect → re; circle/ellipse → four kappa cubic arcs; line/polyline/polygon; path → m/l/c/h (quadratics elevated to cubics, elliptic arcs flattened to cubics).
  • Fills / strokes — solid colors via rg/RG (+ f/f*/S/B), any CSS color gamut-mapped into sRGB. Dashes via d; caps/joins via J/j.
  • Opacity & blend — folded group opacity and blendMode become deduplicated /ExtGState entries (ca/CA/BM).
  • Linear & radial gradients — emitted as PDF shadings (ShadingType 2 / 3) with a Type 3 stitching function over one Type 2 exponential segment per stop pair. objectBoundingBox coordinates are resolved against the node's local bounds. The gradient clips its geometry (W n) and paints with sh.

Images

Raster nodes are embedded as image XObjects (raw RGB FlateDecode stream plus an 8-bit gray /SMask from the alpha channel, drawn with Do) only when the node's src is a raw-RGBA data URI:

data:image/x-urvis-rgba;base64,<uint32 LE width><uint32 LE height><RGBA bytes>

Use encodeRawRgba(width, height, rgba) to produce one. This is the only image form embeddable without a host PNG/JPEG decoder — the core image geometry carries a src string, not pixels. Nodes whose src is a normal URL or a data:image/png / image/jpeg URI are skipped (a host that has decoded pixels should re-emit them through the raw-RGBA form). Decoding standard image formats is out of scope for this package.

Text

Text nodes embed subset TrueType fonts and draw shaped glyphs:

import { toPDF } from "@urvis/pdf";
import { FontStore, HarfBuzzShaper } from "@urvis/text";

const fonts = new FontStore();
fonts.register(fontBytes);
const shaper = await HarfBuzzShaper.load(fonts);
const bytes = await toPDF([sceneRoot], { pageSize: "a4", dpi: 96, fonts, shaper });
  • Each FontHandle used across all pages is subsetFonted once into a self-contained TrueType, embedded as a Type0 font whose descendant is a CIDFontType2 (/Encoding /Identity-H, /CIDToGIDMap /Identity, so a CID is the subset glyph id). /W widths are the glyphs' advances in 1000-per-em units; /DW is 1000. /FontFile2 is the Flate-compressed subset (with /Length1).
  • A /ToUnicode CMap reverses the original font's cmap per used glyph; ligatures (no cmap preimage) get their source text from the export-time shaping alignment, so copy/extract recovers the original characters (e.g. the fi ligature → f,i).
  • Content per line: BT, /F<n> <sizePx> Tf, Tm = [1 0 0 -1 x baseline] (the negated vertical scale un-flips the y-up glyphs against the page's y-down flip; baseline = block y + ascent), then a TJ of 2-byte big-endian CIDs with kerning deviations from natural advances as thousandths-of-em adjustments.
  • opts.fonts and opts.shaper are required for text nodes; otherwise PdfTextNeedsFontsError (naming the node id) is thrown. A glyph run shaped by CanvasShaper (font.handle === null, no glyph outlines) throws PdfNeedsGlyphDataError — re-export with a HarfBuzzShaper.

Known gaps / fallbacks

  • Conic & freeform gradients and tiled Pattern paints have no analytic PDF form. They currently fall back to a solid fill of the first stop / first point color (patterns fall back to black). Full rasterization into an image XObject is not yet implemented.
  • Text: one shaped run per text node (no automatic line breaking), left-to-right fills only; capHeight/StemV in the /FontDescriptor are heuristic (ascent-derived and 80 respectively).
  • Clip rects / masks / filters on nodes are not yet emitted.