跳到內容

無障礙:標記基本元件與 PDF/UA-2 結構模型

NextPDF Core 提供支援無障礙撰寫的基本元件:邏輯結構樹、標準角色對映、標記內容(marked-content)的標記,以及 BCP-47 語言屬性,並與 ISO 14289-2(PDF/UA-2)和 ISO 32000-2 §14.7 定義的結構樹模型一致。輸出檔案是否符規,取決於最終文件本身、撰寫者的內容選擇,以及外部檢查器。這不是函式庫能代你做出的保證。

Terminal window
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 名稱,例如 H1H6PLLITableFigureLink)、標記內容識別碼項目,以及用於替代文字、替換文字、標題與語言的選用無障礙屬性。單一元素可橫跨多頁,每頁累積一筆識別碼項目,讓 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(解析)為一個明確的語言。

符號類別摘要
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(): boolrootHasChildren(): bool方法檢查這棵樹是否存在,以及是否有後代節點。
StructureElementfinal class對應單一結構元素的值物件(替代文字、替換文字、標題、語言、識別碼)。
RoleMap::standard(): array<string,string>static標準結構型別詞彙表(ISO 32000-2 Table 368 加上 PDF 2.0 型別)。
Bcp47Validator::isWellFormed/isValid/validate/normalise方法RFC 5646 語法檢查與以登錄為依據的語言標記驗證。
AccessibilityAutoFixerRegistryfinal 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 警告,讓這種塌縮可被觀察到。
  • 自動修復器不會自動執行。 內建的修復器(EmptyTagStripperLegacyLangNormaliserRootLangFallback)隨 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