Skip to content
getnextpdf.com

Migrate from FPDF to NextPDF

This guide helps you move an FPDF-based codebase to NextPDF core. FPDF is one of the most widely deployed legacy PHP Portable Document Format (PDF) libraries, and its drawing surface — AddPage, SetFont, Cell, MultiCell, Write, Text, Image, Output driven by a manual x/y cursor — maps cleanly onto NextPDF’s own cell/text API, because NextPDF’s low-level drawing methods follow the same FPDF/TCPDF lineage. NextPDF is not a drop-in FPDF clone, though: it is a modern PDF 2.0 engine with strict types, font subsetting, signing, PDF/A, and accessibility (tagged PDF). The two real shifts are the unit model (NextPDF works in PDF points; FPDF defaults to millimetres) and the output verbs (a typed OutputDestination enum instead of FPDF’s 'I'/'D'/'F'/'S' characters).

There is no FPDF class shim in core. Rewrite each call site using the verb mapping. If you want the smallest initial change for a TCPDF 6.x codebase instead, see the TCPDF compatibility adapter, which ships a near-source-compatible drop-in path; FPDF has no such adapter.

Terminal window
composer require nextpdf/core:^3

Keep setasign/fpdf (or your fpdf/fpdf) installed while you migrate. Remove it after the final cutover (see safe migration sequence).

FPDF and NextPDF share the same mental model: a document made of pages, a cursor (the current x/y position), and verbs that draw at or advance that cursor. SetXY, Cell, Ln, and MultiCell all read and mutate the cursor in both libraries, so most procedural FPDF code translates line for line.

The differences are deliberate, not accidental:

  • Units. FPDF’s constructor (new FPDF($orientation, $unit, $size)) defaults to millimetres. NextPDF works in PDF points (1 pt = 1/72 in, ISO 32000-2 §7). There is no document-wide unit knob — convert mm to points once (pt = mm * 72 / 25.4).
  • Y direction stays the same for you. Like FPDF, NextPDF’s user coordinates put y = 0 at the page top and increase downward, so cursor arithmetic ports directly. NextPDF converts to PDF-native bottom-left origin internally.
  • Construction is explicit. FPDF folds orientation, unit, and size into the constructor; NextPDF takes an immutable NextPDF\Core\Config value object (page size, margins, fonts directory) and an explicit addPage().
  • Always Unicode, always subset. FPDF’s core build is Latin-1 and needs the tFPDF/UTF-8 variant for Unicode. NextPDF is UTF-8 throughout and always embeds fonts as subset programs (ISO 32000-2 §9). FPDF’s AddFont/font-metric files have no analogue; register a TrueType/OpenType font directory and select the family by name.

The core entry points used below are Document::createStandalone(), Document::addPage(), Document::setFont(), Document::cell(), Document::multiCell(), Document::text(), Document::write(), Document::ln(), Document::image(), the cursor accessors (setXY/setX/ setY/getX/getY), Document::output(?string, OutputDestination), Document::save(string $path): void, Document::getPdfData(): string, and the NextPDF\Core\Config value object. The full reference for these core drawing, text, and output methods lives in the core modules and the reference index, auto-generated from PHPDoc. The Html module is related reading for HTML-to-PDF, not the reference for the verbs on this page.

FPDF’s public method names are long-standing and well known. The NextPDF column below is confirmed against core source signatures (see Evidence / traceability).

