FPDF : Table with MultiCells

1786 views
PDF_php_FPDF_PDF_Generator_ipdata_code.jpg

Why MultiCell Tables Are the Hardest Part of FPDF

Building a basic PDF in FPDF is straightforward. Adding an image, setting a font, drawing a title — all trivial. But the moment you need a data table where cells contain variable-length text, everything gets complicated fast.

The core challenge: PDF is not HTML. There is no <td> that stretches automatically. There is no overflow: hidden. There is no flexbox. Every millimetre is your responsibility.

Specifically, FPDF’s Cell() function — the natural first attempt at table cells — does not wrap text. It clips, overflows, or truncates. The moment a product description, a customer name, or an address is longer than expected, your table layout breaks.

The solution is MultiCell(). But MultiCell() introduces a new problem: it moves the Y cursor after drawing, which means you can’t simply draw the next column to the right. You have to save positions, restore them, calculate row heights in advance, and handle page breaks manually.

This guide solves every piece of that puzzle. By the end you’ll have:

  • A deep understanding of Cell() vs MultiCell() with real examples
  • A complete PDF_MC_Table class with every method explained line by line
  • Five production-ready table styles (invoice, data grid, schedule, comparison, summary)
  • Dynamic row height calculation using the NbLines() algorithm explained in full
  • Manual page break detection that keeps rows from splitting across pages
  • Column width strategies: equal, weighted, pixel-perfect, and content-adaptive
  • Styling: zebra rows, colored headers, status badges, alignment control
  • Real-world integration: MySQL/PDO data, arrays, WooCommerce, CSV import
  • A complete error reference for every FPDF table mistake

 

PDF Table Generation Architecture

 

Part 1 — Foundation: Cell() vs MultiCell() vs Rect()

Before writing a single table, you need to understand the three drawing primitives FPDF gives you and when each is appropriate.

Cell($w, $h, $txt, $border, $ln, $align, $fill)

Draws a single-line rectangle containing text.

Parameters:
  $w      → Width in mm (0 = to right margin)
  $h      → Height in mm
  $txt    → Text to display
  $border → 0=none, 1=all, or string: 'LRTB'
  $ln     → 0=right, 1=next line, 2=below
  $align  → 'L', 'C', 'R'
  $fill   → true/false — fill background with SetFillColor()

Behaviour: If text is wider than the cell, it overflows silently. There is no wrap. Text is never clipped — it bleeds into the next cell or off the page.

Use when: Column headers, numeric values, dates, status labels, short codes — anything guaranteed to fit in one line.

// Good use of Cell() — short, predictable content
$pdf->SetFont('Arial', 'B', 9);
$pdf->Cell(30,  8, 'Invoice #',  1, 0, 'C', true);
$pdf->Cell(25,  8, 'Date',       1, 0, 'C', true);
$pdf->Cell(80,  8, 'Description',1, 0, 'C', true);
$pdf->Cell(25,  8, 'Amount',     1, 1, 'C', true); // $ln=1 → next line

MultiCell($w, $h, $txt, $border, $align, $fill)

Draws a multi-line rectangle that wraps text automatically.

Parameters:
  $w      → Width in mm (0 = to right margin)
  $h      → Height per line in mm (NOT total height)
  $txt    → Text to display (can contain \n for forced breaks)
  $border → 0=none, 1=all, or string: 'LRTB'
  $align  → 'L', 'C', 'R', 'J' (justified)
  $fill   → true/false

Behaviour: Text wraps at word boundaries when it exceeds $w. Each wrapped line adds $h mm to the total cell height. After drawing, the cursor moves below the cell (always — you cannot control this with $ln).

The cursor movement problem: After MultiCell() draws, you cannot draw the next column to the right — the cursor has moved down. You must save $x and $y before drawing, then use SetXY($x + $w, $y) to position for the next column.

// ❌ WRONG — this draws column B below column A, not beside it
$pdf->MultiCell(80, 6, $long_description, 1, 'L');
$pdf->MultiCell(25, 6, '₹1,200', 1, 'R');  // This appears below, not beside!

// ✅ CORRECT — save position, restore for each column
$x = $pdf->GetX();
$y = $pdf->GetY();

$pdf->MultiCell(80, 6, $long_description, 1, 'L');

$pdf->SetXY($x + 80, $y);  // ← Restore to right of first column
$pdf->MultiCell(25, 6, '₹1,200', 1, 'R');

Rect($x, $y, $w, $h, $style)

Draws a plain rectangle. No text.

$style → 'D' = border only | 'F' = fill only | 'FD' or 'DF' = both

Use when: Drawing the border around a MultiCell independently (to control border appearance separately from text), or filling background colours at a fixed height.

// Draw a fixed-height border around a MultiCell
// (useful when you need all cells in a row to be the same height)
$pdf->Rect($x, $y, $col_width, $row_height, 'D');     // Border
$pdf->SetXY($x + 2, $y + 1);                          // Padding
$pdf->MultiCell($col_width - 4, 5, $text, 0, 'L');    // Text, no border

 

Side-by-Side Comparison

Feature Cell() MultiCell() Rect()
Text rendering Single line Multi-line, wrapping None
Text overflow Silent overflow Wraps correctly N/A
Cursor after draw Right / below (controlled by $ln) Always below Unchanged
Border support Yes Yes Border only
Fill support Yes Yes Yes
Height control Fixed $h Per-line $h × lines Fixed $h
Best for Headers, numbers, dates Descriptions, notes, addresses Backgrounds, borders

 

Part 2 — The NbLines() Algorithm: Calculating Row Height Before Drawing

The single most important technique for FPDF tables is calculating a row’s height before you draw it. Without this, you cannot:

  • Detect whether a row will overflow onto the next page
  • Synchronize the height of multiple MultiCell columns in the same row
  • Draw a Rect() border at exactly the right height

The NbLines() method — from the classic FPDF script by Olivier Plathey — computes how many lines a MultiCell of a given width will occupy for a given string, using the current font’s character widths.

Here is the full implementation with every line annotated:

/**
 * Calculate the number of lines a MultiCell($w, ..., $txt) will occupy.
 *
 * This replicates FPDF's internal word-wrapping logic but only counts lines
 * instead of drawing anything. The result × line_height = total cell height.
 *
 * @param  float  $w    Cell width in mm (same value you'll pass to MultiCell)
 * @param  string $txt  The text content (same value you'll pass to MultiCell)
 * @return int          Number of lines the MultiCell will occupy
 */
