無障礙:標記基本元件與 PDF/UA-2 結構模型
NextPDF Core 提供支援無障礙撰寫的基本元件:邏輯結構樹、標準角色對映、標記內容(marked-content)的標記,以及 BCP-47 語言屬性,並與 ISO 14289-2(PDF/UA-2)和 ISO 32000-2 §14.7 定義的結構樹模型一致。輸出檔案是否符規,取決於最終文件本身、撰寫者的內容選擇,以及外部檢查器。這不是函式庫能代你做出的保證。
composer require nextpdf/core概念總覽
標題為「概念總覽」的區段已標記的 PDF 帶有一棵邏輯結構樹,其根節點只包含單一的 Document 結構元素。輔助技術會讀取這棵樹,推導出不依賴視覺版面的有意義閱讀順序(ISO 32000-2 §14.7.2;ISO 14289-2 §8.2.5.2)。NextPDF 透過 NextPDF\Accessibility 命名空間中三個相互協作的型別,為這套模型建模。
StructureTree 管理整個階層。它會為每一頁配置標記內容識別碼,追蹤父節點與子節點的巢狀關係,並依 ISO 32000-2 §14.7 序列化結構樹根、各結構元素、父樹、角色對映表,以及 PDF 2.0 標準結構命名空間。createRoot() 會建立強制要求的單一 Document 元素,並帶有語言屬性。addElement() 會掛上有型別的子元素。hasRoot() 和 rootHasChildren() 會回報這棵樹是否存在,以及它是否有後代節點。
StructureElement 是對應單一結構元素字典的值物件。它會記錄標準結構型別(Table 368 名稱,例如 H1 到 H6、P、L、LI、Table、Figure、Link)、標記內容識別碼項目,以及用於替代文字、替換文字、標題與語言的選用無障礙屬性。單一元素可橫跨多頁,每頁累積一筆識別碼項目,讓 kids 陣列能跨頁邊界參照標記內容。
TaggedContentEmitter 銜接 HTML 管線與結構樹。啟用 Document::enableTaggedPdf() 時,HTML renderer(渲染器)會接上這個 emitter,讓區塊層級元素產生成對的標記內容運算子與對應的結構元素節點。HtmlToStructureMap 提供表格驅動的對映,將 HTML 標籤對應到 PDF 結構型別(ISO 14289-2 §8)。這個 emitter 會將裝飾性的頁眉與頁尾這類重複內容導向 artifact,使其不進入閱讀順序。
語言標記由 Bcp47Validator(RFC 5646)驗證。它提供格式正確的語法檢查,以及以登錄為依據的有效性檢查。嚴格模式(ConformancePolicy::strictUa2())會在 API 邊界拒絕格式錯誤的標記,而不是在寫入時悄悄丟棄它們。這符合 ISO 14289-2 §8.4.4 要求:catalog 語言項目必須 resolve(解析)為一個明確的語言。
API 介面
標題為「API 介面」的區段| 符號 | 類別 | 摘要 |
|---|---|---|
Document::enableTaggedPdf(string $lang = 'en', ?ConformancePolicy $policy = null): static | 方法 | 啟用結構樹與 HTML 橋接;設定 mark-info 與 catalog 語言項目。 |
Document::setLanguage(string $lang): static | 方法 | 設定文件層級的自然語言(BCP-47)。 |
Document::isTaggedPdfEnabled(): bool | 方法 | 目前啟用的符規模式是否強制要求結構標記。 |
StructureTree::createRoot(string $lang = 'en'): int | 方法 | 建立強制要求的單一 Document 根元素。 |
StructureTree::addElement(int $parentIndex, string $type, int $pageIndex, ...): int | 方法 | 掛上一個有型別的子結構元素。 |
StructureTree::hasRoot(): bool 和 rootHasChildren(): bool | 方法 | 檢查這棵樹是否存在,以及是否有後代節點。 |
StructureElement | final class | 對應單一結構元素的值物件(替代文字、替換文字、標題、語言、識別碼)。 |
RoleMap::standard(): array<string,string> | static | 標準結構型別詞彙表(ISO 32000-2 Table 368 加上 PDF 2.0 型別)。 |
Bcp47Validator::isWellFormed/isValid/validate/normalise | 方法 | RFC 5646 語法檢查與以登錄為依據的語言標記驗證。 |
AccessibilityAutoFixerRegistry | final class | 採選擇加入(opt-in)的 PSR-11 風格登錄表,用於存放啟發式結構修復器。 |
程式碼範例 — 快速上手
標題為「程式碼範例 — 快速上手」的區段<?php
declare(strict_types=1);
use NextPDF\Core\Document;
$doc = Document::createStandalone();
// The BCP 47 tag drives the catalog language entry and the// structure-tree root language attribute.$doc->enableTaggedPdf(lang: 'en');$doc->setTitle('Tagged accessibility demo');$doc->addPage();
// Semantic HTML maps to structure elements: h1 to /H1, p to /P,// ul and li to /L plus /LI. Text runs are wrapped in// marked-content operators with stable identifiers.$doc->writeHtml('<h1>Document title</h1><p>Body paragraph.</p>');
$doc->save(__DIR__ . '/output/tagged.pdf');程式碼範例 — 正式環境
標題為「程式碼範例 — 正式環境」的區段<?php
declare(strict_types=1);
use NextPDF\Conformance\ConformancePolicy;use NextPDF\Core\Document;use NextPDF\Exception\InvalidConfigException;use Psr\Log\LoggerInterface;
final class AccessibleReportWriter{ public function __construct(private readonly LoggerInterface $logger) { }
public function render(string $html, string $bcp47Lang, string $outPath): void { $doc = Document::createStandalone();
try { // strictUa2() rejects malformed BCP 47 tags at the API // boundary (ISO 14289-2 §8.4.4) instead of dropping silently. $doc->enableTaggedPdf($bcp47Lang, ConformancePolicy::strictUa2()); } catch (InvalidConfigException $e) { $this->logger->error('Rejected language tag for tagged PDF', [ 'lang' => $bcp47Lang, 'reason' => $e->getMessage(), ]);
throw $e; }
$doc->setTitle('Quarterly accessibility report') ->setLanguage($bcp47Lang) ->addPage();
$doc->writeHtml($html);
// The engine emits a Degraded / ComplianceRisk advisory directing // the caller to validate externally; surface it to operators // rather than treating tagged output as certified. foreach ($doc->getWarnings() as $warning) { $this->logger->warning('Tagged-PDF advisory', [ 'code' => $warning->code->value, 'message' => $warning->message, ]); }
$doc->save($outPath); }}邊界情況與陷阱
標題為「邊界情況與陷阱」的區段- 呼叫順序。 先呼叫
enableTaggedPdf(),再呼叫writeHtml()。HTML 管線會在 parser(剖析器)建構時檢查符規模式,不會回頭替已經繪製的內容補接 emitter。 - 空的結構樹。 已呼叫
enableTaggedPdf()但未掛上任何結構後代的文件,不會在其中繼資料中宣告 PDF/UA-2。發布的判斷依據是rootHasChildren()而非hasRoot(),因為宣告為 PDF/UA-2 卻帶著空結構樹的檔案會被驗證器拒絕(ISO 14289-2 §5;由EmptyTaggedPdfDoesNotAdvertisePdfUa2Test驗證)。 - 符規模式塌縮。 在同一份文件上同時呼叫
enablePdfA()與enableTaggedPdf(),會讓單值的符規判別器塌縮為後者覆蓋前者。副作用(結構樹、mark-info)仍是疊加性的,而且會發出一則CONFORMANCE_MODE_CLOBBERED警告,讓這種塌縮可被觀察到。 - 自動修復器不會自動執行。 內建的修復器(
EmptyTagStripper、LegacyLangNormaliser、RootLangFallback)隨NextPDF\Accessibility\AutoFixer\*一併提供,但從不自動註冊。使用方必須明確地在AccessibilityAutoFixerRegistry上註冊它們。
已知限制
標題為「已知限制」的區段NextPDF 會發出與 PDF/UA-2 結構樹模型一致的結構,但不會自動撰寫它無法推斷的語意。以下這些需要撰寫者自行提供標記或屬性,不會自動為你產生:
- 影像與其他非文字內容的替代文字;
- 超出 HTML 標記可表達範圍之外的表格表頭範圍,以及表頭與儲存格的關聯;
- 當可見連結文字本身無法說明用途時,連結用途的說明文字;
- 在視覺上排列成清單、卻沒有清單標記的內容,其清單語意;
- 當原始順序與預期閱讀順序不同時,經修正後的閱讀順序;
- 模稜兩可內容在「裝飾性」與「有意義」之間的分類判斷。
此函式庫不執行任何端到端的 PDF/UA-2 驗證。執行階段本身會發出一則 Degraded/ComplianceRisk 提示(PDFUA2_FOUNDATIONAL),引導呼叫端使用外部檢查器驗證輸出,以供正式環境簽核。請以 PDF/UA 檢查器(例如 veraPDF)驗證。NextPDF 不會代你宣稱符規。最終文件是否符規,取決於撰寫者的選擇加上驗證器,而不是呼叫 API 本身。
結構樹的建構與結構元素的數量呈線性關係。識別碼配置在每個標記內容序列上為攤銷後的常數時間。序列化是對元素集合的單次線性掃描。以 HTML 驅動的標記,其主要成本是 HTML 管線本身,而非標記的發出。performance_budget 中宣告的每份配方上限(1500 ms 牆鐘時間、64 MB 尖峰記憶體)適用於典型的多頁語意文件。大型文件的擴展與元素數量呈線性關係,而非與頁數呈線性關係。
安全性注意事項
標題為「安全性注意事項」的區段語言標記與無障礙屬性會流入 PDF 名稱物件與字串物件。NextPDF 會透過 PdfStringEscaper 對它們進行跳脫,使格式錯誤或具惡意的語言、替代文字、替換文字與標題值,無法跳脫其 PDF 物件脈絡。嚴格模式還會在 API 邊界拒絕未登錄的 BCP-47 標記,在輸入抵達寫入器之前先縮小輸入面。無障礙屬性可能帶有撰寫者提供的自由文字。請將它們視為不受信任的輸出,並套用與其他文件內容相同的審查。關於 profile 檢查器的行為,請參閱 Conformance 模組一節。
本頁將函式庫的行為對映到條款識別碼,並不宣稱你的輸出符規。所引用的條款皆為改寫,絕非原文引述。關於條款層級的對照表與明確的未涵蓋範圍,請參閱 PDF/UA-2 規格對映一節。引用的條款雜湊記錄於 docs/public/modules/core/_normative-evidence-a11y.md。