FPDFNextPDFNotes
new FPDF($orient, $unit, $size)Document::createStandalone($config)Orientation/unit/size constructor args become a NextPDF\Core\Config (pageSize, margins, fontsDirectory). No $unit — work in points. Default createStandalone() page is A4 portrait.
$pdf->AddPage($orient, $size)$doc->addPage($size, $orientation)Direct map. $size is a PageSize value object; $orientation is the Orientation enum (Portrait/Landscape).
$pdf->SetFont($family, $style, $size)$doc->setFont($family, $style, $size)Direct map. $style uses the same ''/'B'/'I'/'BI' (plus 'U' underline) codes.
$pdf->Cell($w, $h, $txt, $border, $ln, $align, $fill)$doc->cell($w, $h, $txt, $border, $newLine, $align, $fill)Direct map. $align is the Alignment enum (Left/Center/Right/Justify); $border accepts bool or an 'LTRB' string; $ln becomes the bool $newLine.
$pdf->MultiCell($w, $h, $txt, $border, $align, $fill)$doc->multiCell($w, $h, $txt, $border, $align)Word-wraps on actual font metrics. No $fill argument; paint a filled rect() first if you need a background.
$pdf->Write($h, $txt, $link)$doc->write($h, $txt, $link)Flowing text from the cursor; $link attaches a URL link annotation.
$pdf->Text($x, $y, $txt)$doc->text($x, $y, $txt)Absolute-position text. Direct map.
$pdf->Ln($h)$doc->ln($h)Line break to the left margin; 0 = default line height.
$pdf->Image($file, $x, $y, $w, $h)$doc->image($file, $x, $y, $w, $h)Direct map; $x/$y/$w/$h are nullable (null = current cursor / intrinsic size).
$pdf->SetXY($x, $y) / SetX / SetY$doc->setXY($x, $y) / setX / setYDirect map. getX()/getY() read the cursor.
$pdf->SetMargins($l, $t, $r)$doc->setMargins(new Margin($t, $r, $bottom, $l))One Margin value object; constructor order is (top, right, bottom, left)not FPDF’s (left, top, right). FPDF SetMargins has no bottom argument (its bottom margin comes from SetAutoPageBreak($auto, $margin)), so choose $bottom yourself — commonly equal to the top margin, or pass the auto-page-break margin.
$pdf->SetAutoPageBreak($auto, $margin)$doc->setAutoPageBreak($auto, $margin)Direct map.
$pdf->SetDrawColor / SetFillColor / SetTextColor$doc->setDrawColor / setFillColor / setTextColorRGB (r, g, b), or a single value for grayscale.
$pdf->Line / Rect / SetLineWidth$doc->line / rect / setLineWidthDirect map. rect() takes a style string ('S'/'F'/'DF').
$pdf->SetTitle/SetAuthor/SetSubject/SetKeywords/SetCreator$doc->setTitle/setAuthor/setSubject/setKeywords/setCreatorDirect map. Lands in the ISO 32000-2 §14 information dictionary / Extensible Metadata Platform (XMP).
$pdf->Output($dest, $name)$doc->output($name, OutputDestination::…)FPDF destination chars (I/D/F/S) map to the OutputDestination enum; note the argument order swaps (name first in NextPDF).
$pdf->Output('S')$doc->getPdfData()Returns the PDF bytes.
$pdf->Output('F', $path)$doc->save($path)Writes to a file path.
$pdf->GetStringWidth($s)(no public method)String width is computed internally during cell()/multiCell() wrapping; there is no public per-string measurement verb. Drive wrapping through multiCell() instead of measuring by hand.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Contracts\Alignment;
use NextPDF\Core\Document;
// FPDF:
// $pdf = new FPDF(); // mm, A4 portrait
// $pdf->AddPage();
// $pdf->SetFont('Arial', 'B', 16);
// $pdf->Cell(40, 10, 'Invoice');
// $pdf->Output('F', 'out.pdf');
// NextPDF — points, default page is A4 portrait:
$doc = Document::createStandalone();
$doc->setTitle('Invoice');
$doc->addPage();
$doc->setFont('Helvetica', 'B', 16.0);
$doc->cell(113.4, 28.3, 'Invoice', false, true, Alignment::Left); // ~40mm x ~10mm in points
$doc->save(__DIR__ . '/out.pdf');
echo "Wrote out.pdf\n";

