* @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. require __DIR__ . '/../vendor/autoload.php'; define('K_PATH_FONTS', (string) realpath(__DIR__ . '/../vendor/tecnickcom/tc-lib-pdf-font/target/fonts')); $pdf = new \Com\Tecnick\Pdf\Tcpdf( unit: \Com\Tecnick\Pdf\Page\Unit::Millimeter, isunicode: true, subsetfont: false, compress: true, mode: \Com\Tecnick\Pdf\PdfConformance::None, objEncrypt: null, ); $pdf->setCreator('tc-lib-pdf'); $pdf->setAuthor('Nicola Asuni'); $pdf->setSubject('tc-lib-pdf example: 054'); $pdf->setTitle('Page Groups & Mixed Numbering'); $pdf->setKeywords('TCPDF tc-lib-pdf page groups numbering roman arabic TOC bookmarks'); $pdf->setPDFFilename('054_page_groups_numbering.pdf'); $pdf->setViewerPreferences(['DisplayDocTitle' => true]); // Open with the outline panel visible and full-width zoom. $pdf->setDisplayMode( zoom: \Com\Tecnick\Pdf\DisplayZoom::FullWidth, layout: \Com\Tecnick\Pdf\Page\PageLayout::SinglePage, mode: \Com\Tecnick\Pdf\Page\PageDisplayMode::UseOutlines, ); $pdf->enableDefaultPageContent(); // ---------- // Fonts $titleFont = $pdf->font->insert($pdf->pon, 'helvetica', 'B', 16); $chFont = $pdf->font->insert($pdf->pon, 'helvetica', 'B', 14); $bodyFont = $pdf->font->insert($pdf->pon, 'helvetica', '', 12); $smallFont = $pdf->font->insert($pdf->pon, 'helvetica', '', 10); // ---------- // Helpers /** Convert an integer 1–3999 to an uppercase Roman numeral. */ $toRoman = static function (int $n): string { $map = [ 1000 => 'M', 900 => 'CM', 500 => 'D', 400 => 'CD', 100 => 'C', 90 => 'XC', 50 => 'L', 40 => 'XL', 10 => 'X', 9 => 'IX', 5 => 'V', 4 => 'IV', 1 => 'I', ]; $result = ''; foreach ($map as $val => $sym) { while ($n >= $val) { $result .= $sym; $n -= $val; } } return $result; }; /** * Render a page number footer for the current page. * @param \Com\Tecnick\Pdf\Tcpdf $pdf * @param int $pid Page PID * @param string $label Visible page number string (e.g. "I", "1") * @param array{out: string} $font Font descriptor */ $addPageFooter = static function (\Com\Tecnick\Pdf\Tcpdf $pdf, int $pid, string $label, array $font): void { $pdf->page->addContent($font['out'], $pid); $pdf->page->addContent( $pdf->getTextCell( txt: '— ' . $label . ' —', posx: 0, posy: 282, width: 210, height: 0, offset: 0, linespace: 1, valign: \Com\Tecnick\Pdf\TextVAlign::Top, halign: \Com\Tecnick\Pdf\TextHAlign::Center, ), $pid, ); }; // ===| FRONT MATTER |======================================================== // Pages i, ii (TOC placeholder + Preface) // These pages use Roman numerals in the footer. // ----- Page i: TOC placeholder ----- $tocPage = $pdf->addPage(['format' => 'A4']); $pdf->setBookmark( name: 'Table of Contents', link: '', level: 0, page: $tocPage['pid'], posx: 0, posy: 0, fstyle: 'B', color: 'gray', ); $addPageFooter($pdf, $tocPage['pid'], $toRoman(1), $smallFont); // Reserve header text for this page $pdf->page->addContent($titleFont['out'], $tocPage['pid']); $pdf->page->addContent( $pdf->getTextCell( txt: 'TABLE OF CONTENTS', posx: 15, posy: 20, width: 180, height: 0, offset: 0, linespace: 1, valign: \Com\Tecnick\Pdf\TextVAlign::Top, halign: \Com\Tecnick\Pdf\TextHAlign::Center, ), $tocPage['pid'], ); // The actual TOC content is injected by addTOC() at the end of the script. // ----- Page ii: Preface ----- $prefacePage = $pdf->addPage(['format' => 'A4']); $pdf->setBookmark( name: 'Preface', link: '', level: 0, page: $prefacePage['pid'], posx: 0, posy: 0, fstyle: '', color: 'gray', ); $addPageFooter($pdf, $prefacePage['pid'], $toRoman(2), $smallFont); $prefaceHtml = '
This document demonstrates mixed page numbering conventions used in publishing workflows. Front matter (title, preface, table of contents) carries Roman numerals (i, ii, iii …), while the body text uses Arabic numerals (1, 2, 3 …).
In tc-lib-pdf, both logical page numbers displayed in footers and structural bookmarks are controlled independently. The PDF page object index is always zero-based; the visible label is rendered as regular text content. Named destinations remain stable regardless of the visible numbering scheme.
'; $pdf->addHTMLCell(html: $prefaceHtml, posx: 15, posy: 30, width: 175, height: $prefacePage['pid']); // ===| BODY |================================================================ // Chapters 1–3, Arabic page numbers 1–3 /** @var array' . htmlspecialchars($ch['bodyText']) . '
'; foreach ($ch['sub'] as $subTitle) { $chHtml .= 'Section content would go here.
'; } $pdf->addHTMLCell(html: $chHtml, posx: 15, posy: 20, width: 175, height: $chPage['pid']); } // ===| GENERATE TOC |======================================================== // addTOC() injects the outline entries onto the reserved TOC page (page i), // starting at y=35 (below the "TABLE OF CONTENTS" heading). // It uses the current font for rendering — activate bodyFont first. $pdf->page->addContent($bodyFont['out'], $tocPage['pid']); $pdf->addTOC(page: $tocPage['pid'], posx: 15, posy: 35, width: 175); // ---------- $rawpdf = $pdf->getOutPDFString(); $pdf->renderPDF(rawpdf: $rawpdf);