跳转到内容
getnextpdf.com

从 FPDF 迁移到 NextPDF

本指南帮助你把一个基于 FPDF 的代码库迁移到 NextPDF 核心。FPDF 是部署最广泛的旧式 PHP 可移植文档格式(PDF)库之一,而它的绘图界面——由一个手动 x/y 游标驱动的 AddPageSetFontCellMultiCellWriteTextImageOutput——干净地映射到 NextPDF 自己的 cell/text API 上,因为 NextPDF 的低层绘图方法沿袭了相同的 FPDF/TCPDF 血统。不过,NextPDF 并非 FPDF 的直接替代克隆:它是一个现代 PDF 2.0 引擎,具备严格类型、字体子集化、签名、PDF/A 以及无障碍性(tagged PDF)。两个真正的转变是单位模型(NextPDF 以 PDF 点为单位工作;FPDF 默认为毫米)以及输出动词(一个带类型的 OutputDestination enum,而不是 FPDF 的 'I'/'D'/'F'/'S' 字符)。

核心中没有 FPDF 类垫片。请使用动词映射重写每个调用点。如果你想为一个 TCPDF 6.x 代码库做最小的初始改动,请改看 TCPDF 兼容适配器,它出货一条近乎源码兼容的直接替代路径;FPDF 没有这样的适配器。

Terminal window
composer require nextpdf/core:^3

在迁移期间保留 setasign/fpdf(或你的 fpdf/fpdf)已安装。在最终切换之后再移除它(参见安全迁移序列)。

FPDF 与 NextPDF 共享相同的心智模型:一份由页面构成的文档、一个游标(当前的 x/y 位置),以及在该游标处绘制或推进它的动词。SetXYCellLnMultiCell 在两个库中都会读取并改变游标,因此大多数过程式 FPDF 代码可以逐行翻译。

这些差异是刻意的,而非偶然的:

  • 单位。 FPDF 的构造函数(new FPDF($orientation, $unit, $size))默认为毫米。NextPDF 以 PDF 点为单位工作(1 pt = 1/72 in,ISO 32000-2 §7)。没有文档级的单位旋钮——把毫米一次性转换为点(pt = mm * 72 / 25.4)。
  • Y 方向对你而言保持不变。 与 FPDF 一样,NextPDF 的用户坐标把 y = 0 置于页面顶部并向下递增,因此游标算术可直接移植。NextPDF 在内部转换为 PDF 原生的左下角原点。
  • 构造是显式的。 FPDF 把朝向、单位与尺寸折叠进构造函数;NextPDF 接受一个不可变的 NextPDF\Core\Config 值对象(页面尺寸、边距、字体目录)以及一个显式的 addPage()
  • 始终 Unicode,始终子集化。 FPDF 的核心构建是 Latin-1,需要 tFPDF/UTF-8 变体才能支持 Unicode。NextPDF 通篇是 UTF-8,并始终把字体作为子集程序嵌入(ISO 32000-2 §9)。FPDF 的 AddFont/字体度量文件没有对应物;注册一个 TrueType/OpenType 字体目录,并按名称选择字体家族。

下文用到的核心入口点是 Document::createStandalone()Document::addPage()Document::setFont()Document::cell()Document::multiCell()Document::text()Document::write()Document::ln()Document::image()、游标访问器(setXY/setX/setY/getX/getY)、Document::output(?string, OutputDestination)Document::save(string $path): voidDocument::getPdfData(): string,以及 NextPDF\Core\Config 值对象。这些核心绘图、文本与输出方法的完整参考位于核心模块参考索引中,由 PHPDoc 自动生成。Html 模块 是 HTML-to-PDF 的相关读物,而不是本页这些动词的参考。

FPDF 的公共方法名由来已久且广为人知。下方的 NextPDF 列已对照核心源码签名核实(参见证据 / 可追溯性)。

