PDF/R,標準化為 ISO 23504-1,是針對掃描文件的 PDF 設定檔:每一頁恰好帶著一張條帶影像,其他什麼也沒有。PDFium Component 透過 ValidatePdfRCompliance 從 Delphi、Lazarus 與 C++Builder 對它進行驗證,該函式讀取一個串流,並回傳一致性等級加上一組具體問題
這個設定檔存在的原因,是掃描器與文件擷取系統需要一個比 PDF/A 更窄的目標。一份封存 PDF 可以包含該部分允許的任何東西;一份光柵 PDF 則刻意貧乏,於是任何符合規範的閱讀器都能以完全相同的方式顯示它,而任何符合規範的寫入器都能從一份掃描產出它,而不需要撰寫引擎
PDF/R 禁止了哪些 PDF/A 允許的東西?
實務上,是文字。一個光柵頁面承載掃描影像,其他什麼也沒有,所以頁面上的一個字型資源就是違規——在 ISO 23504-1 §6.5.2 之下被回報為 pvriFontForbidden。這會讓那些為了可搜尋性而加入不可見 OCR 文字層的人感到意外,這在 PDF/A 工作流程中是正常且有用的事,但單純不是 PDF/R
頁面與影像的關係同樣嚴格。§6.5.1 規定每一頁恰好是一張條帶影像,所以當影像數與頁數不符時,pvriPageImageMismatch 會觸發——沒有影像的頁面和有兩張影像的頁面兩者都不符合規範。而 pvriBadMediaBox 則回報 MediaBox 不具有 [0 0 w h] 形式的頁面(§6.5.3),因為一份掃描沒有理由坐落在偏移的原點上
uses FPdfPdfr;
var
Src: TFileStream;
Res: TPdfRValidationResult;
begin
Src := TFileStream.Create('scan-batch-0142.pdf', fmOpenRead or fmShareDenyWrite);
try
Res := ValidatePdfRCompliance(Src);
if Res.IsCompliant then
Memo1.Lines.Add('PDF/R-1 conformant')
else
begin
if pvriFontForbidden in Res.Issues then
Memo1.Lines.Add('A page names a font resource; a raster page carries no text');
if pvriPageImageMismatch in Res.Issues then
Memo1.Lines.Add('Image count does not match page count');
if pvriForbiddenImageFilter in Res.Issues then
Memo1.Lines.Add('A strip image uses an encoding outside the white list');
end;
finally
Src.Free;
end;
end;
哪些影像編碼被允許
四種,而這份白名單之所以短,是有理由的。§6.6 承認 /CCITTFaxDecode、/DCTDecode、/JPXDecode 與 /FlateDecode——二值傳真、JPEG、JPEG 2000 與無失真 deflate,這四者合起來涵蓋了所有重要的掃描器輸出。其他一切都被回報為 pvriForbiddenImageFilter,包含 /LZWDecode、/RunLengthDecode、/ASCII85Decode、/ASCIIHexDecode、/JBIG2Decode 與 /Crypt
這些拒絕之中,有兩個值得去理解而不是死記。/JBIG2Decode 對二值掃描的壓縮極為有效,而且在 PDF/A 中完全合法,但它的符號字典重建會替換成視覺上相似的字形——這是掃描數字的一個有紀錄的失敗模式——而一個整個目的就是忠實光柵重現的設定檔,不能容許這種風險。ASCII 篩選器被排除則是相反的原因:它們讓檔案膨脹,卻沒有增添任何光柵設定檔所需要的東西
在讀取任何頁面之前就觸發的結構規則
PDF/R 也約束容器。pvriObjStmPresent 會回報一個 /Type /ObjStm 串流,設定檔完全禁止它——物件串流會使一個光柵閱讀器本應能執行的簡單循序剖析變得複雜。pvriBadHeader 回報 %PDF-1.4 到 1.7 與 %PDF-2.0 之外的標頭,而 pvriEncryptVersionMismatch 則依 §6.2.3,回報一份標頭不是 %PDF-2.0 的加密檔案
Catalog 與 Info 字典是白名單制,而不僅僅是被檢查。pvriProhibitedCatalogEntry 與 pvriProhibitedInfoEntry 會在項目落在允許集合之外時觸發,而 pvriInfoXmpMismatch 會在 Info 項目與其 XMP 對應項不一致時觸發。一份缺少 Catalog /Metadata 串流、缺少 trailer /ID,以及缺少 %PDF-raster-1.0 頁尾標記,也各有自己的問題項目
為什麼儲存選項記錄省略了 Title 與 Author
TPdfRSaveOptions 帶有 Creator、Producer、CreationDate、ModDate、DocumentId 與 InstanceId,而且刻意沒有 Title、Author、Subject 或 Keywords 的欄位。這四個是 §6.4.3 所禁止的項目,所以一份公開了它們的記錄,等於是在邀請呼叫端透過一個符合規範的 API 寫出不符合規範的檔案
兩個布林選項控制轉換既有 PDF 時的清理動作。StripInfoOptionalEntries 預設為 True,會從來源 Info 字典移除 Title、Author、Subject、Keywords 與 Trapped。StripCatalogOptionalEntries 同樣預設為 True,會移除 Names、Outlines、StructTreeRoot、OutputIntents、Lang 等項目,只留下 §6.3 的白名單。把任一項設為 False,你會保留這些項目——並失去一致性,而這偶爾正是呼叫端對內部檔案真正想要的
var
Opts: TPdfRSaveOptions;
Src, Dest: TFileStream;
begin
Opts := TPdfRSaveOptions.Default;
Opts.Creator := 'Capture Station 4';
Opts.Producer := 'PDFium Component';
Src := TFileStream.Create('scan-in.pdf', fmOpenRead or fmShareDenyWrite);
try
Dest := TFileStream.Create('scan-pdfr.pdf', fmCreate);
try
InjectPdfRMarkers(Src, Dest, Opts); // markers + metadata, not page content
finally
Dest.Free;
end;
finally
Src.Free;
end;
end;
請注意標記注入不做的事:它增添中繼資料與識別,而無法供應頁面內容。一份帶有未承載條帶影像的來源頁面,在注入後仍然會在 pvriPageImageMismatch 上失敗,因為缺少的影像從來就不是中繼資料問題
PDF/R 在擷取管線中的定位
在交付物就是掃描本身、而保真度就是整份合約的情況下使用它——證據影像、支票與匯款擷取、來自大尺寸掃描器的工程圖面封存。一旦文件需要可搜尋的文字、標記、嵌入附件或任何其他光柵設定檔剝除的東西,就改用 PDF/A
一種常見且可行的安排是兩者都產出:一份永不改變的 PDF/R 原件,以及一份帶有 OCR 層以供檢索的 PDF/A 衍生檔。兩個驗證器各自獨立,所以同一項批次工作可以根據每份產物實際宣稱的設定檔來檢查它。關於這一對裡的封存側,請見 PDF/A 封存合規與 PDF/A 預檢驗證的筆記;而對於面向列印的輸出,請見 驗證可列印 PDF/X 文件的逐步解說
PDFium Component 將 PDFium 引擎帶進 Delphi、C++Builder 與 Lazarus,提供一套 VCL API,以及針對 PDF/A、PDF/X、PDF/E、PDF/UA 與 PDF/R 的一致性驗證器——支援的標準與 IDE 版本請見 PDFium Component 產品頁