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

p5.book

v1.0.2

Published

PDF book generator addon for p5.js 2.x

Readme

p5.book

npm version npm downloads GitHub Actions

A simple PDF book generator for p5.js 2.0.
Turn your generative sketches into real, multi-page PDFs — no build tools, no npm, just <script> tags.

Start Here

Setup

Add these three scripts to your HTML, in order:

<!-- 1. p5.js 2.0 -->
<script src="https://cdn.jsdelivr.net/npm/p5@2/lib/p5.min.js"></script>

<!-- 2. jsPDF (required by p5.book) -->
<script src="https://unpkg.com/jspdf@latest/dist/jspdf.umd.min.js"></script>

<!-- 3. p5.book -->
<script src="https://unpkg.com/p5.book@latest/p5.book.js"></script>

Quick Start

If you want to start fast, open the starter sketch above and remix it.

let book;

function setup() {
  // 5×8 inch book, 10 pages — canvas auto-created with correct aspect ratio
  book = createBook(5, 8, 10);

  // or use a named size:
  // book = createBook("A5", 10);
}

function draw() {
  // background fades from black to white across the book
  background(lerp(0, 255, book.progress));

  if (book.isFirstPage()) {
    fill(255);
    text("My Book", 50, 50);
  } else if (book.isLastPage()) {
    fill(0);
    text("The End", 50, 50);
  } else {
    fill(0);
    text("Page " + book.pageNumber, 50, 50);
  }

  // capture this frame — PDF downloads automatically on the last page
  book.addPage();
}

That's it! Run your sketch — it will loop through all pages and download book.pdf when done.

How It Works

Each call to draw() produces one page.
book.addPage() captures the canvas and adds it to the PDF.
After the last page, the PDF is saved and the sketch stops automatically.

setup()  →  createBook(w, h, pages)
draw()   →  [draw your art]  →  book.addPage()  →  repeat...  →  PDF saved!

API

createBook(...) — two ways to use it

Option A — named paper size:

book = createBook("A4", 10); // A4, 10 pages
book = createBook("A5", 10, "zine.pdf"); // A5, custom filename
book = createBook("letter", 20); // US letter

Supported names (case-insensitive): A3 A4 A5 A6 letter legal tabloid — any format jsPDF knows.

Option B — custom dimensions:

book = createBook(5, 8, 20); // 5×8 inches (default unit)
book = createBook(15, 20, 20, "cm"); // 15×20 cm
book = createBook(150, 200, 20, "mm"); // 150×200 mm
book = createBook(5, 8, 20, "in", "zine"); // with custom filename

You can also pass an options object as the last argument:

book = createBook(5, 8, 20, { imageType: "png" }); // lossless PNG (larger file)
book = createBook(5, 8, 20, { jpegQuality: 0.98 }); // JPEG quality 0–1 (default 0.92)
book = createBook(5, 8, 20, { progressBar: true }); // full-screen overlay instead of corner counter

| Unit | Description | | ------ | ---------------- | | "in" | Inches (default) | | "cm" | Centimeters | | "mm" | Millimeters | | "px" | Pixels | | "pt" | Points |

book.addPage()

Capture the current canvas as the next page. Call this once at the end of draw().

function draw() {
  // ... your drawing code ...
  book.addPage(); // always last
}

book.page

Current page index, 0-based. The first page is 0.

book.pageNumber

Current page number, 1-based. Easier to display to readers.

text("Page " + book.pageNumber, 20, 20); // shows "Page 1", "Page 2", ...

book.totalPages

The total number of pages you passed to createBook().

book.progress

A number from 0.0 (first page) to 1.0 (last page). Perfect for animations or colors that gradually evolve across the whole book.

// background gets lighter page by page
background(lerp(0, 255, book.progress));

// shape grows across the book
circle(width / 2, height / 2, book.progress * width);

book.isFirstPage() / book.isLastPage()

Convenience helpers so you don't have to write book.page === 0 etc.

if (book.isFirstPage()) {
  text("Cover", 50, 50);
} else if (book.isLastPage()) {
  text("The End", 50, 50);
} else {
  text("Page " + book.pageNumber, 50, 50);
}

book.isLeftPage() / book.isRightPage()

