PDFlibPas 在 Delphi 中透過 TPDFlib.CheckFilePDFUACompliance 並把目標 Part 指定為 2 來驗證 PDF/UA-2(ISO 14289-2)檔案,這會執行一條獨立的 PDF/UA-2 設定檔:透過 PDF 2.0 命名空間與 /RoleMapNS 解析結構角色,把遭截斷或無法讀取的輸入回報為失敗,並傳回一份絕不宣稱完整認證的涵蓋範圍報告
最後那一點比聽起來更重要。無障礙驗證這個領域,綠色勾勾一旦被複製進採購試算表,就不會再有人質疑。一個悄悄跳過半套標準、卻照樣印出「通過」的驗證器,比沒有驗證器更糟,因為它把未知變成了假裝的已知。PDFlibPas 採取相反的立場:機器查得了的就查,查了什麼大聲講清楚,每一次「跑不完」都當成一項發現,而不是當成成功
為什麼不能用 PDF/UA-1 的規則驗證 PDF/UA-2?
PDF/UA-2 不是把 PDF/UA-1 的 Part 號碼加一而已。PDF/UA-1 立足於 PDF 1.7 與單一扁平的 role map;PDF/UA-2 立足於 PDF 2.0(ISO 32000-2),在這個版本裡,每個結構元素都可以隸屬於一個命名空間,而每個命名空間各自帶有自己的 role map。拿 UA-1 檢查器去對付 UA-2 檔案,既會漏掉真正的問題,也會憑空捏造假問題
假問題才是難堪的部分。PDF/UA-1 驗證器通常強制兩類規則:Note 元素必須帶 ID,以及不帶編號的 H 標題允許怎麼用。這兩條規則都搬不過去。在 PDF 2.0 的結構命名空間裡,註腳改用 FENote 表達,標題從 H1 到 Hn,沒有六層上限。PDF/UA-2 更進一步,直接禁止不帶編號的 H 角色。把 UA-1 的標題邏輯套到這種樹上,您不是對標準允許的東西挑毛病,就是對標準唯一禁止的那件事放行
所以 PDFlibPas 的 PDF/UA-2 設定檔是一條獨立的程式碼路徑,不是舊路徑上的一個旗標。它也不願重用 UA-1 的條號。與 UA-1 檢查器共用的診斷項目,在 UA-2 報告裡會重新綁定到 ISO 14289-2;而凡是還沒對照標準原文核實過的條號,報告寧可把條號欄位留空,也不瞎猜。空的條號看起來不如一個言之鑿鑿的條號那麼令人滿意,但在沒人確認過對應關係時,這是唯一站得住腳的輸出
PDFlibPas 如何透過 RoleMapNS 解析角色
PDFlibPas 會把每個結構角色解析成已知命名空間裡的標準角色,跨越命名空間追完 /RoleMapNS 鏈,並回報任何停在自訂角色上、形成循環或違反對應規則的鏈。這是 PDF/UA-2 結構驗證的核心,因為輔助科技只認得標準角色;自訂的 /InvoiceLine 在落到 P 或 TR 這類角色之前,對輔助科技毫無意義
解析器從元素的 /S 名稱與它的 /NS 命名空間出發。沒有命名空間,元素就隱含位於 PDF 1.7 標準命名空間,適用的是結構樹根裡舊式的 /RoleMap。有命名空間,適用的就是該命名空間字典自己的 /RoleMapNS,而且對應可以透過一個雙元素陣列指向另一個命名空間裡的角色。由此得出的檢查相當具體:
- 結構元素參照的命名空間必須是間接字典、帶有效的
/NSURI,並且列在結構樹根的/Namespaces陣列裡(診斷碼 10054) - 自訂角色最終必須解析成 PDF 1.7 命名空間、PDF 2.0 命名空間(
http://iso.org/pdf2/ssn)或 MathML 命名空間裡的標準角色;循環鏈與走不到底的鏈回報為 10055 - 標準角色不得重新對應到自訂角色,角色也不得在自己的明示命名空間內被重新對應
- 結構根必須恰好包含一個位於 PDF 2.0 命名空間的
Document元素(10057),且每個元素的/P都必須指向它實際的父元素(10058) - 不帶編號的
H,或未對應到 PDF 2.0 角色的舊式 PDF 1.7Note,回報為 10056
有個細節值得抄進您自己的工具裡:PDFlibPas 驗證的是每一條宣告出來的對應,不只樹上走得到的那幾條。某個目前沒有任何元素在用的 role map 裡有一條壞掉的項目,仍然是潛在缺陷,因為下一次內容更新就會開始用到它。MathML 的處理也很誠實:MathML 命名空間被認可為有效的解析目標,但 MathML 詞彙本身不在驗證範圍內,涵蓋範圍報告也明說了這一點
從 Delphi 執行 PDF/UA-2 檢查
TPDFlib.CheckFilePDFUACompliance 是單次呼叫的進入點:傳入檔案、密碼、目標 Part 與選項,取回一個裝著編碼問題的字串清單 handle。handle 為零且 LastErrorCode 等於零,代表已實作的檢查沒找到任何問題;LastErrorCode 非零,代表呼叫本身就失敗了
var
Lib: TPDFlib;
Issues, I: Integer;
begin
Lib := TPDFlib.Create;
try
// TargetPart: 0 = 依 XMP pdfuaid:part 自動偵測,1 = PDF/UA-1,2 = PDF/UA-2
// Options: 1 = 遇到第一個失敗就停,0 = 收集全部問題
Issues := Lib.CheckFilePDFUACompliance('report.pdf', '', 2, 0);
if Issues = 0 then
begin
if Lib.LastErrorCode <> 0 then
Writeln('Validation did not run, error ', Lib.LastErrorCode)
else
Writeln('No issues from the implemented PDF/UA-2 checks');
end
else
try
for I := 1 to Lib.GetStringListCount(Issues) do // 項目從 1 起算
Writeln(Lib.GetStringListItem(Issues, I));
finally
Lib.ReleaseStringList(Issues);
end;
finally
Lib.Free;
end;
end;
目標 Part 要慎重選。目標設 0 會從 XMP 詮釋資料讀 pdfuaid:part 並據此挑設定檔,適合通用的收件匣驗證器。自己的輸出管線,請明確傳 2。如果產生器忘了寫入識別資訊,自動偵測會退回 UA-1 規則,您就驗了錯的標準;明確傳 2 則會對缺失的 Part 回報診斷碼 10050,pdfuaid:rev 不是 2024 時加報 10051,有效 PDF 版本不是 2.0 時加報 10052。在產出端,TPDFlib.SetPDFUA2Mode 會寫入這份識別資訊、把版本升到 2.0,並把 PDF 2.0 結構命名空間設為預設;命名空間模型本身在PDF 2.0、PDF/A-4 與 PDF/UA-2 命名空間詳解一文裡有完整說明
驗證器的預算用完時會發生什麼?
PDFlibPas 採 fail-closed 原則:PDF/UA-2 檢查器一旦碰到走訪上限、循環、剖析器上限,或打不開的文件,就會發出一條診斷,而沒有診斷的結果絕不會被升格為通過。診斷碼 10059 正是保留給這種「讀不完」的情況
這些預算寫得明白,公布在 coverage JSON 裡:結構深度 256、十萬個結構物件、任何 role map 鏈最多 256 跳。會設這些數字,是因為結構樹來自不可信任的輸入。一個懷有惡意或單純壞掉的檔案,可以把元素巢狀到幾千層深、讓子元素指回祖先,或建出一條穿過幾百個命名空間的 role map 鏈。沒有預算的驗證器不是崩潰就是掛掉;有預算卻沒有診斷的驗證器則做著更隱晦也更糟的事:提前停手,在它讀過的部分沒找到錯,就回報成功
同樣的原則在上層也適用。如果底層讀取器回傳的分數低於 100 卻沒有任何問題行——剖析在任何規則跑起來之前就中斷時會發生這種事——PDFlibPas 會插入一條 10059,讓空清單不可能被誤認成乾淨的結果。檢查途中丟出的例外也一樣變成一條 10059。這只是幾行程式碼的事,卻堵住了驗證管線最常見的說謊方式
與 PDF/A 共用一次剖析並產出報告
批次作業時,validation pipeline API 只剖析檔案一次,多項檢查都跑在共用的剖析結果上。RunPDFUAValidationPipelineTask 是這個家族裡的 PDF/UA 成員,接受與單次呼叫函式相同的目標 Part 值,讓 PDF/A 加 PDF/UA-2 的 preflight 不必為一份大文件付出兩次完整讀取的代價
var
Pipe, PDFAIssues, UAIssues: Integer;
Json: WideString;
begin
Pipe := Lib.BeginValidationPipeline('archive.pdf', '');
if Pipe = 0 then
Exit;
try
PDFAIssues := Lib.RunValidationPipelineTask(Pipe, 1, 0); // 1 = PDF/A
UAIssues := Lib.RunPDFUAValidationPipelineTask(Pipe, 2, 0); // PDF/UA-2
// ... 檢查並釋放兩個 handle,做法同前一個範例 ...
Writeln('Parses: ', Lib.GetValidationPipelineParseCount(Pipe));
// ComplianceTests: 1 = PDF/A,2 = PDF/UA,0 = 兩者都要;ReportFormat 1 = JSON
Json := Lib.CreateValidationPipelineReport(Pipe, 0, 0, 1);
finally
Lib.ReleaseValidationPipeline(Pipe);
end;
end;
檔案識別為 part 2 時,結構化報告會把無障礙章節標成 PDF/UA-2 與 ISO 14289-2,而且每個 PDF/UA 章節都帶著 "fullCertification": false 與內嵌的涵蓋範圍物件。文字版 preflight 報告會加一行,說明只跑了已實作的檢查,完整的無障礙認證還需要額外驗證與人工審查。更完整的報告格式、規則 ID 與 CI 關卡模式,見PDF/A 與 PDF/UA preflight 指南
涵蓋範圍報告實際上保證了什麼?
TPDFlib.GetPDFUAReadValidationCoverageJSON 只保證一件事:精確列出 PDF/UA-2 檢查器查了什麼、沒查什麼。對 part 2 它回傳固定為 false 的 fullCertification、一個 checked 陣列、一個 unverified 陣列,以及那些走訪上限。它不保證檔案無障礙,而且設計上就讓任何程式碼路徑都無法讓它改口
var
Coverage: WideString;
begin
Coverage := Lib.GetPDFUAReadValidationCoverageJSON(2);
// 把它存在驗證結果旁邊,而不是取代驗證結果
TFile.WriteAllText('report.coverage.json', Coverage, TEncoding.UTF8);
if Pos('"fullCertification":false', Coverage) > 0 then
Writeln('Machine checks only; schedule a human accessibility review');
end;
checked 清單涵蓋識別資訊(part、修訂版、prefix)、有效 PDF 版本、標題詮釋資料、/MarkInfo、DisplayDocTitle、命名空間參照、role map 解析、文件根、結構父元素、樹循環,以及 H 與 Note 規則。unverified 清單同樣具體:內容標記與閱讀順序、結構包含關係、字型與 Unicode 對應、註解與表單、自然語言、替代描述、MathML 詞彙,以及人工無障礙審查。其中好幾項甚至不是機器可判定的。沒有任何剖析器能告訴您替代文字是否真的描述了那張圖表,或閱讀順序對用聽的人來說是否合理
把 coverage JSON 當成結果的一部分。每次驗證執行都把它歸檔保存,等稽核人員問起某個日期的「通過」到底是什麼意思,您就能交出確切的範圍,而不是靠記憶和版本號去重建
與 veraPDF 交叉驗證
PDF/UA-2 核心曾用 veraPDF 1.30.3 命令列驗證器的 ua2 設定檔做交叉比對,在涵蓋範圍重疊之處,兩邊結果一致。一份另行產生的有效核心樣本兩個工具都通過;宣告 pdfuaid:rev 為 2023 的樣本,與使用不帶編號 H 角色的樣本,兩個工具都判失敗
第四個樣本才值得聊聊。它包含完全沒有標記的繪圖內容,veraPDF 抓到了,PDFlibPas 的核心檢查卻沒有,因為內容標記不在目前涵蓋範圍內。這個缺口正是 content-tagging-and-reading-order 排在 unverified 清單第一位的原因。多跑一個驗證器是便宜的保險,兩個工具意見相左,往往正是找到各自檢查邊界最快的路。至於同一問題的產出端——一開始就建出能通過的樹——請看tagged PDF 結構樹指南
FAQ
PDFlibPas 會把檔案認證為 PDF/UA-2 合規嗎?
不會。PDFlibPas 跑的是已實作的 PDF/UA-2 機器檢查,並回傳一份 fullCertification 恆為 false 的涵蓋範圍報告,逐項列出還需要其他工具或人工審查的領域
CheckFilePDFUACompliance 該傳什麼 TargetPart 值?
確定檔案必須是 PDF/UA-2 時就傳 2,這樣缺了識別資訊會被回報出來。傳 0 表示從 XMP 的 pdfuaid:part 自動偵測 Part,傳 1 則強制套用 PDF/UA-1 規則
PDF/UA-2 報告裡的診斷碼 10059 是什麼意思?
診斷碼 10059 代表驗證器無法跑完:檔案剖析不了或打不開、超出走訪上限、發現循環,或發生了例外。它一律算失敗,絕不算通過
PDF/UA-2 驗證、共用 validation pipeline 與 PDF 2.0 產出 API 都隨 losLab PDF Library for Delphi 出貨,同捆的還有 PDF/A preflight 與結構化驗證報告