289 lines
9.7 KiB
PHP
289 lines
9.7 KiB
PHP
<?php
|
||
|
||
/**
|
||
* E054_page_groups_numbering.php
|
||
*
|
||
* Demonstrates publishing-style mixed page numbering: Roman-numeral front matter
|
||
* followed by Arabic body pages, with a generated table of contents and
|
||
* multi-level bookmarks as cross-reference anchors.
|
||
*
|
||
* What this demonstrates:
|
||
* - setBookmark(): hierarchical outline entries with styles and colors.
|
||
* - setNamedDestination(): stable jump targets independent of page order.
|
||
* - addTOC(): auto-generated TOC page linked back to bookmarked sections.
|
||
* - setDisplayMode(): open with outlines panel showing.
|
||
* - Logical vs physical page numbering: inject visible Roman/Arabic labels
|
||
* via page header content while the PDF page object index remains 0-based.
|
||
*
|
||
* @since 2026-05-01
|
||
* @category Library
|
||
* @package Pdf
|
||
* @author Nicola Asuni <info@tecnick.com>
|
||
* @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 = '<h2 style="font-family: helvetica; text-align: center;">Preface</h2>
|
||
<p style="font-family: helvetica; font-size: 10pt;">
|
||
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 …).
|
||
</p>
|
||
<p style="font-family: helvetica; font-size: 10pt;">
|
||
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.
|
||
</p>';
|
||
$pdf->addHTMLCell(html: $prefaceHtml, posx: 15, posy: 30, width: 175, height: $prefacePage['pid']);
|
||
|
||
// ===| BODY |================================================================
|
||
// Chapters 1–3, Arabic page numbers 1–3
|
||
|
||
/** @var array<int, array{title: string, sub: list<string>, color: string, bodyText: string}> $chapters */
|
||
$chapters = [
|
||
[
|
||
'title' => 'Chapter 1 — Foundations',
|
||
'sub' => ['1.1 History', '1.2 Key Concepts'],
|
||
'color' => '#1a3c6e',
|
||
'bodyText' => 'This chapter introduces the foundational ideas. Page number 1 (Arabic) appears in the footer. The bookmark entry for this chapter points to this page via a named destination.',
|
||
],
|
||
[
|
||
'title' => 'Chapter 2 — Methods',
|
||
'sub' => ['2.1 Approach A', '2.2 Approach B'],
|
||
'color' => '#1a6e3c',
|
||
'bodyText' => 'This chapter details the methodology. Page number 2 (Arabic) appears below. Bookmark level-1 entries for subsections use the same page pid with a vertical offset.',
|
||
],
|
||
[
|
||
'title' => 'Chapter 3 — Results',
|
||
'sub' => ['3.1 Findings', '3.2 Conclusions'],
|
||
'color' => '#6e1a1a',
|
||
'bodyText' => 'Final chapter with results and conclusions. Page number 3 (Arabic). The TOC on page i links back to all three chapter bookmarks.',
|
||
],
|
||
];
|
||
|
||
foreach ($chapters as $idx => $ch) {
|
||
$arabicNum = $idx + 1;
|
||
$chPage = $pdf->addPage(['format' => 'A4']);
|
||
|
||
// Register named destination so TOC links are stable.
|
||
$destName = $pdf->setNamedDestination(name: 'chapter-' . $arabicNum, page: $chPage['pid'], posx: 0, posy: 0);
|
||
|
||
// Top-level bookmark (bold, chapter color)
|
||
$pdf->setBookmark(
|
||
name: $ch['title'],
|
||
link: $destName,
|
||
level: 0,
|
||
page: $chPage['pid'],
|
||
posx: 0,
|
||
posy: 0,
|
||
fstyle: 'B',
|
||
color: $ch['color'],
|
||
);
|
||
|
||
// Sub-section bookmarks (indented, normal weight)
|
||
foreach ($ch['sub'] as $si => $subTitle) {
|
||
$subY = 70.0 + ($si * 20.0);
|
||
$pdf->setBookmark(
|
||
name: $subTitle,
|
||
link: '',
|
||
level: 1,
|
||
page: $chPage['pid'],
|
||
posx: 0,
|
||
posy: $subY,
|
||
fstyle: '',
|
||
color: $ch['color'],
|
||
);
|
||
}
|
||
|
||
// Page footer with Arabic numeral
|
||
$addPageFooter($pdf, $chPage['pid'], (string) $arabicNum, $smallFont);
|
||
|
||
// Chapter content
|
||
$chHtml =
|
||
'<h2 style="font-family: helvetica; color: '
|
||
. $ch['color']
|
||
. ';">'
|
||
. htmlspecialchars($ch['title'])
|
||
. '</h2>
|
||
<p style="font-family: helvetica; font-size: 10pt;">'
|
||
. htmlspecialchars($ch['bodyText'])
|
||
. '</p>';
|
||
foreach ($ch['sub'] as $subTitle) {
|
||
$chHtml .=
|
||
'<h3 style="font-family: helvetica; color: '
|
||
. $ch['color']
|
||
. ';">'
|
||
. htmlspecialchars($subTitle)
|
||
. '</h3>
|
||
<p style="font-family: helvetica; font-size: 10pt;">Section content would go here.</p>';
|
||
}
|
||
$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);
|