* @copyright 2002-2026 Nicola Asuni - Tecnick.com LTD
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
* @link https://github.com/tecnickcom/tc-lib-pdf
*
* This file is part of tc-lib-pdf software library.
*/
// NOTE: local file reads (images, fonts, attachments) are restricted to an allowlist of
// trusted paths that covers this package tree, so run the examples in place. To read assets
// from other locations, list them in the 'allowedPaths' entry of the fileOptions constructor
// parameter (see E047_remote_resources_security.php).
// NOTE: run make fonts in the project root to generate the dependencies and example fonts.
// autoloader when using Composer
require __DIR__ . '/../vendor/autoload.php';
// define fonts directory
\define('K_PATH_FONTS', \realpath(__DIR__ . '/../vendor/tecnickcom/tc-lib-pdf-font/target/fonts'));
$pdf = new \Com\Tecnick\Pdf\Tcpdf();
$pdf->setCreator('tc-lib-pdf');
$pdf->setAuthor('Nicola Asuni');
$pdf->setSubject('tc-lib-pdf example: 087');
$pdf->setTitle('HTML Table Grid');
$pdf->setKeywords('TCPDF tc-lib-pdf example html table colspan rowspan height page-break-inside');
$pdf->setPDFFilename('087_html_table_grid.pdf');
$selectFont = function (string $family = 'helvetica', string $style = '', float $size = 10) use ($pdf): void {
$font = $pdf->font->insert($pdf->pon, $family, $style, $size);
$pdf->page->addContent($font['out']);
};
// Reference geometry drawn over the cases.
$guideStyle = [
'lineWidth' => 0.3,
'lineCap' => 'butt',
'lineJoin' => 'miter',
'dashArray' => [1, 1],
'dashPhase' => 0,
'lineColor' => '#cc2244',
];
/**
* Writes a note in absolute page coordinates.
*/
$note = function (string $txt, float $posx, float $posy, float $width, string $style = '', float $size = 8) use (
$pdf,
$selectFont,
): void {
$selectFont('helvetica', $style, $size);
$pdf->page->addContent($pdf->getTextCell(
txt: $txt,
posx: $posx,
posy: $posy,
width: $width,
valign: \Com\Tecnick\Pdf\TextVAlign::Top,
halign: \Com\Tecnick\Pdf\TextHAlign::Left,
));
};
/**
* Adds a page with a title and returns the ordinate of the first case.
*/
$drawTitle = function (string $title, string $expected) use ($pdf, $note): float {
$pdf->addPage();
$note($title, 10, 8, 190, 'B', 12);
$note('Expected: ' . $expected, 10, 15, 190, '', 8);
return 26.0;
};
/**
* Draws a dashed line at the bottom of the current page region.
*/
$markRegionBottom = function () use ($pdf, $guideStyle, $note): void {
$region = $pdf->page->getRegion();
$bottom = $region['RY'] + $region['RH'];
$pdf->page->addContent($pdf->graph->getLine(5, $bottom, 205, $bottom, $guideStyle));
$note('region bottom', 170, $bottom - 4, 35);
};
// ----------
// Part 1: a table kept together is moved only when it does not fit.
// ----------
$keepTable =
'
| HEADING |
'
. '| Subheading |
'
. \str_repeat('Description sub description text | middle cell | right cell |
', 5)
. '| summary | sum |
';
foreach ([
'page-break-inside: avoid' => 'style="page-break-inside: avoid;"',
'nobr="true"' => 'nobr="true"',
] as $label => $attr) {
$posy = $drawTitle(
'Keep-together tables (' . $label . ')',
'the first five tables fit on this page; the sixth does not fit and starts on the next page.',
);
$markRegionBottom();
$selectFont();
$pdf->addHTMLCell(
html: \str_repeat('' . $keepTable, 6),
posx: 10,
posy: $posy,
width: 190,
);
$note('The sixth table, moved to this page as a whole.', 10, 60, 190);
}
// ----------
// Part 2: rows with different cell counts share one column grid.
// ----------
$posy = $drawTitle(
'Rows with different cell counts',
'every cell stays inside the dashed frame (the declared table width); the columns line up across rows.',
);
$gridCases = [
'later row with more cells' => '| A1 | B1 |
| A2 | B2 | C2 |
',
'rowspan in the first row' =>
'| A (rowspan 2) | B1 |
'
. '| B2 | C2 |
| A3 | B3 | C3 |
',
'widths on a later row' =>
'| A1 | B1 |
'
. '| A2 20% | B2 30% | C2 50% |
',
'width on a new column' => '| A1 | B1 |
| A2 | B2 | C2 50% |
',
'colspan wider than the first row' =>
'| A1 | B1 |
'
. '| spans four columns |
| A3 | B3 | C3 | D3 |
',
];
foreach ($gridCases as $label => $rows) {
$note($label, 10, $posy, 45);
$selectFont();
$pdf->addHTMLCell('', 60, $posy, 140);
$height = $pdf->getLastBBox()['y'] + $pdf->getLastBBox()['h'] - $posy;
$frame = $pdf->graph->getRect(59, $posy - 1, 142, \max(10.0, $height + 2), 'D', ['all' => $guideStyle]);
$pdf->page->addContent($frame);
$posy += \max(14.0, $height + 8);
}
// ----------
// Part 3: the row estimate of a spanning cell uses the width of all its columns.
// ----------
$spanText = \str_repeat('Lorem ipsum dolor sit amet, consectetur adipiscing elit. ', 3);
foreach ([
'colspan="2" row near the page bottom' =>
'| A | B |
| ' . $spanText . ' |
',
'single column reference' => '| A |
| ' . $spanText . ' |
',
] as $label => $rows) {
$drawTitle(
'Spanning cell near the page bottom: ' . $label,
'the table starts 15mm above the region bottom and both of its rows stay on this page.',
);
$markRegionBottom();
$region = $pdf->page->getRegion();
$selectFont();
$pdf->addHTMLCell(
'',
15,
$region['RY'] + $region['RH'] - 15,
180,
);
}
// ----------
// Part 4: explicit cell heights set the minimum row height.
// ----------
$posy = $drawTitle(
'Cell height: the declared height is the minimum height of the row',
'each table row has the height in its label; the dashed line marks that height from the table top.',
);
$heightCases = [
['', 'no height: content height', 0.0],
[' | ', 'height="100" (100px = 26.46mm)', 26.46],
[' | ', 'style="height:100px" (26.46mm)', 26.46],
[' | ', 'style="height:30mm"', 30.0],
[' | ', 'height lower than the content: content height', 0.0],
[' | ', 'th with style="height:20mm"', 20.0],
];
foreach ($heightCases as [$cell, $label, $expected]) {
$note($label, 10, $posy, 50);
$tag = \str_starts_with($cell, ' | addHTMLCell(
'' . $cell . 'cell' . $tag . '>| second cell | ',
65,
$posy,
120,
);
if ($expected > 0.0) {
$pdf->page->addContent($pdf->graph->getLine(62, $posy + $expected, 188, $posy + $expected, $guideStyle));
}
$posy += \max(12.0, $expected + 6.0);
}
$note('rowspan="2" with style="height:30mm": the two rows share the height', 10, $posy, 50);
$selectFont();
$pdf->addHTMLCell(
''
. '| spanning cell | row 1 | '
. '| row 2 | ',
65,
$posy,
120,
);
$pdf->page->addContent($pdf->graph->getLine(62, $posy + 30.0, 188, $posy + 30.0, $guideStyle));
// ----------
$rawpdf = $pdf->getOutPDFString();
$pdf->renderPDF(rawpdf: $rawpdf);
|