FPDFNextPDF说明
new FPDF($orient, $unit, $size)Document::createStandalone($config)朝向/单位/尺寸构造参数变成一个 NextPDF\Core\ConfigpageSizemarginsfontsDirectory)。没有 $unit——以点为单位工作。默认 createStandalone() 页面是 A4 纵向。
$pdf->AddPage($orient, $size)$doc->addPage($size, $orientation)直接映射。$size 是一个 PageSize 值对象;$orientationOrientation enum(Portrait/Landscape)。
$pdf->SetFont($family, $style, $size)$doc->setFont($family, $style, $size)直接映射。$style 使用相同的 ''/'B'/'I'/'BI'(外加 'U' 下划线)代码。
$pdf->Cell($w, $h, $txt, $border, $ln, $align, $fill)$doc->cell($w, $h, $txt, $border, $newLine, $align, $fill)直接映射。$alignAlignment enum(Left/Center/Right/Justify);$border 接受 bool 或一个 'LTRB' 字符串;$ln 变成 bool $newLine
$pdf->MultiCell($w, $h, $txt, $border, $align, $fill)$doc->multiCell($w, $h, $txt, $border, $align)基于真实字体度量进行单词换行。没有 $fill 参数;如果你需要背景,请先绘制一个填充的 rect()
$pdf->Write($h, $txt, $link)$doc->write($h, $txt, $link)从游标开始的流动文本;$link 附加一个 URL 链接注释。
$pdf->Text($x, $y, $txt)$doc->text($x, $y, $txt)绝对位置文本。直接映射。
$pdf->Ln($h)$doc->ln($h)换行到左边距;0 = 默认行高。
$pdf->Image($file, $x, $y, $w, $h)$doc->image($file, $x, $y, $w, $h)直接映射;$x/$y/$w/$h 可空(null = 当前游标 / 固有尺寸)。
$pdf->SetXY($x, $y) / SetX / SetY$doc->setXY($x, $y) / setX / setY直接映射。getX()/getY() 读取游标。
$pdf->SetMargins($l, $t, $r)$doc->setMargins(new Margin($t, $r, $bottom, $l))一个 Margin 值对象;构造顺序是 (top, right, bottom, left)——不是 FPDF 的 (left, top, right)。FPDF SetMargins 没有底部参数(其底部边距来自 SetAutoPageBreak($auto, $margin)),因此请自行选择 $bottom——通常等于顶部边距,或传入自动分页边距。
$pdf->SetAutoPageBreak($auto, $margin)$doc->setAutoPageBreak($auto, $margin)直接映射。
$pdf->SetDrawColor / SetFillColor / SetTextColor$doc->setDrawColor / setFillColor / setTextColorRGB (r, g, b),或单个值表示灰度。
$pdf->Line / Rect / SetLineWidth$doc->line / rect / setLineWidth直接映射。rect() 接受一个样式字符串('S'/'F'/'DF')。
$pdf->SetTitle/SetAuthor/SetSubject/SetKeywords/SetCreator$doc->setTitle/setAuthor/setSubject/setKeywords/setCreator直接映射。落入 ISO 32000-2 §14 信息字典 / 可扩展元数据平台(XMP)。
$pdf->Output($dest, $name)$doc->output($name, OutputDestination::…)FPDF 目标字符(I/D/F/S)映射到 OutputDestination enum;注意参数顺序互换(在 NextPDF 中名称在前)。
$pdf->Output('S')$doc->getPdfData()返回 PDF 字节。
$pdf->Output('F', $path)$doc->save($path)写入到一个文件路径。
$pdf->GetStringWidth($s)(无公共方法)字符串宽度在 cell()/multiCell() 换行期间于内部计算;没有公共的按字符串测量动词。请通过 multiCell() 驱动换行,而不是手动测量。
<?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";

这个示例与 examples/04-text-and-fonts.php 对齐。它使用一个显式的页面尺寸、边距、一个已注册的字体目录,以及一个 FPDF 代码库已在使用的游标驱动 cell 模型。

<?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);
  • 单位。 你从 FPDF 复制过来的每个数值坐标、宽度、高度与边距,默认都以毫米计。在移植期间,把它们乘以 72 / 25.4 一次以得到点。两者混用会悄无声息地把一切尺寸算错。
  • Output() 参数顺序。 FPDF 是 Output($dest, $name);NextPDF 是 output($name, $dest)。目标是 OutputDestination enum,而不是一个字符。对于文件 / 字符串输出,请优先使用 save() / getPdfData()
  • SetMargins 顺序。 FPDF 是 (left, top, right);NextPDF 的 Margin 值对象是 (top, right, bottom, left)。请重新排序,不要照搬。
  • 字体。 FPDF 的 AddFont() + .php 度量文件没有对应物。把 TrueType/OpenType 文件放进字体目录,并用家族名称调用 setFont()。核心 Base14 名称(HelveticaTimesCourier)无需文件即可解析;在 PDF/A 或 tagged PDF 下,它们会被自动替换为一个可嵌入字体。
  • GetStringWidth 没有公共的字符串测量方法。如果你的 FPDF 代码通过测量字符串来手动布局列,请把那一段切换到 multiCell()(它基于度量换行)或固定宽度的 cell() 调用。

NextPDF 以单遍流式(架构决策记录 ADR-001)发出内容;峰值内存跟踪文档大小,而不是一个被保留的对象树。本指南示例的预算是 wall_ms: 2000, peak_mb: 128。对于长文档,请跨 addPage() 调用驱动内容——这与一个 FPDF 报表已在使用的循环形态相同。

  • 元数据。 SetTitle()/SetAuthor() 映射到写入 ISO 32000-2 §14 信息字典 / XMP 的带类型 setter。绝不要在那里存储秘密。
  • 图像路径。 image() 在读取前拒绝流包装器协议与内嵌的 NUL 字节。请传入应用控制的路径。
  • 没有文档内代码。 NextPDF 不执行任何文档内脚本;FPDF 中的任何东西都不会改变这一点。