Check if the current page is on the left or right side of a spread. Only meaningful when setSpread(true) is enabled. Returns false for cover and back cover (which are solo pages).

book.setSpread(true);

function draw() {
  background(255);

  if (book.isLeftPage()) {
    // content for left pages
    textAlign(RIGHT);
    text("Page " + book.pageNumber, width - 20, 30);
  } else if (book.isRightPage()) {
    // content for right pages
    textAlign(LEFT);
    text("Page " + book.pageNumber, 20, 30);
  }

  book.addPage();
}

Note: Page numbering in spreads:

  • Page 0 (cover) — solo, neither left nor right
  • LTR (default): odd indices (1, 3, 5...) = left, even indices (2, 4, 6...) = right
  • RTL: odd indices (1, 3, 5...) = right, even indices (2, 4, 6...) = left
  • Last page (back cover) — solo, neither left nor right

book.setDPI(dpi)

Set the canvas DPI for high-quality print output. Call in setup(), before addPage().

book.setDPI(300); // standard print resolution

This automatically adjusts pixelDensity() based on your trim width, so you don't need to call pixelDensity() yourself.

book.setBleed(amount, [unit])

Add bleed and automatically enable print marks. Call this in setup(), before addPage().

book.setBleed(0.125); // 1/8" bleed — US standard
book.setBleed(3, "mm"); // 3mm bleed  — European standard

The PDF page expands to include bleed on all four sides plus a mark margin. Two sets of marks are drawn at each corner on every page:

| Mark | Style | Meaning | | --------------- | ------------ | ---------------------------------- | | Trim marks | solid lines | Where the paper gets cut | | Bleed marks | dashed lines | Where artwork should extend to |

book.bleed

A p5.Graphics buffer the same size as the full bleed area. Draw into it to fill the bleed zone — typically just a background() that extends to the cut edge.

function draw() {
  // draw into the bleed buffer — fills the bleed zone in the PDF
  book.bleed.background(220); // or an image, gradient, etc.

  // draw your trim content on the main canvas as usual
  fill(0);
  text("Page " + book.pageNumber, 20, 20);

  book.addPage();
}

Rules:

  • book.bleed is a p5.Graphics object — use any p5 drawing methods on it
  • Only the portion outside the trim box is visible in the PDF (the bleed zone)
  • If setBleed() was not called, book.bleed is a no-op — all calls are silently ignored
  • If setBleed() is called but you never draw into book.bleed, the bleed area stays blank (print marks are still drawn)

book.setCropMarks(enabled)

Show or hide crop marks (trim + bleed guides). setBleed() enables them automatically; call this to turn them off while keeping the bleed.

book.setBleed(0.125);
book.setCropMarks(false); // bleed is still there, marks are not drawn

book.setPrintMarks(enabled) is still supported as a backward-compatible alias. book.setCropMark(enabled) and book.setTrimMark(enabled) are also supported as aliases.

book.setSpread(viewEnabled, [exportEnabled])

Set spread behavior for viewer and export in one call. Call in setup(), before addPage().

book.setSpread(true); // backward-compatible: view=true, export=true
book.setSpread(true, false); // viewer spread on, export spread off
book.setSpread(true, true); // explicit: both on

Equivalent to:

book.setViewSpread(true);
book.setExportSpread(true);

book.setViewSpread(enabled)

Control spread pairing in the viewer/preview only.

book.setViewSpread(true); // show interior pages as spreads in viewer

book.setExportSpread(enabled)

Control spread pairing in main PDF export/print only.

book.setExportSpread(false); // export single pages even if viewer shows spreads

Common workflow (recommended):

book.setViewSpread(true);
book.setExportSpread(false);

Requirements:

  • Total page count must be even
  • Must be called before the first addPage()

When export spread is enabled:

  • Page 1 (cover) is displayed alone
  • Pages 2–3, 4–5, etc. are displayed as spreads
  • Last page (back cover) is displayed alone
  • PDF export automatically creates spread pages without inner bleed at the gutter

book.setDirection(dir)

Set reading direction. Default is "ltr" (left-to-right). Use "rtl" for right-to-left books (Arabic, Hebrew, manga, etc.). Call in setup(), before addPage().

book.setSpread(true);
book.setDirection("rtl"); // right-to-left reading order

