註解(annotation)不是頁面內容。當您呼叫 TextOut 或繪製矩形時,這些標記會成為頁面內容串流的一部分,嵌入到轉譯器(renderer)所繪製的位元組中。註解是一個獨立的字典,透過其 /Annots 陣列掛在頁面上,並擁有自己的矩形、外觀和生命週期。閱讀器可以開啟、移動、隱藏或剝離它,而無需觸碰底層頁面的任何字形。這種分離是註解存在的全部原因,也是最先讓人感到驚訝的兩個問題的根源:註解會落在哪裡,以及一旦特定的檢視器獲取它之後它看起來會像什麼
HotPDF 透過頁面物件上的一系列 AddXxxAnnotation 呼叫,顯露了 ISO 32000 註解子類型。它們都共用相同的形式:在 PDF 使用者空間中將註解固定在頁面上的矩形、某些負載(文字、印章名稱、一對點)以及顏色。設定好矩形,大部分的工作就完成了。其餘的工作就是瞭解哪些子類型自帶外觀,以及哪些子類型依賴檢視器來繪製它們

矩形是註解本身,而非文字
每個註解呼叫都接收一個 TRect,該矩形的含義與您傳遞給 TextOut 的座標不同。對於文字筆記,它是可點選的熱區(hotspot),即筆記圖示所在的小區域,點選它會彈出註解。對於正方形或自由文字方塊,它是標記的可見範圍。對於印章,它是印章圖案縮放至其中的方框。這些數字是 PDF 使用者空間的點數,從頁面左下角量測且 Y 軸向上增加,這與 HotPDF 其他部分所使用的約定相同
文字筆記是最輕量級的子類型。您為其提供正文文字、圖示矩形、是否預設開啟的旗標、圖示名稱以及顏色
Pdf.CurrentPage.AddTextAnnotation(
'Reviewer: confirm the totals on this line before sign-off.',
Rect(120, 700, 140, 720), // icon hotspot, ~20pt square
False, // closed until the reader clicks it
taComment, // bubble icon
clBlue);
此處的矩形刻意設得較小(每邊大約 20 點),因為在有人點選它之前,文字筆記僅僅是一個圖示。將矩形設大並不會得到一個巨大的筆記;您只會得到一個過大的點選目標,而圖示被固定在某一個角上。Open 旗標控制當文件載入時是否顯示彈出內容。如果將幾個筆記設定為 True,它們會相互堆疊並覆蓋在內容之上,因此請將此設定保留給您確實希望讀者立即看到的那個筆記
圖示名稱來自 THPDFTextAnnotationType,它對應到標準的筆記圖示:taComment、taKey、taNote、taHelp、taParagraph、taNewParagraph 和 taInsert。類型僅改變圖示。它不會改變行為,而且值得注意的是,並非每個檢視器都能繪製全部七個圖示;在舊的和新的閱讀器中都安全可用的是 taComment、taNote 和 taHelp
自由文字書寫在頁面上,但仍保持為註解
自由文字註解看起來像內容,因為文字在不需要點選的情況下也是可見的,像說明文字(caption)一樣位於其矩形中。但它仍然是一個註解,帶有所暗示的全部可分離性,這正是您對於日後應該允許被移除的審查印章或草稿標籤所需要的。其簽名將圖示和開啟旗標替換為對齊值(justification)
Pdf.CurrentPage.AddFreeTextAnnotation(
'DRAFT - not for distribution',
Rect(200, 210, 400, 235), // the box the text is laid into
ftCenter, // ftLeftJust / ftCenter / ftRightJust
clRed);
此處的矩形比文字筆記的矩形更重要,因為文字會在其中換行和對齊。若方框高度太短,文字會在底邊被裁剪;太窄文字會在您不打算換行的地方換行。對齊方式來自 THPDFFreeTextAnnotationJust,且只有三個值。因為自由文字是一種標記註解(markup annotation),在編輯器中開啟檔案的讀者可以將其作為一個單元進行選取、移動或刪除,這就是決定您是選用自由文字還是僅使用 TextOut 繪製文字的關鍵區別。如果標籤必須是永久性的,請繪製它。如果是編輯性的且打算移除,請將其設為註解
用於指向物件的幾何與線條標記
正方形、圓形和線條是您用來指向某個區域,而不是用文字描述它的標記。AddCircleSquareAnnotation 透過 csCircle 或 csSquare 的 THPDFCSAnnotationType 涵蓋了這兩種外框形狀,並由矩形給出該形狀的邊界
// A box drawn around a figure that needs attention
Pdf.CurrentPage.AddCircleSquareAnnotation(
'Check this region against the source data',
Rect(50, 300, 120, 360),
csSquare,
clGreen);
// A line, given two points rather than a rectangle
var
StartPt, EndPt: THPDFCurrPoint;
begin
StartPt.X := 130; StartPt.Y := 360;
EndPt.X := 250; EndPt.Y := 320;
Pdf.CurrentPage.AddLineAnnotation(
'Points from the note to the figure',
StartPt, EndPt,
clBlue);
end;
請注意,線條註解打破了矩形模式:它接收兩個 THPDFCurrPoint 記錄(起點和終點),因為線條是由其端點定義的,而非由邊界框(bounding box)定義。顏色設定筆劃。如果您想要箭頭,HotPDF 具有接受線條結尾樣式的 AddLineAnnotation 多載版本,但單純的三引數形式會繪製一條裸線,這通常是圖解標示(callout)所需要的
文字標記子類型在您已經配置好的區域上運作。AddHighlightAnnotation 接收一個矩形、選擇性的內容以及預設為黃色的顏色,並像螢光筆一樣對該區域進行著色。它旨在放置在真實文字之上,因此矩形應與您繪製的字詞邊界相匹配,這意味著您通常需要從傳遞給 TextOut 的相同座標來計算它,而不是靠猜測
印章依賴檢視器來轉譯它們
印章註解是最有可能在不同的閱讀器之間呈現不同外觀的註解,其原因值得瞭解。AddStampAnnotation 透過 THPDFStampAnnotationType 命名標準印章,其值包括 satApproved、satConfidential、satFinal、satDraft 和 satForComment
Pdf.CurrentPage.AddStampAnnotation(
'Approved for release on review',
Rect(50, 400, 200, 440),
satApproved,
clGreen);
印章名稱是一項請求。PDF 定義了標準印章名稱集,但沒有定義其背後的圖案,因此每個檢視器都有自己對「APPROVED」或「CONFIDENTIAL」的轉譯,少數檢視器對於不認識的名稱甚至完全不轉譯任何內容。矩形控制了圖案縮放至其中的方框,而顏色是檢視器可能會或可能不會遵循的提示。如果印章在任何地方都看起來完全相同,最可靠的途徑根本不是標準印章:使用 TextOut 和繪圖呼叫自己繪製該標記,或將其放置為您控制外觀的自由文字註解。只有當您想要檢視器熟悉的樣式且能容忍差異時,才使用標準印章
檔案附件遵循相同的「矩形加負載」形式。AddFileAttachmentAnnotation 接收說明、要內嵌的檔案路徑、迴紋針圖示的矩形以及顏色。檔案封裝在 PDF 內部,而圖示是讀者用來擷取該檔案的控制代碼
註解與 AcroForm 欄位有何不同
花費最多時間的混淆是將註解視為表單欄位。兩者都透過 /Annots 附加到頁面上,且表單欄位實際上是一個特殊的註解子類型(widget),這就是為什麼它們看起來有些關聯。它們是不可互換的。表單欄位保存一個值、擁有一個名稱、參與定位鍵順序(tab order),且可以被提交、重設或撰寫指令碼;您是使用 AddTextField、AddCheckBox 和 AddPushButton 呼叫來建立這些欄位,而不是使用本頁面上的註解呼叫。標記註解保存一項評語(comment)或一個形狀,沒有可提交的值,並且在您需要收集輸入時是錯誤的工具
實際測試非常簡單。如果使用者需要輸入、選擇或點選,並讓文件記住它,您需要的是 AcroForm 欄位。如果您只是在留下一筆筆記、標記一個區域或蓋上一個隨檔案同行而非資料的狀態印章,您需要的是註解。將它們混淆會產生外觀正確但行為錯誤的文件:沒有人能填寫的「欄位」,或者在表單重設時消失的註解。與欄位類型、驗證 and 提交動作相關的互動方面是其專屬主題,在AcroForm 欄位與動作逐步指南中有所討論
組合頁面
這些部分與 HotPDF 的其他部分一樣進行組合。設定文件屬性、呼叫 BeginDoc、使用文字和圖形呼叫繪製所需的任何頁面內容、在上方加入註解,並以 EndDoc 關閉。註解附加到 CurrentPage 上,因此在 AddPage 之後,它們會降落在新頁面上,如果您在分頁後加入,本意要在第一頁顯示的筆記將會默默地出現在第二頁上
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'annotated.pdf';
Pdf.Compression := cmFlateDecode;
Pdf.FontEmbedding := True;
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(50, 740, 0, 'Quarterly figures, draft for review');
Pdf.CurrentPage.AddTextAnnotation(
'Confirm the totals before sign-off.',
Rect(50, 720, 70, 740), False, taComment, clBlue);
Pdf.CurrentPage.AddFreeTextAnnotation(
'DRAFT', Rect(450, 720, 540, 745), ftCenter, clRed);
Pdf.CurrentPage.AddStampAnnotation(
'For comment', Rect(50, 660, 180, 695), satForComment, clGreen);
Pdf.EndDoc;
finally
Pdf.Free;
end;
當輸出看起來不正確時,值得建立的最後一個直覺:在判定程式碼損壞之前,先在多個檢視器中開啟該檔案。印章和較罕見的筆記圖示往往是罪魁禍首,且因為註解是對閱讀器的請求,而不是繪製好的像素,因此 Acrobat 與輕量級檢視器之間的差異通常是規格正常運作的結果,而非您呼叫中的 Bug
這裡顯示的註解呼叫均屬於適用於 Delphi 和 C++Builder 的 HotPDF 元件的一部分