function NbLines(float $w, string $txt): int
{
    // ── Guard: font must be set before character widths are available ────────
    if (!isset($this->CurrentFont)) {
        $this->Error('No font has been set');
    }

    // ── Step 1: Get character width table for current font ───────────────────
    // $cw is an array mapping each ASCII character to its width in 1/1000 units
    // e.g. $cw['A'] = 722, $cw['i'] = 278, $cw[' '] = 278
    $cw = $this->CurrentFont['cw'];

    // ── Step 2: Calculate maximum line width in font units ───────────────────
    // $this->cMargin = cell margin (internal padding), default ~1mm
    // $this->FontSize = current font size in points
    // Formula: convert mm width → font units, subtract cell margins
    if ($w == 0) {
        // Width 0 = auto: stretch to right margin from current X position
        $w = $this->w - $this->rMargin - $this->x;
    }
    $wmax = ($w - 2 * $this->cMargin) * 1000 / $this->FontSize;

    // ── Step 3: Normalize the string ────────────────────────────────────────
    // Remove carriage returns (\r) — only \n is used for line breaks
    $s  = str_replace("\r", '', (string)$txt);
    $nb = strlen($s);

    // Trim trailing newline (it won't add a visible line)
    if ($nb > 0 && $s[$nb - 1] == "\n") {
        $nb--;
    }

    // ── Step 4: Simulate word-wrap character by character ───────────────────
    $sep = -1;  // Position of last seen space (word boundary)
    $i   = 0;   // Current character index
    $j   = 0;   // Start of current line
    $l   = 0;   // Accumulated line width in font units
    $nl  = 1;   // Line count (starts at 1 — even empty text = 1 line)

    while ($i < $nb) {
        $c = $s[$i];

        // Forced line break (\n in the text)
        if ($c == "\n") {
            $i++;
            $sep = -1;
            $j   = $i;
            $l   = 0;
            $nl++;
            continue;
        }

        // Track the last word-break opportunity (space character)
        if ($c == ' ') {
            $sep = $i;
        }

        // Add current character's width to accumulated line width
        $l += $cw[$c];

        if ($l > $wmax) {
            // Line is full — need to wrap
            if ($sep == -1) {
                // No space found — break mid-word (very long word or narrow column)
                if ($i == $j) {
                    $i++; // Advance past this character to prevent infinite loop
                }
            } else {
                // Wrap at the last space
                $i = $sep + 1;
            }
            $sep = -1;
            $j   = $i;
            $l   = 0;
            $nl++;
        } else {
            $i++;
        }
    }

    return $nl;
}

Using NbLines() to Calculate Row Height

// Given 4 columns with these widths and this row data:
$widths = [30, 80, 30, 40];
$data   = ['INV-0042', 'Premium WordPress Theme with 12 months support included', '3', '₹8,500'];

$line_height = 6; // mm per line
$max_lines   = 0;

foreach ($data as $i => $cell_text) {
    $lines     = $this->NbLines($widths[$i], $cell_text);
    $max_lines = max($max_lines, $lines);
}

$row_height = $line_height * $max_lines;
// If column 1 needs 1 line and column 2 needs 3 lines,
// $row_height = 6 × 3 = 18mm for the entire row

 

Part 3 — The Complete PDF_MC_Table Class (Production Version)

This is a comprehensive, production-ready upgrade of the original FPDF script, with full styling, column weight support, zebra rows, colored headers, status cell rendering, and robust page break handling.

<?php
require('fpdf.php');

/**
 * PDF_MC_Table — Production-grade FPDF table engine with MultiCell support.
 *
 * Features:
 *   - Proportional or fixed column widths
 *   - Automatic row height from NbLines()
 *   - Manual page break detection (keeps rows intact)
 *   - Header repeat on new pages
 *   - Zebra striping
 *   - Colored styled headers
 *   - Per-column alignment
 *   - Status badge rendering
 *   - Full-width summary/total rows
 *   - UTF-8 via utf8_decode()
 */
class PDF_MC_Table extends FPDF
{
    // ── Configuration ─────────────────────────────────────────────────────────
    protected array  $col_widths   = [];
    protected array  $col_aligns   = [];
    protected array  $col_headers  = [];
    protected float  $line_height  = 6;      // mm per text line
    protected float  $page_bottom  = 20;     // mm from bottom before page break
    protected bool   $repeat_header = true;  // Re-draw headers on page break
    protected int    $row_count    = 0;      // Tracks for zebra striping

    // ── Brand Colors (RGB) ────────────────────────────────────────────────────
    protected array $color_header   = [30,  64,  175]; // Blue-700
    protected array $color_row_even = [248, 250, 252]; // Slate-50
    protected array $color_row_odd  = [255, 255, 255]; // White
    protected array $color_border   = [203, 213, 225]; // Slate-300
    protected array $color_text     = [30,  30,  30];  // Near-black
    protected array $color_total    = [241, 245, 249]; // Slate-100

    // ─────────────────────────────────────────────────────────────────────────
    //  CONFIGURATION METHODS
    // ─────────────────────────────────────────────────────────────────────────

    /**
     * Set fixed column widths in mm.
     * Total should equal page content width (GetPageWidth - margins).
     */
    public function SetWidths(array $widths): void
    {
        $this->col_widths = $widths;
    }

    /**
     * Set proportional column weights that auto-calculate actual widths.
     * Example: SetWeights([2, 1, 0.8, 1]) on A4 with 15mm margins
     * → widths calculated from 180mm printable width in those proportions.
     */
    public function SetWeights(array $weights): void
    {
        $printable   = $this->GetPageWidth() - $this->lMargin - $this->rMargin;
        $total_weight = array_sum($weights);
        $this->col_widths = array_map(
            fn($w) => ($w / $total_weight) * $printable,
            $weights
        );
    }

    /**
     * Set column alignments. 'L' = left (default), 'C' = center, 'R' = right.
     */
    public function SetAligns(array $aligns): void
    {
        $this->col_aligns = $aligns;
    }

    /**
     * Set column header labels (used when repeat_header = true).
     */
    public function SetHeaders(array $headers): void
    {
        $this->col_headers = $headers;
    }

    /**
     * Set the line height (mm per text line) for MultiCell rows.
     */
    public function SetLineHeight(float $height): void
    {
        $this->line_height = $height;
    }

    /**
     * Set brand color scheme.
     */
    public function SetColors(array $header, array $even, array $odd, array $border): void
    {
        $this->color_header   = $header;
        $this->color_row_even = $even;
        $this->color_row_odd  = $odd;
        $this->color_border   = $border;
    }