陈述规范条款
页面格式/朝向映射到页面边界框。ISO 32000-2§7
字体作为嵌入的/子集的字体程序写入。ISO 32000-2§9
标题 / 元数据落入信息字典 / XMP。ISO 32000-2§14
线条、矩形与图像是内容流绘制。ISO 32000-2§8

NextPDF 产出 ISO 32000-2 内容;它不主张与 FPDF 的视觉一致性。每当你更换渲染器时,请重新审阅输出。

不适用。NextPDF 核心覆盖此处所述的 FPDF 迁移路径。


运行 FPDF(或 tFPDF)进行服务器端、过程式 PDF 生成的团队。如果你的代码是一连串由 SetXYLn 驱动的 AddPage / SetFont / Cell / MultiCell / Image / Output 调用,那么动词映射就覆盖了你的整个界面。

在范围内:FPDF 绘图动词、游标模型、字体、颜色、线条与矩形、元数据,以及输出。不在范围内:FPDF 的 AddFont 度量文件工具,以及第三方 FPDF 脚本扩展(条码、旋转、书签)——请把那些映射到相应的 NextPDF 模块(Barcode、Transforms、Navigation),此处不予涵盖。

行为上的兼容性,而非直接替代垫片:核心不提供 FPDF 类垫片。请重写每个调用点。这些动词高度对齐,是因为 NextPDF 的 cell/text API 共享 FPDF/TCPDF 血统,但单位模型、Output 参数顺序,以及 Margin/enum 类型有所不同——所以照搬是错的,翻译才是对的。

FPDF 构造NextPDF说明
$unit(默认 'mm'(无对应物)以 PDF 点为单位工作。在移植期间用 pt = mm * 72 / 25.4 一次性转换尺寸。
$orientation'P'/'L'addPage() 上的 Orientation enum,或交换 PageSize 宽/高横向 = 宽 > 高。
$size'A4'[w,h]Config->pageSizePageSize 值对象)命名格式变成显式的点尺寸;存在 PageSize::A4()A0() 以及 Letter/Legal 工厂。
SetMargins($l, $t, $r)Config->marginsMargin VO)构造顺序 (top, right, bottom, left)
AddFont($family, $style, $file)字体目录 + 按名称 setFont()丢弃度量文件;把 TTF/OTF 放进 Config->fontsDirectory
  • 字体目录。 FPDF 的每字体 AddFont 注册坍缩为一个字体目录加上 setFont() 家族匹配。从 Config->fontsDirectory(默认搜索路径)开始;当字体分布在多处时,通过 FontRegistry::addFontDirectory()Document::addFontDirectory() 注册额外目录。
  • 始终 Unicode。 没有 Latin-1 默认值,也没有单独的 tFPDF 构建;UTF-8 输入是常态。
  • 始终子集化。 NextPDF 始终子集化嵌入字体(ISO 32000-2 §9);FPDF 的字体嵌入选择没有对应物,也不需要。
  • 重新基线化字形。 字体匹配与回退是引擎特定的;一个 FPDF 字体别名可能需要一个确切的家族名称。替换差异是预期的,而非缺陷。
  • 单位转换(mm → pt)——最常见的移植错误;见上文。
  • Output 参数顺序互换,且目标变成一个 enum。
  • Margin / Alignment / Orientation 是带类型的对象/enum,而不是字符或位置式的 (l, t, r) 三元组。
  • 没有公共的 GetStringWidth——通过 multiCell() 驱动换行。
  • 独立的栅格化——在密集内容上,行换行与分页可能有所不同;请重新基线化视觉差异。

这些是有记录的行为差异,而非任一引擎的缺陷。

  • FPDF $unit 选择器——未建模(始终为点)。
  • AddFont() + .php/.z 度量文件——由一个字体目录替代。
  • GetStringWidth()——没有公共的字符串测量动词。
  • FPDF 的 'I'/'D'/'F'/'S' 目标字符——由 OutputDestination enum + save()/getPdfData() 替代。

