技術文章

Delphi PDF/A 驗證:設備色、輸出意圖與透明

veraPDF 會在一份從沒畫過 RGB 的檔案上標記 ISO 19005-1 6.2.3.3,把光禿禿的 re f 稱為未校準,或在第 1 頁通過的同時讓一模一樣的第 2 頁失敗。PDF/A 判這些規則,看的是每一頁實際畫了什麼,所以 Delphi 的 PDFium Component PDF/A 驗證器會把每一頁追進表單、圖樣、字形與註解,然後才裁定色彩與透明

6.2.3.3 與 6.2.4.3 實際要求什麼

ISO 19005-1 6.2.3.3 把三條獨立規則塞進同一段。合規檔案可以用 DeviceRGB 或 DeviceCMYK,但不得兩者都用;凡用到未校準的色彩空間,檔案就必須帶 PDF/A-1 OutputIntent;DeviceRGB 只能配 profile 為 RGB 的意圖,DeviceCMYK 只能配 CMYK 的。DeviceGray 不在那份清單上,因為條文接著解釋了任一意圖下灰階如何轉換。意圖本身是選配:6.2.2 說檔案可以指定一個,而且只認帶 /S /GTS_PDFA1、且 /DestOutputProfile 裡有有效 ICC profile 串流的 OutputIntents 條目。所以要求每份檔案都帶意圖的檢查器,會把從不畫設備色的合規文件也拒掉。Part 1 沒提 DefaultRGB 或 DefaultCMYK,但經它們重映射的色彩會在以 CIE 為基礎的空間裡渲染(ISO 32000-1 §8.6.5.6),滿足了 6.2.3.1 的設備無關要求;veraPDF 的 6.2.3.3 測試套件把它當成已校準,PDFium Component 對這條的解讀相同。混用禁令另立一處:同時畫 RGB 與 CMYK 的 PDF/A-1 檔案,就算兩者都有 Default 條目校準照樣失敗。ISO 19005-2 6.2.4.3 撤掉這條禁令,按各自條件校準每個空間,如下表右欄所示,所以 PDFium Component 為每個 part 各跑一遍掃描,混用檢查只施於 PDF/A-1 宣告

繪製到的設備空間PDF/A-1(ISO 19005-1 6.2.3.3)PDF/A-2 及之後(ISO 19005-2 6.2.4.3)
DeviceRGB作用範圍內有 DefaultRGB 或 RGB 意圖;不得與 DeviceCMYK 並存作用範圍內有 DefaultRGB,或一個 RGB OutputIntent
DeviceCMYK作用範圍內有 DefaultCMYK 或 CMYK 意圖;不得與 DeviceRGB 並存作用範圍內有 DefaultCMYK,或一個 CMYK OutputIntent
DeviceGray作用範圍內有 DefaultGray,或任何 PDF/A-1 OutputIntent作用範圍內有 DefaultGray,或任何 PDF/A OutputIntent

怎樣才算用了一個色彩空間?

用一個色彩空間,指的是在讀取器會渲染的路徑上用它畫東西。宣告是不夠的。veraPDF 套件用一份 6.2.3.3 通過的檔案把這點講清楚:一個具名的 Form XObject 用 K 與 k 設了 CMYK,躺在頁面的資源字典裡,卻從不被頁面內容參照,而檔案的 OutputIntent 是 RGB。全檔搜尋設備色名稱,會把這份合規檔案判失敗,又會漏掉一份在 CMYK 意圖下只畫 DeviceRGB、兩個名稱根本不打架的檔案。PDFium Component 改為沿繪圖的圖結構走訪,建立在其位元組級 PDF/A 預檢掃描器之上。從每一頁出發,這趟走訪統計以下來源畫出的色彩:

  • 頁面內容串流,以及它們以 Do 叫用的每個 Form XObject,巢狀表單也算
  • 以 scn 或 SCN 選用的圖樣,按該圖樣自己的 Resources 解讀
  • 以 Tf 選用的 Type 3 字型的 CharProcs
  • 影像 XObject 與行內影像,按它們自己字典指名的色彩空間
  • 註解外觀串流,畫在頁面之上;當 widget 的 /AP /N 是狀態字典時,每個狀態都算,因為切換欄位時畫的是 /AS 沒選的那個

