setasign/fpdi-tcpdf
Lightweight bridge between FPDI and TCPDF that lets PHP developers import pages from existing PDF files directly into new TCPDF documents. Handy for merging files, adding stamps or watermarks, reordering pages, and assembling reports without rebuilding layouts from scratch.
What you are actually installing. setasign/fpdi-tcpdf is a thin Composer package: it pins compatible versions of setasign/fpdi and tecnickcom/tcpdf and ships the bridge class setasign\Fpdi\Tcpdf\Fpdi (a TCPDF subclass that pulls in FpdiTrait + FpdfTplTrait). It is archived as of 2020-02-19, so treat it as vendored legacy code, not a dependency you can count on. First action on any project that has it: composer why setasign/fpdi-tcpdf and composer show setasign/fpdi-tcpdf setasign/fpdi tecnickcom/tcpdf to see the versions you are actually locked to.
Where to look first. vendor/setasign/fpdi-tcpdf/src/Tcpdf/Fpdi.php — that one file is the whole bridge and it is MIT-licensed, which matters later. Then the FPDI docs for setSourceFile / importPage / useTemplate.
First use case: stamp dynamic data onto a supplied PDF. Almost every day-to-day use of this package is "customer/template PDF in, personalised PDF out."
use setasign\Fpdi\Tcpdf\Fpdi;
$pdf = new Fpdi('P', 'mm', 'A4', true, 'UTF-8', false);
$pdf->setPrintHeader(false);
$pdf->setPrintFooter(false);
$pdf->setAutoPageBreak(false);
$pageCount = $pdf->setSourceFile(storage_path('app/templates/invoice.pdf'));
for ($i = 1; $i <= $pageCount; $i++) {
$tpl = $pdf->importPage($i);
$size = $pdf->getTemplateSize($tpl); // in mm, the document unit
$pdf->AddPage($size['orientation'], [$size['width'], $size['height']]);
$pdf->useTemplate($tpl, 0, 0, $size['width'], $size['height']);
if ($i === 1) {
$pdf->SetFont('helvetica', '', 11);
$pdf->SetXY(150, 40);
$pdf->Cell(40, 6, 'INV-2024-0042', 0, 0, 'R');
}
}
return response()->streamDownload(
fn () => print $pdf->Output('invoice.pdf', 'S'),
'invoice.pdf',
['Content-Type' => 'application/pdf']
);
1. Template stamping as a service. Wrap the bridge in a single injectable class rather than instantiating Fpdi across controllers. This is the seam that lets you swap the library or vendor the bridge class later without touching call sites.
final class PdfStamper
{
public function __construct(private array $config) {}
public function stamp(string $sourcePath, array $fields): string
{
$pdf = $this->make();
// ... setSourceFile / importPage / useTemplate / Cell ...
return $pdf->Output('', 'S'); // raw bytes
}
}
2. Merge / append. Call setSourceFile() once per file, then loop pages. Do not re-call it per page — it resets parser state and is measurably slower on large sources.
foreach ($paths as $path) {
$pages = $pdf->setSourceFile($path);
for ($i = 1; $i <= $pages; $i++) {
$tpl = $pdf->importPage($i);
$size = $pdf->getTemplateSize($tpl);
$pdf->AddPage($size['orientation'], [$size['width'], $size['height']]);
$pdf->useTemplate($tpl, 0, 0, $size['width'], $size['height']);
}
}
3. Overlay / watermark. Import the content page as the background template, then draw on top. For repeated overlays (letterhead on every page) build it once as an FPDI template via the FpdfTplTrait and useTemplate it repeatedly instead of re-importing — this keeps the output size flat.
4. N-up imposition. Place several imported pages on one TCPDF page by computing a grid and passing explicit w/h to useTemplate, preserving aspect ratio yourself:
$size = $pdf->getTemplateSize($tpl);
$ratio = $size['height'] / $size['width'];
$w = $cellWidth;
$h = $w * $ratio;
$pdf->useTemplate($tpl, $x, $y, $w, $h);
5. Laravel integration tips. Bind K_PATH_CACHE to storage/app/tcpdf (created and writable) before instantiating — TCPDF writes temp files there and fails opaquely otherwise. For anything over a few dozen pages, push the work to a queue job, return the bytes from Output($name, 'S'), and Storage::put() them; stream from the controller afterwards.
6. The exit pattern (do this early). MIT licensing makes extraction trivial:
composer remove setasign/fpdi-tcpdf
composer require setasign/fpdi:^2.6 tecnickcom/tcpdf:^6.7
Then copy vendor/setasign/fpdi-tcpdf/src/Tcpdf/Fpdi.php into app/Support/Pdf/TcpdfFpdi.php, change the namespace to App\Support\Pdf, and rewrite the use setasign\Fpdi\Tcpdf\Fpdi; imports. The class is self-contained (it extends \TCPDF and only uses FPDI traits), so this decouples you from the abandoned package while keeping behaviour byte-identical. It also lets you unpin TCPDF, which is the real win — TCPDF itself is still maintained and your pinned copy almost certainly is not.
composer update that needs a newer tecnickcom/tcpdf will conflict with it. This alone justifies the extraction pattern above on day one.setPrintHeader(false), setPrintFooter(false), setAutoPageBreak(false) before adding imported pages, or TCPDF will draw headers/footers and inject page breaks into your imported content. Also set setMargins(0,0,0) when positioning with absolute coordinates.mm; the source PDF is in points. getTemplateSize() returns values in your document unit, so use those for both AddPage() and useTemplate() — never mix a hard-coded point size with an mm page.useTemplate(). The auto-scaling behaviour when only one dimension is supplied has bitten enough people that explicit values are the safer convention; compute aspect ratio yourself.AddPage() → useTemplate(). Calling useTemplate() before a page exists throws; calling importPage() after switching source files invalidates prior template IDs.setSourceFile() throws PdfParserException / CrossReferenceException on a file that opens fine in a viewer, encryption is the first thing to check. Wrap parsing in try/catch and surface a domain error rather than a 500.memory_limit in the job, and make sure K_PATH_CACHE exists and is writable. A K_PATH_CACHE pointing at a non-existent directory produces confusing TCPDF errors rather than a clear "permission denied."$pdf->SetCreator()/SetTitle() for sanity in output, and catch FPDI exceptions separately from TCPDF's Error() abort path. $pdf->Output($path, 'F') vs 'S' is the difference between a side effect and a return value — in Laravel always prefer 'S' and let the framework handle delivery.setPageFormat behaviour, subclass the bridge (or your vendored copy) rather than patching vendor/; the overridden methods worth knowing are _beginpage, _endpage, and setPageFormat, which is exactly where the FPDI/TCPDF impedance mismatch lives.How can I help you explore Laravel packages today?