Search by

dskripchenko / laravel-php-pdf

dskripchenko

Laravel bridge for dskripchenko/php-pdf — the MIT PDF toolkit. Pdf facade, config-driven fonts and page setup, and a response()->pdf() macro. GPL-free alternative to mpdf/FPDI stacks.

Package info

github.com/dskripchenko/laravel-php-pdf

pkg:composer/dskripchenko/laravel-php-pdf

Statistics

Installs: 582

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

v1.5.0 2026-10-18 16:04 UTC

README

Laravel bridge for dskripchenko/php-pdf — the pure-PHP, MIT-licensed PDF toolkit (generate, read, merge). No GPL friction, faster than mpdf/dompdf, and conformance-validated on every push.

🌐 English · Deutsch · Русский · 中文

Tests Latest Version License: MIT PHP Laravel

Installation

composer require dskripchenko/laravel-php-pdf

The service provider and the Pdf facade register automatically (package discovery). Optionally publish the config:

php artisan vendor:publish --tag=php-pdf-config

Usage

HTML → PDF

use Dskripchenko\LaravelPhpPdf\Facades\Pdf;

// bytes
$bytes = Pdf::fromHtml('<h1>Invoice #1234</h1>')->bytes();

// file
Pdf::fromHtml(view('invoices.show', $data)->render())->save(storage_path('invoice.pdf'));

// HTTP responses
Route::get('/invoice', fn () => Pdf::fromHtml($html)->inline('invoice.pdf'));
Route::get('/invoice/download', fn () => Pdf::fromHtml($html)->download('invoice.pdf'));

// Streamed responses — rendered straight into the output buffer, never
// held in memory as one string. For very large documents.
Route::get('/report', fn () => Pdf::fromHtml($html)->stream('report.pdf'));
Route::get('/report/download', fn () => Pdf::fromHtml($html)->streamDownload('report.pdf'));

The HTML may carry <style> blocks — the cascade, selectors, @media print — with php-pdf 1.19 or later. Page size, orientation and margins come from config/php-pdf.php: an @page rule in the HTML is not applied by Pdf::fromHtml(). To let @page decide, parse the HTML with php-pdf and render the result: Pdf::render(\Dskripchenko\PhpPdf\Document::fromHtml($html)) (the configured fonts apply, the default metadata does not).

response()->pdf()

The macro accepts a PendingPdf, a php-pdf Document (either layer), a filled form (Pdf::fillForm()), or raw bytes — the mpdf Output('', 'D') habit, the Laravel way:

return response()->pdf(Pdf::fromHtml($html), 'invoice.pdf');               // inline
return response()->pdf(Pdf::fromHtml($html), 'invoice.pdf', inline: false); // download

Programmatic documents

$document = Pdf::builder()
    ->heading(1, 'Quarterly report')
    ->paragraph('Q1 revenue exceeded the forecast by 12%.')
    ->build();

return Pdf::render($document)->download('report.pdf');

Everything from the underlying toolkit is reachable — charts, barcodes, AcroForm fields, PDF/A, encryption, digital signatures, and reading/merging existing PDFs. See the php-pdf documentation.

Filling existing forms

Pdf::fillForm() opens a PDF form produced elsewhere — a file path or the PDF bytes — and returns php-pdf's ExistingFormFiller. The configured fonts.default is used for values the form's own fonts cannot show, so Cyrillic or CJK values work without extra code:

$form = Pdf::fillForm(storage_path('templates/application.pdf'))
    ->setValues([
        'full_name' => 'Иван Петров',
        'agree' => true,
    ])
    ->stampImage(0, storage_path('signature.png'), x: 400, y: 700, width: 120, height: 40)
    ->flatten();

return response()->pdf($form, 'application.pdf', inline: false);

fields() lists the template's fields with their types and accepted options; see filling an existing form for the value rules and what flatten() does.

Digital signatures

Configure the key once (php-pdf.signing, below), then:

// Sign a generated document. A SignatureField in it is signed with its
// stamp filled from the certificate; otherwise the signature is invisible.
return Pdf::fromHtml($html)->signed(['reason' => 'Approved'])->download('contract.pdf');

// Sign an existing PDF — or one signed before; earlier signatures stay valid.
$signed = Pdf::sign(storage_path('contract.pdf'), ['reason' => 'Countersigned']);

// Certify for form filling, lock fields, sign a named field…
$signed = Pdf::sign(storage_path('contract.pdf'), ['certification' => 2, 'lock' => ['amount'], 'field' => 'approver']);

