lmoreli / laravel-pdf
Modern PDF generation library for Laravel with multiple digital signatures support. Built on top of TCPDF with a fluent PHP 8.3+ API.
Requires
- php: ^8.3
- ext-openssl: *
- illuminate/support: ^10.0|^11.0|^12.0
- tecnickcom/tcpdf: ^6.7
Requires (Dev)
- orchestra/testbench: ^9.0|^10.0
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^11.0
This package is auto-updated.
Last update: 2026-03-05 00:07:02 UTC
README
Modern PDF generation library for Laravel with multiple digital signatures support. Built on PHP 8.3+ with a fluent API on top of TCPDF.
✨ Features
- 🎯 Fluent PHP 8.3+ API — Typed properties, enums, readonly classes, named arguments
- ✍️ Multiple Digital Signatures — Sign PDFs with two or more certificates (certification + approval)
- 📄 Full PDF Generation — Text, tables, images, HTML, barcodes, QR codes
- 🎨 HTML to PDF — Convert HTML/CSS content including tables and styles
- 🔧 Laravel Integration — Service Provider, Facade, config, Blade views
- 📊 Barcodes — QR Code, Code 128, EAN-13, DataMatrix, PDF417
- 🔒 Document Security — Encryption, password protection, permissions
- 🌐 Unicode — Full Unicode support (UTF-8, RTL, CJK)
- 📝 Certificate Support — PFX/P12, PEM, and file-based certificates
📦 Installation
composer require lmoreli/laravel-pdf
Laravel Auto-Discovery
The service provider and facade are auto-registered via Laravel's package discovery.
Publish Config (Optional)
php artisan vendor:publish --tag=laravelpdf-config
🚀 Quick Start
Basic PDF
use LMoreli\LaravelPDF\PDFDocument; $pdf = PDFDocument::create() ->setTitle('My Document') ->addPage() ->setFont('helvetica', 'B', 16) ->cell(0, 10, 'Hello World!', 0, 1, 'C') ->lineBreak(10) ->setFont('helvetica', '', 12) ->multiCell(0, 0, 'This is a paragraph of text.', align: 'J') ->save('/path/to/output.pdf');
From HTML
$pdf = PDFDocument::fromHTML('<h1>Hello</h1><p>World!</p>') ->save('/path/to/output.pdf');
Using Laravel Facade
use LMoreli\LaravelPDF\Facades\PDF; // In a controller: public function downloadReport() { $pdf = PDF::fromView('pdfs.report', ['data' => $reportData]); return $pdf->response('report.pdf'); }
From Blade View
$pdf = PDFDocument::fromView('pdfs.invoice', [ 'invoice' => $invoice, 'items' => $items, ]); return $pdf->response('invoice.pdf', inline: false); // Force download
✍️ Digital Signatures
Single Signature
use LMoreli\LaravelPDF\Support\Certificate; use LMoreli\LaravelPDF\Signature\SignatureAppearance; $certificate = Certificate::fromPFX('/path/to/certificate.pfx', 'password'); $pdf = PDFDocument::create() ->addPage() ->writeHTML('<h1>Signed Document</h1>') ->addCertificationSignature( certificate: $certificate, appearance: SignatureAppearance::at(x: 15, y: 250, width: 80, height: 20, page: 1), info: [ 'Name' => 'John Doe', 'Reason' => 'Document certification', 'Location' => 'São Paulo, Brazil', ], ) ->save('/path/to/signed.pdf');
Dual Signatures (Key Feature! 🔑)
use LMoreli\LaravelPDF\Support\Certificate; use LMoreli\LaravelPDF\Signature\SignatureAppearance; use LMoreli\LaravelPDF\Enums\CertificatePermission; // Load two different certificates $cert1 = Certificate::fromPFX('/path/to/cert1.pfx', 'pass1'); $cert2 = Certificate::fromPFX('/path/to/cert2.pfx', 'pass2'); $pdf = PDFDocument::create() ->addPage() ->writeHTML($contractHTML) ->addDualSignatures( firstCertificate: $cert1, secondCertificate: $cert2, firstAppearance: SignatureAppearance::at( x: 15, y: 250, width: 80, height: 20, page: 1, name: 'Signature - Company', ), secondAppearance: SignatureAppearance::at( x: 105, y: 250, width: 80, height: 20, page: 1, name: 'Signature - Director', ), firstInfo: [ 'Name' => 'Company ABC', 'Reason' => 'Contract certification', ], secondInfo: [ 'Name' => 'John Director', 'Reason' => 'Contract approval', ], ) ->save('/path/to/dual-signed.pdf');
Signatures with Config Profiles
In config/laravelpdf.php:
'signatures' => [ 'certificates' => [ 'company' => [ 'type' => 'pfx', 'path' => storage_path('certs/company.pfx'), 'password' => env('PDF_CERT_COMPANY_PASSWORD'), ], 'director' => [ 'type' => 'pfx', 'path' => storage_path('certs/director.pfx'), 'password' => env('PDF_CERT_DIRECTOR_PASSWORD'), ], ], ],
Usage:
$certManager = app('laravelpdf.certificates'); $companyCert = $certManager->get('company'); $directorCert = $certManager->get('director'); $pdf->addDualSignatures($companyCert, $directorCert);
📊 Barcodes & QR Codes
// QR Code $pdf->qrCode('https://example.com', x: 10, y: 50, size: 40); // Code 128 $pdf->code128('ABC-12345', x: 10, y: 100, w: 60, h: 15); // EAN-13 $pdf->ean13('7891234567890', x: 10, y: 130); // DataMatrix $pdf->dataMatrix('Hello World', x: 10, y: 160, size: 30); // PDF417 $pdf->pdf417('Large data content...', x: 10, y: 200);
📋 Tables
Programmatic Table
$pdf->table( headers: ['Name', 'Email', 'Role'], rows: [ ['Alice', 'alice@example.com', 'Admin'], ['Bob', 'bob@example.com', 'User'], ['Carol', 'carol@example.com', 'Editor'], ], widths: [50, 80, 50], options: [ 'headerBg' => [41, 86, 219], 'headerColor' => [255, 255, 255], 'altRowBg' => [240, 245, 255], ], );
HTML Table
$pdf->htmlTable(' <table border="1" cellpadding="4"> <tr><th>Col 1</th><th>Col 2</th></tr> <tr><td>A</td><td>B</td></tr> </table> ');
🎨 Drawing & Graphics
// Lines $pdf->line(10, 50, 200, 50); // Rectangles $pdf->rect(10, 60, 50, 30, 'DF'); // Draw + Fill // Rounded rectangles $pdf->roundedRect(10, 100, 80, 40, 5); // Circles $pdf->circle(50, 160, 20); // Transparency $pdf->setAlpha(0.5);
📄 Headers & Footers
Callback-Based
$pdf->setHeaderCallback(function ($pdf) { $pdf->setFont('helvetica', 'B', 10) ->setTextColorHex('#666666') ->cell(0, 10, 'My Company - Confidential', 'B', 1, 'C'); }); $pdf->setFooterCallback(function ($pdf) { $pdf->setFont('helvetica', 'I', 8) ->cell(0, 10, 'Page ' . $pdf->getPage(), 'T', 0, 'C'); });
Simple Header
$pdf->setSimpleHeader('Company Name', 'Document Title', '/path/to/logo.png', 30);
🔒 Document Protection
$pdf->setProtection( permissions: ['print', 'copy'], userPass: 'user123', ownerPass: 'owner456', mode: 2, // AES-128 );
🔧 Configuration
Certificate Types
// From PFX/P12 file $cert = Certificate::fromPFX('/path/to/cert.pfx', 'password'); // From PEM files $cert = Certificate::fromFiles( certPath: '/path/to/cert.pem', keyPath: '/path/to/key.pem', password: 'key-password', ); // From PEM strings $cert = Certificate::fromPEM($certPEM, $keyPEM, $password); // Validate $cert->validate(); // Throws on invalid echo $cert->getSubjectName(); echo $cert->isValid() ? 'Valid' : 'Expired';
Output Options
// Save to file $pdf->save('/path/to/output.pdf'); // Get as string $content = $pdf->toString(); // Get as base64 $base64 = $pdf->toBase64(); // Stream inline to browser $pdf->stream('document.pdf'); // Force download $pdf->download('document.pdf'); // Laravel HTTP response return $pdf->response('document.pdf', inline: true);
📁 Project Structure
LaravelPDF/
├── composer.json
├── config/
│ └── laravelpdf.php # Laravel config
├── src/
│ ├── PDFDocument.php # Main class (fluent API)
│ ├── LaravelPDFServiceProvider.php
│ ├── Enums/
│ │ ├── CertificatePermission.php
│ │ ├── OutputDestination.php
│ │ ├── PageFormat.php
│ │ ├── PageOrientation.php
│ │ ├── SignatureType.php
│ │ └── Unit.php
│ ├── Exceptions/
│ │ ├── PDFException.php
│ │ └── SignatureException.php
│ ├── Facades/
│ │ └── PDF.php
│ ├── Signature/
│ │ ├── DigitalSignature.php # Signature config DTO
│ │ ├── IncrementalPDFSigner.php # Core signing engine
│ │ ├── MultiSignatureManager.php # Multi-sig orchestrator
│ │ └── SignatureAppearance.php # Visual positioning
│ ├── Support/
│ │ ├── Certificate.php # Certificate wrapper
│ │ ├── CertificateManager.php # Config-based profiles
│ │ ├── ExtendedTCPDF.php # TCPDF bridge
│ │ └── TimestampAuthority.php # RFC 3161 TSA
│ └── Traits/
│ ├── ManagesBarcodes.php
│ ├── ManagesContent.php
│ ├── ManagesFonts.php
│ ├── ManagesHeaders.php
│ ├── ManagesHTML.php
│ ├── ManagesImages.php
│ ├── ManagesMetadata.php
│ ├── ManagesPages.php
│ ├── ManagesSignatures.php
│ └── ManagesTables.php
├── examples/
│ ├── 01_basic.php
│ ├── 02_html_to_pdf.php
│ ├── 03_dual_signatures.php
│ └── 04_laravel_controller.php
└── tests/
🔬 How Multi-Signature Works
LaravelPDF implements multiple digital signatures using PDF Incremental Updates, the standard mechanism defined in the PDF specification (ISO 32000):
-
First Signature (Certification): The initial PDF is generated and signed normally using PKCS#7 detached signatures. This sets the DocMDP permissions.
-
Second Signature (Approval): The already-signed PDF is appended with an incremental update containing:
- A new signature field (widget annotation)
- A new signature dictionary with ByteRange placeholder
- Updated AcroForm fields array
- Updated page annotations
- New xref table and trailer pointing to the previous one
-
Integrity: Each signature covers all bytes of the PDF up to that point (except its own signature placeholder), so previous signatures remain valid.
┌────────────────────────┐
│ Original PDF Content │ ← Signed by Certificate 1
│ %%EOF │
├────────────────────────┤
│ Incremental Update │ ← Signed by Certificate 2
│ (new sig field, xref) │
│ %%EOF │
└────────────────────────┘
📋 Requirements
- PHP 8.3 or higher
- OpenSSL extension
- Laravel 10, 11, or 12 (for Laravel integration)
📄 License
MIT License. See LICENSE for details.
🤝 Credits
- Built on top of TCPDF
- Created by Lucas Moreli