    // ─────────────────────────────────────────────────────────────────────────
    //  CORE TABLE METHODS
    // ─────────────────────────────────────────────────────────────────────────

    /**
     * Draw the styled header row.
     */
    public function TableHeader(): void
    {
        if (empty($this->col_headers)) return;

        $this->SetFont('Arial', 'B', 9);
        $this->SetFillColor(...$this->color_header);
        $this->SetTextColor(255, 255, 255);
        $this->SetDrawColor(...$this->color_header);

        foreach ($this->col_headers as $i => $label) {
            $align = $this->col_aligns[$i] ?? 'C';
            $this->Cell(
                $this->col_widths[$i],
                $this->line_height + 2,
                utf8_decode($label),
                1, 0, $align, true
            );
        }

        $this->Ln();
        $this->SetTextColor(...$this->color_text);
        $this->SetDrawColor(...$this->color_border);
        $this->SetFont('Arial', '', 9);
        $this->row_count = 0; // Reset zebra counter after header
    }

    /**
     * Draw a standard data row with automatic height and zebra striping.
     *
     * @param array $data   Cell values — one per column
     */
    public function Row(array $data): void
    {
        // ── Calculate row height ─────────────────────────────────────────────
        $row_height = $this->CalculateRowHeight($data);

        // ── Check page break BEFORE drawing ─────────────────────────────────
        $this->CheckPageBreak($row_height);

        // ── Zebra fill color ─────────────────────────────────────────────────
        $fill_color = ($this->row_count % 2 === 0)
            ? $this->color_row_even
            : $this->color_row_odd;

        // ── Draw all cells ───────────────────────────────────────────────────
        $start_x = $this->GetX();
        $start_y = $this->GetY();

        $this->SetFillColor(...$fill_color);

        foreach ($data as $i => $cell_text) {
            $col_w = $this->col_widths[$i] ?? 30;
            $align = $this->col_aligns[$i] ?? 'L';

            // Step A: Draw border rectangle at exact row height
            $this->Rect($start_x, $start_y, $col_w, $row_height, 'FD');

            // Step B: Print text inside (no border — Rect handles it)
            $this->SetXY($start_x + 1, $start_y + 1); // +1mm inner padding
            $this->MultiCell(
                $col_w - 2,          // Subtract padding from both sides
                $this->line_height,
                utf8_decode((string)$cell_text),
                0,                   // No border — handled by Rect above
                $align,
                false
            );

            $start_x += $col_w;
        }

        // ── Advance cursor below the row ─────────────────────────────────────
        $this->SetXY($this->lMargin, $start_y + $row_height);
        $this->row_count++;
    }

    /**
     * Draw a full-width summary/total row (e.g., invoice totals).
     *
     * @param string $label  Left-side label (spans first N-1 columns)
     * @param string $value  Right-side value (last column)
     * @param bool   $bold   Make text bold?
     * @param array  $color  Background RGB (defaults to $color_total)
     */
    public function TotalRow(
        string $label,
        string $value,
        bool   $bold  = false,
        array  $color = []
    ): void {
        $fill_color = $color ?: $this->color_total;
        $font_style = $bold ? 'B' : '';

        $total_width  = array_sum($this->col_widths);
        $last_width   = end($this->col_widths);
        $label_width  = $total_width - $last_width;

        $this->SetFont('Arial', $font_style, 9);
        $this->SetFillColor(...$fill_color);
        $this->SetTextColor(...$this->color_text);

        $this->Cell($label_width, $this->line_height + 1, utf8_decode($label), 1, 0, 'R', true);
        $this->Cell($last_width,  $this->line_height + 1, utf8_decode($value), 1, 1, 'R', true);

        $this->SetFont('Arial', '', 9);
    }

    /**
     * Draw a full-width section divider row (colored banner inside table).
     *
     * @param string $label  Text to display across the full table width
     * @param array  $color  Background RGB
     */
    public function SectionRow(string $label, array $color = [100, 116, 139]): void
    {
        $total_width = array_sum($this->col_widths);
        $this->SetFont('Arial', 'B', 9);
        $this->SetFillColor(...$color);
        $this->SetTextColor(255, 255, 255);
        $this->Cell($total_width, $this->line_height, utf8_decode('  ' . $label), 1, 1, 'L', true);
        $this->SetTextColor(...$this->color_text);
        $this->SetFont('Arial', '', 9);
    }

    // ─────────────────────────────────────────────────────────────────────────
    //  PAGE BREAK + HEIGHT CALCULATION
    // ─────────────────────────────────────────────────────────────────────────

    /**
     * Calculate the total height a row will occupy in mm.
     * Uses NbLines() to find the tallest cell, then multiplies by line_height.
     */
    protected function CalculateRowHeight(array $data): float
    {
        $max_lines = 1;
        foreach ($data as $i => $text) {
            $w     = $this->col_widths[$i] ?? 30;
            $lines = $this->NbLines($w - 2, (string)$text); // -2 for padding
            $max_lines = max($max_lines, $lines);
        }
        return $this->line_height * $max_lines + 2; // +2mm for top+bottom padding
    }

    /**
     * Trigger a manual page break if $h mm won't fit on the current page.
     * After the break, re-draw headers if $repeat_header is true.
     */
    protected function CheckPageBreak(float $h): void
    {
        $bottom_limit = $this->GetPageHeight() - $this->bMargin - $this->page_bottom;
        if ($this->GetY() + $h > $bottom_limit) {
            $this->AddPage($this->CurOrientation);
            if ($this->repeat_header) {
                $this->TableHeader();
            }
        }
    }

    /**
     * Calculate the number of lines a MultiCell will occupy.
     * Replicates FPDF's internal word-wrap logic without drawing anything.
     */
    public function NbLines(float $w, string $txt): int
    {
        if (!isset($this->CurrentFont)) {
            $this->Error('No font has been set');
        }
        $cw   = $this->CurrentFont['cw'];
        $wmax = ($w - 2 * $this->cMargin) * 1000 / $this->FontSize;
        $s    = str_replace("\r", '', $txt);
        $nb   = strlen($s);
        if ($nb > 0 && $s[$nb - 1] == "\n") $nb--;

        $sep = -1; $i = 0; $j = 0; $l = 0; $nl = 1;

        while ($i < $nb) {
            $c = $s[$i];
            if ($c == "\n") { $i++; $sep = -1; $j = $i; $l = 0; $nl++; continue; }
            if ($c == ' ')  $sep = $i;
            $l += $cw[$c] ?? 0;
            if ($l > $wmax) {
                if ($sep == -1) { if ($i == $j) $i++; }
                else            { $i = $sep + 1; }
                $sep = -1; $j = $i; $l = 0; $nl++;
            } else {
                $i++;
            }
        }
        return $nl;
    }
}

 

