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

@polotno/pdf-export

v0.11.0

Published

Convert Polotno JSON into vector PDFs in browsers and Node.js, with custom-font embedding and natively written PDF/X-4 and PDF/X-1a print output

Readme

@polotno/pdf-export

Export Polotno design JSON as a vector PDF. Text stays selectable, shapes stay vector, and custom fonts are embedded.

The package supports regular PDF and PDF/X-4. It also supports the legacy PDF/X-1a format. All modes work in Node.js and browsers without Ghostscript.

Runtime: Node.js 22.13 or later, or a modern browser.

License: Non-production use is free, with no time limit. Production use requires a Polotno subscription. Read the license.

Install

npm install @polotno/pdf-export

Usage

Use the default entry in Node and the browser entry in browsers.

import { jsonToPDFBytes } from '@polotno/pdf-export'; // Node
import { jsonToPDFBytes } from '@polotno/pdf-export/browser'; // browser

const bytes = await jsonToPDFBytes(json); // Uint8Array, both runtimes

Both entries accept the same export options. blockPrivateNetwork and fetchGuard are Node-only.

All export functions accept metadata with title, author, application, and producer fields.

Node.js

import { jsonToPDF } from '@polotno/pdf-export';

await jsonToPDF(json, './output.pdf'); // writes the file

Browser

import { jsonToPDFBlob } from '@polotno/pdf-export/browser';

const blob = await jsonToPDFBlob(store.toJSON()); // application/pdf

Print export

| Mode | Option | Use | Transparency | | ----------------- | -------------- | ------------------------- | ------------------------------------------- | | Regular PDF | Omit pdfx | General vector PDF | Preserved | | PDF/X-4 | pdfx: 'x-4' | Preferred print format | Preserved | | PDF/X-1a (legacy) | pdfx: 'x-1a' | Vendors that require X-1a | Transparent pages become opaque CMYK images |

PDF/X-4 is the preferred print format. If your vendor requires PDF/X-1a, use that format. Both PDF/X modes need an ICC output intent. Ask your vendor for the correct profile and identifier. The deprecated pdfx1a: true option still selects PDF/X-1a, but it cannot be combined with pdfx.

import { readFile } from 'node:fs/promises';

const profile = new Uint8Array(await readFile('./CoatedFOGRA39.icc'));
await jsonToPDF(json, './print-ready.pdf', {
  pdfx: 'x-4',
  outputIntent: { profile, identifier: 'FOGRA39' },
  colorMode: 'cmyk', // optional for X-4; default is 'preserve-rgb'
});

Both runtimes accept profile bytes. Fetch the bytes in a browser:

const response = await fetch('/CoatedFOGRA39.icc');
const profile = new Uint8Array(await response.arrayBuffer());

Common European profiles include Coated FOGRA39 and PSO Coated v3. GRACoL is common in North America.

PDF/X-4 preserves ICC-based RGB by default. colorMode: 'cmyk' converts fills, strokes, and gradients through the output intent. Raster images stay ICC-based RGB. Pure RGB black can become rich black. The exporter has no black-generation option.

PDF/X-1a is always CMYK and does not accept colorMode. It handles each page as follows:

  • An opaque page stays vector, with selectable text and spot separations.
  • A transparent process-color page becomes one opaque CMYK image. The pdfxRasterDpi option sets its resolution and defaults to 300.
  • A page that combines transparency with a spot color causes an error.

Transparency includes opacity, shadows, soft image alpha, SVG masks, and transparent gradients. onWarning reports each flattened page and lost process-color overprint. The default handler uses console.warn.

The color engine loads only for PDF/X. The X-1a rasterizer loads only for transparent pages. Both components are included. Node flattening requires Node.js 22.13 or later. Browser flattening requires OffscreenCanvas (Safari 16.4 or later). A strict browser CSP must allow script-src 'wasm-unsafe-eval' for CMYK conversion.

Spot colors and overprint

Map a design color to a named separation ink:

await jsonToPDF(json, './output.pdf', {
  pdfx: 'x-4',
  outputIntent: { profile, identifier: 'FOGRA39' },
  spotColors: {
    'rgba(255,215,0,1)': {
      name: 'Gold Foil',
      cmyk: [0, 0.15, 0.5, 0], // fallback for viewers without spot support
      overprint: true,
    },
  },
});

