Modern PDF generation library for Laravel with multiple digital signatures support. Built on PHP 8.3+ with a fluent API on top of TCPDF.
- π― 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
composer require lmoreli/laravel-pdfThe service provider and facade are auto-registered via Laravel's package discovery.
php artisan vendor:publish --tag=laravelpdf-configuse 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');$pdf = PDFDocument::fromHTML('<h1>Hello</h1><p>World!</p>')
->save('/path/to/output.pdf');use LMoreli\LaravelPDF\Facades\PDF;
// In a controller:
public function downloadReport()
{
$pdf = PDF::fromView('pdfs.report', ['data' => $reportData]);
return $pdf->response('report.pdf');
}$pdf = PDFDocument::fromView('pdfs.invoice', [
'invoice' => $invoice,
'items' => $items,
]);
return $pdf->response('invoice.pdf', inline: false); // Force downloaduse 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');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');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);// 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);$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],
],
);$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>
');// 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);$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');
});$pdf->setSimpleHeader('Company Name', 'Document Title', '/path/to/logo.png', 30);$pdf->setProtection(
permissions: ['print', 'copy'],
userPass: 'user123',
ownerPass: 'owner456',
mode: 2, // AES-128
);// 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';// 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);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/
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 β
ββββββββββββββββββββββββββ
- PHP 8.3 or higher
- OpenSSL extension
- Laravel 10, 11, or 12 (for Laravel integration)
MIT License. See LICENSE for details.
- Built on top of TCPDF
- Created by Lucas Moreli