When "rtl" is set:

  • Spread pairs are physically flipped in the PDF and viewer — the first inner page appears on the right side
  • isLeftPage() / isRightPage() return correct values for RTL layout
  • Viewer arrow keys flip: goes to the previous spread, goes to the next
  • textBox() column order is reversed and canvas.direction is set to "rtl" so Arabic/Hebrew text renders correctly

book.setSaddleStitch(enabled)

Enable saddle-stitch imposition button in the viewer. Page count must be divisible by 4.

book.setSaddleStitch(true);

When enabled, the viewer shows a dropdown to choose between:

  • PDF (all pages) — include cover + interiors
  • PDF (no cover) — interiors only (and in separate-cover-stock mode, also excludes first/last interior)
  • Saddle Stitch — printer spread imposition for saddle-stitch binding

book.setSkipCoversInMainPDF(enabled)

Control whether the main PDF export includes the cover and back cover.

book.setSkipCoversInMainPDF(true); // main PDF exports interior pages only
book.setSkipCoversInMainPDF(false); // include cover + back cover in main PDF

When enabled:

  • Download -> PDF exports interior pages only
  • Print (PDF mode) prints interior pages only
  • book.saveCover() still exports the cover file

book.setSeparateCoverStock(enabled)

Enable perfect-binding cover workflow when the cover is printed on a separate sheet.

book.setSeparateCoverStock(true);

When enabled:

  • Main PDF export automatically excludes:
    • front cover + back cover
    • first interior + last interior
  • book.saveCover() exports a 2-page cover PDF:
    • Page 1: back cover + spine + front cover
    • Page 2: first interior + inner spine + last interior

Inside spine styling (page 2 middle panel):

book.innerSpine = "#ffffff"; // default, blank/paper-like
book.innerSpine = "#f5f0e8"; // custom color

Disable to return to normal single-page cover export:

book.setSeparateCoverStock(false);

book.setSpineThickness(thickness, [unit])

Override the automatic spine calculation with an explicit value.

book.setSpineThickness(12); // 12 mm
book.setSpineThickness(0.47, "in"); // inches
book.setSpineThickness(null); // revert to auto-calculated spine thickness

If both setPageThickness() and setSpineThickness() are used, setSpineThickness() takes precedence.

book.bleedWidth / book.bleedHeight

Trim size + bleed on both sides, in the book's unit. Read-only. These are provided for reference — you don't need to use them to size your canvas anymore.

// book = createBook(5, 8, 10)  +  book.setBleed(0.125)
console.log(book.bleedWidth); // 5.25
console.log(book.bleedHeight); // 8.25

book.save([filename])

Manually trigger the PDF download. You usually don't need this — it's called automatically after the last addPage().

book.save("my-zine.pdf");

When setSpread(true) is enabled, this exports the spread-format PDF automatically.

book.saveAllPages([filename])

Export all pages as PDF (cover + interiors).

book.saveAllPages();

book.saveNoCover([filename])

Export interior-focused PDF.

book.saveNoCover();

Behavior:

  • Default: excludes front and back cover
  • With book.setSeparateCoverStock(true): also excludes first and last interior pages

book.saveCoversOnly([filename])

Export only cover pages as PDF.

book.saveCoversOnly();

book.saveSaddleStitch([filename])

Export a saddle-stitch imposition PDF. Page count must be divisible by 4.

book.saveSaddleStitch(); // downloads "book-saddle.pdf"
book.saveSaddleStitch("my-zine-print.pdf");

Pages are reordered into printer spreads for saddle-stitch binding:

  • For an 8-page book: [8,1], [2,7], [6,3], [4,5]

Note: This is called automatically when the user selects "Saddle Stitch" in the viewer dropdown (if setSaddleStitch(true) was enabled).

book.exportFrames([format])

Download every captured page as an individual image file. Useful for importing pages into InDesign, Affinity Publisher, Figma, or video compositing tools.

book.exportFrames(); // PNG (default)
book.exportFrames("png"); // explicit PNG
book.exportFrames("jpeg"); // JPEG, using the book's jpegQuality setting

Files are named <basename>-0001.png, -0002.png, … and downloaded one by one with a small stagger to avoid browser throttling. Also accessible via Frames (PNG) / Frames (JPG) in the viewer's download dropdown.