This example aligns with examples/04-text-and-fonts.php. It uses an explicit page size, margins, a registered fonts directory, and the cursor-driven cell model that an FPDF codebase already uses.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Contracts\Alignment;
use NextPDF\Contracts\OutputDestination;
use NextPDF\Core\Config;
use NextPDF\Core\Document;
use NextPDF\ValueObjects\Margin;
use NextPDF\ValueObjects\PageSize;
// Equivalent of: new FPDF('P', 'mm', 'A4') + SetMargins(20, 16, 20)
// i.e. FPDF left=20mm, top=16mm, right=20mm. FPDF SetMargins has no bottom
// argument, so we pick bottom = top = 16mm. Convert each mm to points
// (pt = mm * 72 / 25.4): 16mm = 45.354pt, 20mm = 56.693pt.
// Margin constructor order is (top, right, bottom, left) — NOT FPDF's (L, T, R).
$config = new Config(
pageSize: new PageSize(595.276, 841.890, 'A4'),
margins: new Margin(45.354, 56.693, 45.354, 56.693), // top,right,bottom,left in points
fontsDirectory: __DIR__ . '/fonts',
);
$doc = Document::createStandalone($config);
$doc->setTitle('Quarterly Report');
$doc->setAuthor('Finance');
$doc->addPage();
// SetFont + Cell, the FPDF way — but in points and with a real Unicode font.
$doc->setFont('DejaVuSans', 'B', 18.0);
$doc->setTextColor(30, 58, 138);
$doc->cell(0, 24.0, 'Quarterly Report', false, true, Alignment::Left);
$doc->setFont('DejaVuSans', '', 11.0);
$doc->setTextColor(0, 0, 0);
$doc->multiCell(0, 16.0, "Body text wraps on real font metrics. Unicode is "
. "native, so accented and non-Latin characters need no tFPDF variant — "
. "register the family in the fonts directory and select it by name.");
// Equivalent of $pdf->Output('D', 'report.pdf'):
$doc->output('report.pdf', OutputDestination::Download);
  • Units. Every numeric coordinate, width, height, and margin you copy from FPDF is in millimetres by default. Multiply by 72 / 25.4 to get points, once, during the port. Mixing the two silently mis-sizes everything.
  • Output() argument order. FPDF is Output($dest, $name); NextPDF is output($name, $dest). The destination is the OutputDestination enum, not a character. Prefer save() / getPdfData() for file / string output.
  • SetMargins order. FPDF is (left, top, right); the NextPDF Margin value object is (top, right, bottom, left). Re-order, do not transcribe.
  • Fonts. FPDF’s AddFont() + .php metric files have no equivalent. Put the TrueType/OpenType file in the fonts directory and call setFont() with the family name. Core Base14 names (Helvetica, Times, Courier) resolve without a file; under PDF/A or tagged PDF they are auto-substituted with an embeddable font.
  • GetStringWidth. There is no public string-measurement method. If your FPDF code measures strings to lay out columns by hand, switch that block to multiCell() (which wraps on metrics) or fixed-width cell() calls.

NextPDF emits content in a single streaming pass (architecture decision record ADR-001); peak memory tracks document size, not a retained object tree. The budget for this guide’s example is wall_ms: 2000, peak_mb: 128. For long documents, drive content across addPage() calls — the same loop shape an FPDF report already uses.

  • Metadata. SetTitle()/SetAuthor() map to typed setters writing the ISO 32000-2 §14 information dictionary / XMP. Never store secrets there.
  • Image paths. image() rejects stream-wrapper schemes and embedded NUL bytes before reading. Pass application-controlled paths.
  • No in-document code. NextPDF executes no in-document scripts; nothing in FPDF changes that.
StatementSpecClause
Page format/orientation map to the page boundary box.ISO 32000-2§7
Fonts are written as embedded/subset font programs.ISO 32000-2§9
Title / metadata land in the info dictionary / XMP.ISO 32000-2§14
Lines, rectangles, and images are content-stream painting.ISO 32000-2§8

NextPDF produces ISO 32000-2 content; it does not assert visual identity with FPDF. Re-review output whenever you change renderer.

