跳转到内容

布局:页眉、页脚、栏、小册子、页面管理器

Layout 模块包含各个页面装饰引擎,Document 外观(facade)会把工作委派给它们:页眉与页脚绘制、多栏布局、骑马钉小册子拼版,以及结构性页面操作。这是一个小而稳定的模块:六个类,全部都是 @since 1.0.0

Terminal window
composer require nextpdf/core:^3

Layoutsrc/Layout/,六个类,@since 1.0.0)是 HasLayout concern 下的引擎层。应用代码调用 Document 上的外观方法;trait 会把每次调用路由到其中一个引擎。在 manifest 中,该模块标记为 standard 风险和 internal 稳定性,唯一依赖它的是 Core。你应通过外观使用它,而不是直接构造这些类。

HeaderFooter 负责绘制重复出现的页眉与页脚。它会为每个区带保留标题、描述、标志、字体、边距与颜色等状态。它按需通过 renderHeader()renderFooter() 生成 PDF 内容流操作符。默认页脚会输出一个右对齐的 "page / total" 字符串。setHeaderCallback()setFooterCallback() 会用调用方提供的 closure 替换默认布局。getHeaderContentHeight() 会返回页眉占用的垂直空间,让页面正文从其下方开始排版。当文档处于 tagged-PDF 模式时,上游的 HasPages 会抑制自动页眉,因为页眉内容位于结构树之外。

ColumnLayout 负责管理多栏流式排版。setEqualColumns(int $count, float $totalWidth, float $gap = 5) 会把可用宽度均分为等宽栏。setColumnsArray() 则接受显式的 ColumnDefinition 位置与宽度。光标可通过 selectColumn() 选定某一栏,或通过 nextColumn() 前进到下一栏。getCurrentColumnX() / getCurrentColumnWidth() 会返回当前活动栏的几何信息。栏数无效、间距为负,或计算出的栏宽非正时,会引发 PageLayoutException

BookletLayout 会为骑马钉(中缝对折)装订重新排序页面。reorderPages() 会把页面列表补齐到四的倍数(一张小册子纸张容纳四个页面格位),再由外向内拼版,使对折、装订后的纸张能按顺序阅读。getMarginAdjustments() 会返回指定位置各侧的内侧(书脊)与外侧(边缘)边距。getSheetCount() 会返回某个页数需要多少张双面纸张。重新排序只会改变内容的摆放位置。底层 PDF 页面序列保持线性,并与页面树一致;页面树定义了文档中页面的排列顺序(ISO 32000-2 §7.7)。

PageManager 提供与内容绘制分离的结构性页面操作。movePage()copyPage()deletePage() 会以引用方式操作一个 PageData 数组。页面区域(addPageRegion()isInRegion()getRegionOffset())用于定义禁止写入的区带。页面组(startPageGroup()getGroupPageNo())支持分节页码。PageRegionColumnDefinition 是这些引擎使用的两个值载体。Writer 模块会把生成的页面序列化为页面树,其 Kids 项是一个数组,包含指向某个页面树节点直属子节点的间接引用(ISO 32000-2 §7.7.3.2)。

符号类型稳定性起始版本
HeaderFooter::setHeaderData(string, string, string, float): self方法稳定1.0.0
HeaderFooter::setHeaderFont(string, float): self / setHeaderMargin(float): self方法稳定1.0.0
HeaderFooter::setFooterFont(string, float): self / setFooterMargin(float): self方法稳定1.0.0
HeaderFooter::setHeaderCallback(Closure): self / setFooterCallback(Closure): self方法稳定1.0.0
HeaderFooter::getHeaderContentHeight(): float方法稳定1.0.0
HeaderFooter::renderHeader(float, float, float, float, int, int): string方法稳定1.0.0
HeaderFooter::renderFooter(float, float, float, float, int, int, string): string方法稳定1.0.0
ColumnLayout::setEqualColumns(int, float, float): self方法稳定1.0.0
ColumnLayout::setColumnsArray(array): self / resetColumns(): self方法稳定1.0.0
ColumnLayout::selectColumn(int): self / nextColumn(): bool方法稳定1.0.0
ColumnLayout::getCurrentColumnX(float): float / getCurrentColumnWidth(float): float方法稳定1.0.0
BookletLayout::setBooklet(bool, float, float): void方法稳定1.0.0
BookletLayout::reorderPages(array): array方法稳定1.0.0
BookletLayout::getMarginAdjustments(int): array{left: float, right: float}方法稳定1.0.0
BookletLayout::getSheetCount(int): int方法稳定1.0.0
PageManager::movePage(int, int, array): void / copyPage(int, array): void / deletePage(int, array): void方法稳定1.0.0
PageManager::addPageRegion(float, float, float, float): void / isInRegion(float, float): bool方法稳定1.0.0
PageManager::getRegionOffset(float, float, float, float): float方法稳定1.0.0
PageManager::startPageGroup(): void / getGroupPageNo(int): int方法稳定1.0.0
PageRegion / ColumnDefinition值载体稳定1.0.0
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('Header and Footer');
$doc->setHeaderData(
title: 'NextPDF Example',
description: 'Header and Footer Demonstration',
);
$doc->setHeaderFont('helvetica', 10);
$doc->setHeaderMargin(5);
$doc->setFooterFont('helvetica', 8);
$doc->setFooterMargin(10);
$doc->addPage();
$doc->setFont('helvetica', 'B', 16);
$doc->cell(0, 12, 'Document with Header and Footer', newLine: true);
$doc->save(__DIR__ . '/output/13-header-footer.pdf');

