技術文章

以 HotXLS 從 Excel 圖片產生加標籤 PDF 圖形元素

當 HotXLS 以啟用自動加標籤的方式把工作表匯出為 PDF,攜帶替代文字的工作表圖片現在會以獨立的 /Figure 結構元素發出,帶 Unicode /Alt 項、頁面局部的密集標記內容識別碼與精確的父樹項。沒有替代文字的圖片維持裝飾性 artifact,圖表也維持 artifact。這個精確範圍很重要:它讓有資訊的圖片可被螢幕閱讀器觸及,而它不等於完整的 PDF/UA 符合

它背後的機制比功能描述更有意思,因為其中兩個屬於那種會悄悄產出結構有效、結構卻指向錯誤內容的 PDF 的細節

什麼算有資訊的圖片?

只有非空的 AltTextTXLSXImage.AltText 屬性來回轉換圖片非視覺屬性中的 OOXML descr 屬性——正是 Excel 儲存使用者在替代文字窗格裡輸入之文字的地方。那是檔案中唯一的訊號,表明作者認為這張圖片承載資訊而非裝飾,所以它是匯出器唯一信任的訊號

兩個擦邊情況刻意不被接受。與描述分開儲存的標題欄位不是替代品:標題是物件的名稱,不是它的文字等價物,把它提升為 /Alt 會產出通過自動檢查、卻向螢幕閱讀器朗讀「Picture 3」的文件。空描述也不是要用佔位符填補的缺口;它意味著圖片維持 artifact,這對 logo 或分隔線是正確結果。圖表目前也維持 artifact,因為圖表的文字等價物是它的資料,從數列合成一段文字是發明而不是擷取

AltText 非空的圖片匯出為帶自己 MCID 的 PDF Figure 結構元素;空描述與圖表維持 artifact
只有 AltText 裡的作者描述代表有資訊的圖片;單獨的標題永遠不會變成替代文字
uses
  lxHandleX, lxPDF;

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  Exporter: TXLSPDFExport;
  I: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('regional-review.xlsx');
    Sheet := Book.Sheets.ByPos[0];

    // 匯出前先稽核:沒有描述的圖片
    // 會以裝飾性 artifact 匯出
    for I := 0 to Sheet.Images.Count - 1 do
      if Sheet.Images[I].AltText = '' then
        Sheet.Images[I].AltText := DescribeImage(Sheet.Images[I].Name);

    Exporter := TXLSPDFExport.Create;
    try
      Exporter.TagMode := xlsPdfTagsAutomatic;
      Exporter.DocumentLanguage := 'en-US';
      Exporter.SaveAsPDF(Sheet, 'regional-review.pdf');
    finally
      Exporter.Free;
    end;
  finally
    Book.Free;
  end;
end;

為什麼頁面需要單一的 MCID 配置器?

因為父樹是以標記內容識別碼為索引的陣列,兩個配置器會產生兩個宣稱同一槽位的項目。加標籤 PDF 以雙向連接內容與結構。內容側,頁面內容串流的一段被攜帶頁內唯一 /MCID 號碼的 BDCEMC 運算子包住。結構側,頁面字典攜帶 /StructParents 鍵,指名文件 /ParentTree 的一列,那一列是陣列,索引 n 處的元素是擁有 MCID n 的結構元素

一個工作表頁面包含表格儲存格,現在還有圖形。如果儲存格標籤器從零開始數識別碼,圖形標籤器也從零開始數,第一個圖形就會宣稱第一個儲存格已擁有的槽位。產出的檔案沒有任何地方畸形到會被剖析器拒絕:結構樹完好,標記內容平衡,驗證器看到的是一份有父樹的文件。螢幕閱讀器得到的,是被朗讀成圖片的表格儲存格,或帶著儲存格文字朗讀的圖片。因此匯出器從兩個標籤器共享的頁面層級計數器配置,並且只在頁面物件編號已知之後才凍結頁面記錄,因為父樹列在它指向的頁面取得身分之前無從寫起

獨立的儲存格與圖形標籤器在父樹槽位零相撞;一個頁面層級 MCID 計數器讓每個標記只對應一個擁有者
相撞的檔案仍能通過結構驗證器;只有螢幕閱讀器的朗讀是錯的

Figure 必須包住整個可見實例

天真的做法是包住呼叫影像 XObject 的 Do 運算子,因為那正是畫出圖片的運算子。這不夠。工作表圖片往往帶著背後的陰影與周圍的剪裁路徑一起繪製,那些標記是可見物件的一部分。留在 /Figure 範圍之外,它們就成了未標記內容,正是結構稽核會標記的那種狀態

所以標記內容範圍在陰影之前開啟、在圖片繪製之後關閉,連剪裁一起涵蓋。在共享正確的地方保留共享:顯示同一圖片負載的兩個儲存格仍參照同一個影像 XObject,因為那是資源層級的最佳化,與語意無關。每個可見實例得到的是自己的 MCID 與自己的結構元素,因為同一 logo 出現在兩處,就是讀者遇到的兩個東西。圖片放置與定位這些物件的 EMU 幾何在圖片幾何文章中涵蓋

BDC 標記在陰影與剪裁之前開啟 Figure 範圍,EMC 在 Do 影像繪製之後關閉,涵蓋整個可見實例
只包住影像運算子會讓陰影與剪裁成為未標記內容;儲存格之間的資源共享保留

工作表頁面上的閱讀順序

閱讀順序是匯出器必須做的決定,因為試算表不像文件那樣有排好的流程。採用的規則穩定且容易解釋:每頁先表格,再按繪製順序排圖形。讀者因此先聽到頁面的表格內容、再聽到它的圖片,而不是圖片以繪圖物件碰巧在檔案中佔據的位置交錯出現

那個順序是按頁而不是按文件,這在會分頁成數十頁的活頁簿上很重要:每頁的結構分支自包含,在頁面之間移動的讀者不會跳回較早的表格。如果您需要控制工作表一開始怎麼分頁,頁面設定與列印範圍的互動在保護與頁面設定文章中描述

這認證什麼,不認證什麼

它認證的是:有資訊的圖片帶著作者提供的描述觸及輔助科技,而且內容到結構的對應是正確的,而不只是存在。它不讓輸出 PDF/UA 符合,那樣描述會是實作無法支撐的宣稱:圖表仍是 artifact,完整的符合性聲明需要稽核每一種結構型別、每一套字型,以及文件中繼資料整體

如果您的需求是歸檔或符合性設定檔而不是無障礙改善,那是另一種匯出設定與另一組檢查,見PDF/A 歸檔匯出文章。兩者可以結合,但它們回答的是不同的稽核者

給報表管線的一個實務建議:在活頁簿產生的地方稽核替代文字,而不是在匯出時。產生器知道每張圖表影像或內嵌圖示代表什麼,可以往 AltText 寫入真實描述;匯出時的檢查只能告訴您缺了描述。HotXLS 從 Delphi 與 C++Builder 原生讀寫 XLS、XLSX、ODS 與 CSV,不依賴 Excel,匯出設定選項列於 HotXLS Delphi spreadsheet component 產品頁