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

@boxpdf/reader

v0.2.2

Published

A streaming, memory-bounded PDF reader and structured-data extractor for JavaScript.

Readme

@boxpdf/reader

A streaming, memory-bounded PDF reader and structured-data extractor for JavaScript.

Defining contract

The reader parses and extracts content from large PDFs without buffering the complete file. Explicit byte and object cache budgets govern memory use.

peak parser-owned memory <= configured caches
  + active page window
  + largest permitted decoded object
  + measured fixed overhead

Every input implements a random-access source:

interface PdfSource {
  readonly size: number;
  read(offset: number, length: number): Promise<Uint8Array>;
}

Package boundaries

@boxpdf/reader
├── source          random-access file, HTTP, Blob, and memory inputs
├── store           bounded sparse byte cache
├── syntax          PDF lexer, objects, xref, and page tree
├── content         graphics state, fonts, CMaps, and text operations
└── structure       optional reading order, paragraphs, lists, and tables

separate output packages
├── @boxpdf/html-writer  streaming visual or semantic HTML output
└── render               future Canvas, SVG, or bitmap rendering

The core result preserves positioned text and provenance. Semantic structure is inferred separately and includes confidence and diagnostic reasons. A future renderer will consume page operations without becoming a dependency of text or table extraction.

HTML writer

@boxpdf/html-writer streams PDF pages to HTML through an awaited write callback. The default visual profile preserves display presentation; the semantic profile turns page geometry into reflowable reading order, nested sections, paragraphs, lists, cards, label/value groups, and tables. A bounded document window joins tables continued across pages and removes repeated page furniture without buffering the complete PDF. See packages/html-writer.

Usage

Read a local file in Node:

import { openPdf } from "@boxpdf/reader";
import { fileSource } from "@boxpdf/reader/node";

const source = await fileSource("large.pdf");
const pdf = await openPdf(source, {
  chunkSize: 64 * 1024,
  maxBytes: 16 * 1024 * 1024,
  maxXrefCacheBytes: 16 * 1024 * 1024,
});

try {
  for await (const page of pdf.pages()) {
    console.log(page.number, page.spans);
  }
  console.log(pdf.stats);
} finally {
  pdf.close();
  await source.close();
}

HTTP sources prefer byte-range support. Servers that return a complete HTTP 200 response are also supported: the reader keeps only the requested slice in memory and emits one transfer-efficiency warning per source.

import { httpSource, openPdf } from "@boxpdf/reader";

const pdf = await openPdf(await httpSource("https://example.com/large.pdf"));
const firstPage = await pdf.getPage(0);

v1 support

The v1 parser supports:

  • classic cross-reference tables, cross-reference streams, and /Prev chains
  • compressed object streams and linearized PDFs
  • lazy page-tree lookup with inherited media boxes, rotation, and resources
  • unfiltered, Flate, and ASCII-hex streams with decoded-size limits
  • literal and hexadecimal strings, common text operators, graphics transforms, WinAnsi text, UTF-16 strings, Type 0 fonts, and common ToUnicode mappings
  • Standard 14, explicit, embedded TrueType, and PFA/PFB Type 1 horizontal metrics
  • horizontal and vertical CID widths, vertical origins, TJ displacement, and top-to-bottom structure grouping
  • bounded sparse byte caching with source-read and resident-byte telemetry
  • a configurable packed-xref byte ceiling with resident-byte telemetry
  • semantic HTML with heuristic reading-order and table inference based on repeated text alignment

Encrypted PDFs, damaged-file repair, inline images in content streams, uncommon stream filters, Type1C/CFF width recovery, named CMap collections without a ToUnicode map or usable embedded TrueType cmap, OCR, and rendering are outside the v1 support surface. Unsupported filters and configured resource limits fail with explicit errors.

Compatibility oracle

PDF.js is the primary behavioral oracle for page geometry, decoded text, text direction, and positioning. Tests compare normalized positioned characters and allow different text-span boundaries. Poppler is an independent secondary oracle for the HTML writer. Its corpus gate preserves exact geometry and decoded-text agreement on every fixture known to agree across both engines. The report records RTL and font-decoding differences between the engines.

The full corpus gate accepts all 118 supported, unencrypted fixtures (100%). Of those, 115 match PDF.js and three named fixtures intentionally diverge where the visual oracle confirms improved .notdef, rotated-origin, or UTF-16 behavior. The two remaining fixtures in the fixed 120-file denominator are encrypted and intentionally unsupported. This score covers page count, dimensions, rotation, normalized decoded characters, and the first text origin on unrotated pages. Focused geometry tests additionally compare span width, height, font size, and transformed positions. Structure goldens cover reading order, inferred word boundaries, table rows, and semantic HTML. The scoring limits and the layout gap found after v0.1.1 are recorded in docs/PARITY_PLAN.md.

Memory tests extract from 10 MB and 1 GB virtual random-access PDFs with the same 64 KiB byte-cache limit. They assert cache residency, maximum individual source reads, total bytes fetched, process RSS, and ArrayBuffer growth.

Memory comparison

pnpm memory:compare runs @boxpdf/reader, raw PDF.js, and unpdf in isolated processes against the same logical PDF. The default comparison uses 10 MiB and 100 MiB inputs. PDF.js and unpdf receive complete Uint8Array inputs; @boxpdf/reader receives a random-access source. PDF.js URL and range loading are separate input modes and are outside this comparison.

One Node 24 run produced these measurements:

| Input | Engine | Peak RSS | RSS growth | ArrayBuffer growth | Source data read | |---:|---|---:|---:|---:|---:| | 10 MiB | @boxpdf/reader | 49.26 MiB | 3.64 MiB | 77.06 KiB | 140 KiB | | 10 MiB | PDF.js | 130.13 MiB | 84.49 MiB | 10.15 MiB | entire input | | 10 MiB | unpdf | 82.37 MiB | 36.74 MiB | 10.14 MiB | entire input | | 100 MiB | @boxpdf/reader | 49.32 MiB | 3.64 MiB | 77.06 KiB | 140 KiB | | 100 MiB | PDF.js | 220.09 MiB | 174.44 MiB | 100.15 MiB | entire input | | 100 MiB | unpdf | 172.37 MiB | 126.75 MiB | 100.14 MiB | entire input |

Peak RSS includes the Node process baseline, which was about 46 MiB in this run. RSS and allocator behavior vary by operating system and dependency version. The benchmark script is the source of current measurements.

Repository layout

  • src/source.ts: public random-access source contract and portable sources
  • src/store/: bounded sparse byte storage
  • src/syntax/: PDF lexical and object parsing
  • src/content/: page content and text interpretation
  • src/structure/: reading-order and table inference
  • src/render/: reserved boundary for an optional renderer adapter
  • test/unit/: focused parser and API tests
  • test/fixtures/: fixture integrity and golden extraction tests
  • test/oracle/: differential PDF.js tests
  • test/memory/: isolated-process memory-bound tests
  • fixtures/: revision-pinned OSS corpus and provenance

Development

pnpm install
pnpm fixtures:fetch
pnpm test:quick
pnpm test:oracle
pnpm test:memory
pnpm quality