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

@quario/pdf

v0.11.1

Published

Tiny, browserless PDF render target for quario. Pagination, embedded fonts and deterministic bytes, with no Chromium.

Readme

@quario/pdf

The PDF render target for quario. Renders a report definition straight to a paginated PDF. No headless browser, no print CSS, deterministic bytes.

quario does the typesetting — pagination, keep-together, tables, fonts, the document outline — through @quario/layout. That is the same layout the viewer paints on screen, so the preview and the document break their pages in the same places. @cantoo/pdf-lib writes the file. You get typesetting plus pdf-lib: no Chromium in your container, no page-load race, no fonts-not-ready flake.

Contents

Install

npm install quario @quario/pdf

@cantoo/pdf-lib arrives as a dependency. The engine is a peer, installed beside it. ESM-only, Node 22+, and browser-ready through any standards-based ESM bundler. The renderer returns bytes, so the host decides where they go.

Embedding TrueType fonts also needs the optional peer:

npm install fontkit

It loads only when you pass fonts to the factory. The base-14 families need nothing extra.

Quick start

import { writeFileSync } from "node:fs";
import { pdf } from "@quario/pdf";
import { quario } from "quario";

const report = quario().report(schema, funcs);
const bytes = await report.render(
  pdf({ page: { size: "A4", margin: 54 }, meta: { title: "Sales 2026" } }),
  data,
);

writeFileSync("sales.pdf", bytes);

Any schema the HTML target renders works here unchanged. Add page bands when you want running headers and page numbers. Page bands are the one part of the schema that only paginated targets consume:

const paged = {
  ...schema,
  page: {
    header: [{ type: "text", value: "{{ $.params.title }}", style: { size: 8, color: "#777777" } }],
    footer: [
      { type: "text", value: "{{ page.number }} / {{ page.total }}", style: { size: 8, align: "right" } },
    ],
  },
};

visible: "=page.number > 1" on a band is the cover-page recipe. Gate band visibility on page.number only. The layout reserves band heights before it knows the real page count, probing as a two-page document. =page.total > 10 therefore reserves only the 8 pt gap a declared band always costs. page.total is fine in band text.

API

pdf(options?)

The target factory takes this target's host options, validates them at the call, and returns the target you pass to render. The factory refuses an option it does not know. It throws a TypeError at the call for an unknown key. It throws one also for a key with a value of the wrong type. report() compiles once and report.render(pdf(options), data) resolves the document bytes. One compile serves any number of configurations (A4 and letter from the same report). Compile at startup and render per request. Definition problems throw at report(), at compile time.

const report = quario().report(schema, funcs);
const bytes = await report.render(pdf({ page: { size: "letter" } }), data);

The compiled report carries stream (the raw event generator), names, functions, and paths, like every quario report. Engine-level options (query budgets, the license key) live on the instance, and q.license settles with the verification result.

Rendering is asynchronous and returns the loop between batches, so a large report never blocks the host. Render-time failures reject with located errors.

Options

Page size and fonts are target configuration. The margin is the target's unless the document declares page.margin, in which case the document's margin wins. A document and a target that both declare one reject the render.

{
  page: { size: "A4", margin: 54 },   // 'A4' | 'letter' | [width, height] in points
  meta: { title, author, subject },   // optional /Info — strings only, never a date
  fonts: { Inter: { regular, bold, italic, boldItalic } },  // TrueType bytes
}

size defaults to A4 (595.28 × 841.89 pt). An array is a custom [width, height] in points. An unknown size name is an error the factory throws. margin applies to all four sides and defaults to 54 (0.75 in). Text renders at 10pt when nothing declares a size: a document's type size is the document's own, so it is the report's style.size, not a host option. Line leading is 1.4× a line's largest font size.

Layout