可抵達性裡較隱晦的那一半,是內容從頭到尾沒指名的色彩。q 0 0 10 10 re f Q 不含任何色彩運算元,它照樣畫出東西。圖形狀態的初始色彩空間是 DeviceGray(ISO 32000-1 Table 52),所以這次填色是 DeviceGray 黑,除非存在 PDF/A OutputIntent 或 DefaultGray,否則在每個 part 底下都未校準。因此掃描會追蹤是否已有東西選過色彩:rg、k、g 與其描邊形式選的是設備空間,cs 與 CS 選的是它們指名的空間,而在任何選擇之前到達的填色、描邊或文字運算元就記為 DeviceGray。/CS0 cs 1 0 0 /P0 scn 已經選過空間,所以那個 scn 不是灰

PDFium Component 在 Delphi 裡裁定 PDF/A 設備色之前走訪的可抵達繪製路徑:表單、圖樣、Type 3 字形、影像與註解外觀串流
宣告色彩空間並不夠——走訪統計每一頁畫了什麼,包括光禿禿的 re f 從初始狀態繼承的設備灰

行內影像把色彩空間與原始樣本直接帶進內容串流,掃描器兩邊都得小心讀。/CS 可以是縮寫(G、RGB、CMYK、I),也可以是資源名稱,例如解析成 [/Indexed /DeviceRGB 255 13 0 R] 的名字,其 base 計入;而 ID 與 EI 之間的位元組是任意二進位資料,所以 EI 只有夾在空白之間才算結束影像,否則壓縮樣本裡的一個 rg 就成了幻影運算元

% 第 1 頁:作用範圍內有 DefaultRGB,還有一個沒有任何東西叫用的 XObject
4 0 obj << /Type /Page /Parent 2 0 R /Contents 5 0 R
           /Resources << /ColorSpace << /DefaultRGB 9 0 R >>
                         /XObject << /Fm0 6 0 R >> >> >>
endobj
% 第 2 頁:同一份內容串流,沒有 ColorSpace 子字典
7 0 obj << /Type /Page /Parent 2 0 R /Contents 5 0 R /Resources << >> >>
endobj
5 0 obj << /Length 46 >> stream
1 0 0 rg 72 72 200 100 re f
q 0 0 10 10 re f Q
endstream
endobj
6 0 obj << /Type /XObject /Subtype /Form /BBox [0 0 50 50] /Length 24 >> stream
0 0 0 1 k 0 0 50 50 re f
endstream
endobj
9 0 obj [/ICCBased 10 0 R]
endobj

開頭的每個症狀都能從這段片段推導出來。/Fm0 永遠遇不到 Do,所以它的 k 無關緊要。兩頁上光禿禿的 re f 是 DeviceGray,沒有任何東西校準它。第 1 頁的 rg 由第 1 頁繪製時所用資源裡的 DefaultRGB 涵蓋;第 2 頁跑同一份串流,資源裡卻沒有那個映射,同樣的位元組在那裡就未校準。PDF/A-1b 宣告下的 Expected:pvaiMissingOutputIntent 與 pvaiMissingIccProfile,由第 2 頁的 RGB 與兩頁的灰驅動。表單什麼都沒貢獻;一旦被叫用,它的 CMYK 會再疊上 PDF/A-1 混用禁令

預設色彩空間屬於實際繪製的那份資源字典

Default 映射是在當下資源字典的 ColorSpace 子字典裡查找的(ISO 32000-1 §8.6.5.6),所以它們的局部性與資源本身一模一樣;文件沒有全域預設。影像 XObject 沒有自己的 Resources,字典寫著 /ColorSpace /DeviceCMYK 的影像,由實際畫它的那個頁面或表單的 DefaultCMYK 校準,同一份影像從兩處繪製,可以一處已校準、另一處沒有。Form XObject、平鋪圖樣與 Type 3 字形程序自帶 Resources,頁面的 DefaultCMYK 伸不進它們裡面,它們自己字典裡的映射才有效。PDFium Component 對映射的作用範圍照同樣方式處理。判定結果以 TPdfAValidationResult 記錄回傳:偵測到的 Conformance 加上一個 Issues 集合。要替一個資料夾做初步分類,這些就夠了

uses
  System.SysUtils, System.Classes, System.TypInfo, FPdfPdfa;

