Skip to content
Dragon edited this page Jun 4, 2026 · 2 revisions

Cells

cell() is the workhorse of the page layout API. It draws a rectangular text block at a given position, optionally with a border, fill, and explicit or auto-derived size. Use it for everything from simple labels to full table-like row/column grids.

Basic usage

use DragonOfMercy\PhpPdf\Border;
use DragonOfMercy\PhpPdf\BorderStyle;
use DragonOfMercy\PhpPdf\Fit;
use DragonOfMercy\PhpPdf\TextAlign;
use DragonOfMercy\PhpPdf\VerticalAlign;

$page->setFont(Font::helvetica(), 12);

// Header: centered, bordered, filled.
$page->cell(
    x: 20, y: 20, w: 170, h: 10,
    text: 'Invoice #2026-001',
    border: Border::all()->withWidth(0.3),
    fill: Color::rgb(242, 242, 242),
    align: TextAlign::CENTER,
    verticalAlign: VerticalAlign::MIDDLE,
);

// Wrapping prose with a dashed border; height determined by content.
$result = $page->cell(
    x: 20, y: 35, w: 170,
    text: 'Long paragraph that wraps automatically across multiple lines.',
    border: Border::all()->withStyle(BorderStyle::DASHED),
);

// Right-aligned with a custom text color, positioned below the previous cell.
$page->cell(
    x: 20, y: $result->y + 2, w: 170, h: 8,
    text: 'Total: 1234.56 EUR',
    textColor: Color::rgb(192, 0, 0),
    align: TextAlign::RIGHT,
);

// Long word condensed to fit a narrow cell (no wrapping).
$page->cell(
    x: 20, y: 80, w: 40, h: 8,
    text: 'Antidisestablishmentarianism',
    border: Border::all(),
    fit: Fit::CONDENSE,
);

// Width auto-derived from the longest text line plus horizontal padding.
$page->cell(x: 20, y: 95, text: 'Auto-sized label', border: Border::all());

Named parameters

Parameter Type Description
x float Left edge in the document unit; also sets the row-start anchor for NEWLINE
y float Top edge in the document unit
w float Width; omit to auto-size from text (requires non-empty text)
h float Height; omit to auto-size from content
text string Cell content; \n forces a line break
border Border Border descriptor; Border::all(), Border::none(), Border::sides(...)
fill Color Background fill color
align TextAlign Horizontal alignment (LEFT, CENTER, RIGHT, JUSTIFY)
verticalAlign VerticalAlign Vertical alignment (TOP, MIDDLE, BOTTOM)
textColor Color Text color override for this cell
fit Fit Overflow strategy: NONE (word-wrap, default), CONDENSE (Tz scaling), SHRINK (reduce font size)
padding CellPadding|float Per-call padding override
ln NextPosition Cursor position after rendering (see Cursor flow)

When w is omitted the cell auto-sizes its width to fit the longest line of text plus horizontal padding. Omitting both w and text raises an error.

Justified text

TextAlign::JUSTIFY fills each wrapped line to the cell's inner width by widening the gaps between words. The last line of every paragraph (text between \n, including a single short line) stays left-aligned, following standard typographic practice. Justification needs a fixed width and the default Fit::NONE word-wrap: an auto-width cell (no w), or Fit::CONDENSE / Fit::SHRINK, falls back to left alignment. It works the same for the standard 14 fonts and embedded custom fonts.

$page->cell(
    w: 80,
    text: 'Lorem ipsum dolor sit amet consectetur adipiscing elit sed do eiusmod tempor incididunt.',
    align: TextAlign::JUSTIFY,
);

CellResult

cell() returns a CellResult value object:

Property Type Description
x float Right edge of the cell (bottom-right anchor, in the document unit)
y float Bottom edge of the cell (bottom-right anchor, in the document unit)
effectiveWidth float Resolved width (useful when the cell was auto-sized from text)
height float Rendered height in the document unit
lineCount int Number of text lines rendered
brokenWords int Count of words that were force-broken to fit the width
textOverflow bool true if text was clipped because it exceeded the cell area
page Page Page on which the cell was actually emitted (may differ when auto-page-break triggered a new page)