Part 4 — Five Complete Real-World Table Examples

Example 1: Professional Invoice Table

<?php
require('fpdf.php');
require('PDF_MC_Table.php'); // The class above

$pdf = new PDF_MC_Table('P', 'mm', 'A4');
$pdf->SetMargins(15, 15, 15);
$pdf->SetAutoPageBreak(true, 20);
$pdf->AliasNbPages();
$pdf->AddPage();

// ── Invoice Header ────────────────────────────────────────────────────────
$pdf->SetFont('Arial', 'B', 18);
$pdf->SetTextColor(30, 64, 175);
$pdf->Cell(0, 12, 'INVOICE', 0, 1, 'R');

$pdf->SetFont('Arial', '', 9);
$pdf->SetTextColor(80, 80, 80);
$pdf->Cell(0, 6, 'Invoice #: INV-2025-0089', 0, 1, 'R');
$pdf->Cell(0, 6, 'Date: 15 July 2025', 0, 1, 'R');
$pdf->Cell(0, 6, 'Due: 29 July 2025', 0, 1, 'R');
$pdf->Ln(6);

// ── Bill To ───────────────────────────────────────────────────────────────
$pdf->SetFont('Arial', 'B', 9);
$pdf->SetTextColor(30, 30, 30);
$pdf->Cell(0, 6, 'Bill To:', 0, 1);
$pdf->SetFont('Arial', '', 9);
$pdf->Cell(0, 5, 'Acme Corporation Pvt. Ltd.', 0, 1);
$pdf->Cell(0, 5, '14, Business Park, Sector 62, Noida, UP - 201301', 0, 1);
$pdf->Cell(0, 5, 'GSTIN: 09ABCDE1234F1Z5', 0, 1);
$pdf->Ln(8);

// ── Line Items Table ──────────────────────────────────────────────────────
$pdf->SetFont('Arial', '', 9);
$pdf->SetHeaders(['#', 'Description', 'HSN', 'Qty', 'Rate (₹)', 'Amount (₹)']);
$pdf->SetWeights([0.4, 3.5, 0.8, 0.5, 1, 1]);
$pdf->SetAligns(['C', 'L', 'C', 'C', 'R', 'R']);
$pdf->SetLineHeight(6);
$pdf->TableHeader();

$items = [
    ['1', 'WordPress Custom Theme Development — Responsive design with 5 page templates, mobile-first layout', '998314', '1',  '45,000', '45,000'],
    ['2', 'WooCommerce Store Setup — Product catalog, payment gateway (Razorpay), shipping configuration',      '998314', '1',  '30,000', '30,000'],
    ['3', 'SEO Optimization Package — On-page SEO for 20 pages, schema markup, sitemap, robots.txt',           '998313', '1',  '18,000', '18,000'],
    ['4', 'Annual Hosting & Maintenance Plan — SSD hosting, daily backups, SSL, uptime monitoring',            '997331', '12', '2,000',  '24,000'],
    ['5', 'Domain Registration — .com domain for 2 years',                                                     '997331', '2',  '900',    '1,800'],
];

foreach ($items as $item) {
    $pdf->Row($item);
}

// ── Totals ────────────────────────────────────────────────────────────────
$pdf->Ln(2);
$pdf->TotalRow('Subtotal',        '₹1,18,800', false);
$pdf->TotalRow('CGST @ 9%',      '₹10,692',   false);
$pdf->TotalRow('SGST @ 9%',      '₹10,692',   false);
$pdf->TotalRow('TOTAL PAYABLE',   '₹1,40,184', true, [30, 64, 175]);

// Override text color for total row (white on blue)
// Note: Do this before TotalRow() call for the final row if needed

$pdf->Output('D', 'invoice-INV-2025-0089.pdf');

Example 2: Employee Attendance Sheet (Multi-Page with Header Repeat)

$pdf = new PDF_MC_Table('L', 'mm', 'A4'); // Landscape
$pdf->SetMargins(10, 10, 10);
$pdf->SetAutoPageBreak(true, 15);
$pdf->AliasNbPages();
$pdf->AddPage();

$pdf->SetFont('Arial', 'B', 14);
$pdf->Cell(0, 10, 'Monthly Attendance Report — July 2025', 0, 1, 'C');
$pdf->SetFont('Arial', '', 9);
$pdf->Cell(0, 6, 'Department: Engineering | Total Working Days: 23', 0, 1, 'C');
$pdf->Ln(4);

$pdf->SetHeaders(['EMP ID', 'Employee Name', 'Designation', 'Present', 'Absent', 'Leave', 'OT Hrs', 'Status']);
$pdf->SetWeights([0.8, 2, 1.8, 0.7, 0.7, 0.7, 0.7, 1]);
$pdf->SetAligns(['C', 'L', 'L', 'C', 'C', 'C', 'C', 'C']);
$pdf->SetLineHeight(6);
$pdf->TableHeader();

// Simulating 35 rows to demonstrate multi-page header repeat
$employees = [
    ['EMP001', 'Rajesh Kumar Sharma',     'Senior PHP Developer',       '22', '0', '1', '8',  'Regular'],
    ['EMP002', 'Priya Nair',              'UI/UX Designer',             '20', '2', '1', '0',  'Regular'],
    ['EMP003', 'Mohammed Arif Khan',      'DevOps Engineer',            '23', '0', '0', '12', 'Regular'],
    ['EMP004', 'Sunita Devi Raghuwanshi', 'QA Test Lead',               '18', '3', '2', '0',  'Warning'],
    ['EMP005', 'Aakash Verma',            'Junior WordPress Developer',  '21', '1', '1', '4',  'Regular'],
    ['EMP006', 'Lalitha Subramaniam',     'Project Manager',            '22', '0', '1', '0',  'Regular'],
    ['EMP007', 'Deepak Singh Tomar',      'Backend Developer (Laravel)', '19', '4', '0', '0',  'Warning'],
    // ... (repeat or loop for a large dataset)
];

// Repeat rows to fill multiple pages for demonstration
$all_employees = array_merge($employees, $employees, $employees, $employees);
foreach ($all_employees as $emp) {
    $pdf->Row($emp);
}

$pdf->Output('D', 'attendance-july-2025.pdf');

Example 3: Product Catalog Table from MySQL/PDO