procedure ReportColorFindings(const FileName: string; Log: TStrings);
const
  ColorRules: TPdfAValidationIssues = [pvaiMissingOutputIntent,
    pvaiMissingIccProfile, pvaiMixedDeviceColorSpaces, pvaiBlendSpaceUndefined];
var
  Source: TFileStream;
  Res: TPdfAValidationResult;
  Found: TPdfAValidationIssues;
  Issue: TPdfAValidationIssue;
begin
  Source := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
  try
    Res := ValidatePdfACompliance(Source);   // 從位置 0 開始讀
  finally
    Source.Free;
  end;
  Log.Add(ExtractFileName(FileName) + ': ' +
    GetEnumName(TypeInfo(TPdfAConformance), Ord(Res.Conformance)));
  Found := Res.Issues * ColorRules;
  for Issue in Found do
    case Issue of
      pvaiMissingOutputIntent:
        Log.Add('  reachable uncalibrated device color, no PDF/A OutputIntent');
      pvaiMissingIccProfile:
        Log.Add('  no OutputIntent with a DestOutputProfile ICC stream');
      pvaiMixedDeviceColorSpaces:
        Log.Add('  RGB/CMYK use disagrees with the intent, or PDF/A-1 paints both');
      pvaiBlendSpaceUndefined:
        Log.Add('  transparent page without a blending color space');
    end;
  if Found = [] then
    Log.Add('  device color and blending rules satisfied');
end;

走訪抵達未校準設備色、而檔案又沒有 PDF/A OutputIntent 時,pvaiMissingOutputIntent 與 pvaiMissingIccProfile 會一起出現。pvaiMissingIccProfile 單獨出現,代表意圖存在但沒帶 profile,6.2.2 不承認那是 PDF/A 意圖。pvaiMixedDeviceColorSpaces 同時涵蓋 PDF/A-1 混用禁令與分量不匹配:四分量的意圖配未校準的 DeviceRGB,或三分量的意圖配 DeviceCMYK。這個集合指名的是規則而非頁面,所以在長文件或合併過的文件裡,要比對哪些頁面宣告了 Default 條目、哪些沒有

Delphi 裡同一份 DeviceCMYK 影像從兩份資源字典繪製:如 PDFium Component 所回報,一頁由 DefaultCMYK 校準,另一頁未校準
預設色彩空間屬於實際繪製的資源字典,所以同一份影像可以在頁面繪製處獲得校準、在表單繪製處則沒有

透明頁面什麼時候需要自己的混合空間?

透明頁面只有在文件沒有 PDF/A OutputIntent 時,才需要自己的混合色彩空間。ISO 19005-2 6.2.10(PDF/A-4 為 6.2.9)把意圖定為 ISO 32000-1 §11.3.4 意義下的預設混合色彩空間;沒有意圖時,凡含透明的頁面都必須帶 Group,其屬性字典(ISO 32000-1 Table 147)裡要有 CS。那個 CS 同樣受 6.2.4 約束,所以 sRGB 意圖下 /DeviceCMYK 的頁面群組會像缺失一樣被回報,PDFium Component 對兩者都拋 pvaiBlendSpaceUndefined;CalRGB 或 ICCBased 群組空間沒有這種依賴。PDF/A-1 摸不到這個問題,因為 ISO 19005-1 6.4 完全排除了透明

難的是含有透明這個措辭。ISO 19005-2 附錄 A 是規範性的,從頁面向外測試;全檔統計 /SMask 鍵或 /ca 值,回答的是另一個問題。位元組搜尋還會與別處的透明語法相撞:/S /Trans 這個被禁止的轉場動作,是每個 /S /Transparency 群組的前綴,而 /Screen 既是被禁止的註解子類型、又是標準混合模式,所以名稱要配上所屬的鍵才有意義。跟著附錄 A,PDFium Component 在從頁面可抵達下列任一項時,就把該頁視為透明:

  • 內容以 gs 選用、且帶 soft mask 字典、ca 或 CA 低於 1、或混合模式不是 Normal 的 ExtGState;宣告了卻從未選用的不算
  • 以 Do 叫用、其 /Group 帶 /S /Transparency、或自身內容符合上述任一條件的 Form XObject;平鋪圖樣比照表單判定
  • 帶 SMask 串流或 SMaskInData 值大於 0 的影像 XObject
  • 頁面以之顯示文字的 Type 3 字型的字形描述,比照表單判定
  • 註解外觀串流,含每個 widget 狀態,以及註解字典本身上的混合模式