Padding

Cell padding controls the space between the cell border and its text content. Values are in the document unit.

use DragonOfMercy\PhpPdf\CellPadding;

// Uniform: same value on all four sides.
$page->setCellsPadding(2);

// Via the CellPadding value object - three named constructors:
$page->setCellsPadding(CellPadding::all(2));                    // top=right=bottom=left=2
$page->setCellsPadding(CellPadding::symmetric(1, 4));           // vertical=1, horizontal=4
$page->setCellsPadding(CellPadding::sides(top: 1, bottom: 3));  // omitted sides default to 0

// One-shot override on a single cell:
$page->cell(x: 20, y: 20, w: 60, h: 8, text: 'Tight',
    padding: CellPadding::sides(left: 4, right: 1));

// Document-level default applied to pages created after this call:
$doc->setDefaultCellsPadding(CellPadding::symmetric(2, 4));

The built-in default is 2 pt on all sides when neither the page nor the document configures one. Negative padding values raise PdfException at construction time.

Border width default

Border::all(), Border::none(), and Border::sides(...) leave the line width unset, deferring to a configurable default. The initial value is 0.25 in the document unit. Border::withWidth(x) always wins over the default.

$doc->setDefaultBorderWidth(0.5);   // document-wide default
$page->setDefaultBorderWidth(1.0);  // per-page override (pass null to revert to the document default)

$page->cell(x: 20, y: 20, w: 50, h: 8, text: 'Default 1.0', border: Border::all());
$page->cell(x: 20, y: 30, w: 50, h: 8, text: 'Explicit',    border: Border::all()->withWidth(0.3));

Cursor flow with ln

cell() can drive an internal cursor so subsequent calls can omit x and y. The ln parameter (a NextPosition enum) controls where the cursor is left after rendering. Without ln (or with NextPosition::NONE), the cursor is unchanged.

use DragonOfMercy\PhpPdf\NextPosition;

$page->setCellsPadding(2);

// Row of three cells: only the first call sets x/y.
// The first two pass ln: RIGHT to continue the row;
// the third uses NEWLINE to drop to the next row.
$page->cell(x: 20, y: 20, w: 40, h: 8, text: 'Name',  border: Border::all(), ln: NextPosition::RIGHT);
$page->cell(           w: 60, h: 8, text: 'Email', border: Border::all(), ln: NextPosition::RIGHT);
$page->cell(           w: 30, h: 8, text: 'Phone', border: Border::all(), ln: NextPosition::NEWLINE);

// Cursor is now at (20, 28), ready for the next row.
$page->cell(w: 40, h: 8, text: 'Alice',           border: Border::all(), ln: NextPosition::RIGHT);
$page->cell(w: 60, h: 8, text: 'alice@host.test', border: Border::all(), ln: NextPosition::RIGHT);
$page->cell(w: 30, h: 8, text: '+41 21 000 0000', border: Border::all());

NextPosition cases

Case Cursor after rendering
RIGHT Moves to the right edge of the cell just drawn (continue the row)
NEWLINE Returns to the x at which the row started, advances y by the rendered height
BELOW Stays at the cell's left edge, advances y (vertical stack at the same column)
NONE Cursor is left unchanged (useful for stamps or overlays)

An explicit x passed to cell() always becomes the new row-start anchor used by NEWLINE. Calling cell() without x before any cursor is set raises an error.

Direct cursor access

The cursor is also exposed directly, useful for seeding a position, jumping mid-flow, or reading after a stack of cells:

$page->setXY(20, 20);           // seed the cursor and set the row-start anchor
$page->cell(w: 50, h: 8, text: 'Header', ln: NextPosition::NEWLINE);
$y = $page->getY();             // bottom of the row, ready for the body
Method Description
getX(): float Current cursor x (document unit)
getY(): float Current cursor y (document unit)
setX(float $x) Set cursor x; also redefines the row-start anchor
setY(float $y) Set cursor y
setXY(float $x, float $y) Set both; also redefines the row-start anchor

See also

Clone this wiki locally