pontedilana/weasyprint-bundle
Symfony bundle that integrates WeasyPrint to turn HTML and CSS into high-quality PDFs. Render Twig templates through a simple service, with configurable binary path and clean Symfony integration for invoices, reports, and printable documents.
@page, running headers/footers, page counters, widow/orphan control, footnotes, and PDF bookmarks. That is genuinely hard to replicate with Chrome or a naive DOM→PDF library, and it is exactly what finance/legal-facing documents require.Bundle class, DI extension, Symfony config tree, and Symfony's Process component. It cannot be composer required into a Laravel app and used as-is. The 3.1.0 release reinforces this: it raises the minimum versions of symfony/http-foundation, symfony/http-kernel, and symfony/process, i.e. it is versioned against Symfony's support matrix, not Laravel's. For a Laravel codebase the honest decision path is: adopt WeasyPrint (or not) on engine merits, then write a ~150-line Laravel service provider / facade that does what this bundle does. The bundle remains useful as a validated design reference and a source of edge cases (font loading, base URL handling, temp-file cleanup, timeout/exit-code handling, URL-scheme allowlisting).@import, remote fonts) becomes an outbound-request primitive: SSRF against internal services, file:// reads, metadata endpoints. 3.1.0 adds a pdf.allowed_schemes config for precisely this, gated on php-weasyprint 2.6+. Treat scheme allowlisting (plus a network egress policy on the render worker) as a launch prerequisite for any self-service templating, not a follow-up.python3 -m weasyprint invoked from a queue job. That is a real architectural decision — CPU-bound rendering belongs in a queue, never in a request cycle — and it should be made once, deliberately, rather than discovered during a traffic spike. Two operational details from this release feed straight into that decision: bare pdf.binary names are now resolved against PATH, so image builds must guarantee weasyprint is on PATH or set an absolute path; and the timeout can now be disabled entirely via process_timeout: false — a capability to keep out of any request-path configuration.Consider it (as a reference implementation, or directly if you are on Symfony) when:
pdf.allowed_schemes for this, but verify the underlying php-weasyprint version is 2.6+ or the setting silently does nothing.Look elsewhere when:
Process conventions are load-bearing here.composer require away, pure PHP, no second runtime, and is the pragmatic choice when layout ambition is low.For executives — one paragraph: We need to generate PDFs for [invoices/statements/reports] as we scale [initiative]. There are two credible paths: rent a document API, or own the rendering in-house. Owning it costs roughly one to two engineer-weeks of setup plus modest ongoing infrastructure — a small service in our deployment — and removes per-document fees, vendor lock-in, and the compliance question of shipping customer financial data to a third party. At our projected volume the in-house path pays back within [one to two quarters], and it gives us unlimited iteration on layout, which is the part customers actually notice. The specific open-source package in front of us is a small, single-maintainer Symfony component (40 stars, no downstream users, MIT-licensed, actively released; the current 3.1.0 is a security-and-configuration release that raises its dependency floors and adds URL-scheme allowlisting). We should not take a dependency on it as a library. We should adopt the engine it wraps — WeasyPrint, a mature and widely used HTML-to-PDF renderer — and write our own thin integration, borrowing this package's design as a reference. That gets us the capability with none of the supply-chain risk, and at a cost of roughly two days of engineering over doing nothing.
For engineering — the technical framing:
Process glue; we can own that. 3.1.0 adds no new capability we couldn't port in an hour — it is a hardening release, not a feature release.@page margins and named pages, running headers/footers, counter(page)/counter(pages), break-inside: avoid, footnotes, and real PDF bookmarks. That is what makes multi-page invoices and statements look professional without hand-computing layout.PdfRenderer contract backed by a queued job that writes the rendered HTML to a temp path and shells out to weasyprint with an explicit timeout, then cleans up. Add font assets to the image, pin the Python and WeasyPrint versions (and the php-weasyprint equivalent if we vendor the reference code), and add a golden-file snapshot test per document template so layout regressions are caught in CI rather than by a customer.https (plus data: if needed), and pair it with egress restrictions on the worker, because template-driven PDF rendering is a classic SSRF path; (2) resolve the binary from a known absolute path rather than relying on PATH in a minimal container; (3) never disable the process timeout — keep a hard deadline per render and fail the job cleanly, since an unbounded WeasyPrint process on a stuck document is a worker-pool outage.How can I help you explore Laravel packages today?