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.

Maintainers

Package info

github.com/llm1998/PDFMake

pkg:composer/lmoreli/laravel-pdf

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

dev-main 2026-03-04 23:58 UTC

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):

  1. First Signature (Certification): The initial PDF is generated and signed normally using PKCS#7 detached signatures. This sets the DocMDP permissions.

  2. 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
  3. 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