Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Pdflib Laravel Package

evosys21/pdflib

A PHP library for FPDF, TCPDF, and tFPDF that adds advanced multicell and table support. Build tag-formatted text with custom fonts, colors, links, alignment, and padding, plus feature-rich tables for PDF reports.

View on GitHub
Deep Wiki
Context7

Getting Started

What this actually is. evosys21/pdflib is a plain PHP library, not a Laravel package. There is no service provider, no facade, no config/pdflib.php, no publishable assets, and no Blade/HTML/CSS layer. It is the maintained continuation of the old interpid PDF Addons and gives you two things on top of FPDF / TCPDF / tFPDF:

  • Advanced Multicell — a MultiCell replacement that accepts a tag-based markup string (<b>, <i>, <u>, <a href>, custom styles), supports alignment/justification, padding, backgrounds, frames, tabs, sub/superscripts, and per-tag fonts/colours.
  • Advanced Table — tables whose cells are themselves multicells, with col/row spans, vertical + horizontal alignment, automatic page breaks, header repetition, images in cells, and transparency.

Install and verify the package name first.

composer require evosys21/pdflib

The README badge still points at the Packagist slug evosys21/phplib, so confirm the installed name in composer.json after the first install — this is a one-line check that saves an hour of confusion.

Look here first, in this order:

  1. examples/Tcpdf/ — the runnable examples (example-multicell-1-overview.php, example-table-1-overview.php, plus the PDF/PNG previews). This is the fastest way to learn the API; the docs are reference material, the examples are tutorials.
  2. docs/multicell.md — the tag grammar and the setStyle() / style-definition contract.
  3. docs/table.md — table construction, cell properties, and the splitting modes.
  4. src/Tcpdf/Pdf.php (or Fpdf/Pdf.php, Tfpdf/Pdf.php) — the wrapper class you must instantiate.

Pick your engine before you write code. Three base classes ship: EvoSys21\PdfLib\Fpdf\Pdf, EvoSys21\PdfLib\Tcpdf\Pdf, EvoSys21\PdfLib\Tfpdf\Pdf. FPDF is fast and tiny but Latin‑1 only; tFPDF adds UTF‑8/TrueType; TCPDF has the richest feature set (barcodes, more page options) at the cost of weight and a cache directory. Choose once — the multicell/table API is the same above all three, but your base object is not swappable at runtime.

First use case — an inline, styled paragraph:

use EvoSys21\PdfLib\Tcpdf\Pdf;
use EvoSys21\PdfLib\Multicell;

$pdf = new Pdf();
$pdf->AddPage();
$pdf->SetFont('helvetica', '', 10);

$multicell = new Multicell($pdf);
// Define the paragraph style and a few inline tags (see docs/multicell.md
// for the exact setStyle() argument order in the version you install).
$multicell->setStyle('p', 'helvetica', 10, 'darkgray');
$multicell->setStyle('b', 'helvetica', 10, 'black', 'bold');

$multicell->multiCell(
    0,   // width: 0 = to right margin
    5,   // line height
    '<p>This clause is <b>binding</b> and supersedes all prior terms.</p>'
);

That is the whole mental model: you hand it a markup string, not an array of styled spans, and it lays it out. Tables are the same strings placed into cells.

Deliver it through Laravel, never by echoing the buffer. Use the string output mode and let Laravel own the response:

$pdf->Output('invoice.pdf', 'S'); // returns the PDF as a string

Implementation Patterns

Wrap generation in a single-purpose service class, not a controller. PDF assembly is the part of your app most likely to be re-run from a job, a command, or a mail attachment, so bind one class per document type:

// app/Pdf/InvoicePdf.php
final class InvoicePdf
{
    public function __construct(private Invoice $invoice) {}

    public function render(): string
    {
        $pdf = new \EvoSys21\PdfLib\Tcpdf\Pdf();
        // ... compose
        return $pdf->Output('invoice.pdf', 'S');
    }
}

// bind with a factory or resolve per-document: app(InvoicePdf::class, ['invoice' => $invoice])

Controller pattern — stream, don't download twice. For on-demand documents:

public function show(Invoice $invoice)
{
    $binary = app(InvoicePdf::class, ['invoice' => $invoice])->render();

    return response($binary, 200, [
        'Content-Type'        => 'application/pdf',
        'Content-Disposition' => 'inline; filename="invoice-'.$invoice->number.'.pdf"',
    ]);
}

response()->streamDownload() also works, but you already have a string — hand it over directly and skip the stream complexity.

Queued generation for anything heavy. Tables with hundreds of rows, images, or multi-language fonts are CPU- and memory-bound and will blow past PHP's max_execution_time under load. Push to a job, write to a disk, and email/link the file:

Storage::disk('local')->put("invoices/{$invoice->id}.pdf", $pdf->render());

For very large tables, generate in chunks (one document section per chunk is simplest) rather than fighting memory in a single giant table.

