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. .pdf is appended if missing.
configure (HashRef) - Default font/style overrides keyed by element type (text, h1-h6, toc). Each accepts a font sub-hash with family, size, colour, line_height.
page_offset (Int, default 0) - Added to page numbers in headers/footers.

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 index
parent (Str) - Title of the parent outline for nesting
dest (Str, default 'Fit') - Destination type: Fit, FitH, FitV, XYZ
left, top, zoom (Num) - Destination parameters

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 with text, size, colour, italic keys.
x (Num, default 72) - Left edge
y (Num) - Bottom edge; omit to use cursor position
w (Num, default 300) - Width
h (Num, default 70) - Height
bg_colour / fill_colour (Str, default '#fffbeb') - Background fill
colour (Str, default '#92400e') - Default text colour
size (Num, default 10) - Default font size
padding (Num, default 12) - Inner padding in points
line_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 display
x (Num, default 72) - Left edge
y (Num) - Bottom edge; omit to use cursor position
w (Num, default 200) - Width
h (Num, default 50) - Height
bg_colour / fill_colour (Str, default '#e5e7eb') - Background fill
colour (Str, default '#111827') - Text and border colour
size (Num, default 20) - Font size
border (Num, default 0) - Border width; 0 means no border
border_colour (Str) - Border colour; defaults to colour

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.