Guaranteed normative behavior a conforming target must produce:

  • Table header rows repeat after every page break. An unstyled table draws no rules. The target draws an authored border on a header row or a total row at the width written.
  • A group header always travels with its first content unit: the first detail lines, or a table's header plus its first row. An instance's headers also repeat at the top of every page it continues onto (outermost first, then content) and stop when the instance ends.
  • The total rows keep the last data row with them.
  • A row that fits on a page is never split across one.
  • A break on a group turns a page at every boundary its position names.
  • reset: "page" on a group restarts page.number / page.total at the page its leading edge opened.
  • The target honours authored column width percentages. It measures the remaining columns from content and scales them to fill the rest. A table whose authored shares leave the width-less columns nothing is a definition error, so there is no over-commitment case.
  • Hidden cells keep their column slot.
  • Page bands render on every page, their heights reserved out of the body area.
  • A report header declaring height pins a box of that many points from the page top. Its items pack from the top and the next band starts where the box ends.
  • Every group instance opens with a half-line gap, dropped at a page top. Authored spaceBefore drops at a fresh page or strip top the same way.
  • An item or row a page cannot hold whole still carries its box: each slice draws the sides the break left it, the top on the first and the bottom on the last.
  • A split lays out as one block at the height of its tallest slot. Every slot's box is the split's height, and a slot's valign places its content in that slack, as a table cell's does in its row's.
  • Page columns are strips: the flow fills one to its foot, moves to the next, and turns the page once the last is spent. A table restates its headings at a strip head, and a group's headers only at a page head.
  • The band-role defaults (a bold, larger report header, and bold group headers) sit under the author's own style. uppercase capitalises the drawn string before the target measures it.
  • An image draws at the size its fit dictates (natural at 96 dpi capped at the content width, width scaled to it, aspect ratio preserved) and is never split across a page break: one that does not fit the remaining height moves whole to a fresh page.

Best-effort current implementation, pinned only as a regression guard where the layout suite holds it, and changeable without a breaking change: keeping a whole group instance together where a fresh strip or a fresh page could hold it, paragraph widow and orphan avoidance, the gutter between page-column strips and whether a region balances, slicing a row taller than the page, downscaling an image taller than any page, embedding repeated image bytes once, and where an image's page break lands. Do not write definitions that depend on these.

Bands flow vertically in walk order, matching the HTML target's divs. Group nesting adds no indentation, but every instance that draws opens with a structural half-line gap so groups read as blocks without authored margins.

Fonts and text

The base-14 Helvetica, Times, and Courier families carry the default output (family: "sans", "serif", "mono", each with regular, bold, italic, and bold-italic faces), using WinAnsi encoding. Characters a face cannot draw render as ? rather than failing the report, because quario does not trust cell text, and one stray character must not take a document down. WinAnsi is the base-14 limit. A TrueType face's cmap is its own.

For full Unicode, supply TrueType families and select them by name:

import { readFileSync } from "node:fs";

const target = pdf({
  fonts: { Inter: { regular: readFileSync("Inter-Regular.ttf"), bold: readFileSync("Inter-Bold.ttf") } },
});
// then, in the schema: style: { family: "Inter" }

Missing variants fall back to the family's regular. Embedded text uses the font's own metrics. The target subsets it to the glyphs the document uses and carries a ToUnicode map, so extraction and copy see the original text. quario trusts fonts as host assets, like registered functions. The factory refuses a mapping without a regular (options.fonts.<name>.regular: required). Bytes the parser cannot read reject the render, located at the same option path.

Links

A styled run that declares href becomes a link annotation over the rectangle it drew, with no border. An external URL is a URI action; an href written as # and a group's label goes to that instance's own outline destination, and a link to a label no instance carried is dropped rather than invented. A run wrapping across two lines is two rectangles, so it is two annotations.

The engine admits a URL against the host's scheme allowlist before this target sees it, so a javascript: URL never reaches an annotation. See quario({ schemes }).

Outline

A grouped report carries a document outline (bookmarks) mirroring the group tree: one open entry per instance, nested by depth, titled by the group's label, else by the instance's first group-header text, else name: key. A group declares label to name its entry independently of what the page shows. Ungrouped reports have no outline.

Determinism

This target pins the document's dates rather than stamping them, and meta is opt-in, so the same schema, data, and options produce byte-identical output. That makes report bytes cacheable, diffable, and safe to compare in tests.

Unlicensed marking

An unlicensed render draws the wording from report-start.marking once per page, after everything else: a translucent grey line corner-to-corner over content and page furniture. The per-page presence is normative. Exact geometry is best-effort. A licensed render draws nothing.

Page furniture

Paper size and fonts are options, not schema. The margin is an option a document may declare in its stead. Authored watermarks and finer break control stay out of scope. Keep-together and continuation headers are by construction, above.

The full contract is The PDF target, with each declaration's fate in the support matrix.

Documentation

The quario documentation is the reference. The report schema is the normative specification of what a report may declare, and @quario/pdf is this package's own API.

License

Commercial software with readable source. Evaluation is free, unlimited, and watermarked. Per-developer licenses at getquario.com. See the bundled LICENSE.

Pass your license key once, on the instance. quario verifies it offline:

const q = quario({ license: "quario_..." });
await q.license; // { licensed: true, licensee: "Acme BV", id: "1-ACME" }