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

@react-pdf/svgkit

v0.1.0

Published

pdfkit-compatible drawing context that renders react-pdf documents to SVG

Downloads

591

Readme

@react-pdf/svgkit

A pdfkit-shaped drawing context that renders to SVG

A pdfkit-compatible drawing context for react-pdf. @react-pdf/render draws react-pdf's resolved layout tree by calling pdfkit-style methods (fill, stroke, moveTo, text, ...) on a context object. @react-pdf/svgkit implements that same context shape but emits SVG strings instead of PDF drawing operations, so a resolved document can be rendered to SVG without pulling in pdfkit or a PDF viewer. Useful for lightweight in-browser previews, such as a REPL or documentation site.

Installation

yarn add @react-pdf/svgkit

Usage

import FontStore from '@react-pdf/font';
import layout from '@react-pdf/layout';
import render from '@react-pdf/render';
import SVGDocument from '@react-pdf/svgkit';

const fontStore = new FontStore();
const resolved = await layout(documentTree, fontStore);

const ctx = new SVGDocument({ idPrefix: 'doc1-' });
render(ctx, resolved);

ctx.pages; // string[] — one `<svg>…</svg>` per page

render() calls ctx.end() itself once every page has been drawn, so consumers never call it directly. After render() returns, ctx.pages holds one serialized <svg> string per page.

idPrefix

Clip path and gradient ids are generated sequentially per document (clip-1, grad-1, ...). Destination ids are derived from the document's own id props (dest-<id>). Pass idPrefix when embedding several documents' SVG output in the same DOM so none of these ids collide:

const ctx = new SVGDocument({ idPrefix: 'doc2-' });

Document info

Pass info (or set ctx.info directly before calling render()) to embed document metadata. It accepts the same keys pdfkit does — Title, Author, Subject, Keywords, Creator, Producer, CreationDate, ModificationDate — and Title/Subject land as native <title>/<desc> elements, with Title, Author, Keywords, Subject and CreationDate also emitted as Dublin Core RDF inside a <metadata> element (the same convention Inkscape and Illustrator use), one per page:

const ctx = new SVGDocument({
  info: { Title: 'Invoice #42', Author: 'Acme Inc.' },
});

Any key left unset is simply omitted — an empty info produces no extra markup at all.

Bookmarks

Bookmarked elements (<View bookmark="Chapter 1"> and friends) don't have a native SVG outline to render into, so they render as an empty, positioned <g id="..."> marker on their page plus a machine-readable outline tree in a <metadata> element on the first page:

<metadata>
  <rpdf:outline xmlns:rpdf="https://react-pdf.org/ns">
    <rpdf:item title="Chapter 1" page="0" href="#bookmark-1">
      <rpdf:item title="Section 1.1" page="0" href="#bookmark-2"/>
    </rpdf:item>
  </rpdf:outline>
</metadata>

A consumer builds a sidebar by DOMParser-parsing that fragment, walking the rpdf:item elements for title/page/expanded, and using href to jump to the matching marker (e.g. document.getElementById(...) or location.hash) — on the page indicated by page, not necessarily the one the outline metadata itself lives on.

Text

How text is rendered depends on whether the font was registered (embedded) or is one of the standard 14 PDF fonts:

  • Registered fonts render as glyph-outline <path> elements, one per glyph, positioned using the same shaping output the PDF renderer uses. This is pixel-faithful to the PDF output. An invisible <text> run (fill-opacity="0") is layered on top of the outlines, pdf.js-style, so the text stays selectable, copyable, searchable, and accessible to screen readers even though the visible glyphs are vector art.
  • Standard fonts (Helvetica, Times, Courier and their variants) have no embedded outline data, so they render as <text> elements with per-glyph x positions and a CSS font-family fallback (e.g. Helvetica, Arial, sans-serif). The text is selectable, but its exact shape depends on whichever matching font the viewer has installed.

Annotations