book.finish([filename])

Manually show the viewer and stop the loop. Use when you don't know the total page count upfront (e.g. text reflow).

let textLeft = "...long text...";

function draw() {
  background(255);
  textLeft = book.textBox(textLeft, 20, 20, width - 40, height - 40);
  book.addPage();

  if (!textLeft) {
    book.finish(); // all text placed — show viewer
  }
}

When a total page count is passed to createBook(), the viewer opens automatically after the last page — you don't need finish().

book.setViewerMode(mode)

Set which viewer mode opens by default after the book finishes rendering. Use "3d" when you want the detailed view to be the 3D book viewer.

book.setViewerMode("3d"); // open directly in the 3D viewer
book.setViewerMode("grid"); // open in thumbnail grid view
book.setViewerMode("flipbook"); // default page-by-page view

Accepted values are "flipbook" (default), "grid", and "3d". Call this in setup() before the viewer opens.

book.textBox(str, x, y, w, h)

Draw wrapped text into a rectangular box with automatic line breaks. Returns overflow text that didn't fit.

let overflow = "Long text here...";

function draw() {
  background(255);

  // render text, get what didn't fit
  overflow = book.textBox(overflow, 20, 20, width - 40, height - 40);

  book.addPage();

  // stop when all text has been placed
  if (!overflow) {
    book.finish();
  }
}

Respects current textSize(), textLeading(), textAlign(), book.letterSpacing(), and book.columnNum().

Chinese, Japanese, and Korean text is automatically detected and wrapped character-by-character (no space needed between characters).

book.columnNum(n, [gutter])

Set the number of columns for textBox(). Returns the current column count if called with no arguments.

book.columnNum(2, 20); // 2 columns, 20px gutter
book.columnNum(1); // back to single column

let cols = book.columnNum(); // get current count

Persists like textSize() — affects all subsequent textBox() calls until changed.

book.letterSpacing(px)

Set CSS letter-spacing. Persists like textSize(). Negative values tighten, positive loosen. Applied to both the main canvas and the bleed layer.

book.letterSpacing(-2); // tighten — good for large headlines
book.letterSpacing(4); // loosen — spaced-out labels
book.letterSpacing(0); // reset to normal

book.bleed.draw(fn)

Scoped drawing into the bleed layer. fn receives the bleed p5.Graphics object as its argument, so you can route drawing calls without prefixing every line with book.bleed..

book.bleed.draw((g) => {
  g.background(20);
  g.noFill();
  g.stroke(255);
  for (let i = 50; i < 800; i += 50) {
    g.ellipse(g.width / 2, g.height / 2, i, i);
  }
});

If setBleed() was not called, bleed.draw() is a no-op — the function is never called.

book.spine

A p5.Graphics buffer (200 × canvas-height px) for drawing the book spine. Its content appears in the 3D viewer as the spine face, and can be used to design your spine artwork.

function setup() {
  book = createBook(5.5, 8.5, 200);

  // draw spine content once at the start
  book.spine.background(20);
  book.spine.fill(200);
  book.spine.textAlign(CENTER, CENTER);
  book.spine.textSize(12);
  // rotated text runs bottom-to-top along the spine
  book.spine.push();
  book.spine.translate(100, book.spine.height / 2);
  book.spine.rotate(-HALF_PI);
  book.spine.text("My Book Title", 0, 0);
  book.spine.pop();
}

Or use the scoped helper:

book.spine.draw((g) => {
  g.background(20);
  g.fill(200);
  g.noStroke();
  g.textSize(12);
  g.push();
  g.translate(100, g.height / 2);
  g.rotate(-HALF_PI);
  g.textAlign(CENTER, CENTER);
  g.text("My Book Title", 0, 0);
  g.pop();
});

Notes:

  • The spine canvas is 200 px wide × canvas height tall. Scale your drawings accordingly.
  • If you never draw on book.spine, a default spine is auto-generated (dark gradient + filename).
  • Spine thickness in the 3D viewer is automatically calculated from totalPages (≈ 0.1 mm per leaf).

Full Example

