NAME
PDF::Make::Builder - High level chainable PDF document builder
SYNOPSIS
use PDF::Make::Builder;
# Simple document
PDF::Make::Builder->new(file_name => 'report.pdf')
->add_page(page_size => 'A4')
->title('Quarterly Report')
->author('Jane Smith')
->add_h1(text => 'Introduction')
->add_text(text => 'Revenue increased 15% year-over-year.')
->add_image(image => 'chart.jpg', w => 400)
->save;
# With configuration defaults
my $b = PDF::Make::Builder->new(
file_name => 'styled.pdf',
configure => {
text => { font => { family => 'Times', size => 11 } },
h1 => { font => { family => 'Helvetica', size => 24 } },
},
);
$b->add_page(page_size => 'Letter', padding => 36)
->add_h1(text => 'Title')
->add_text(text => 'Body text inherits Times 11pt.')
->save;
DESCRIPTION
PDF::Make::Builder is the recommended high level API for creating PDF documents. It wraps the low level PDF::Make::Document and PDF::Make::Canvas with a chainable, bottom-left coordinate system that handles page layout, word-wrap, font management, and content placement automatically.
Every method returns $self, enabling fluent method chaining.
CONSTRUCTOR
new(%args)
my $b = PDF::Make::Builder->new(
file_name => 'output.pdf', # required
configure => { ... }, # optional defaults
page_offset => 0, # page number offset
);
file_name(Str, required) - Output filename..pdfis appended if missing.configure(HashRef) - Default font/style overrides keyed by element type (text,h1-h6,toc). Each accepts afontsub-hash withfamily,size,colour,line_height.
METHODS
Page Management
add_page(%args)
$b->add_page(
page_size => 'A4', # A4, Letter, Legal, A3, A5, B5, Tabloid
padding => 20, # margin in points
columns => 1, # number of text columns
background => '#fff', # hex background color
);
Add a new page and make it the current page. Finalises the previous page's content stream automatically.
open_page($num)
$b->open_page(2); # switch to page 2 (1-based)
Switch the current page to an existing page by number.
remove_page($index)
$b->remove_page(0); # remove first page (0-based)
Remove a page by 0-based index. Remaining pages are renumbered.
move_page($from, $to)
$b->move_page(2, 0); # move page 3 to position 1
Move a page from one position to another (0-based indices).
duplicate_page($index)
$b->duplicate_page(0); # duplicate first page
Create a copy of a page (layout only, not content).
rotate_page($index, $degrees)
$b->rotate_page(0, 90); # rotate first page 90 degrees
Rotate a page. Valid values: 0, 90, 180, 270.
set_columns($n)
$b->set_columns(2);
Set the number of text columns on the current page.
Metadata
All metadata methods set values on the underlying PDF Info dictionary.
title($text)
$b->title('My Document');
author($text)
$b->author('Jane Smith');
subject($text)
$b->subject('Annual Report');
keywords($text)
$b->keywords('pdf, report, 2026');
creator($text)
$b->creator('MyApp v2.0');
producer($text)
$b->producer('PDF::Make');
Text Content
add_text(%args)
$b->add_text(
text => 'Hello world',
font => { family => 'Helvetica', size => 12, colour => '#333' },
align => 'left', # left, center, right
indent => 0, # first-line indent in points
padding => 5, # vertical padding
);
Add a paragraph of word-wrapped text at the current cursor position. Overflows to new pages automatically.
add_h1(%args) .. add_h6(%args)
$b->add_h1(text => 'Chapter Title');
$b->add_h2(text => 'Section');
Add headings with preset font sizes. H1 is largest (24pt bold), H6 is smallest (10pt bold). Accepts the same arguments as add_text.
add_lines(@lines)
$b->add_lines(
'First line of text.',
'Second line of text.',
{ text => 'Styled line', font => { size => 14, colour => '#c00' } },
);
Convenience wrapper that calls add_text for each element in @lines. Each element may be a plain string (used as text => $str) or a HashRef of named arguments passed directly to add_text.
Shapes
add_line(%args)
$b->add_line(
x => 72, # start X
ex => 523, # end X
fill_colour => '#000',
type => 'solid', # solid, dashed, dotted
);
add_box(%args)
$b->add_box(x => 72, w => 200, h => 100, fill_colour => '#eee');
add_circle(%args)
$b->add_circle(cx => 200, cy => 400, r => 50, fill_colour => '#0066cc');
add_ellipse(%args)
$b->add_ellipse(cx => 200, cy => 400, rx => 80, ry => 40);
add_pie(%args)
$b->add_pie(
cx => 200, cy => 400, r => 50,
start_angle => 0, end_angle => 90,
fill_colour => '#cc0000',
);
Images
add_image(%args)
$b->add_image(
image => 'photo.jpg', # path to JPEG or PNG
w => 300, # width (height auto-calculated)
align => 'center', # left, center
);
Fonts
load_font(%args)
$b->load_font(family => 'Courier', size => 10, colour => '#333');
Set the default font for subsequent text operations.
load_ttf($path, %args)
$b->load_ttf('fonts/MyFont.ttf', name => 'MyFont');
$b->add_text(text => 'Set in MyFont', font => { family => 'MyFont' });
Load a TrueType font file and make it available as a font family, alongside the Standard 14 ones. name is the family name you then pass as font => { family => ... }; it defaults to the font's own PostScript name, so $b->load_ttf($path) on its own is usable.
The font is embedded in the saved document as a subset: only the glyphs actually drawn with it are included, and a /ToUnicode map is written so the text still extracts and searches as text. Measurement uses the font's own hmtx advances, so wrapping and alignment match what is drawn.
One file is one face. A loaded family has no bold or italic variants to synthesise, so bold => 1 against it draws the face as supplied; load the bold file under its own name if you need both.
Loading the same name twice from the same path is a no-op; from a different path it is an error.
Embedded fonts and save()
Nothing is written to the document until save() (or to_bytes), because the subset is built from the glyphs the finished content streams reference. A font that is loaded but never drawn with is not embedded at all.
Table of Contents
add_toc(%args)
$b->add_toc(title => 'Contents');
Initialise a table of contents. TOC entries are collected from headings and rendered during save().
Headers and Footers
add_page_header(%args)
$b->add_page_header(
cb => sub { my ($builder, $page, $num) = @_; ... },
height => 30,
);
Add a repeating header to all pages (current and future).
add_page_footer(%args)
$b->add_page_footer(
cb => sub { my ($builder, $page, $num) = @_; ... },
height => 30,
);
Add a repeating footer to all pages.
remove_page_header()
Remove the page header from subsequent pages.
remove_page_footer()
Remove the page footer from subsequent pages.
remove_page_header_and_footer()
Remove both header and footer from subsequent pages.
Outlines (Bookmarks)
add_outline($title, %args)
$b->add_outline('Chapter 1', page => 0);
$b->add_outline('Section 1.1', page => 0, parent => 'Chapter 1');
$b->add_outline('Chapter 2', page => 1, dest => 'FitH', top => 700);
Add a PDF outline (bookmark) entry.
page(Int, default 0) - 0-based page indexparent(Str) - Title of the parent outline for nestingdest(Str, default 'Fit') - Destination type: Fit, FitH, FitV, XYZleft,top,zoom(Num) - Destination parameters
Links and Actions
add_link(%args)
# External URL
$b->add_link(url => 'https://example.com', rect => [72, 700, 200, 720]);
# Builder coordinates (bottom-left origin)
$b->add_link(url => 'https://example.com', x => 72, y => 140, w => 220, h => 28);
# Internal page link
$b->add_link(page => 3, rect => [72, 670, 200, 690]);
# Named action (NextPage, PrevPage, FirstPage, LastPage, Print)
$b->add_link(action => 'NextPage', rect => [72, 640, 200, 660]);
# Link to external PDF
$b->add_link(file => 'other.pdf', file_page => 0, rect => [72, 610, 200, 630]);
Add a clickable link annotation. Requires rect as [x0, y0, x1, y1] in PDF coordinates. Provide one of: url (external), page (internal GoTo), action (named action), or file (external PDF).
For builder-layer coordinates (bottom-left origin), provide x, y, w, and h instead of rect. These are converted to PDF annotation coordinates automatically.
Attachments
attach(%args)
$b->attach(
name => 'data.csv', # required
data => $csv_string, # provide data or path
path => '/path/to/file', # alternative to data
mime => 'text/csv', # auto-detected if omitted
description => 'Raw export data',
);
Embed a file attachment in the PDF.
Watermarks
add_watermark(%args)
$b->add_watermark(
text => 'DRAFT', # required
opacity => 0.3,
rotation => 45,
color => [0.8, 0.2, 0.2],
size => 72,
);
Add a text watermark to all pages. See PDF::Make::Watermark for all options.
Layers (Optional Content Groups)
add_layer($name, %args)
$b->add_layer('Dimensions', visible => 1);
Create a named layer on the current page.
begin_layer($name)
$b->begin_layer('Dimensions');
Start drawing on the named layer.
end_layer()
$b->end_layer;
Stop drawing on the current layer.
Redaction
mark_redaction(%args)
$b->mark_redaction(
page => 0, # 0-based page index
rect => [100, 700, 300, 720],
overlay_color => [0, 0, 0],
overlay_text => 'REDACTED',
);
Mark a rectangular area for redaction.
apply_redactions()
Apply all redaction marks across all pages.
sanitize()
Remove all metadata (title, author, etc.) from the document.
Color Spaces
set_color_space($type, %args)
$b->set_color_space('sRGB');
$b->set_color_space('separation',
name => 'PANTONE 185 C', c => 0, m => 0.81, y => 0.69, k => 0);
Register a color space in the document.
Tagged PDF (Accessibility)
enable_tagging()
$b->enable_tagging;
Enable tagged PDF output with a structure tree for accessibility.
Form Fields
add_field(%args)
Structured mode (cursor-based component rendering):
$b->add_field(
type => 'text',
name => 'email',
label => 'Email Address',
w => 300,
default_value => 'user@example.com',
);
$b->add_field(
type => 'checkbox',
name => 'agree',
label => 'I agree to the terms',
w => 16, h => 16,
);
Raw mode (explicit coordinates):
$b->add_field(
type => 'text',
name => 'email',
raw_mode => 1,
rect => [72, 700, 300, 720],
default => 'user@example.com',
);
Single form API with two modes:
=over 4
=item * Structured mode via C<type>/C<name> (cursor-based layout,
labels, styled borders). Supported types: C<text>, C<checkbox>, C<radio>,
C<combo>/C<dropdown>, C<listbox>/C<list>, C<button>.
=item * Raw mode via C<raw_mode =E<gt> 1> or explicit coordinates
(C<rect>, C<x>, C<y>) for direct widget placement.
=back
See L<PDF::Make::Builder::Form::Field> for structured-mode properties.
flatten_form()
Burn form field appearances into page content (makes fields non-editable).
Encryption
encrypt(%args)
$b->encrypt(password => 'secret', algorithm => 'AES-256');
$b->encrypt(
user_password => 'read',
owner_password => 'admin',
permissions => 0x04,
algorithm => 'AES-128', # RC4-40, RC4-128, AES-128, AES-256
);
Configure PDF encryption. Applied during save().
Digital Signatures
sign(%args)
$b->sign(pkcs12 => 'cert.p12', password => 'secret', reason => 'Approval');
Configure a digital signature. Applied during save().
Annotations
add_note(%args)
Visual note (drawn callout box with lines of text):
$b->add_note(
lines => [
'Note: Review section 3.2 before final sign-off.',
{ text => '-- QA Team, 2026-04-21', size => 9, italic => 1 },
],
bg_colour => '#fffbeb',
colour => '#92400e',
x => 72, w => 300, h => 70,
);
Annotation note (PDF viewer sticky note):
$b->add_note(
rect => [72, 700, 92, 720],
text => 'Review this section',
icon => 'Comment', # Note, Comment, Key, Help, Paragraph, Insert
open => 1, # show expanded
);
When lines (or text as an ArrayRef) is supplied the method draws a coloured rectangle with the lines rendered inside it. Omit y to use cursor-relative placement.
Visual note options:
lines(ArrayRef, required) - Lines to render. Each element is a plain string or a HashRef withtext,size,colour,italickeys.x(Num, default 72) - Left edgey(Num) - Bottom edge; omit to use cursor positionw(Num, default 300) - Widthh(Num, default 70) - Heightbg_colour/fill_colour(Str, default '#fffbeb') - Background fillcolour(Str, default '#92400e') - Default text coloursize(Num, default 10) - Default font sizepadding(Num, default 12) - Inner padding in pointsline_gap(Num, default 14) - Vertical gap between lines in points
add_stamp(%args)
Visual stamp (drawn box with centred bold label):
$b->add_stamp(
text => 'APPROVED',
bg_colour => '#dcfce7',
colour => '#16a34a',
size => 24,
x => 72,
w => 200,
h => 50,
);
Annotation stamp (PDF viewer rubber-stamp annotation):
$b->add_stamp(
rect => [400, 700, 550, 750],
type => 'Approved', # Draft, Approved, Confidential, Final, etc.
);
When text is supplied the method draws a coloured rectangle with centred bold text on the current page. Provide y to place it at an absolute coordinate, or omit y to use cursor-relative placement (the cursor advances past the stamp automatically).
Visual stamp options:
text(Str, required) - The label to displayx(Num, default 72) - Left edgey(Num) - Bottom edge; omit to use cursor positionw(Num, default 200) - Widthh(Num, default 50) - Heightbg_colour/fill_colour(Str, default '#e5e7eb') - Background fillcolour(Str, default '#111827') - Text and border coloursize(Num, default 20) - Font sizeborder(Num, default 0) - Border width; 0 means no borderborder_colour(Str) - Border colour; defaults tocolour
Annotation stamp types: Draft, Approved, Experimental, NotApproved, AsIs, Expired, NotForPublicRelease, Confidential, Final, Sold, Departmental, ForLegalReview.
Bates Numbering
add_bates(%args)
$b->add_bates(
prefix => 'ACME',
start => 1,
digits => 6,
suffix => '-2026',
position => 'bottom_right',
);
Apply Bates numbering to all pages. See PDF::Make::Watermark for full options.
Custom Metadata
set_meta($key, $value)
$b->set_meta('Department', 'Engineering');
Set a custom metadata key in the PDF Info dictionary.
get_meta($key)
my $val = $b->get_meta('Department');
Get a custom metadata value.
Page Info
page_count()
my $n = $b->page_count;
Returns the number of pages in the document.
Text Extraction
extract_text($file, $page_index)
my $text = $b->extract_text('existing.pdf', 0);
Extract text from page $page_index (default 0) of an existing PDF file.
Opening Existing PDFs
open_existing($file, %args)
my $b = PDF::Make::Builder->open_existing('input.pdf',
file_name => 'output.pdf',
);
$b->add_page->add_text(text => 'New page appended');
$b->save;
Class method. Parses an existing PDF and creates a Builder with pages matching the original dimensions. New content can then be added and saved.
Output
to_bytes()
my $pdf_data = $b->to_bytes;
Finalise and return the PDF as a byte string (instead of writing to file). Useful for serving PDFs over HTTP or embedding in other formats.
Lifecycle
save()
$b->save;
Finalise all pages, apply encryption/signatures, render headers/footers and TOC, then write the PDF to file_name.
onsave($key, $method, %args)
Register a callback to run during save.
PAGE SIZES
Supported named sizes for add_page(page_size => ...):
A3 842 x 1191 pt
A4 595 x 842 pt
A5 420 x 595 pt
B5 499 x 709 pt
Letter 612 x 792 pt
Legal 612 x 1008 pt
Tabloid 792 x 1224 pt
SEE ALSO
PDF::Make for the distribution overview.
PDF::Make::Document, PDF::Make::Canvas for the low-level API.
PDF::Make::Builder::Text, PDF::Make::Builder::Font, PDF::Make::Builder::Page, PDF::Make::Builder::Image.
AUTHOR
LNATION <email@lnation.org>
LICENSE
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.