// ── Fetch from database ───────────────────────────────────────────────────
$pdo  = new PDO('mysql:host=localhost;dbname=shop;charset=utf8', 'user', 'pass');
$stmt = $pdo->prepare("
    SELECT
        p.sku,
        p.name,
        c.name        AS category,
        p.description,
        p.price,
        p.stock_qty,
        p.status
    FROM products p
    JOIN categories c ON c.id = p.category_id
    WHERE p.active = 1
    ORDER BY c.name, p.name
    LIMIT 200
");
$stmt->execute();
$products = $stmt->fetchAll(PDO::FETCH_ASSOC);

// ── Build PDF ─────────────────────────────────────────────────────────────
$pdf = new PDF_MC_Table('L', 'mm', 'A4');
$pdf->SetMargins(10, 10, 10);
$pdf->SetAutoPageBreak(true, 15);
$pdf->AliasNbPages();
$pdf->AddPage();

$pdf->SetFont('Arial', 'B', 14);
$pdf->Cell(0, 10, 'Product Catalog — ' . date('F Y'), 0, 1, 'C');
$pdf->SetFont('Arial', '', 9);
$pdf->Cell(0, 5, 'Total Products: ' . count($products), 0, 1, 'C');
$pdf->Ln(4);

$pdf->SetHeaders(['SKU', 'Product Name', 'Category', 'Description', 'Price (₹)', 'Stock', 'Status']);
$pdf->SetWeights([0.8, 2, 1.2, 3.5, 1, 0.7, 0.8]);
$pdf->SetAligns(['C', 'L', 'L', 'L', 'R', 'C', 'C']);
$pdf->SetLineHeight(5);

// Group by category with section rows
$current_category = '';
$pdf->TableHeader();

foreach ($products as $p) {
    // Insert section divider when category changes
    if ($p['category'] !== $current_category) {
        $current_category = $p['category'];
        $pdf->SectionRow($current_category, [71, 85, 105]); // Slate-600
    }

    $pdf->Row([
        $p['sku'],
        $p['name'],
        $p['category'],
        $p['description'],
        number_format($p['price'], 2),
        $p['stock_qty'],
        $p['status'],
    ]);
}

$pdf->Output('D', 'product-catalog-' . date('Y-m') . '.pdf');

Example 4: Project Schedule / Gantt Summary Table

$pdf = new PDF_MC_Table('L', 'mm', 'A4');
$pdf->SetMargins(10, 10, 10);
$pdf->SetAutoPageBreak(true, 15);
$pdf->AddPage();

$pdf->SetFont('Arial', 'B', 13);
$pdf->Cell(0, 10, 'Project Schedule — Website Redesign Initiative', 0, 1, 'C');
$pdf->SetFont('Arial', '', 9);
$pdf->Cell(0, 5, 'Project Duration: 01 Jul 2025 – 30 Sep 2025 | Total Milestones: 6', 0, 1, 'C');
$pdf->Ln(5);

$pdf->SetHeaders(['Phase', 'Task / Milestone', 'Owner', 'Start', 'End', 'Duration', '% Done', 'Blocker?']);
$pdf->SetWeights([0.8, 3, 1.5, 0.9, 0.9, 0.7, 0.7, 0.8]);
$pdf->SetAligns(['C', 'L', 'L', 'C', 'C', 'C', 'C', 'C']);
$pdf->SetLineHeight(6);

$milestones = [
    ['1', 'Discovery & Requirements Gathering — stakeholder interviews, scope document, user stories', 'PM Office',   '01 Jul', '07 Jul', '7 days',  '100%', 'No'],
    ['1', 'Wireframes & UI Mockups (All 12 page types)',                                               'UX Team',     '08 Jul', '18 Jul', '11 days', '100%', 'No'],
    ['2', 'Frontend Development — HTML/CSS/JS for all approved mockups, responsive implementation',    'Dev: Alice',  '19 Jul', '05 Aug', '18 days', '85%',  'No'],
    ['2', 'WordPress Theme Development — ACF integration, custom post types, REST API endpoints',      'Dev: Bob',    '22 Jul', '10 Aug', '20 days', '70%',  'No'],
    ['3', 'WooCommerce Setup — products, payment, shipping, tax rules, order emails customisation',    'Dev: Carol',  '01 Aug', '15 Aug', '15 days', '45%',  'Yes'],
    ['3', 'Third-party Integrations — Razorpay, Mailchimp, Google Analytics 4, Facebook Pixel',       'Dev Team',    '10 Aug', '20 Aug', '11 days', '20%',  'No'],
    ['4', 'QA Testing — cross-browser, mobile, performance audit, accessibility (WCAG 2.1 AA)',        'QA: Dev',     '20 Aug', '02 Sep', '14 days', '0%',   'No'],
    ['4', 'Staging → Production Migration, DNS cutover, SSL, CDN configuration',                      'DevOps',      '03 Sep', '07 Sep', '5 days',  '0%',   'No'],
    ['5', 'UAT Sign-off by Client',                                                                    'PM Office',   '08 Sep', '12 Sep', '5 days',  '0%',   'No'],
    ['6', 'Go-Live & Post-Launch Support Window (2 weeks)',                                            'Full Team',   '15 Sep', '30 Sep', '16 days', '0%',   'No'],
];

$pdf->TableHeader();
foreach ($milestones as $row) {
    $pdf->Row($row);
}

$pdf->Output('D', 'project-schedule-q3-2025.pdf');

Example 5: Comparison / Feature Matrix Table

$pdf = new PDF_MC_Table('P', 'mm', 'A4');
$pdf->SetMargins(15, 15, 15);
$pdf->SetAutoPageBreak(true, 20);
$pdf->AddPage();

$pdf->SetFont('Arial', 'B', 13);
$pdf->SetTextColor(30, 64, 175);
$pdf->Cell(0, 10, 'PHP PDF Library Comparison Matrix — 2025', 0, 1, 'C');
$pdf->SetTextColor(30, 30, 30);
$pdf->Ln(4);

$pdf->SetHeaders(['Feature', 'FPDF', 'TCPDF', 'mPDF', 'Dompdf']);
$pdf->SetWeights([3, 1, 1, 1, 1]);
$pdf->SetAligns(['L', 'C', 'C', 'C', 'C']);
$pdf->SetLineHeight(6);

$comparison = [
    ['Pure PHP (no extensions needed)',         '✔', '✔', '✔', '✔'],
    ['HTML → PDF conversion',                   '✖', '~', '✔', '✔'],
    ['CSS support',                             '✖', '~', '✔', '✔'],
    ['Unicode / multibyte text',                '~', '✔', '✔', '✔'],
    ['Indic scripts (Hindi, Tamil, Telugu)',    '✖', '✔', '✔', '✖'],
    ['RTL text (Arabic, Hebrew)',               '✖', '✔', '✔', '~'],
    ['Image support (PNG, JPG, GIF)',           '✔', '✔', '✔', '✔'],
    ['Barcode / QR code generation',            '✖', '✔', '~', '✖'],
    ['Digital signatures',                      '✖', '✔', '✖', '✖'],
    ['Custom headers & footers',                '✔', '✔', '✔', '✖'],
    ['Multi-column layouts',                    '✔', '✔', '✔', '✔'],
    ['Page numbering',                          '✔', '✔', '✔', '✔'],
    ['File size (library only)',                '~150KB', '~20MB', '~30MB', '~5MB'],
    ['Composer installable',                    '✔', '✔', '✔', '✔'],
    ['Active maintenance (2025)',               '✔', '✔', '✔', '✔'],
    ['Learning curve',                          'Low', 'High', 'Medium', 'Low'],
    ['Best for',                                'Tables, invoices, reports', 'Complex docs', 'HTML → PDF', 'HTML → PDF'],
];

$pdf->TableHeader();
foreach ($comparison as $row) {
    $pdf->Row($row);
}

$pdf->Output('D', 'php-pdf-comparison-2025.pdf');

 

Part 5 — Column Width Strategies: A Complete Reference

Column width is the first layout decision in every table. Get it wrong and the entire PDF looks broken. Here are all four strategies:

Strategy 1: Fixed Widths (Manual)

// You measure the columns and hardcode them.
// Best when you know exactly what content each column holds.
$pdf->SetWidths([20, 90, 25, 25]);
// Sum = 160mm = 190mm page width - 15mm left margin - 15mm right margin

Pros: Precise control. Cons: Breaks if margins change or you add a column.

Strategy 2: Proportional Weights (Recommended)

// Weights express relative importance.
// [2, 5, 1.5, 1.5] = description gets 5/10 = 50% of printable width
$pdf->SetWeights([2, 5, 1.5, 1.5]);
// Automatically calculates correct widths for any margin/page-size combination

Pros: Responds to margin changes. Readable. Cons: You still need to tune the ratios.

Strategy 3: Content-Adaptive (Dynamic)

// Measure the longest content in each column, then set widths accordingly.
// Useful when generating PDFs from database content of unknown length.

$col_count = count($headers);
$max_widths = array_fill(0, $col_count, 0);

foreach ($rows as $row) {
    foreach ($row as $i => $cell) {
        $pdf->SetFont('Arial', '', 9);
        $text_width = $pdf->GetStringWidth(utf8_decode($cell)) + 4; // +4mm padding
        $max_widths[$i] = max($max_widths[$i], $text_width);
    }
}

// Also measure headers
foreach ($headers as $i => $hdr) {
    $pdf->SetFont('Arial', 'B', 9);
    $max_widths[$i] = max($max_widths[$i], $pdf->GetStringWidth($hdr) + 6);
}

// Cap total at printable width
$total         = array_sum($max_widths);
$printable     = $pdf->GetPageWidth() - $pdf->lMargin - $pdf->rMargin;
$scale_factor  = min(1, $printable / $total);
$final_widths  = array_map(fn($w) => $w * $scale_factor, $max_widths);

$pdf->SetWidths($final_widths);

Strategy 4: Mixed (Fixed + Proportional)

// Sometimes you know exact widths for some columns (like an ID or date)
// but need the rest to share remaining space proportionally.

$printable   = $pdf->GetPageWidth() - 30; // 30mm margins total
$fixed_id    = 15;
$fixed_date  = 25;
$fixed_amt   = 28;
$remaining   = $printable - $fixed_id - $fixed_date - $fixed_amt;

$pdf->SetWidths([
    $fixed_id,
    $remaining * 0.55,  // Description: 55% of remaining
    $remaining * 0.45,  // Notes: 45% of remaining
    $fixed_date,
    $fixed_amt,
]);

 

Part 6 — Alignment Patterns Explained

// All alignment values FPDF understands:
// 'L' = Left  (default for most data)
// 'C' = Center (good for IDs, dates, status, numbers)
// 'R' = Right  (always for currency, quantities, percentages)
// 'J' = Justified (only in MultiCell — both edges aligned)

// Typical invoice alignment:
$pdf->SetAligns(['C', 'L', 'C', 'C', 'R', 'R']);
//               #   Desc  HSN  Qty  Rate  Amount

// Typical attendance sheet alignment:
$pdf->SetAligns(['C', 'L', 'L', 'C', 'C', 'C', 'C', 'C']);
//               ID   Name  Dept  P    A    L    OT   Status

// Right-aligning numbers makes vertical scanning much easier:
// ✅ Good:         ❌ Bad:
//   1,200.00          1,200.00
//      45.50        45.50
//  12,880.00      12,880.00
//  ─────────      ─────────
//  14,125.50      14,125.50

 

Part 7 — Common Errors and Complete Fixes

Error 1: Columns Appear Stacked (Not Side by Side)

Cause: You called multiple MultiCell() without resetting SetXY() between them.

// ❌ Wrong — second cell appears BELOW the first
$pdf->MultiCell(80, 6, $desc,   1, 'L');
$pdf->MultiCell(30, 6, $amount, 1, 'R'); // This goes below!

// ✅ Correct — use Rect for border + MultiCell for text
$x = $pdf->GetX();
$y = $pdf->GetY();

$pdf->Rect($x,      $y, 80, $row_h, 'FD');
$pdf->SetXY($x + 1, $y + 1);
$pdf->MultiCell(78, 6, $desc, 0, 'L');

$pdf->Rect($x + 80, $y, 30, $row_h, 'FD');
$pdf->SetXY($x + 81, $y + 1);
$pdf->MultiCell(28, 6, $amount, 0, 'R');

$pdf->SetXY($pdf->lMargin, $y + $row_h);

Error 2: Row Splits Across a Page Break

Cause: FPDF’s auto page break fires mid-row, splitting cells across two pages.

// ❌ Wrong — no manual check
foreach ($rows as $row) {
    $pdf->Row($row); // FPDF may split this row across pages
}

// ✅ Correct — CheckPageBreak() before every row
$height = $this->CalculateRowHeight($row);
$this->CheckPageBreak($height);
// Then draw the row

Error 3: Text Overflow on Single-Line Cells

Cause: Using Cell() for long/variable-length text.

// ❌ Wrong — description overflows
$pdf->Cell(80, 6, 'This is a very long product description that will overflow', 1);

// ✅ Correct — MultiCell wraps automatically
// (but you must use the Rect+MultiCell pattern for multi-column rows)

Error 4: Inconsistent Row Heights (Ragged Table)

Cause: Each column’s MultiCell is drawn at its own natural height instead of the row maximum.

// ❌ Wrong — each cell gets its own height
$pdf->MultiCell(80, 6, $long_text,  1); // 3 lines = 18mm tall
$pdf->SetXY(...);
$pdf->MultiCell(30, 6, $short_text, 1); // 1 line = 6mm tall — mismatched!

// ✅ Correct — calculate $row_height first, then use Rect for the border
$row_height = $this->CalculateRowHeight($data);
// Use $row_height for all Rect() calls in this row

Error 5: NbLines() Returns Wrong Count

Cause: Font not set before calling NbLines(), or calling it before AddPage().

// ❌ Wrong — no font set yet
$lines = $pdf->NbLines(80, $text);

// ✅ Correct — always set font first
$pdf->SetFont('Arial', '', 9);
$pdf->AddPage(); // Must add a page before NbLines() can access font metrics
$lines = $pdf->NbLines(80, $text);

Error 6: Special Characters Show as “?”

// ❌ Wrong — UTF-8 passed directly to FPDF built-in fonts
$pdf->Cell(80, 6, 'Réservé & Société');

// ✅ Correct — decode UTF-8 to Latin-1 for built-in fonts
$pdf->Cell(80, 6, utf8_decode('Réservé & Société'));

// For Indic scripts (Hindi etc.) — switch to mPDF with TTF fonts
// FPDF built-in fonts do not support Unicode beyond Latin-1

Error 7: Table Appears on Wrong Page / Disappears

Cause: AddPage() not called before drawing, or drawing before setting font.

// ✅ Always follow this order:
$pdf = new PDF_MC_Table();
$pdf->AliasNbPages();      // 1. Alias before any page
$pdf->SetMargins(15,15,15);// 2. Set margins
$pdf->SetAutoPageBreak(true, 20); // 3. Auto page break
$pdf->AddPage();           // 4. Add first page
$pdf->SetFont('Arial','',9);// 5. Set font
// 6. Now configure and draw table
$pdf->SetHeaders([...]);
$pdf->SetWeights([...]);
$pdf->TableHeader();
foreach ($rows as $row) {
    $pdf->Row($row);
}
$pdf->Output('D', 'file.pdf');

 

Part 8 — Performance Tips for Large Datasets

When generating PDFs from 500+ rows of database data, performance matters:

Tip 1: Pre-Decode UTF-8 in Batch

// Decode all strings once before the loop, not inside Row() on every call
$rows = array_map(function($row) {
    return array_map('utf8_decode', $row);
}, $rows);

Tip 2: Pre-Calculate All Row Heights

// Calculate heights in one pass, then draw in a second pass
$heights = [];
foreach ($rows as $row) {
    $heights[] = $pdf->CalculateRowHeight($row);
}
// Now draw with pre-calculated heights (avoids redundant NbLines() calls)

Tip 3: Increase PHP Memory Limit for Large PDFs

 
// At the top of your PDF generation script
ini_set('memory_limit', '256M');
ini_set('max_execution_time', '120');

Tip 4: Buffer Output — Don’t Stream Prematurely

// Generate the full PDF in memory, then output
// Use Output('S') to get as a string for email attachments
$pdf_string = $pdf->Output('S'); // S = return as string
// Attach $pdf_string to PHPMailer or WordPress wp_mail()

Tip 5: Save to File for Caching

$path = __DIR__ . '/cache/report-' . date('Ymd') . '.pdf';
if (!file_exists($path)) {
    $pdf->Output('F', $path); // Generate once
}
// Serve the cached file on subsequent requests
readfile($path);

 

Part 9 — Complete Error Reference Table

Error / Symptom Root Cause Fix
Columns stacked vertically MultiCell moves cursor down; no SetXY reset Use Rect for border + SetXY($x+w, $y) before each column
Row split across pages No manual page break check Call CheckPageBreak($height) before every Row()
Text overflow (not wrapping) Using Cell() for variable-length text Switch to MultiCell() with Rect() border pattern
Ragged/uneven row heights Each column drawn at its own natural height Pre-calculate $row_height = line_height × max(NbLines)
NbLines() crashes No font set / no page added Call SetFont() and AddPage() before any NbLines()
Special chars show as ? UTF-8 passed to Latin-1 font Wrap all strings with utf8_decode()
Table disappears or is empty AddPage() not called; wrong method order Follow: AliasNbPages → SetMargins → AddPage → SetFont → Table
Headers not repeated on page 2 $repeat_header false or not calling TableHeader() in CheckPageBreak() Set $this->repeat_header = true; call TableHeader() in CheckPageBreak()
Border color bleeds into next row SetDrawColor not reset after header Reset SetDrawColor to border color after TableHeader()
Total width wider than page Column widths don’t account for margins Use SetWeights() or verify sum = GetPageWidth() – lMargin – rMargin
Memory exhausted on large data Default 128MB not enough ini_set(‘memory_limit’, ‘256M’)
{nb} appears as literal text AliasNbPages() not called Call $pdf->AliasNbPages() before first AddPage()

 

The goal of this script is to show how to build a table from MultiCells. As MultiCells go to the next line after being output, the base idea consists in saving the current position, printing the MultiCell and resetting the position to its right.

There is a difficulty, however, if the table is too long: page breaks. Before outputting a row, it is necessary to know whether it will cause a break or not. If it does overflow, a manual page break must be done first.

To do so, the height of the row must be known in advance; it is the maximum of the heights of the MultiCells it is made up of. To know the height of a MultiCell, the NbLines() method is used: it returns the number of lines a MultiCell will occupy.

<?php
require('fpdf.php');

class PDF_MC_Table extends FPDF
{
    protected $widths;
    protected $aligns;

    function SetWidths($w)
    {
        // Set the array of column widths
        $this->widths = $w;
    }

    function SetAligns($a)
    {
        // Set the array of column alignments
        $this->aligns = $a;
    }

    function Row($data)
    {
        // Calculate the height of the row
        $nb = 0;
        for($i=0;$i<count($data);$i++)
            $nb = max($nb,$this->NbLines($this->widths[$i],$data[$i]));
        $h = 5*$nb;
        // Issue a page break first if needed
        $this->CheckPageBreak($h);
        // Draw the cells of the row
        for($i=0;$i<count($data);$i++)
        {
            $w = $this->widths[$i];
            $a = isset($this->aligns[$i]) ? $this->aligns[$i] : 'L';
            // Save the current position
            $x = $this->GetX();
            $y = $this->GetY();
            // Draw the border
            $this->Rect($x,$y,$w,$h);
            // Print the text
            $this->MultiCell($w,5,$data[$i],0,$a);
            // Put the position to the right of the cell
            $this->SetXY($x+$w,$y);
        }
        // Go to the next line
        $this->Ln($h);
    }

    function CheckPageBreak($h)
    {
        // If the height h would cause an overflow, add a new page immediately
        if($this->GetY()+$h>$this->PageBreakTrigger)
            $this->AddPage($this->CurOrientation);
    }

    function NbLines($w, $txt)
    {
        // Compute the number of lines a MultiCell of width w will take
        if(!isset($this->CurrentFont))
            $this->Error('No font has been set');
        $cw = $this->CurrentFont['cw'];
        if($w==0)
            $w = $this->w-$this->rMargin-$this->x;
        $wmax = ($w-2*$this->cMargin)*1000/$this->FontSize;
        $s = str_replace("\r",'',(string)$txt);
        $nb = strlen($s);
        if($nb>0 && $s[$nb-1]=="\n")
            $nb--;
        $sep = -1;
        $i = 0;
        $j = 0;
        $l = 0;
        $nl = 1;
        while($i<$nb)
        {
            $c = $s[$i];
            if($c=="\n")
            {
                $i++;
                $sep = -1;
                $j = $i;
                $l = 0;
                $nl++;
                continue;
            }
            if($c==' ')
                $sep = $i;
            $l += $cw[$c];
            if($l>$wmax)
            {
                if($sep==-1)
                {
                    if($i==$j)
                        $i++;
                }
                else
                    $i = $sep+1;
                $sep = -1;
                $j = $i;
                $l = 0;
                $nl++;
            }
            else
                $i++;
        }
        return $nl;
    }
}
?>

 

Example

Here’s an example of multi-page table with random content:

<?php
require('mc_table.php');

function GenerateWord()
{
    // Get a random word
    $nb = rand(3, 10);
    $w = '';
    for($i=1;$i<=$nb;$i++)
        $w .= chr(rand(ord('a'), ord('z')));
    return $w;
}

function GenerateSentence()
{
    // Get a random sentence
    $nb = rand(1, 10);
    $s = '';
    for($i=1;$i<=$nb;$i++)
        $s .= GenerateWord().' ';
    return substr($s, 0, -1);
}

$pdf = new PDF_MC_Table();
$pdf->AddPage();
$pdf->SetFont('Arial', '', 14);
// Table with 20 rows and 4 columns
$pdf->SetWidths(array(30, 50, 30, 40));
for($i=0;$i<20;$i++)
    $pdf->Row(array(GenerateSentence(), GenerateSentence(), GenerateSentence(), GenerateSentence()));
$pdf->Output();
?>

Benefits of Using MultiCell in FPDF Tables

  1. Automatic Text Wrapping: Long content automatically wraps into multiple lines without breaking the table layout.
  2. Professional PDF Layouts: Creates clean and structured PDF reports suitable for business documents.
  3. Dynamic Content Support: Handles variable-length data from forms, databases, APIs, and user-generated content.
  4. Better Readability: Ensures all text remains visible inside table cells without truncation.
  5. Flexible Row Heights: Rows automatically expand based on content length.
  6. Ideal for Large Reports: Useful when generating invoices, reports, certificates, and LMS documents.
  7. Improved User Experience: Users receive properly formatted PDFs regardless of content size.
  8. Easy Integration with PHP Applications: Works seamlessly with WordPress, Laravel, CodeIgniter, and custom PHP projects.
  9. Supports Multiple Languages:Can be extended to handle multilingual content and UTF-8 compatible fonts.
  10. Open-Source and Lightweight:FPDF is free, fast, and easy to implement.

Why Use MultiCell Instead of Cell()?

Feature Cell() MultiCell()
Single Line Text Yes Yes
Auto Text Wrapping No Yes
Dynamic Height No Yes
Long Paragraph Support No Yes
Table Report Generation Limited Excellent
Professional Layout Basic Advanced

What You Can Build Now

The NbLines() algorithm and the Rect() + MultiCell() pattern are the two cornerstones of every well-built FPDF table. Together they solve the three hardest FPDF table problems:

1. Dynamic row height — NbLines() tells you exactly how tall a row will be before you draw it, letting you synchronize all columns in a row to the same height.

2. Page break control — CheckPageBreak() using the pre-calculated height ensures no row ever splits across two pages, and headers repeat automatically on continuation pages.

3. Multi-column alignment — The Rect() border + SetXY($x, $y) reset pattern lets you place any number of MultiCell columns side by side with perfect alignment, regardless of how many lines each one wraps to.

With the PDF_MC_Table class from this guide, you have a drop-in engine for:

  • Invoices and billing documents
  • Employee and attendance reports
  • Product catalogs from databases
  • Project schedules and milestone trackers
  • Feature comparison matrices
  • Compliance and audit documents

The class is designed to be extended — add your own StatusRow(), ImageCell(), or SignatureRow() methods using the same Rect() + SetXY() foundation.

 

Note*:
Orignal Source: https://www.fpdf.org/en/script/script3.php
Author: Olivier

Frequently Asked Questions

+

1.What is MultiCell in FPDF?

MultiCell is an FPDF function that creates cells capable of displaying multiple lines of text with automatic word wrapping.
+

2.Why should I use MultiCell instead of Cell()?

MultiCell automatically wraps long text and adjusts row height, while Cell() only supports a single line of content.
+

3.Can MultiCell be used inside PDF tables?

Yes. MultiCell is commonly used to create dynamic PDF tables where content length varies between rows.
+

4.How do I create automatic row heights in FPDF?

Calculate the number of lines required for each cell and set the row height based on the tallest cell before rendering the row.
+

5.Does FPDF support large datasets?

Yes. FPDF can generate reports with thousands of records, although performance optimization may be required for very large exports.
+

6.Is FPDF free for commercial use?

Yes. FPDF is an open-source PHP library that can be used in both personal and commercial projects.
Previous Article

How to Generate PDF with Full-Width Rows in WordPress Using FPDF

Next Article

How to Enable WebP Support in WordPress — The Complete Developer's Guide (2025)

Write a Comment

Leave a Comment

Your email address will not be published. Required fields are marked *

Subscribe to our Newsletter

Subscribe to our email newsletter to get the latest posts delivered right to your email.
Pure inspiration, zero spam ✨