依赖这些的代码不会逐字“迁移”。请用上面的各行重新表达它。

  1. 在 FPDF 旁边加入 nextpdf/core;暂且保留 FPDF 已安装。
  2. 选择一份低风险文档。通过单位映射转换构造函数,然后用动词映射移植每个动词。把每个毫米坐标转换为点。
  3. 把该文档的字体放进 Config->fontsDirectory 并按家族名称选择它们;丢弃 AddFont 调用。
  4. 为相同输入生成两份 PDF,并对它们做视觉差异比对。差异(字体替换、行换行)对于独立引擎是预期的——请逐文档接受它们。
  5. 把任何基于 GetStringWidth 的手动布局替换为 multiCell() 或固定宽度的 cell() 调用。
  6. 逐文档重复,风险最低者优先;在最后一次切换之前保持 FPDF 已安装。
  7. 在最终切换之后,从 composer.json 中移除 FPDF。
  • 在你更改代码之前,为有代表性的文档快照 FPDF 输出(黄金输入;字节将会不同)。
  • 对每份已迁移的文档,用你自己的检查(视觉差异 + 文本提取)来断言验收。NextPDF 的 cell/font 行为由 examples/04-text-and-fonts.php 加上核心 tests/ 的 Font 与文本输出套件演练。迁移验收是文档特定的,并仍由你负责。
  • 为每份已迁移的文档添加一个回归测试。

本页上每个 NextPDF 行为陈述,都由一个仓库内的源码签名、示例或架构决策记录(ADR)支撑,或者,对于 PDF 格式属性,由 frontmatter citations: 中的 ISO 32000-2 条款以及合规性表支撑。FPDF 行为仅被断言为“独立引擎——预期有记录在案的差异”;本页不主张任何仓库内制品无法证明的对等性。

NextPDF 行为主张仓库内证据(路径)
AddPage 映射到 addPage(?PageSize, Orientation): staticsrc/Core/Concerns/HasPages.phpaddPage())。
SetFont($family, $style, $size) 映射到 setFont(string, string, float): static''/'B'/'I'/'BI'/'U' 样式。src/Core/Concerns/HasTypography.phpsetFont())。
Cell 映射到 cell($w, $h, $txt, $border, $newLine, $align, $fill): staticsrc/Core/Concerns/HasTextOutput.phpcell())。
MultiCell 映射到 multiCell($w, $h, $txt, $border, $align): static(基于度量的换行)。src/Core/Concerns/HasTextOutput.phpmultiCell()wrapText())。
Write/Text/Ln 映射到 write()/text()/ln()src/Core/Concerns/HasTextOutput.phpwrite()text()ln())。
SetXY/SetX/SetY/GetX/GetY 直接映射;SetMargins 接受一个 Margin VO。src/Core/Concerns/HasPages.phpsetXY()getX()setMargins());src/ValueObjects/Margin.php(top, right, bottom, left))。
Image 映射到 image($file, ?$x, ?$y, ?$w, ?$h): static;拒绝 scheme/NUL 路径。src/Core/Concerns/HasImages.phpimage()assertImageFilePath())。
Line/Rect/SetLineWidth/SetDrawColor/SetFillColor/SetTextColor 直接映射。src/Core/Concerns/HasDrawing.phpline()rect()setLineWidth());src/Core/Concerns/HasColors.phpsetDrawColor()setFillColor()setTextColor())。
createStandalone() 默认页面是 A4 纵向(595.276 × 841.890 pt)。src/Core/Document.phpcreateStandalone());src/ValueObjects/PageSize.phpA4())。
输出目标是 OutputDestination enum(Inline/Download/File/String);Output('S')getPdfData()Output('F', $p)save($p)src/Contracts/OutputDestination.phpsrc/Core/Concerns/HasOutput.phpoutput())。
SetTitle/SetAuthor/… 映射到带类型的元数据 setter;落入信息字典 / XMP。src/Core/Concerns/HasMetadata.phpsetTitle()setAuthor());ISO 32000-2 §14(frontmatter citations:)。
字体始终作为子集程序嵌入。src/Core/Concerns/HasTypography.phpbuildFontData());ISO 32000-2 §9(frontmatter citations:)。
内容以单遍发出。docs/architecture/adr/ADR-001-stream-based-rendering-pipeline.md

两个包在最终切换之前都保持安装,因此按调用点回滚意味着把那个调用点还原到 FPDF 路径。在最终切换之后,回滚意味着从版本控制恢复 FPDF 与先前的代码。不涉及任何数据迁移。

参见性能。单遍模型移除了任何被保留的缓冲成本。新增的每文档成本是急切的字体解析(第 3 步),它可通过字体目录缓存。

  • 把毫米坐标照搬为点而不做 * 72 / 25.4 转换。
  • Output() 保持 FPDF 的 ($dest, $name) 顺序,或传入一个字符而不是 OutputDestination enum。
  • SetMargins($l, $t, $r) 直接照搬进 Margin(其顺序是 top, right, bottom, left)。
  • 指望 AddFont 度量文件能移植;请改为把 TTF/OTF 放进字体目录。
  • 去找一个 GetStringWidth 对应物;请用 multiCell() 做换行。
  • 指望字节/像素一致的输出(独立引擎——本指南从不主张直接替代或 100% 兼容)。