setasign/fpdi
Import pages from existing PDFs and reuse them as templates in FPDF, TCPDF, or tFPDF. FPDI 2 is a modern, namespaced, PSR-4 compatible rewrite with major performance and memory improvements, no special PHP extensions required.
## Getting Started
### Minimal Setup
1. **Installation**:
Add FPDI to your Laravel project via Composer, paired with your preferred PDF library (FPDF, TCPDF, or tFPDF):
```bash
composer require setasign/fpdf setasign/fpdi
For TCPDF:
composer require tecnickcom/tcpdf setasign/fpdi
First Use Case: Import a PDF template and overlay dynamic content. Example with FPDF:
use setasign\Fpdi\Fpdi;
$pdf = new Fpdi();
$pdf->AddPage();
$pdf->setSourceFile(public_path('template.pdf'));
$tplId = $pdf->importPage(1); // Import first page
$pdf->useTemplate($tplId, 10, 10, 100); // Place template at (10,10) with 100mm width
$pdf->SetFont('Arial');
$pdf->SetXY(120, 50);
$pdf->Write(0, 'Dynamic Content Here');
$pdf->Output('output.pdf');
Key Files:
vendor/setasign/fpdi/src/ (for advanced use cases).Template-Based PDF Generation:
setSourceFile() to load a template, then importPage() to import specific pages. Overlay dynamic content with useTemplate().$pdf = new Fpdi();
$pdf->AddPage();
$pdf->setSourceFile(storage_path('templates/invoice.pdf'));
$tplId = $pdf->importPage(1);
$pdf->useTemplate($tplId, 10, 10, 180); // Full-page template
$pdf->SetFont('Arial', 'B', 12);
$pdf->SetXY(50, 50);
$pdf->Cell(0, 10, "Order #{$order->id}");
Dynamic Data Injection:
storage/app/templates/ and fetch them via Eloquent.$templatePath = storage_path("templates/{$user->plan}_contract.pdf");
$pdf->setSourceFile($templatePath);
$tplId = $pdf->importPage(1);
$pdf->useTemplate($tplId, 0, 0, 210); // Full-page
$pdf->SetFont('Times', '', 10);
$pdf->SetXY(100, 100);
$pdf->MultiCell(0, 5, $user->customTerms);
Multi-Page Templates:
importPage() and useTemplate().$pages = [1, 3, 5]; // Pages to import
foreach ($pages as $page) {
$tplId = $pdf->importPage($page);
$pdf->AddPage();
$pdf->useTemplate($tplId, 0, 0);
// Add dynamic content per page
}
Integration with Laravel Services:
namespace App\Services;
use setasign\Fpdi\Fpdi;
class PdfGenerator {
public function generateFromTemplate(string $templatePath, array $data): string {
$pdf = new Fpdi();
$pdf->AddPage();
$pdf->setSourceFile($templatePath);
$tplId = $pdf->importPage(1);
$pdf->useTemplate($tplId, 0, 0);
foreach ($data as $key => $value) {
$pdf->SetFont('Arial');
$pdf->SetXY(50, 50 + ($key * 10));
$pdf->Write(0, "$key: $value");
}
return $pdf->Output('S', 'generated.pdf');
}
}
$generator = new PdfGenerator();
$pdfContent = $generator->generateFromTemplate(
storage_path('templates/report.pdf'),
['user' => $user->name, 'date' => now()->format('Y-m-d')]
);
return response($pdfContent)->header('Content-Type', 'application/pdf');
Handling Complex Templates:
getTemplateSize() to calculate dimensions and setPageFormat() to match template sizes.$size = $pdf->getTemplateSize($tplId);
$pdf->setPageFormat([$size['width'], $size['height']], 'P');
Template Size Mismatch:
useTemplate() fails if the template dimensions don’t match the page. Use getTemplateSize() to debug:
$size = $pdf->getTemplateSize($tplId);
error_log("Template size: {$size['width']}x{$size['height']}");
$pdf->setPageFormat([$size['width'], $size['height']], 'P');
Memory Exhaustion:
$pdf->setSourceFile(Storage::disk('s3')->readStream('large_template.pdf'));
Font and Encoding Issues:
$pdf->SetFont('Arial', '', 12); // Fallback to a standard font
Recursive PDF Structures:
TCPDF-Specific Quirks:
Fpdi class (setasign\Fpdi\Tcpdf\Fpdi) has slight API differences (e.g., AddPage() vs AddPage()).use setasign\Fpdi\Tcpdf\Fpdi;
$pdf = new Fpdi('P', 'mm', 'A4');
Dynamic Content Overlap:
getTemplateSize() to align content:
$size = $pdf->getTemplateSize($tplId);
$pdf->SetXY(10, $size['height'] - 30); // Place text at bottom
Log Template Metadata:
Use getInfo() to inspect PDF properties:
$info = $pdf->getInfo();
error_log("PDF Info: " . print_r($info, true));
Visual Debugging:
$pdf->Output('debug_' . time() . '.pdf');
Handle Exceptions: Wrap FPDI operations in try-catch blocks:
try {
$tplId = $pdf->importPage($pageNumber);
} catch (\setasign\Fpdi\PdfParser\PdfParserException $e) {
Log::error("Failed to import page: " . $e->getMessage());
// Fallback to a default template
}
Reuse Template Objects: Cache imported templates if reused across multiple PDFs:
static $cachedTemplates = [];
if (!isset($cachedTemplates[$templatePath])) {
$pdf->setSourceFile($templatePath);
$cachedTemplates[$templatePath] = $pdf->importPage(1);
}
$tplId = $cachedTemplates[$templatePath];
Stream Large Templates: Avoid loading entire PDFs into memory:
$stream = Storage::disk('s3')->readStream('large_template.pdf');
$pdf->setSourceFile($stream);
How can I help you explore Laravel packages today?