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/paginate

v1.0.1

Published

Standalone pagination engine for react-pdf

Readme

@react-pdf/paginate

Standalone pagination engine for react-pdf

A pure, framework-agnostic pagination engine that packs a laid-out tree of boxes into fixed-height pages. It derives the spacing between siblings from their positions, carries container edges across page breaks, honors forced breaks and repeating headers, and supports content that can't be known until its page number is.

The engine is independent of React and PDF rendering — it takes a vertical box model in and returns placed fragments out. React-PDF uses it to paginate Yoga layout trees, but any consumer that can describe its content as boxes with tops and heights can use it directly. Internally the tree is compiled to a box/glue/penalty item stream; that representation is an implementation detail.

Installation

yarn add @react-pdf/paginate

Usage

import { createPaginator } from '@react-pdf/paginate';

const paginator = createPaginator([
  { box: { top: 0, height: 40 }, data: 'a' },
  { box: { top: 40, height: 40 }, data: 'b' },
  { box: { top: 80, height: 40 }, data: 'c' },
]);

const pages = [];

while (!paginator.done) {
  pages.push(paginator.next(100));
}
// => [
//   [{ box: { top: 0, height: 40 }, data: 'a', part: {...} },
//    { box: { top: 40, height: 40 }, data: 'b', part: {...} }],
//   [{ box: { top: 0, height: 40 }, data: 'c', part: {...} }],
// ]

API

createPaginator(nodes)

Stepwise pagination: fills one page per call, so each page can have its own height — the hook for page templates whose chrome varies per page.

  • nodes: FlowNode[] — the flow itself, top to bottom. The stream is a vertical flow by definition; rows live inside it as direction: 'row' containers. Page geometry (padding, chrome) is the caller's business.
  • next(height) packs exactly one page against height and returns its PlacedNode[]. Calling it after done throws.
  • done is true once the content stream is exhausted.
  • Internal state is sealed — there is no way to modify the in-flight stream between pages, by design. Callers own termination.

The node schema

A node is one of three shapes, told apart by what it carries: children makes it a container, materialize makes it lazy, neither makes it a leaf. Mixing them (children plus split, say) is rejected.

Every node accepts id?: string (a label surfaced in warnings), repeat?: boolean and break?: boolean (below). Leaves and containers also accept data?: unknown — an opaque payload the engine never reads and hands back on the corresponding PlacedNode.

Box

The engine reasons about vertical space only; widths never enter.

{
  top: number;            // border-box top, relative to the parent's border box
  height: number;         // border-box height
  marginTop?: number;     // numbers only — resolve 'auto' margins upstream
  marginBottom?: number;
  edgeTop?: number;       // top border + padding, summed
  edgeBottom?: number;
}

Blank space between siblings — flex gaps, distributed free space — is not declared anywhere: the engine derives it from consecutive boxes and drops it at page breaks, the way margins collapse in print. Container edges instead travel with the container and continue across breaks.

LeafNode

A measured, indivisible (or splittable) unit of content.

{
  box: Box;
  split?: (avail: number) => [LeafNode, LeafNode] | null;
  absolute?: boolean;
}

If split is provided and the leaf doesn't fit, the engine calls it with the available content height (margins already accounted for). Return the fragment that fits and the remainder, or null to move the whole leaf to the next page. Remainders may split again on later pages.

An absolute leaf takes no space and never splits: it lands on the page the flow reaches it on, in source order, and its box passes through untouched — for content positioned outside the flow that should still ride with it.

ContainerNode

{
  box: Box;
  children: FlowNode[];
  direction: 'row' | 'column';
}

A column stacks children vertically and may break between (or inside) them. A row's children sit side by side at a shared y; a cell pushed down by alignment keeps its offset glued to it, so an aligned band never strands at a page bottom on its own.

A column whose children overlap vertically (flex-wrap into visual columns) is treated as an unsplittable leaf: its boxes no longer describe a vertical flow, so there is nowhere sane to break inside it.

LazyNode

Content that depends on the page it lands on.

{
  box: Box;  // first-pass geometry, used only for spacing between siblings
  materialize: (ctx: { pageNumber: number }) => FlowNode;
}

materialize runs when the engine reaches the node, with the page number it would land on. If nothing from it fits there, it re-materializes on the next page with the updated number — so page-number labels stay accurate across re-flow.

Flags

  • break: true ends the current page before the node (ignored on a container's first child).
  • minPresenceAhead: number keeps the node company: no page break may land within that many points after it, so either that much of what follows shares its page or the node moves to the next one. Inert when the node is first on its page (moving it wouldn't help) and when nothing follows.
  • repeat: true re-emits a fresh copy of the node at the head of every continuation its parent column creates — the way a table header repeats on each page the table spans. Rules:
    • A node re-emits only on continuations created after it fully placed. It never appears before the flow reaches it, and a mid-split node continues its remainder instead of adding a copy; splitting consumes its identity.
    • Repetition is scoped to the immediate parent and ends with it.
    • A repeat lazy re-materializes on every page it repeats on, with that page's number.
    • A page that places nothing but repeats stops repeating, so content always advances.

Placed output

{
  box: { top: number; height: number };
  part: { isFirst: boolean; isLast: boolean };
  data?: unknown;
  children?: PlacedNode[];
}

top is the node's margin-box y — relative to the page for top-level nodes, to the parent's border box below. height is what the fragment occupied on this page; a container that continues occupied through the page bottom, so split edges and backgrounds run to the page edge for free. part says whether this placement is the first and/or last fragment of its source node — the hook for drawing borders only where a container actually starts and ends, or suppressing a bookmark on continuations.

Spacing and synthetic wrappers dissolve into positions on the way out: the output contains only nodes you put in (or that your split/materialize closures returned).

License

MIT