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 => 'MF1');

Load a TrueType font file and register it in the document.

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.