Colors match by value, not syntax or alpha. For example, #FFD700 and rgb(255,215,0) select the same ink. Text, lines, figures, and SVG elements support spot colors. The cmyk process-color fallback uses values from 0 to 1.

overprint: true prints the spot ink over the process colors. This setting can prevent white halos around a foil or varnish plate.

PDF/X-4 preserves spot-color transparency. PDF/X-1a rejects a page that combines a spot color with transparency. SVG masks and clip paths count as transparency. line and table colors ignore alpha. Use element opacity for these elements.

Layout and image detail

Set the bleed in pixels on each page. Then include it during export:

const json = {
  pages: [{ bleed: 36, children: [] }],
};

await jsonToPDF(json, './print-ready.pdf', {
  includeBleed: true,
  cropMarkSize: 18,
});

bleed sets all four sides. bleedTop, bleedRight, bleedBottom, and bleedLeft override individual sides. A value of 0 disables that side.

cropMarkSize reserves a separate margin and draws crop marks at the trim edge. Element coordinates stay relative to the trim corner. Page backgrounds extend through the bleed area.

dpi converts design pixels to PDF points. The option overrides the design DPI, which defaults to 72. The conversion is points = pixels * 72 / dpi.

imagePpi limits the raster detail per printed inch. It defaults to 300, the print-shop standard. Use 450 for premium stock or 150 for proofs. This option only removes pixels.

Set imagePpi for the output medium, not for a target file size. A lower value can create a larger file because a JPEG below the limit embeds unchanged.

imageQuality sets the JPEG quality (0 to 1) for photos the exporter must decode — a cropped, filtered, or downsampled photo. It defaults to 0.85. Only opaque images with a JPEG source re-encode as JPEG. PNG art, transparent images, and unmodified pass-through JPEGs never do. Set imageQuality: 1 to embed decoded rasters lossless instead; expect several times larger files.

An untouched crop or aspect fit does not decode the photo at all: the exporter embeds the source bytes once and writes the crop into the page. Different crops of one photo share a single embedded image.

A PDF/X file also embeds the outputIntent ICC profile you pass. A large press profile (ECI FOGRA39 is 1.8 MB, about 1.4 MB compressed) sets the minimum size of every file. Your print vendor can supply a smaller profile when file size matters.

Fonts and text

The exporter embeds design fonts and Google Fonts as subsets. Regular PDFs can keep standard PDF fonts unembedded. When a font lacks a glyph, the exporter selects a fallback by writing system. Set fallbackFont to choose the family. Color emoji render in black and white.

A font that cannot load causes a FONT_FAILED error. Set skipFontError: true to substitute fallbackFont, or Helvetica when no fallback is set. A regular PDF draws a missing-glyph box when no font covers a character; PDF/X fails.

PDF/X embeds every font. It replaces Arial, Times New Roman, and Courier with Arimo, Tinos, and Cousine. Symbol and ZapfDingbats require a registered font file.

Text that is taller than its box shrinks by default. Set textOverflow: 'resize' to keep the authored font size and natural text height. The exporter does not support 'ellipsis'.

Progress and cancellation

const controller = new AbortController();

await jsonToPDF(json, './output.pdf', {
  onProgress: (progress) => console.log(progress),
  signal: controller.signal,
});

onProgress receives a value from 0 to 1. The value never decreases. It measures completed work, not elapsed time. Errors from this callback are logged.

signal cancels the export before the final write and leaves no partial output file. A later abort is ignored. An active response stream can finish first. Shared font downloads cannot be canceled.

Assets and network access

The browser fetches remote images, fonts, and SVG sources during export. These assets require CORS access. Google Fonts works by default. Other assets must send Access-Control-Allow-Origin or use a data: URL.

Node.js blocks cloud-metadata addresses by default. Use blockPrivateNetwork: true to also block loopback and private addresses. Use fetchGuard to replace these rules with your own URL policy. The exporter applies the policy to every redirect.

// Use the built-in strict policy.
await jsonToPDF(json, './output.pdf', {
  blockPrivateNetwork: true,
});

// Or replace the built-in policy.
await jsonToPDF(json, './output.pdf', {
  fetchGuard: (url) => isAllowed(url),
});

Errors

Export failures are plain Error objects with a stable code and structured details. Supported codes are DESIGN_INVALID, IMAGE_FAILED, FONT_FAILED, FETCH_FAILED, and EXPORT_FAILED. Use code instead of the message text in application logic.