A Table flows through boxes exactly like a Textflow: each fitTable() places as many rows as
fit and returns Done, BoxFull or BoxEmpty.
$table = $doc->table(
[Column::mm(55), Column::auto(), Column::mm(30), Column::mm(20, new CellStyle(alignment: Alignment::Right))],
new CellStyle(padding: Padding::mm(1, 0, 3, 0)), // every cell's base style
'body', // every text cell's base text style
);
$table->addHeaderRow(['Reference', 'Project', '', 'Price'], new CellStyle(borderBottom: new Border(0.25)));
$table->addRow([$ref, Cell::text($name)->spanning(2), sprintf('$%0.2f', $price)]);
$table->addRow([null, null, Cell::text('TOTAL', 'cell-title'), Cell::text($total, 'amount')], group: 1);
$result = $page->fitTable($table, Box::fromMm(15, 48, 180, 176));
Column::pt(72) or Column::mm(55) are fixed; Column::percent(30) is a share of the box
width; Column::auto() is sized from its content, at least its widest word and at most its
longest line, with the remaining width shared in proportion. A column can carry a CellStyle
its cells inherit. Columns wider than the box throw.
Cells are given as strings (plain text), Textflows, anything placeable (images, templates,
SVG) or null for empty, or as Cell objects for control:
Cell::text('Plain', 'style-name', $cellStyle);
Cell::markup('With <b>markup</b>');
Cell::of($image, Fit::meet());
Cell::text('Wide')->spanning(columns: 3, rows: 2);
Cell::text('Right')->styled(new CellStyle(alignment: Alignment::Right));
addRow($cells, $style, $group, $minHeight) and addHeaderRow($cells, $style, $minHeight)
accept a prepared Row as well. Header rows are drawn again at the top of every box the table
continues in (TableOptions(repeatHeaders: false) turns that off). Consecutive rows sharing a
group id are kept together across boxes when they can be.
addFooterRow($cells, $style, $minHeight) rows are drawn straight after the last row placed in
every box, the last box included, and their height is reserved in each. To fill them per box,
for a subtotal carried forward, pass a callback; it is told which body rows the box holds and
returns one row for each footer row, or null to draw the rows as added:
$table->addFooterRow(['Carried forward', '']);
$table->onFooter(function (FooterContext $box) use ($amounts): array {
$sum = array_sum(array_slice($amounts, 0, $box->lastRow + 1));
return [[$box->isLastBox ? 'Total' : 'Carried forward', number_format($sum, 2)]];
});
The callback’s rows are laid out at the heights reserved for the footer rows added with
addFooterRow(), so give those the content or $minHeight the tallest footer needs.
measure() counts the footers once.
CellStyle layers table < column < row < cell, later non-null values winning: padding
(Padding::all(), symmetric(), mm()), background, border for all sides or
borderTop/Right/Bottom/Left (new Border($width, $color, $dash)), verticalAlignment,
alignment (horizontal, applied to text and placeables), firstBaseline and text (a
TextStyle or name layered under the cell’s own text).
Text in a cell starts with its ascender at the top of the padding. To match PDFlib’s
firstlinedist=leading, put the first baseline one leading down for the whole table with
TableOptions(firstBaseline: FirstBaseline::Leading), or for some cells with
CellStyle(firstBaseline: ...). Rows are measured and drawn with the same setting.
A row that does not fit the space left at the top of a box continues its text in the next box
(TableOptions(splitRows: false) disables this; such a row then reports BoxEmpty). Row and
column spans are honoured; a spanning cell grows the last row it covers when it needs more
height.
$table->lastFit() returns a TableFitInfo: result, height used, rows placed, and the canvas
coordinates of every row edge (rowEdges, bottom()) and column edge (columnEdges), so you
can draw your own rules or continue below the table. measure($doc, $width) gives the height
the remaining rows need.
Document::flow() drains any flowable (a Textflow or a Table) across as many pages as needed,
calling a closure to make each page:
$pages = $doc->flow($table, [Box::fromMm(15, 48, 180, 176), Box::fromMm(15, 20, 180, 250)], function (Document $doc, int $n) use ($template): Page {
$page = $doc->addPage(PageSize::A4);
$page->place($template, $page->mediaBox(), Fit::entire());
return $page;
});
With a list of boxes the first page uses the first box, later pages the following ones, and the
last box repeats. An entry may itself be a list of boxes, the page’s frames (columns): content
fills them in turn and moves to a new page after the last, or at a <pagebreak>; a
<columnbreak> moves to the next frame. To share the first page with other content, fit the table on that page
yourself and call flow() for the rest when isDone() is false.