Getting Started
This guide walks you through setting up @barrierbreak/a11ydocs-pdf and its companion licensing package. Follow Steps 1 through 4 for initial package setup and environment configuration (applicable to both frontend and backend projects), then jump to either the Frontend (Browser) or Backend (Node.js) implementation section depending on your architecture.
Shared Setup
1. Point npm at the private registry
The package is published to a private registry. Point the @barrierbreak scope at it once in your project's .npmrc (public npm stays untouched for every other package). The registry requires an auth token for install access, read from an NPM_TOKEN environment variable rather than committed to the file directly:
@barrierbreak:registry=https://npm.barrierbreak.net
//npm.barrierbreak.net/:_authToken=${NPM_TOKEN}
NPM_TOKEN should be set as an environment variable in your shell, CI secrets, or .env file rather than hardcoded — the ${NPM_TOKEN} syntax lets npm substitute it in at install time, so .npmrc itself is safe to commit without leaking credentials. Reach out to your BarrierBreak contact if you don't yet have a token.
2. Set NPM_TOKEN environment variable
Before installing, export the token referenced in .npmrc above so npm can authenticate against the private registry. Run this in your terminal, pasting your token after the =:
export NPM_TOKEN=<your-token-here>
This sets the variable for your current terminal session — re-run it (or add it to your shell profile, e.g. ~/.zshrc or ~/.bashrc) to persist across new terminal windows. In CI pipelines, set NPM_TOKEN as a secret or environment variable in your pipeline configuration instead.
3. Install the packages
Two packages are required: the PDF engine itself and the companion licensing package that enforces the license at runtime.
npm install @barrierbreak/a11ydocs-pdf
npm install @barrierbreak/a11ydocs-pdf-licensing
4. Configure your environment variables
The licensing package requires a license key and the license server URL supplied as environment variables.
- For Client-Side / Bundler environments (e.g., Vite, React):
Prefix keys as required by your bundler (e.g.,
VITE_for Vite):
VITE_A11YDOCS_LICENSE_KEY=<your-a11y-docs-license-key-here>
VITE_A11YDOCS_LICENSE_SERVER=https://a11ydocs.barrierbreak.net
- For Server-Side / Node.js environments:
Standard
process.envvariable names without client prefixes:
A11YDOCS_LICENSE_KEY=<your-a11y-docs-license-key-here>
A11YDOCS_LICENSE_SERVER=https://a11ydocs.barrierbreak.net
Frontend Integration (Browser & React/Vite)
Follow these steps if you are generating and triggering PDF downloads directly inside the user's browser.
5. Initialize the license client
The license client must be created and installed once at application startup — before any document is created or downloaded elsewhere in the app. In a typical React + Vite project, place this in your root entry file (e.g., main.tsx):
import { createLicenseClient } from "@barrierbreak/a11ydocs-pdf-licensing";
export const client = await createLicenseClient({
key: import.meta.env.VITE_A11YDOCS_LICENSE_KEY,
serverUrl:
import.meta.env.VITE_A11YDOCS_LICENSE_SERVER ||
"https://a11ydocs.barrierbreak.net",
});
client.install();
createLicenseClient reads your license key and validates it against the license server, and client.install() patches the license check into the PDF engine so every document created afterward is covered. Exporting client allows download handlers to track page consumption.
6. Create a document
Construct a document, add pages, and format text using standard point dimensions:
import { createDocument, rgb } from "@barrierbreak/a11ydocs-pdf";
const doc = createDocument({
title: "Hello PDF",
info: { author: "a11ydocs" },
});
const page = doc.addPage({ size: "A4" });
page.text("A11yDocs Library", {
x: 56,
y: 780,
fontSize: 18,
color: rgb(0.13, 0.2, 0.34),
});
page.textBlock("Hello from Browser.", {
x: 56,
y: 740,
width: 240,
fontSize: 12,
lineHeight: 16,
align: "center",
});
const bytes = doc.toUint8Array();
7. Download a PDF in the browser
To trigger a file download in client-side applications, serialize the document, check page usage against the license client, and trigger a download link:
import { client } from "./main";
const handleDownloadPdf = async () => {
try {
const doc = await formatPdf(); // Function returning your constructed doc
const bytes = doc.toUint8Array();
// Verify usage against license quotas
await client.checkout(doc.pageCount);
const arrayBuffer = bytes.slice().buffer;
const blob = new Blob([arrayBuffer], { type: "application/pdf" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `Report.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
} catch (e) {
console.error("PDF generation failed:", e);
setDownloadError(
e instanceof Error ? e.message : "Failed to download PDF.",
);
} finally {
await client.flush();
}
};
8. Download PDF from Google Chrome Extension
To download a PDF in a Chrome extension using this code, pass a generated blob URL into chrome.downloads.download(), which prompts the browser to save the file.
Make sure to include the "downloads" permission in your manifest.json file.
{
"permissions": ["downloads"]
}
import { client } from "./main";
const handleDownloadPdf = async () => {
try {
const doc = await formatPdf();
await client.checkout(doc.pageCount);
const url = URL.createObjectURL(await doc.toBlobAsync());
await chrome.downloads.download({
url,
filename: "Report.pdf",
saveAs: true,
});
setTimeout(() => URL.revokeObjectURL(url), 60_000);
} catch (e) {
console.error("PDF generation failed:", e);
} finally {
await client.flush();
}
};
Backend Integration (Node.js Server)
Follow these patterns when generating PDFs on a Node.js server, whether saving files directly to disk or serving them dynamically through an HTTP server (e.g., Express, Fastify, Next.js API Routes).
5. Create a Document Helper & Error Handler
Extract document formatting and error handling into a dedicated module. This file exports a builder function (buildReport) to construct and layout the PDF document dynamically based on input parameters (such as the document title and content lines), as well as a centralized error handler (handleError) to translate engine errors into proper HTTP status codes.
// reports.ts
import {
createDocument,
hex,
PdfEngineError,
PdfErrorCode,
} from "@barrierbreak/a11ydocs-pdf";
export interface ReportInput {
title: string;
lines?: string[];
}
/**
* Formats and constructs the PDF document layout based on input parameters.
*/
export function buildReport({ title, lines = [] }: ReportInput) {
const doc = createDocument({
language: "en-US",
info: { title, author: "pdf-api-server" },
});
const page = doc.addPage({ size: "A4" });
page.text(title, { x: 56, y: 770, fontSize: 20, color: hex("1c2f6b") });
let y = 710;
for (const line of lines) {
page.text(line, { x: 56, y, fontSize: 12 });
y -= 20;
}
return doc;
}
/**
* Translates PDF engine and license errors into standard HTTP responses.
*/
export function handleError(err: unknown, res: any) {
if (err instanceof PdfEngineError) {
const status =
err.code === PdfErrorCode.LICENSE_QUOTA_EXCEEDED
? 402
: err.code === PdfErrorCode.LICENSE_REQUIRED
? 401
: 500;
res.status(status).json({ error: err.code, message: err.message });
return;
}
console.error(err);
res.status(500).json({
error: "internal_error",
message: err instanceof Error ? err.message : String(err),
});
}
6. Serve and Download the PDF via API Handler
In your route handler, call the buildReport helper function with the desired title and dynamic text lines. Converting the document asynchronously via doc.toUint8ArrayAsync() handles authorization, generation, and license usage flushing automatically in a single call. Finally, set the appropriate Content-Disposition header with your formatted filename to trigger the download on the client side.
import { Request, Response } from "express";
import { buildReport, handleError } from "./reports.js";
export const reportHandler = async (req: Request, res: Response) => {
try {
// 1. Pass the desired title and text content to your formatter
const title = "My Custom Report";
const lines = [
"First line of context",
"Second line of context",
"Third line of context",
];
const doc = buildReport({ title, lines });
// 2. Async output: authorizes with the license server, generates the PDF,
// and flushes usage back automatically — no extra licensing code needed.
const bytes = await doc.toUint8ArrayAsync();
// 3. Format filename safely and set download headers
const safeFilename = `${title.replace(/[^\w.-]+/g, "_")}.pdf`;
res.setHeader("Content-Type", "application/pdf");
res.setHeader(
"Content-Disposition",
`attachment; filename="${safeFilename}"`,
);
// 4. Send binary buffer to trigger response download
res.send(Buffer.from(bytes));
} catch (err) {
handleError(err, res);
}
};
Convenience helpers
A few options keep call sites short across both frontend and backend code:
import { createDocument, mm, rgb } from "@barrierbreak/a11ydocs-pdf";
// Document-wide text defaults: fill in font/size/color so calls stay terse.
const doc = createDocument({
defaults: { font: "Helvetica", fontSize: 12, color: rgb(0.1, 0.1, 0.1) },
});
// Author in physical units (mm/cm/inch) instead of raw points.
// Page builder methods are chainable.
doc
.addPage({ size: "A4" })
.text("Title", { x: mm(20), y: mm(270), fontSize: 18, bold: true })
.text("Body copy uses the document defaults.", { x: mm(20), y: mm(258) });
// Node.js shortcut for writing to disk (alias of writeToFile).
await doc.save("out.pdf");
- Units —
mm(),cm(),inch(), andpt()convert to PDF points. bold/italic— resolve to the right font face. See Text and Fonts.defaults— document-widefont,fontSize,color,kerning,direction, overridden per call.- Chaining — page builder methods return the page, so calls compose.
doc.save(path)— Node-only; in the browser usetoUint8Array()ortoBlob().- Top-left origin — see Coordinate system below.
Coordinate system
All positions and sizes are in points (1 pt = 1/72 inch). By default the origin is the bottom-left corner and y grows upward — the native PDF convention. So on an A4 page (842 pt tall) y: 800 is near the top and y: 40 is near the bottom.
If you prefer the screen/CSS convention where y grows downward from the top, pass origin: "top-left" when creating the page:
const page = doc.addPage({ size: "A4", origin: "top-left" });
page.text("Near the top", { x: 56, y: 56 }); // 56 pt down from the top edge
page.rect(56, 100, 200, 80, { stroke: "navy" }); // y is the box's top edge
With origin: "top-left":
- For text,
yis the baseline distance from the top. - For boxes and images (
rect,image,pathrectangles),yis the top edge. - Lines, circles, and ellipses flip their
y/cycoordinates accordingly.
This applies to drawing methods (text, textBlock, richText, rect, line, circle, ellipse, image, path) and annotation/form-field methods. The flow/template layout APIs always use bottom-left coordinates. Use the unit helpers (mm/cm/inch) with either origin.
Common imports
import {
appendPages,
createDocument,
editDocument,
extractPages,
mergeDocuments,
parseDocument,
rgb,
splitDocument,
} from "@barrierbreak/a11ydocs-pdf";
API reference
createDocument()— create a document builderPdfDocument— the document builder it returnsPdfPage— page builder for text, graphics, and annotations