来源:examples/13-header-footer.php。页眉会在每次 addPage() 时绘制;页脚则在页面排版完成时绘制。

由页脚 callback 管理页码文本,栏引擎则驱动双栏正文。两个引擎都通过外观使用。

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('Two-Column Report');
$doc->setFooterCallback(static function (Document $d): void {
$d->setFont('helvetica', '', 8);
$d->text(180.0, 285.0, 'Page ' . ($d->getPage() + 1));
});
$doc->addPage();
$doc->setEqualColumns(2, gap: 8);
$doc->selectColumn(0);
$doc->setFont('helvetica', '', 10);
$doc->multiCell(0, 6, 'Left column flows here.');
$doc->selectColumn(1);
$doc->multiCell(0, 6, 'Right column flows here.');
$doc->save(__DIR__ . '/output/two-column-report.pdf');

来源:基于 examples/13-header-footer.php 中的模式。

  • setEqualColumns() 会拒绝小于 1 的栏数、为负的间距,或计算后栏宽非正的布局。上述每一种情况都会引发 PageLayoutException,而不会返回降级布局。
  • selectColumn() 会忽略超出范围的 Index(索引)并保留当前栏;它绝不会因索引错误而抛出异常。当 nextColumn() 已位于最后一栏时,会返回 false
  • BookletLayout::reorderPages() 会用空白页补齐到四的倍数,这些空白页的尺寸会从最后一页复制而来。空的页面列表会返回空数组。重新排序只影响摆放位置;movePage() 的索引仍然指向逻辑顺序。
  • PageManager::movePage()copyPage()deletePage() 在索引超出范围时会静默地不做任何事:它们使用 isset() 验证,并在不修改数组的情况下直接返回。当缺页属于调用方错误时,请自行验证索引。
  • getHeaderContentHeight() 在页眉被停用,或既没有标题也没有描述时,会返回 0.0。此时页面正文会从上边距开始。
  • 在 tagged-PDF 模式下,自动页眉会在上游被抑制。请为无障碍文档构造具备结构感知的布局。

页眉与页脚绘制会以 O(装饰内容) 的成本,把操作符附加到当前页面缓冲区:成本与写入的标题、描述和分隔线成正比,而不是与文档大小成正比。栏计算每次调用都是 O(1)。BookletLayout::reorderPages() 随页数呈 O(n),并额外执行一次补齐处理;拼版循环会对每个补齐后的格位处理一次。PageManager 对每个点执行区域测试的成本是 O(区域数);页面操作则是 O(n) 的数组切片和拼接。这些引擎都不会在整份文档范围内保留逐页状态,因此长文档不会因此出现内存增长。主要内存成本来自累积的内容流,这正是 流式处理与内存概念 所涵盖的内容。HTML 管线的延迟与内存预算 gate 记载于 PERFORMANCE-BUDGETS;其范围仅限 HTML 绘制路径,不会直接为这些布局引擎设置 gate。该 performance_budget 的 1500 ms / 64 MB 是快速上手示例的整体 canvas 预算上限,而不是每次调用的契约。

这些引擎会接收并处理调用方提供的字符串和一个标志路径。标志路径会流经图像引擎,图像引擎会在嵌入文档前先完成验证。renderHeader()renderFooter() 会在标题、描述和页码文本进入内容流之前,先通过集中式 PDF 字符串转义器进行转义,因此调用方文本无法跳出字面量字符串语法。页眉或页脚 callback 执行调用方代码时,其信任级别与文档其余部分相同;请据此评估它读取的任何外部数据。PageManager 的页面操作只是移动指向既有 PageData 的引用;它们不会解析不受信任的字节。

声明标准条款佐证
为小册子输出而重新排序页面只会改变摆放位置;页面树仍然定义文档中页面的线性排列顺序。ISO 32000-2§7.7
序列化后的页面树节点,其 Kids 项是一个数组,包含指向该节点直属子节点的间接引用。ISO 32000-2§7.7.3.2

各条款均已改写,并映射到词汇表;未复现任何规范性正文。