Not applicable. NextPDF core covers the FPDF migration path described here.


Teams running FPDF (or tFPDF) for server-side, procedural PDF generation. If your code is a sequence of AddPage / SetFont / Cell / MultiCell / Image / Output calls driven by SetXY and Ln, the verb mapping covers your whole surface.

In scope: the FPDF drawing verbs, the cursor model, fonts, colors, lines and rectangles, metadata, and output. Out of scope: FPDF’s AddFont metric-file tooling and third-party FPDF script-extensions (barcodes, rotation, bookmarks) — map those to the corresponding NextPDF modules (Barcode, Transforms, Navigation), which are not covered here.

Behavioral compatibility, not a drop-in shim: core provides no FPDF class shim. Rewrite every call site. The verbs line up closely because NextPDF’s cell/text API shares FPDF/TCPDF lineage, but the unit model, the Output argument order, and the Margin/enum types differ — so a transcription is wrong, a translation is right.

FPDF constructNextPDFNotes
$unit ('mm' default)(no equivalent)Work in PDF points. Convert dimensions with pt = mm * 72 / 25.4 once during the port.
$orientation ('P'/'L')Orientation enum on addPage(), or swap PageSize width/heightLandscape = width > height.
$size ('A4', [w,h])Config->pageSize (PageSize value object)Named formats become explicit point dimensions; PageSize::A4()A0() and Letter/Legal factories exist.
SetMargins($l, $t, $r)Config->margins (Margin VO)Constructor order (top, right, bottom, left).
AddFont($family, $style, $file)fonts directory + setFont() by nameDrop the metric file; place the TTF/OTF in Config->fontsDirectory.
  • Fonts directories. FPDF’s per-font AddFont registration collapses to a fonts directory plus setFont() family matching. Start with Config->fontsDirectory (the default search path); register additional directories via FontRegistry::addFontDirectory() or Document::addFontDirectory() when fonts live in more than one place.
  • Always Unicode. No Latin-1 default and no separate tFPDF build; UTF-8 input is the norm.
  • Always subset. NextPDF always subsets embedded fonts (ISO 32000-2 §9); FPDF’s font-embedding choices have no equivalent and are not needed.
  • Re-baseline glyphs. Font matching and fallback are engine-specific; an FPDF font alias may need an exact family name. Substitution differences are expected, not defects.
  • Unit conversion (mm → pt) — the most common porting mistake; see above.
  • Output argument order swaps and the destination becomes an enum.
  • Margin / Alignment / Orientation are typed objects/enums, not characters or positional (l, t, r) triples.
  • No public GetStringWidth — drive wrapping through multiCell().
  • Independent rasterization — line wrap and pagination on dense content can differ; re-baseline visual diffs.

These are documented behavioral differences, not defects in either engine.

  • FPDF $unit selector — not modeled (always points).
  • AddFont() + .php/.z metric files — replaced by a fonts directory.
  • GetStringWidth() — no public string-measurement verb.
  • FPDF’s 'I'/'D'/'F'/'S' destination chars — replaced by the OutputDestination enum + save()/getPdfData().

Code that depends on these does not “migrate” verbatim. Re-express it with the rows above.

  1. Add nextpdf/core alongside FPDF; keep FPDF installed for now.
  2. Choose one low-risk document. Convert the constructor via the unit map, then port each verb with the verb map. Convert every mm coordinate to points.
  3. Place the document’s fonts in Config->fontsDirectory and select them by family name; drop the AddFont calls.
  4. Generate both PDFs for the same input and visually diff them. Differences (font substitution, line wrap) are expected for independent engines — accept them per document.
  5. Replace any GetStringWidth-based hand-layout with multiCell() or fixed-width cell() calls.
  6. Repeat per document, lowest risk first; keep FPDF installed until the last cutover.
  7. Remove FPDF from composer.json after the final cutover.
  • Snapshot FPDF output for representative documents before you change code (golden inputs; the bytes will differ).
  • For each migrated document, assert acceptance with your own check (visual diff
    • text-extraction). NextPDF’s cell/font behavior is exercised by examples/04-text-and-fonts.php plus the core tests/ Font and text-output suites. Migration acceptance is document-specific and remains your responsibility.
  • Add a regression test per migrated document.

