apply($doc); $doc->addPage(); $template->masthead($doc); // The shared nine brand faces are painted across these two pages — the // specimen is the one sanctioned caller of the eager probe. Noto Sans TC is // specimen-only, so register its directory here and resolve it lazily at the // exact point where the Traditional Chinese sample is painted. $cjkFontFamily = 'NotoSansTC'; $cjkFontDirectory = \dirname(__DIR__) . '/assets/fonts/notosanstc'; $cjkFontPath = $cjkFontDirectory . '/NotoSansTC-Regular.ttf'; if (!\is_file($cjkFontPath)) { throw new RuntimeException("Vendored Traditional Chinese font missing: {$cjkFontPath}"); } $doc->addFontDirectory($cjkFontDirectory); Typography::probeAllFaces($doc); $css = <<<'CSS' CSS; // ── Section 1: Inter weight & style grid ──────────────────────────────── $weightGrid = [ ['label' => 'Regular', 'weight' => 'normal', 'style' => 'normal', 'text' => 'The quick brown fox jumps over the lazy dog.'], ['label' => 'Bold', 'weight' => 'bold', 'style' => 'normal', 'text' => 'Pack my box with five dozen liquor jugs.'], ['label' => 'Italic', 'weight' => 'normal', 'style' => 'italic', 'text' => 'How vexingly quick daft zebras jump!'], ['label' => 'Bold Italic', 'weight' => 'bold', 'style' => 'italic', 'text' => 'Waltz, bad nymph, for quick jigs vex.'], ]; $weightRows = ''; foreach ($weightGrid as $row) { $weightRows .= '
2. Display & Monospace — Barlow & JetBrains Mono
', ); $barlowRows = [ ['label' => 'Regular', 'family' => 'Barlow', 'style' => '', 'text' => 'Precision typesetting, engineered.'], ['label' => 'Medium', 'family' => 'Barlow-Medium', 'style' => '', 'text' => 'Precision typesetting, engineered.'], ['label' => 'Bold', 'family' => 'Barlow', 'style' => 'B', 'text' => 'Precision typesetting, engineered.'], ]; // Label + specimen columns (22% / 78%, mirroring table.np-specimen's // td.np-specimen-label split) so each weight is named, not merely implied // by visual heft. A hairline under every row but the last mirrors the // section-1 HTML specimen table's row rule. $margins = $doc->getMargins(); $labelWidth = ($doc->getPageWidth() - $margins->left - $margins->right) * 0.22; $doc->setDrawColor(...PrintPalette::rgb(PrintPalette::HAIRLINE)); $doc->setLineWidth(0.4); foreach ($barlowRows as $i => $row) { $border = $i === \count($barlowRows) - 1 ? false : 'B'; $doc->setFont(Typography::FAMILY_TEXT, '', 8.0); $doc->setTextColor(...PrintPalette::rgb(PrintPalette::MUTED)); $doc->cell($labelWidth, 16.0, $row['label'], border: $border, newLine: false); $doc->setFont($row['family'], $row['style'], 13.0); $doc->setTextColor(...PrintPalette::rgb(PrintPalette::INK)); $doc->cell(0, 16.0, $row['text'], border: $border, newLine: true); } $doc->ln(4.0); $doc->setFont(Typography::FAMILY_TEXT, '', Typography::SIZE_BODY); $monoBlock = <<<'HTML'function render(): PdfBytes { return $doc->save(); }
HTML; $doc->writeHtml(SampleTemplate::sharedCss() . $css . $monoBlock); // ── Section 3: Unicode script coverage ─────────────────────────────────── $section3 = <<3. Unicode Script CoverageLATIN EXTENDED — GERMAN PANGRAM
Zwölf Boxkämpfer jagen Viktor quer über den großen Sylter Deich.
GREEK — PANGRAM
Ξεσκεπάζω την ψυχοφθόρα βδελυγμία.
CYRILLIC — RUSSIAN PANGRAM
Съешь же ещё этих мягких французских булок, да выпей чаю.
TRADITIONAL CHINESE — TAIWAN
HTML; $doc->writeHtml(SampleTemplate::sharedCss() . $css . $section3); $traditionalChineseSample = '繁體中文排版測試,涵蓋常用字與臺灣慣用詞。'; $doc->setFont($cjkFontFamily, '', 11.0); if (!$doc->fontMetrics()->isFontRegistered(Typography::fontKey($cjkFontFamily))) { throw new RuntimeException('Vendored Traditional Chinese font failed to resolve via setFont().'); } $doc->setTextColor(...PrintPalette::rgb(PrintPalette::INK)); $doc->cell(0, 15.0, $traditionalChineseSample, newLine: true); $doc->setFont(Typography::FAMILY_TEXT, '', Typography::SIZE_BODY); // ── Section 4: Type scale ladder ───────────────────────────────────────── $scale = [ ['pt' => Typography::SIZE_TITLE, 'role' => 'Title — Barlow Bold', 'family' => 'Barlow', 'weight' => 'bold'], ['pt' => Typography::SIZE_SECTION, 'role' => 'Section — Barlow Bold', 'family' => 'Barlow', 'weight' => 'bold'], ['pt' => Typography::SIZE_SUBSECTION, 'role' => 'Subsection — Barlow Bold', 'family' => 'Barlow', 'weight' => 'bold'], ['pt' => Typography::SIZE_BODY, 'role' => 'Body — Inter Regular, 1.45 leading', 'family' => 'Inter', 'weight' => 'normal'], ['pt' => Typography::SIZE_TABLE, 'role' => 'Table — Inter Regular', 'family' => 'Inter', 'weight' => 'normal'], ['pt' => Typography::SIZE_MONO, 'role' => 'Mono — JetBrains Mono', 'family' => 'JetBrainsMono', 'weight' => 'normal'], ['pt' => Typography::SIZE_FOOTER, 'role' => 'Footer — Inter Regular', 'family' => 'Inter', 'weight' => 'normal'], ]; $scaleRows = ''; foreach ($scale as $step) { $ptLabel = \rtrim(\rtrim(\sprintf('%.1F', $step['pt']), '0'), '.'); $scaleRows .= '' . \htmlspecialchars($col['label'], ENT_QUOTES) . '
' . '| ' . $measureText . ' | ' . ($col['width'] === '100%' ? '' : '') . ' |
' . $a['label'] . '
' . '' . $alignText . '
'; } $section6 = <<6. Alignment Quartet {$alignRows} HTML; $doc->writeHtml(SampleTemplate::sharedCss() . $css . $section6); // ── Section 7: letter-spacing & case ───────────────────────────────────── $caseRows = [ ['label' => 'Uppercase, wide tracking', 'transform' => 'uppercase', 'spacing' => '1.5pt'], ['label' => 'Capitalize, normal tracking', 'transform' => 'capitalize', 'spacing' => '0pt'], ['label' => 'Lowercase, tight tracking', 'transform' => 'lowercase', 'spacing' => '-0.3pt'], ]; $caseHtmlRows = ''; foreach ($caseRows as $row) { $caseHtmlRows .= 'Character-spacing (Tc) control applied to letter pairs — AV, WA, TO, TA — that read loosest at zero tracking. This is manual tracking, not automatic glyph-pair kerning.
| 0 pt tracking (off) | {$kernText} |
| −0.4 pt tracking (on) | {$kernText} |