<!DOCTYPE html>
<html>
  <head>
    <script src="https://cdn.jsdelivr.net/npm/p5@2/lib/p5.min.js"></script>
    <script src="https://unpkg.com/jspdf@latest/dist/jspdf.umd.min.js"></script>
    <script src="https://unpkg.com/p5.book@latest/p5.book.js"></script>
  </head>
  <body>
    <script>
      let book;

      function setup() {
        book = createBook(4, 6, 12); // 4×6 in, 12 pages — canvas auto-created
        book.setDPI(300); // high-res for print
        book.setBleed(0.125); // 1/8" bleed
        book.setSpread(true); // spread layout
        book.setSaddleStitch(true); // enable saddle-stitch option
      }

      function draw() {
        // bleed layer — extends to cut edge
        let hue = map(book.progress, 0, 1, 0, 360);
        colorMode(HSB);
        book.bleed.background(hue, 80, 100);

        // trim layer — main canvas
        colorMode(RGB);
        clear(); // keep transparent so bleed shows through

        // circle grows across the book
        noStroke();
        fill(255);
        circle(width / 2, height / 2, book.progress * width);

        // page number
        fill(255);
        textSize(20);
        if (book.isFirstPage()) {
          text("My Book", 20, 30);
        } else if (book.isLastPage()) {
          text("The End", 20, 30);
        } else {
          text("Page " + book.pageNumber, 20, 30);
        }

        book.addPage();
      }
    </script>
  </body>
</html>

Tips

  • CanvascreateBook() auto-creates the canvas at 500 px wide with your book's aspect ratio. No need to call createCanvas().
  • Resolution — use book.setDPI(300) in setup() for crisp print quality. This is preferred over pixelDensity() because it also resizes the canvas height so both axes are exactly on-spec.
  • Bleed size — standard bleed: 0.125 in (US) or 3 mm (Europe).
  • Spread layout — use book.setSpread(true) for books where the cover/back are solo and inner pages pair as spreads.
  • View vs export spread — most workflows prefer book.setViewSpread(true) with book.setExportSpread(false).
  • Saddle-stitch — enable book.setSaddleStitch(true) to add a printer spread export option (page count must be divisible by 4).
  • Text reflow — use book.textBox() with book.columnNum() to flow long text across multiple pages automatically. RTL books use book.setDirection("rtl") — column order and text direction are handled automatically.
  • Slow down — p5.book captures every frame, so frameRate(1) can give you more time to animate per page.
  • Unknown page count — don't pass totalPages to createBook(), use book.finish() when done.
  • Export frames — use book.exportFrames() or the Frames (PNG/JPG) option in the download dropdown to save each page as a standalone image.
  • Viewer shortcuts/ to flip pages, [ first page, ] last page, ? keyboard cheat sheet.
  • Viewer styling — override CSS variables in your stylesheet — see test/style.css for the full list.

Local Dev Testing

Use this when you want to test the library locally before publishing.

  1. Install dependencies:
npm install
  1. Build the library and copy it into the local test harness:
npm run test:public:prep

This creates public/test/p5.book.local.js from your current source.

  1. Start the docs dev server:
npm run dev
  1. Open the local test page:
http://localhost:4321/test/

Quick one-command flow:

npm run test:public

This runs the prep step and then starts the dev server.

Notes:

  • public/test/index.html is wired to ./p5.book.local.js, so /test/ always uses your local build.
  • Re-run npm run test:public:prep after library code changes to refresh the test bundle.

Release

Publishing to npm is automated from GitHub tags.

  1. Update the version in package.json.
  2. Commit and push your changes.
  3. Create and push a tag like v1.0.2.
  4. GitHub Actions will build the package and publish it to npm.

This workflow uses npm trusted publishing (--provenance), so you do not need an NPM_TOKEN secret.

Before the first release, enable trusted publishing for this package in npm and allow GitHub Actions for this repository.

How to verify the connection

  • Run npm whoami in your terminal. If it returns your npm username, your local npm auth is good.
  • Run npm publish --dry-run to confirm the package can build and that package.json metadata looks right.
  • Push a release tag like v1.0.2 and check the GitHub Actions tab for the Release to npm workflow.
  • If the workflow fails at publish time, the most common cause is that npm trusted publishing has not been enabled for the package yet.
  • If you want to confirm npm access manually, run npm ping and npm access ls-packages if you have the right npm account permissions.

License

MIT — free to use, remix, and share.