Every NextPDF behavioral statement on this page is backed by an in-repo source signature, example, or architecture decision record (ADR), or, for PDF-format properties, by the ISO 32000-2 clauses in the frontmatter citations: and the Conformance table. FPDF behavior is asserted only as “independent engine — expect documented differences”; this page claims no parity that an in-repo artifact does not prove.

NextPDF behavioral claimIn-repo evidence (path)
AddPage maps to addPage(?PageSize, Orientation): static.src/Core/Concerns/HasPages.php (addPage()).
SetFont($family, $style, $size) maps to setFont(string, string, float): static; ''/'B'/'I'/'BI'/'U' styles.src/Core/Concerns/HasTypography.php (setFont()).
Cell maps to cell($w, $h, $txt, $border, $newLine, $align, $fill): static.src/Core/Concerns/HasTextOutput.php (cell()).
MultiCell maps to multiCell($w, $h, $txt, $border, $align): static (metric-based wrap).src/Core/Concerns/HasTextOutput.php (multiCell(), wrapText()).
Write/Text/Ln map to write()/text()/ln().src/Core/Concerns/HasTextOutput.php (write(), text(), ln()).
SetXY/SetX/SetY/GetX/GetY map directly; SetMargins takes a Margin VO.src/Core/Concerns/HasPages.php (setXY(), getX(), setMargins()); src/ValueObjects/Margin.php ((top, right, bottom, left)).
Image maps to image($file, ?$x, ?$y, ?$w, ?$h): static; rejects scheme/NUL paths.src/Core/Concerns/HasImages.php (image(), assertImageFilePath()).
Line/Rect/SetLineWidth/SetDrawColor/SetFillColor/SetTextColor map directly.src/Core/Concerns/HasDrawing.php (line(), rect(), setLineWidth()); src/Core/Concerns/HasColors.php (setDrawColor(), setFillColor(), setTextColor()).
createStandalone() default page is A4 portrait (595.276 × 841.890 pt).src/Core/Document.php (createStandalone()); src/ValueObjects/PageSize.php (A4()).
Output destination is the OutputDestination enum (Inline/Download/File/String); Output('S')getPdfData(), Output('F', $p)save($p).src/Contracts/OutputDestination.php; src/Core/Concerns/HasOutput.php (output()).
SetTitle/SetAuthor/… map to typed metadata setters; land in the info dictionary / XMP.src/Core/Concerns/HasMetadata.php (setTitle(), setAuthor()); ISO 32000-2 §14 (frontmatter citations:).
Fonts are always embedded as subset programs.src/Core/Concerns/HasTypography.php (buildFontData()); ISO 32000-2 §9 (frontmatter citations:).
Content is emitted single-pass.docs/architecture/adr/ADR-001-stream-based-rendering-pipeline.md.

Both packages stay installed until the final cutover, so per-call-site rollback means reverting that call site to the FPDF path. After the final cutover, rollback means restoring FPDF and the prior code from version control. No data migration is involved.

See Performance. The single-pass model removes any retained buffer cost. The new per-document cost is eager font resolution (step 3), which is cacheable through the fonts directory.

  • Transcribing millimetre coordinates as points without the * 72 / 25.4 conversion.
  • Leaving Output() in FPDF’s ($dest, $name) order, or passing a character instead of the OutputDestination enum.
  • Transcribing SetMargins($l, $t, $r) straight into Margin (whose order is top, right, bottom, left).
  • Expecting AddFont metric files to port; place the TTF/OTF in the fonts directory instead.
  • Reaching for a GetStringWidth equivalent; use multiCell() for wrapping.
  • Expecting byte/pixel-identical output (independent engines — this guide never claims a drop-in or 100% compatibility).