Map Eloquent models to rows via a presenter, not inline loops. The table API wants arrays of cell values plus per-cell properties. Keep that translation in one place:

$rows = $invoice->lines->map(fn ($line) => [
    $line->description,          // cell values
    number_format($line->qty),
    number_format($line->total, 2),
]);
// then feed $rows into the table builder, applying per-column
// alignment/width defaults and overriding per cell where needed

Set column widths in the same unit the engine uses (mm for FPDF/tFPDF and TCPDF's default user unit, but never assume — check $pdf->GetPageWidth() and derive percentages from it). Hard-coded millimeter widths are the number-one cause of "works in dev, overflows in the customer's A4 template".

Reuse document chrome by subclassing the shipped Pdf class. Header() / Footer() are normal methods on the wrapper. Subclass EvoSys21\PdfLib\Tcpdf\Pdf and override them to get page numbers, letterhead, and a repeated company block across every report — this is the cleanest extension point the library gives you.

Repeated table headers come free; use them. The table auto-splits across pages and re-draws the header row. If you build tables by hand with multicells instead, you lose that and must re-render the header in a page-break hook yourself.

Testing pattern: don't byte-compare PDFs. Either assert on the returned string starting with %PDF-, or extract text with smalot/pdfparser in a feature test and assert the invoice number and total appear. For visual work, keep a small set of golden-path examples and eyeball the PDFs — this library is layout-heavy and unit tests will not catch a broken column.

Version pinning. With 5 stars and 0 dependents you are effectively the QA team. Pin the minor ("evosys21/pdflib": "^1.2") and read the changelog before bumps — 1.1.0 added multicell "disable page break / min height" switches and 1.2.0 changed TEXT_TYPE underlining, both of which can shift existing layouts.

Gotchas and Tips

You must use the library's Pdf class, not raw FPDF/TCPDF. The addons read protected properties (widths, margins, fonts) that the base classes don't expose. If your app already creates new TCPDF() elsewhere, that code will not accept a Multicell or Table — you have to migrate the document onto EvoSys21\PdfLib\Tcpdf\Pdf.

This is not an HTML-to-PDF tool. There is no CSS, no Blade rendering, no <table> support from HTML. If your requirement is "render this Blade view to PDF", reach for Dompdf, Snappy, or Browsershot instead; you'll spend more time re-implementing layouts here than you save. Choose pdflib when you want precise, programmatic, tag-styled documents: invoices, contracts, certificates, statements.

Escape user data before it enters the tag string. The multicell parser interprets <...>, so a customer name containing <b> or a stray > will corrupt the layout — or worse, let someone inject a link tag. Sanitize/escape every interpolated value (htmlspecialchars on < and > at minimum) and treat the markup as a template, never as trusted input.

Character encoding is the classic failure. FPDF core fonts are Latin‑1/cp1252: a UTF‑8 €, ü, curly quote, or any non-Latin script renders as mojibake. Fix by using tFPDF or TCPDF with a real Unicode TrueType font registered via AddFont. Confirm early which engine your document types need; retrofitting Unicode after the layout is built is painful because glyph widths change the wrapping.

TCPDF writes to a cache/temp directory. In containerised Laravel deploys this can mean permission errors or a path that doesn't exist. Check TCPDF's cache constant/config in your bootstrap and point it at storage/app/ — do not rely on the default relative path.

Watch for output-before-headers and stray whitespace. Any echo, BOM, or blank line before Output() corrupts the PDF. This is why the 'S' (return-as-string) mode plus a Laravel response() is the safer pattern than 'I'/'D'.

Layout control knobs worth knowing: multicell padding (left/right/top/bottom) is set per style and is the thing that makes cells look "designed" instead of cramped; the min-height / disable-page-break options (added in 1.1.0) let you keep a signature block or a total row from splitting across pages; and pre-measuring a string's height before writing lets you decide page breaks yourself rather than letting the engine break mid-section.

Debugging layout: temporarily give every cell a visible border and a background colour and regenerate. Because there is no DOM or CSS inspector, a coloured grid of boxes is the fastest way to see whether the problem is widths, padding, or page breaks. Also render to a file with Output('F', storage_path(...)) during development rather than fighting browser caching.

Expect deprecation noise on modern PHP. 1.1.1 explicitly fixed PHP 8.4 deprecations, so stay current — but TCPDF itself still emits notices. Wrap generation in code that tolerates E_DEPRECATED and don't let APP_DEBUG-level error handling turn a warning into a 500 in production.

Fonts and images are your portability risk. TrueType font files and image paths must resolve on every environment (queue worker, CLI command, web). Register fonts with absolute paths from resource_path()/storage_path() and copy them into the repo; relative paths work in public/ by accident and break everywhere else.

Community is thin (5 stars, 0 dependents). There is no Stack Overflow corpus for this library. When something is odd, read src/ — the classes are small and the examples cover most features. Open issues at github.com/evosys21/pdflib/issues and general questions in Discussions; PRs go against main only after an issue is agreed, since main is the next major version.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support