Every PHP shop that ships proprietary software eventually hits the same
wall: the de-facto standard HTML→PDF library, mpdf, is GPL-2.0-only. That
means bundling it into an OEM product, an on-premise installer, or any
closed-source codebase requires either shipping your product under GPL or
negotiating a commercial license. I hit that wall often enough that I
built a replacement.
php-pdf (github.com/dskripchenko/php-pdf)
is a pure-PHP, MIT-licensed PDF toolkit: HTML/CSS input, fluent builders,
TTF embedding with subsetting, barcodes, charts, AcroForm, PKCS#7
signing, PDF/A and PDF/X — plus reading and merging existing PDFs.
"Sounds too good" — so prove it
A two-week-old library claiming production crypto and PDF/A conformance
deserves skepticism. That's why every push to main runs an independent
proof chain in CI:
- PDF/A references (1a/1b/2b/2u/3b) validated with veraPDF — the industry-standard validator, not our own code;
- PDF/X structure checked byte-level plus a Ghostscript render pass;
-
signatures verified by the OpenSSL CLI against the exact
/ByteRangebytes — and a tampered byte must break verification; - renders diffed against golden images with poppler.
Reports are public artifacts on every run:
conformance page.
The migration is mechanical
For the typical mpdf call site, swap one import:
// before
$mpdf = new \Mpdf\Mpdf(['format' => 'A4', 'margin_left' => 15]);
$mpdf->WriteHTML($html);
$mpdf->Output('invoice.pdf', 'F');
// after
$mpdf = new \Dskripchenko\PhpPdf\Compat\Mpdf(['format' => 'A4', 'margin_left' => 15]);
$mpdf->WriteHTML($html);
$mpdf->Output('invoice.pdf', 'F');
The compat facade covers WriteHTML/AddPage/Output (all four
destinations), metadata setters and the common config keys — and it's
exercised by the test suite, so the examples in the
migration guide
are tested code, not aspiration. The guide also has the full native-API
mapping table and a find/replace cheat-sheet for Destination::*
constants.
What the facade deliberately does NOT do: mpdf-specific HTML extensions
(<pagebreak>, <barcode>), SetHeader shortcodes, font-config arrays.
Those map to the native API, which is better at all of them (real block
elements for headers/footers, a first-class Barcode element with 16
formats, engine-level font config).
And it's faster
Reproducible benchmark (isolated subprocesses, medians, pinned competitor
versions — composer bench from a clean clone re-runs everything):
| Scenario | php-pdf | mpdf 8.3.1 |
|---|---|---|
| HTML → PDF article (~5 pages) | 12.0 ms | 64.1 ms |
| 100-page invoice | 576 ms | 2640 ms |
| Image grid (20 pages) | 6.5 ms | 36.1 ms |
Full methodology and all four competitors:
BENCHMARKS.
Honest limitations
- The HTML/CSS subset is narrower than mpdf's in places (
@pagerules map to aPageSetupobject instead). - Nothing auto-embeds fonts: base-14 covers Latin; Cyrillic/Arabic/CJK need a TTF passed to the engine (it is subset automatically).
- Public-key encryption (
/PubSec) is intentionally unimplemented — we won't ship crypto we can't test against real fixtures.
Laravel users: there's a thin bridge —
dskripchenko/laravel-php-pdf
with a Pdf facade and a response()->pdf() macro.
If GPL friction has ever made you postpone a PDF feature, give it a try:
composer require dskripchenko/php-pdf. Issues and failing PDFs from
real-world producers are the most valuable thing you can send my way.
Top comments (0)