// …or drive php-pdf's PdfSigner directly: visible fields, fields for later
// signers, a key in the user's browser (CryptoPro browser plug-in).
// (ExternalSigner is Dskripchenko\PhpPdf\Pdf\Signature\External\ExternalSigner.)
$prepared = Pdf::signer($pdf)->prepare(Pdf::signatureConfig(['signer' => new ExternalSigner($userCert)]));

// Fill a signed form without breaking the signature.
$filled = Pdf::fillForm($signed)->setValue('amount', '100')->toBytes(incremental: true);

// Long-term validation: DSS (B-LT) plus a document timestamp (B-LTA).
$archived = Pdf::ltv($signed);

// Check signatures against the configured trust anchors.
$report = Pdf::validate($archived);
$report->allValid();

// A detached .p7s (CAdES) beside a file, and its check.
$p7s = Pdf::signDetached(storage_path('invoice.xml'));
Pdf::verifyDetached(storage_path('invoice.xml'), $p7s)->isValid();

Signatures are PAdES (ETSI.CAdES.detached) by default; RSA, ECDSA and GOST R 34.10-2012 keys work (GOST needs OpenSSL's gost engine loaded into PHP). Timestamp authorities, OCSP responders and CRLs are reached through Laravel's HTTP client, so proxies apply and Http::fake() works in tests. The full feature set is in the signing guide.

Keys outside PHP: CryptoPro, HSM, cloud KMS

Pdf::sign(), ->signed() and Pdf::signatureConfig() take any php-pdf signer as 'signer'; the configured key is then not needed (the configured timestamp authority, format and reason still apply). Ready-made signers:

use Dskripchenko\PhpPdfCryptoPro\CryptoProCadesSigner;   // dskripchenko/php-pdf-cryptopro
use Dskripchenko\PhpPdfPkcs11\Pkcs11Signer;              // dskripchenko/php-pdf-pkcs11
use Dskripchenko\PhpPdfCloudKms\Aws\AwsKmsSigner;        // dskripchenko/php-pdf-cloud-kms

// CryptoPro CSP on the server — GOST R 34.10-2012.
$signed = Pdf::sign(storage_path('contract.pdf'), ['signer' => CryptoProCadesSigner::fromThumbprint($thumbprint, pin: $pin)]);

// An HSM, a smart card or a token through PKCS#11.
$signed = Pdf::sign(storage_path('contract.pdf'), ['signer' => Pkcs11Signer::open($module, pin: $pin, keyLabel: 'pdf')]);

// AWS KMS (Google Cloud KMS and Azure Key Vault likewise).
return Pdf::fromHtml($html)
    ->signed(['signer' => AwsKmsSigner::create('alias/pdf-signing', $certificatePem, region: 'eu-central-1')])
    ->download('contract.pdf');

To sign with it everywhere, bind it in the container and name the binding in php-pdf.signing.signer — it then replaces the configured certificate and key, and is resolved once:

// AppServiceProvider::register()
$this->app->singleton('pdf.signer', fn () => Pkcs11Signer::open(config('services.hsm.module'), pin: config('services.hsm.pin'), keyLabel: 'pdf'));

// config/php-pdf.php → 'signing' => ['signer' => 'pdf.signer', …]   (or PHP_PDF_SIGNER=pdf.signer)
$signed = Pdf::sign(storage_path('contract.pdf'));

Pdf::signDetached() takes 'signer' and the configured signer too. A CryptoPro signer (a CmsSigner, it returns a whole CMS) needs php-pdf 1.20 or later there.

When those URLs come from users or from uploaded certificates, turn on signing.network.block_private_networks: every address a host resolves to must then be public (no loopback, private, link-local or metadata ranges, unless listed in allowed_cidrs), redirects are checked the same way, the connection goes to the address that was checked, and responses are capped at max_response_bytes.

Configuration

config/php-pdf.php controls page defaults, fonts, default metadata, signing and validation (an excerpt):

'paper' => env('PHP_PDF_PAPER', 'a4'),             // a3..a6, letter, legal, tabloid, executive
'orientation' => env('PHP_PDF_ORIENTATION', 'portrait'),
'margins' => ['top' => null, 'right' => null, 'bottom' => null, 'left' => null], // pt

'fonts' => [
    // Embedded TTFs (required for Cyrillic, Greek, Arabic, CJK — the
    // base-14 defaults cover Latin only). Fonts are subset automatically.
    'default' => storage_path('fonts/DejaVuSans.ttf'),
    'bold' => storage_path('fonts/DejaVuSans-Bold.ttf'),
    // Named families for CSS font-family / RunStyle(fontFamily: ...):
    // A face that is not configured falls back to the regular one.
    'families' => [
        'mono' => ['regular' => storage_path('fonts/DejaVuSansMono.ttf'), 'bold' => storage_path('fonts/DejaVuSansMono-Bold.ttf')],
    ],
    // A FontProvider (container binding or class) for any other family.
    'provider' => null,
],

'metadata' => ['Author' => 'ACME Corp.'],  // default /Info entries

'signing' => [
    // A Signer / CmsSigner bound in the container (CryptoPro, PKCS#11, cloud KMS)…
    'signer' => env('PHP_PDF_SIGNER'),
    // …or a PEM certificate + key (file paths or PEM text)…
    'certificate' => env('PHP_PDF_SIGN_CERT'),
    'key' => env('PHP_PDF_SIGN_KEY'),
    'passphrase' => env('PHP_PDF_SIGN_PASSPHRASE'),
    // …or a PKCS#12 container.
    'pkcs12' => env('PHP_PDF_SIGN_P12'),
    'pkcs12_password' => env('PHP_PDF_SIGN_P12_PASSWORD'),
    'chain' => [],              // intermediate certificates
    'format' => env('PHP_PDF_SIGN_FORMAT', 'pades'),  // or 'pkcs7' (adbe.pkcs7.detached)
    'reason' => null, 'location' => null, 'contact' => null, 'signer_name' => null,
    'timestamp' => [
        'url' => env('PHP_PDF_TSA_URL'),
        'username' => env('PHP_PDF_TSA_USERNAME'), 'password' => env('PHP_PDF_TSA_PASSWORD'),
        'policy' => null, 'headers' => [], 'timeout' => 15,
    ],
    'network' => [
        'block_private_networks' => env('PHP_PDF_SIGN_BLOCK_PRIVATE', false),
        'allowed_cidrs' => [], 'max_response_bytes' => 20 * 1024 * 1024, 'max_redirects' => 3,
    ],
],

'validation' => [
    'trust' => [],              // root certificates for Pdf::validate()
    'system_trust' => false,    // add OpenSSL's system roots
    'fetch_revocation' => false,
],

No fonts at hand? dskripchenko/php-pdf-fonts-liberation ships Liberation Sans, Serif and Mono (Latin, Cyrillic, Greek, Hebrew), metric-compatible with Arial, Times New Roman and Courier New. Name its provider in fonts.provider — it answers for the families not listed in families, font-family: Arial included — and point the default font at it:

'fonts' => [
    'provider' => \Dskripchenko\PhpPdfFontsLiberation\LiberationFontProvider::class,
    'default' => base_path('vendor/dskripchenko/php-pdf-fonts-liberation/assets/fonts/LiberationSans-Regular.ttf'),
    'bold' => base_path('vendor/dskripchenko/php-pdf-fonts-liberation/assets/fonts/LiberationSans-Bold.ttf'),
    'italic' => base_path('vendor/dskripchenko/php-pdf-fonts-liberation/assets/fonts/LiberationSans-Italic.ttf'),
    'bold_italic' => base_path('vendor/dskripchenko/php-pdf-fonts-liberation/assets/fonts/LiberationSans-BoldItalic.ttf'),
],

Migrating from laravel-mpdf / barryvdh wrappers

The underlying toolkit ships compat facades and guides for mpdf and FPDI call sites.

Requirements

  • PHP 8.2+
  • Laravel 11, 12, or 13
  • dskripchenko/php-pdf 1.18+ (1.19+ for <style> CSS in HTML)
  • Extensions: mbstring, zlib, dom (plus openssl for encryption and signatures)

Testing

composer test

The php-pdf family

Package What it adds
php-pdf The core: HTML with CSS or a builder to PDF, read, merge, fill forms, sign and validate.
laravel-php-pdf (this package) Laravel bridge: a Blade view straight to PDF, response()->pdf(), signing from config.
php-pdf-fonts-liberation Liberation Sans, Serif and Mono, metric-compatible with Arial, Times New Roman and Courier New (OFL, kept out of the MIT core).
php-pdf-cryptopro Signs with a key in CryptoPro CSP on the server: GOST R 34.10-2012, Russian qualified signatures.
php-pdf-pkcs11 Signs with a key on an HSM, a smart card or a USB token, through PKCS#11.
php-pdf-cloud-kms Signs with a key in AWS KMS, Google Cloud KMS or Azure Key Vault.

License

MIT. The underlying dskripchenko/php-pdf is MIT as well — no GPL obligations anywhere in the stack.