HotPDF 透過兩個作用於目前已載入文件的函式,把 PDF 註記匯出與匯入為 XFDF,也就是 ExportLoadedAnnotationsToXFDF 與 ImportLoadedAnnotationsFromXFDF。XFDF 是標準化為 ISO 19444-1 的 XML 註記交換格式,這一對函式讓 Delphi 或 C++Builder 程式能把自己的註解交給 Acrobat 或第三方審閱工具,再把標註後的成果收回來,全程都不必改寫註記所依附的頁面內容
想像一下這解決的兩個方向。審閱者在 Acrobat 中打開您產生的報表,在一張沒對齊的圖上放一個紅色箭頭,圈起一個算錯的合計,並在頁邊打了一段筆記,接著把註解匯出成一個小小的 XFDF 檔案。或者反過來:註記由您的程式自己產生,而您得把它們送給某個不用 HotPDF 的人。無論哪個方向,註記都以雙方都看得懂的 XML 移動,PDF 頁面則逐位元組維持原樣
FDF 與 XFDF 有什麼差別?
FDF 與 XFDF 用兩種不同的語法承載同一份酬載,而當您要決定把哪種檔案交給另一套工具時,這個區別就要緊了。FDF 是較舊的 Forms Data Format,定義在 PDF 規範本身之中:它使用 PDF 物件語法,所以一份 FDF 檔案看起來像是精簡版的 PDF,需要懂 PDF 的剖析器才讀得動。XFDF 是同一份資料的 XML 表達方式,獨立標準化為 ISO 19444-1,這表示任何平台上的任何 XML 程式庫都能開啟、比對或產生它。兩種格式都能在 <fields> 樹中承載表單欄位值(由 ISO 19444-1 第 6.3 節規範),也都能在 <annots> 樹中承載註記;HotPDF 把這兩項職責拆開,表單資料走 ExportLoadedFormToXFDF,而 ExportLoadedAnnotationsToXFDF 專門保留給 <annots> 這一側。當您要和網頁服務、Java 審閱伺服器或某支指令碼交換註解時,XFDF 正是那個不會逼對方內嵌一套 PDF 剖析器的格式
在 Delphi 中怎麼把 PDF 註記匯出成 XFDF?
HotPDF 匯出註記的方式,是走過已載入文件的每一頁,為每個支援的註記輸出一個 XFDF 元素,並回傳寫出的註記數量。請先載入 PDF,再以目標路徑呼叫 ExportLoadedAnnotationsToXFDF。整數結果就是被序列化的註記數;結果為零或更小,表示什麼都沒匯出、也沒有產生檔案,這正是在告訴您這份文件裡沒有任何屬於支援子類型的註記
var
Pdf: THotPDF;
Written: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('report-reviewed.pdf', '') > 0 then
begin
// 為每一頁上每個支援的註記各寫出一個 XFDF 元素
Written := Pdf.ExportLoadedAnnotationsToXFDF('comments.xfdf');
if Written <= 0 then
ShowMessage('No supported annotations were found');
end;
finally
Pdf.Free;
end;
end;
產出的 XFDF 是樸素、可讀的 XML。HotPDF 會寫出一個位於 ISO 19444-1 命名空間中的 <xfdf> 根節點、一個 <annots> 容器,以及每個註記各一個子節點,並把以零為起點的頁面索引、色彩與幾何寫成屬性或子元素。一條帶黃色填色與開放箭頭的線段,旁邊放一個已填色的多邊形,序列化出來會像這樣
<?xml version="1.0" encoding="UTF-8"?>
<xfdf xmlns="http://ns.adobe.com/xfdf/">
<annots>
<line page="0" start="72,700" end="220,700"
color="#FF0000" interior-color="#FFFF00"
head="OpenArrow" tail="None">
<contents-richtext>Baseline looks off</contents-richtext>
</line>
<polygon page="0" color="#0000FF" interior-color="#CCE5FF">
<vertices>72,120;180,120;180,200;72,200</vertices>
</polygon>
</annots>
</xfdf>
註記子類型如何對映到 XFDF 元素
每個註記子類型都對映到 ISO 19444-1 中一個特定元素,各自帶著自己的幾何慣例,而 HotPDF 遵循那些結構,不自創一套。線段註記帶著 start 與 end 兩個屬性,裝的是兩個端點的座標對,直接取自註記的 L 陣列,而 LE 線端樣式則變成 head 與 tail 屬性。多邊形與折線註記會把點清單移進 <vertices> 子元素,寫成以分號分隔的 x,y 座標對,而不是屬性,因為預期看到子元素的讀取端,會悄悄丟掉藏在其他任何地方的點。墨跡註記可以裝好幾道各自獨立的筆劃,它會巢狀一個 <inklist> 元素,每道筆劃各一個 <gesture> 子節點,所以多筆劃的簽名走完這趟旅程後仍是分開的手勢,而不是併成一團
豐富文字、色彩與框線樣式會跟著幾何一起存活下來。註解的豐富文字本體寫成 <contents-richtext> 子節點;PDF 存在 IC 陣列中的內部填色,也就是圓形、方形、多邊形或線段箭頭裡的塗色,以及遮蔽方塊的填色,會化為 #RRGGBB 形式的 interior-color 屬性;而框線寬度、虛線樣式與雲朵狀框線效果,則對映到 width、dashes、style 與 intensity 屬性,所以一個以雲朵外框標示的引註,在另一端讀起來仍然是雲朵狀。HotPDF 也保留掛在標註註記上的快顯視窗,把快顯子節點的幾何與它的開啟或關閉狀態匯入註記的 Popup 字典,並且帶著文字註記的開啟狀態與審閱狀態,所以一份審閱過的文件留下的不只是形狀,還有審閱者仰賴的流程中繼資料
把 XFDF 匯回已載入的文件
HotPDF 匯入 XFDF 的方式,是剖析 XML,透過 NewLoadedAnnotation 為每個元素建立一個全新的註記,把它接到元素所指名的頁面上,再回傳它加入了多少個註記。這個流程與匯出對稱:載入基底 PDF,以審閱者的檔案呼叫 ImportLoadedAnnotationsFromXFDF,接著儲存已載入的文件,讓新的標註落地。若檔案不存在或 XML 剖析不過,函式會回傳零,已載入的文件則完全不受影響
var
Pdf: THotPDF;
Added: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('report.pdf', '') > 0 then
begin
Added := Pdf.ImportLoadedAnnotationsFromXFDF('comments.xfdf');
if Added > 0 then
Pdf.SaveLoadedDocument('report-annotated.pdf');
end;
finally
Pdf.Free;
end;
end;
由於每個 XFDF 元素都指名自己的頁面索引,即使您接連匯入好幾個檔案,註記仍會落在當初被標註的那些頁面上,這讓您可以放心地把多位審閱者的註解蒐集到同一份已載入的文件上,最後只存檔一次。下面的範例把兩位審閱者合進一份合併副本。若您想在程式碼中直接建立與編輯註記物件,而不是以檔案交換它們,可以參考 HotPDF 如何直接從 Delphi 建立與編輯 PDF 註記物件
var
Pdf: THotPDF;
Total, I: Integer;
Files: array[0..1] of string;
begin
Files[0] := 'alice-comments.xfdf';
Files[1] := 'bob-comments.xfdf';
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('master.pdf', '') > 0 then
begin
Total := 0;
for I := Low(Files) to High(Files) do
Inc(Total, Pdf.ImportLoadedAnnotationsFromXFDF(Files[I]));
if Total > 0 then
Pdf.SaveLoadedDocument('master-merged.pdf');
end;
finally
Pdf.Free;
end;
end;
哪些能乾淨往返,哪些不能
HotPDF 會往返那些 ISO 19444-1 有給位置的註記子類型,其餘則刻意略過,而不是輸出某種會讓讀取端誤解的東西。支援的集合涵蓋了真實審閱工作中的主力標註型別:文字註解、自由文字、線段、方形、圓形、多邊形、折線、四種文字標註型別(螢光標示、底線、刪除線與波浪底線)、圖章、墨跡與插入符號,再加上檔案附件、音效、遮蔽與連結,總共十八種子類型。子類型落在這份清單之外的註記,匯出時會被跳過,而因為它是被跳過而不是被寫成空的,它並不會灌水函式回傳的數量
豐富文字是誠實該講的但書。HotPDF 保留 <contents-richtext> 本體,讓帶樣式的文字與純文字內容都走完這趟旅程,但 XFDF 攜帶的是註解的文字與樣式標記,不是已呈現好的外觀串流,所以接收端的應用程式會用自己的字型與版面重畫那個快顯視窗,而不是重現 HotPDF 的精確像素。請把這趟往返看成忠於內容與意圖,而不是忠於畫面上每一個像素的呈現結果。若您帶樣式的內容住在 XFA 表單資料裡而非註記串流中,規則就不一樣了,HotPDF 如何處理 XFA exData、豐富文字與超連結涵蓋了那條另外的路徑
字元層級的處理比看起來更嚴格,而那正是您要的。HotPDF 寫出文字時會套用 ISO 19444-1 第 5.8.2 節的逸出規則,把 XML 有特殊意義的字元與控制位元組編碼起來,所以一段含有 ampersand、角括號或換行的註解,產生的是任何相容剖析器都接受的良構 XML,而匯入時它會反向套用同一組規則。這就是為什麼一段從試算表貼過來的筆記,連同標點符號都能原封不動回來,而不會把檔案弄壞
註記交換只是已載入文件 API 能做的其中一塊,而且它與其餘部分能互相組合:匯入審閱者的 XFDF、調整頁面或編輯文件中繼資料、把檔案平面化或重新設定權限,接著為下一輪匯出一份全新的 XFDF。這一切都隨適用於 Delphi 與 C++Builder 的標準版 HotPDF Delphi 元件出貨,其參考文件記載了完整的註記子類型涵蓋範圍,以及配套的表單資料 XFDF 函式