svgkit doesn't emit navigable <a> elements or interactive form widgets. Instead, links, notes and form fields each render an inert annotation alongside their visible output: a pointer-events: none, unpainted <rect> (or, for notes, a data-rpdf-note attribute on the icon's own <g>) marking where the thing is and carrying enough data for a host to build the real interaction. The visible rendering described below is always emitted too — annotations are additive, never a replacement for it.

Links

link()/goTo() render:

<rect x="0" y="-10" width="50" height="10" fill="none" pointer-events="none" data-rpdf-link="https://react-pdf.org"/>
  • External links carry the URL as given in data-rpdf-link.

  • Internal links (goTo) carry a fragment, e.g. data-rpdf-link="#doc1-dest-chapter", which resolves directly via document.querySelector(...) to the matching destination marker — the <g> that addNamedDestination emits, tagged with both id and data-rpdf-dest:

    <g id="doc1-dest-chapter" data-rpdf-dest="chapter"/>

Notes

Note still draws its fixed comment-bubble icon and <title> tooltip; the icon's <g> additionally carries the note's contents:

<g data-rpdf-note="Please review before signing"><rect .../><path .../><title>Please review before signing</title></g>

Form fields

TextInput, Select, List and Checkbox still draw their static approximation of what a PDF viewer shows (the field's value, masked for password and falling back to the first select option for Select/List; a check mark for a checked Checkbox) — an overlaid control naturally covers this baked-in drawing, the same way pdf.js's form layer covers its own static appearance streams. Alongside that drawing, each field gets one annotation rect. Attributes with no value for a given field are omitted:

| Attribute | Meaning | Present on | | --------------------------- | ------------------------------------------------- | ------------------- | | data-rpdf-field | Field type: text, checkbox, combo or list | all | | data-rpdf-field-name | Field name | all, when named | | data-rpdf-field-value | Current value (masked for password) | text, combo, list | | data-rpdf-field-checked | "true" / "false" | checkbox only | | data-rpdf-field-options | JSON array of choices | combo, list | | data-rpdf-field-multiline | "true" when set | text only | | data-rpdf-field-password | "true" when set | text only | | data-rpdf-field-readonly | "true" when set | any, when read-only |

<rect x="40" y="80" width="120" height="20" fill="none" pointer-events="none" data-rpdf-field="text" data-rpdf-field-name="email" data-rpdf-field-value="[email protected]"/>

Because every annotation rect is unpainted and ignores pointer events, none of them block clicks, typing or text selection underneath — a host queries them purely for geometry and data, then builds its own interaction:

document.querySelectorAll('[data-rpdf-field]').forEach((el) => {
  const box = el.getBoundingClientRect(); // position a real control here
  const {
    rpdfField: type,
    rpdfFieldValue,
    rpdfFieldChecked,
    rpdfFieldReadonly,
  } = el.dataset;

  const input = document.createElement(type === 'text' ? 'input' : 'select');
  if (type === 'checkbox') input.checked = rpdfFieldChecked === 'true';
  else input.value = rpdfFieldValue || '';
  input.disabled = rpdfFieldReadonly === 'true';
  // position `input` over `box` and append it — it now covers the
  // baked-in drawing and is the thing the user actually interacts with
});

document.querySelectorAll('[data-rpdf-link]').forEach((el) => {
  const target = el.dataset.rpdfLink;
  const box = el.getBoundingClientRect(); // position an overlay here
  overlay.addEventListener('click', () => {
    if (target.startsWith('#')) {
      document.querySelector(target)?.scrollIntoView({ behavior: 'smooth' });
    } else {
      window.open(target, '_blank', 'noopener');
    }
  });
});

This keeps interaction policy (same-tab vs. new-tab navigation, how an internal jump scrolls, what widget renders a field, whether a note pops up inline or as an alert) in the host's hands rather than baked into svgkit. A standalone .svg file opened directly (e.g. double-clicked in a file browser) has no interactivity by design — annotations only become interactive once a host implements the pattern above. See packages/examples/vite/src/svg-viewer.tsx for a full implementation that overlays real <input>/<select>/<textarea> controls and note popups.

License

MIT