群組也會回頭影響設備色規則,這一點是語料糾正了我們。veraPDF 的 6.2.4.3 套件裡,收著幾份不帶任何 OutputIntent、畫了設備 RGB、卻預期通過的 PDF/A-4 檔案,因為每一頁的 /Group 都指名了已校準的 CS。我們原本以為只有隔離的群組能校準,因為非隔離群組會合成進它的背景——這對頁面群組是錯的:Table 147 註明,直接施加到輸出介質上的頁面,不論 I 為何都實質隔離,所以它的 CS 照樣被採納。在 Form XObject 或註解外觀上,這個旗標就真的說了算;套件裡有一對僅差 /I true 的通過與失敗檔案。DeviceGray 不從群組取得校準,因為 6.2.4.3 要的是 DefaultGray 或意圖,而 CS 為設備空間的群組自己都需要校準。因此 PDFium Component 讓頁面群組不論 /I 為何都能校準,讓表單或外觀群組只在隔離時校準,把灰排除在群組校準之外,同時照樣檢查群組指名的設備空間

Delphi 的 PDF/A 矩陣:透明頁面何時需要自己的混合 CS,以及 PDFium Component 如何判定頁面、表單與外觀群組的校準
輸出意圖就是預設混合空間;頁面群組不論隔離旗標為何都能校準設備色,表單群組則需要 /I true

用一個 OutputIntent 取代每頁的群組

如果寫出檔案的是你自己的程式,先從文件級的 PDF/A OutputIntent 做起。一個 catalog 條目處處校準 DeviceGray,覆蓋每一頁上匹配的設備空間,並充當每個透明頁的預設混合空間。頁面群組與 Default 條目各自只限一份資源字典,之後合併進來、沒帶這些的頁面就是會失敗的那一頁。PDF 2.0 把 OutputIntents 加到頁面物件上(ISO 32000-2 §7.7.3.3 與 §14.11.5);catalog 沒有時,PDFium Component 在 PDF/A-4 也接受這種頁面層級的意圖,而建構在 PDF 1.4 到 1.7 上的 part 1 到 3 只看 catalog。對 RGB 內容,SaveAsPdfAToStream 會加一個建構在PDF/A 寫入端內嵌的 sRGB ICC profile 上的 catalog 意圖,驗證輸出即可看出內容是否真的只有 RGB

uses
  System.SysUtils, System.Classes, PDFium, FPdfPdfa;

function ArchiveWithSrgbIntent(const InFile, OutFile: string;
  out Remaining: TPdfAValidationIssues): Boolean;
var
  Pdf: TPdf;
  Opts: TPdfASaveOptions;
  Output: TMemoryStream;
  Check: TPdfAValidationResult;
begin
  Result := False;
  Remaining := [];
  Pdf := TPdf.Create(nil);
  Output := TMemoryStream.Create;
  try
    Pdf.FileName := InFile;
    Pdf.Active := True;
    if not Pdf.Active then
      Exit;                           // 檔案開不起來
    Opts := TPdfASaveOptions.Default;
    Opts.Conformance := pac2b;        // PDF/A-1 會直接禁止這種透明
    // IccProfileData 留空:使用內建的 sRGB IEC61966-2.1 profile
    if not Pdf.SaveAsPdfAToStream(Output, Opts) then
      Exit;
    Check := ValidatePdfACompliance(Output);
    Remaining := Check.Issues;
    // 純 RGB 內容的 Expected:pvaiMissingOutputIntent 與
    // pvaiBlendSpaceUndefined 都消失。pvaiMixedDeviceColorSpaces 此時代表
    // 有可抵達路徑畫了沒有 DefaultCMYK 校準的 DeviceCMYK。
    if Check.IsCompliant then
    begin
      Output.SaveToFile(OutFile);
      Result := True;
    end;
  finally
    Output.Free;
    Pdf.Free;
  end;
end;

撐過 sRGB 意圖還在的 pvaiMixedDeviceColorSpaces,是走訪在可抵達路徑上回報了 DeviceCMYK;在 PDFium Component 肯把它當 PDF/A-2b 接受之前,這份文件得在畫它的頁面上補 DefaultCMYK 映射,或補一個 CMYK 輸出 profile