-
Notifications
You must be signed in to change notification settings - Fork 0
Advanced Features
modern-pdf-lib v0.15.1 — Layers, outlines, watermarks, linearization, file attachments, text extraction, incremental saves, and low-level PDF operators.
Optional Content Groups (OCGs), commonly called "layers", allow content to be toggled visible or hidden in a PDF viewer. Common use cases include print-only content, language overlays, CAD drawing layers, and conditional watermarks.
import {
PdfLayerManager,
PdfLayer,
beginLayerContent,
endLayerContent,
} from 'modern-pdf-lib';Manages the set of layers for a document and produces the /OCProperties dictionary written into the catalog.
const layers = doc.createLayerManager(); // returns PdfLayerManager
// Add layers (visible by default)
const backgroundLayer = layers.addLayer('Background', true);
const printLayer = layers.addLayer('Print Only', true);
const draftLayer = layers.addLayer('Draft Watermark', false); // hidden by default
// Query
layers.getLayer('Background'); // PdfLayer | undefined
layers.getLayers(); // PdfLayer[]
layers.removeLayer('Draft Watermark');| Method | Returns | Description |
|---|---|---|
addLayer(name, visible?) |
PdfLayer |
Create and register a new layer. |
getLayer(name) |
PdfLayer | undefined |
Look up a layer by name. |
getLayers() |
PdfLayer[] |
All registered layers. |
removeLayer(name) |
void |
Remove a layer by name. |
toOCProperties(registry) |
PdfDict |
Serialize the /OCProperties catalog entry. |
const layer = layers.addLayer('Annotations');
layer.getName(); // 'Annotations'
layer.isVisible(); // true
layer.setVisible(false); // hide by default
layer.getRef(); // PdfRef | undefined (after serialization)import { beginLayerContent, endLayerContent } from 'modern-pdf-lib';
const textLayer = layers.addLayer('Text Content');
// Wrap page operators in layer markers
page.pushOperators(beginLayerContent(textLayer));
page.drawText('Only visible when "Text Content" layer is on', {
x: 72, y: 700, size: 12,
});
page.pushOperators(endLayerContent());The operators produced are:
/OC /Text_Content BDC ← beginLayerContent(textLayer)
... drawing operators ...
EMC ← endLayerContent()
The PDF outline (bookmarks panel) provides hierarchical document navigation. Each item has a title, a destination, and optional visual styling.
import {
PdfOutlineTree,
PdfOutlineItem,
} from 'modern-pdf-lib';
import type {
OutlineDestination,
OutlineItemOptions,
} from 'modern-pdf-lib';const outline = doc.createOutlineTree(); // returns PdfOutlineTree
// Top-level item — navigate to page 0, fit whole page
const ch1 = outline.addItem('Chapter 1', {
type: 'page',
pageIndex: 0,
fit: 'Fit',
});
// Nested item — navigate to a specific vertical position
ch1.addChild('Section 1.1', {
type: 'page',
pageIndex: 1,
fit: 'FitH',
top: 700,
});
ch1.addChild('Section 1.2', {
type: 'page',
pageIndex: 3,
fit: 'XYZ',
left: 72,
top: 750,
zoom: 1.5,
}, {
bold: true,
italic: false,
color: { r: 0.2, g: 0.4, b: 0.8 },
});
// Named destination
outline.addItem('Appendix', {
type: 'named',
namedDestination: 'appendix-start',
});| Method | Returns | Description |
|---|---|---|
addItem(title, dest, opts?) |
PdfOutlineItem |
Add a top-level bookmark. |
removeItem(item) |
void |
Remove a top-level bookmark. |
items |
PdfOutlineItem[] |
Direct access to the items array. |
toDict(registry, pageRefs) |
PdfRef |
Serialize to a PDF /Outlines dict. |
PdfOutlineTree.fromDict(dict, resolver, pageRefMap) |
PdfOutlineTree |
Parse from an existing PDF. |
| Property / Method | Type | Description |
|---|---|---|
title |
string |
Displayed bookmark label. |
destination |
OutlineDestination |
Navigation target. |
children |
PdfOutlineItem[] |
Child items. |
isOpen |
boolean |
Whether children are expanded by default. |
color |
{ r, g, b } | undefined |
Text colour (0–1 range). |
bold |
boolean | undefined |
Bold text style. |
italic |
boolean | undefined |
Italic text style. |
addChild(title, dest, opts?) |
PdfOutlineItem |
Append a child bookmark. |
removeChild(item) |
void |
Remove a direct child. |
fit |
Parameters | Viewport |
|---|---|---|
'Fit' |
— | Fit page in window |
'FitH' |
top |
Fit page width; scroll to top
|
'FitV' |
left |
Fit page height; scroll to left
|
'FitB' |
— | Fit bounding box of page content |
'FitBH' |
top |
Fit bounding box width; scroll to top
|
'FitBV' |
left |
Fit bounding box height; scroll to left
|
'XYZ' |
left, top, zoom
|
Position at (left, top) with zoom level |
Add semi-transparent text watermarks to pages using the standard Helvetica font (no embedding required for watermarks).
import {
addWatermark,
addWatermarkToPage,
} from 'modern-pdf-lib';
import type { WatermarkOptions } from 'modern-pdf-lib';await addWatermark(doc, {
text: 'CONFIDENTIAL',
fontSize: 60,
color: { r: 0.8, g: 0.1, b: 0.1 },
opacity: 0.25,
rotation: 45,
position: 'center', // 'center' | 'top' | 'bottom'
});const registry = doc.getRegistry();
addWatermarkToPage(page, {
text: 'DRAFT',
fontSize: 80,
color: { r: 0.7, g: 0.7, b: 0.7 },
opacity: 0.3,
rotation: 45,
}, registry);| Option | Type | Default | Description |
|---|---|---|---|
text |
string |
required | Watermark text. |
fontSize |
number |
60 |
Font size in points. |
color |
{ r, g, b } |
Light gray | RGB fill colour. |
opacity |
number |
0.3 |
Transparency (0 = invisible, 1 = opaque). |
rotation |
number |
45 |
Rotation angle in degrees. |
position |
'center' | 'top' | 'bottom' |
'center' |
Vertical placement. |
Linearized (or "fast web view") PDFs are structured so that a browser can begin displaying the first page before the entire file has been downloaded.
import { linearizePdf } from 'modern-pdf-lib';
const pdfBytes = await doc.save();
const linearBytes = await linearizePdf(pdfBytes);
// linearBytes is a Uint8Array suitable for streaming
await writeFile('output-linear.pdf', linearBytes);Linearization rewrites the cross-reference table and reorders objects so that first-page objects appear at the beginning of the file. The resulting PDF remains fully compatible with all PDF viewers.
Embed arbitrary files within a PDF document. Embedded files are stored as /EmbeddedFile streams referenced from the catalog's /Names /EmbeddedFiles name tree.
import {
attachFile,
getAttachments,
removeAttachment,
} from 'modern-pdf-lib';
import type { EmbeddedFile } from 'modern-pdf-lib';const csvData = new TextEncoder().encode('name,value\nfoo,1\nbar,2');
doc.attachFile({
name: 'data.csv',
data: csvData,
mimeType: 'text/csv',
description: 'Source data for Figure 3',
creationDate: new Date('2025-01-01'),
modificationDate: new Date(),
});const files = doc.getAttachments();
// files: EmbeddedFile[]
for (const file of files) {
console.log(file.name, file.mimeType, file.data.byteLength);
}interface EmbeddedFile {
name: string;
data: Uint8Array;
mimeType: string;
description?: string;
creationDate?: Date;
modificationDate?: Date;
}Note: PDF/A-1 and PDF/A-2 prohibit embedded file attachments. Use PDF/A-3 if you need to embed files in an archival document.
Extract text content from parsed content streams, with optional position tracking.
import {
extractText,
extractTextWithPositions,
} from 'modern-pdf-lib';
import type { TextItem } from 'modern-pdf-lib';const operators = page.parseContentStream();
const resources = page.getResources();
const text = extractText(operators, resources);
console.log(text);
// "Hello, world!\nThis is the second line."const items: TextItem[] = extractTextWithPositions(operators, resources);
for (const item of items) {
console.log(
`"${item.text}" at (${item.x.toFixed(1)}, ${item.y.toFixed(1)})`,
`font: ${item.fontName}, size: ${item.fontSize}pt`,
);
}interface TextItem {
text: string; // Extracted string
x: number; // Left edge in user-space units
y: number; // Baseline in user-space units
width: number; // Approximate rendered width
height: number; // Approximate rendered height (based on font size)
fontSize: number; // Font size in user-space units
fontName: string; // Font resource name (e.g. "/F1")
}Supported operators:
| Operator | Meaning |
|---|---|
Tj |
Show string |
TJ |
Show array of strings with kerning |
' |
Move to next line and show string |
" |
Set word/character spacing, move to next line, show string |
Td, TD, Tm, T*
|
Text positioning |
Tf |
Font and size selection |
q, Q, cm
|
Graphics state save/restore and matrix |
Supported encodings: WinAnsiEncoding (standard 8-bit) and Identity-H CID fonts with /ToUnicode CMaps.
An incremental save appends only modified or new objects to the end of the original PDF file, leaving the original bytes untouched. This is essential for:
- Digital signature preservation — Signatures are invalidated by full rewrites; incremental saves preserve the original signed bytes.
- Performance on large files — Saving a minor change to a 100 MB PDF takes milliseconds instead of seconds.
- Layered history — Each incremental update is a recoverable layer.
import { saveIncremental } from 'modern-pdf-lib';
const originalBytes = await readFile('signed.pdf');
const doc = await PdfDocument.load(originalBytes);
// Make non-destructive changes (annotations, metadata)
const page = doc.getPage(0);
page.addAnnotation(/* ... */);
const result = await saveIncremental(doc, originalBytes);
// result.bytes — complete updated PDF (original + appended section)
// result.newXrefOffset — byte offset of the new cross-reference sectioninterface IncrementalSaveResult {
bytes: Uint8Array;
newXrefOffset: number;
}The output PDF structure is:
[original PDF bytes — unchanged]
[new/modified objects]
[new cross-reference section with /Prev pointer]
[new trailer]
%%EOF
For direct content-stream manipulation, modern-pdf-lib exports every standard PDF operator as a typed function, plus the PDFOperator base class for building custom operators.
import {
pushGraphicsState, // alias: q
popGraphicsState, // alias: Q
concatMatrix, // cm
moveTo, // m
lineTo, // l
curveTo, // c
closePath, // h
rectangle, // re
stroke, // S
fill, // f
fillAndStroke, // B
setLineWidth, // w
setLineCap, // J
setLineJoin, // j
setDashPattern, // d
setMiterLimit, // M
setFlatness, // i
beginText, // BT
endText, // ET
setFont, // Tf
moveText, // Td
showText, // Tj
showTextAdjusted, // TJ
setCharSpacing, // Tc
setWordSpacing, // Tw
setTextScale, // Tz
setLeading, // TL
setTextRenderMode, // Tr
setTextRise, // Ts
setTextMatrix, // Tm
nextLine, // T*
setFillingColor, // sc / rg / k (depending on colour space)
setStrokingColor, // SC / RG / K
setFillingColorSpace, // cs
setStrokingColorSpace,// CS
paintXObject, // Do
// ... and 40+ more
} from 'modern-pdf-lib';
import { PDFOperator } from 'modern-pdf-lib';import { PDFOperator } from 'modern-pdf-lib';
class SetHalftone extends PDFOperator {
constructor(private readonly halftoneRef: PdfRef) {
super();
}
toString(): string {
return `${this.halftoneRef} sh\n`;
}
}import {
pushGraphicsState,
popGraphicsState,
moveTo, lineTo, closePath, stroke,
setLineWidth, setStrokingColor,
} from 'modern-pdf-lib';
page.pushOperators([
pushGraphicsState(),
setStrokingColor(1, 0, 0), // red
setLineWidth(2),
moveTo(72, 700),
lineTo(500, 700),
lineTo(500, 100),
closePath(),
stroke(),
popGraphicsState(),
].join(''));| Alias | Operator | Function |
|---|---|---|
pushGraphicsState |
q |
Save current graphics state |
popGraphicsState |
Q |
Restore last saved graphics state |
concatMatrix |
cm |
Modify current transformation matrix |
moveTo |
m |
Start new subpath |
lineTo |
l |
Append straight-line segment |
curveTo |
c |
Append cubic Bézier curve |
closePath |
h |
Close current subpath |
rectangle |
re |
Append rectangle |
stroke |
S |
Stroke path |
fill |
f |
Fill path (nonzero winding) |
fillAndStroke |
B |
Fill and stroke path |
endPath |
n |
End path without painting |
clip |
W |
Intersect current clip path |
setLineWidth |
w |
Set line width |
setLineCap |
J |
Set line cap style |
setLineJoin |
j |
Set line join style |
setDashPattern |
d |
Set dash pattern |
beginText |
BT |
Begin text object |
endText |
ET |
End text object |
setFont |
Tf |
Set font and size |
moveText |
Td |
Move text position |
showText |
Tj |
Show text string |
showTextAdjusted |
TJ |
Show text with individual glyph positioning |
nextLine |
T* |
Move to start of next line |
paintXObject |
Do |
Paint form XObject or image |
Full operator reference: ISO 32000-1:2008 Annex A.