* @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 textmiddle cellright cell', 5) . 'summarysum'; 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' => '', 'rowspan in the first row' => '' . '', 'widths on a later row' => '' . '', 'width on a new column' => '', 'colspan wider than the first row' => '' . '', ]; foreach ($gridCases as $label => $rows) { $note($label, 10, $posy, 45); $selectFont(); $pdf->addHTMLCell('
A1B1
A2B2C2
A (rowspan 2)B1
B2C2
A3B3C3
A1B1
A2 20%B2 30%C2 50%
A1B1
A2B2C2 50%
A1B1
spans four columns
A3B3C3D3
' . $rows . '
', 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' => 'AB' . $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( '' . $rows . '
', 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
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 cellrow 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);