前言
在之前的章節中,我們創建了PDF文件,並往裏面添加了內容。不管我們使用的是高級api(例如Paragraph
)或者低級api(例如lineTO()
,MoveTo
,stroke()
),iText會把這些api轉換成pdf的語法,這些pdf語法會被寫入內容流(content stream)。在本章,我們會介紹一種不同特性的內容————註解(如上圖1)。註解並不屬於內容流(content stream),他們通常被放在已經存在內容的上面。註解的種類有很多,大多數的註解可以允許用戶交互。
添加註解
我們從簡單的例子開始講起,如下圖1,我們首先添加了一個Paragraph
類型的文本,然後在這文本之前添加了綠色的註釋。
這個例子的大部分代碼都和第一章的HelloWorld的例子完全一樣,多了的是創建和添加註釋:
PdfAnnotation ann = new PdfTextAnnotation(new Rectangle(20, 800, 0, 0))
.setColor(Color.GREEN)
.setTitle(new PdfString("iText"))
.setContents("With iText, "
+ "you can truly take your documentation needs to the next level.")
.setOpen(true);
pdf.getFirstPage().addAnnotation(ann);
我們通過定義一個Rectangle
的方式來定義文本註釋的位置,然後設置顏色、註釋標題(PdfString
對象)、內容(String
對象)和註釋打開選項,最後通過PdfDocument
對象來獲得第一頁對象然後添加註釋。
這個例子過後,我們來看看下面這個例子,如下圖2,我們創建了一個可視的註釋,如果你鼠標停留在附近的話就會顯示一個原始網站,我們可以通過點擊這個文本來打開這個鏈接,這就是鏈接註釋。
因爲註釋是所在位置是句子的一部分,如果我們需要計算here在句子中的位置的話,這是極其不方便。幸運的是,我們可以把鏈接註釋包裹在一個Link
對象中,iText會自動計算註釋的Rectangle
,代碼如下:
PdfLinkAnnotation annotation = new PdfLinkAnnotation(new Rectangle(0, 0))
.setAction(PdfAction.createURI("http://itextpdf.com/"));
Link link = new Link("here", annotation);
Paragraph p = new Paragraph("The example of link annotation. Click ")
.add(link.setUnderline())
.add(" to learn more...");
document.add(p);
在第2行,我們創建了一個URI,這個URI可以打開iText的官網。我們把這個action當做是鏈接註釋的參數,然後創建了一個link
對象:這個一個接受一個鏈接註釋對象爲參數的基礎繪畫對象。這個鏈接註釋不會被加入內容流中,因爲註釋不屬於內容流! 相反的是,鏈接註釋會被放在特定頁的特定位置上。使文本可以點擊不會改變內容流裏面的文本的外表,我們在here下面加下劃線能讓我們知道在哪點擊鏈接。
不同種類的註釋接受它自己定義的參數,如下圖3是線註釋:
下面代碼展示瞭如何變成的過程:
PdfDocument pdf = new PdfDocument(new PdfWriter(dest));
PdfPage page = pdf.addNewPage();
PdfArray lineEndings = new PdfArray();
lineEndings.add(new PdfName("Diamond"));
lineEndings.add(new PdfName("Diamond"));
PdfAnnotation annotation = new PdfLineAnnotation(
new Rectangle(0, 0),
new float[]{20, 790, page.getPageSize().getWidth() - 20, 790})
.setLineEndingStyles((lineEndings))
.setContentsAsCaption(true)
.setTitle(new PdfString("iText"))
.setContents("The example of line annotation")
.setColor(Color.BLUE);
page.addAnnotation(annotation);
pdf.close();
在這個例子中,我們把註釋添加到了新創的頁面中,在這邊我們沒有創建一個Document
對象。
ISO-32000-2定義了28中不同的註釋,有2種在PDF2.0中被棄用,你可以使用剩餘26種註釋,限於篇幅限制,剩下不同種類的註釋就不再一一給出了,我們等等把注意力移到交互型的註釋,先看一下如下圖4:
這個TextMarkupAnnotation例子,代碼如下,我們可能需要專門的章節來講一下代碼(外國人埋坑能力超強)
PdfAnnotation ann = PdfTextMarkupAnnotation.createHighLight(
new Rectangle(105, 790, 64, 10),
new float[]{169, 790, 105, 790, 169, 800, 105, 800})
.setColor(Color.YELLOW)
.setTitle(new PdfString("Hello!"))
.setContents(new PdfString("I'm a popup."))
.setTitle(new PdfString("iText"))
.setOpen(true)
.setRectangle(new PdfArray(new float[]{100, 600, 200, 100}));
pdf.getFirstPage().addAnnotation(ann);
在下一節中,我們將創建一個包含不同表單字段的交互式表單。該表單中的每個表單字段將與窗口小部件註釋(widget annotation)對應,但這些註釋將被隱式創建。
創建交互式表單
下一個例子中,我們使用AcroForm技術來創建交互式表單。AcroForm這個技術是在PDF1.2(1992)中首次提及的技術,這個技術可以在PDF文檔中填充各種表單字段,例如文本域、選擇框(組合框或者列表等)、按鈕(下壓按鈕、複選框和單選按鈕等)和簽名域
將PDF表單與HTML中的表單進行比較是很誘人的,但這是錯誤的。當文本長度超過HTML表單的可用文本區域時,這個文本區域可以調整大小。可以基於對服務器的查詢,即時更新列表字段的內容。簡而言之,HTML表單可以非常動態。
但是對於AcroForm技術的交互式表單,這是不能實現的。這種形式的表單最好與紙張形式進行比較,每種字段都有其固定位置和固定尺寸。多年來已經放棄了使用PDF表單在網絡瀏覽器中收集用戶數據的想法。HTML表單對於在線數據收集更加用戶友好。
但是這並不意味着AcroForm技術毫無用處,AcroForm技術應用於以下兩種應用場景:
- 當表單相當於數字紙(digital paper):在某些情況下,對錶單有嚴格的形式要求。重要的是數字文檔是相應表單的精確副本。填寫的每個表單都需要符合相同的正式要求。如果是這種情況,那麼使用PDF格式比HTML表格更好。
- 當表單不用於數據收集,但作爲模板:例如:您有一個表單代表一個優惠券或一個活動的入場券。在這個表單上,你有不同的字段,例如誰買的票,事件的日期和時間,和座位號等等。當人們買票時,您不需要重新生成完整的憑證,您可以使用表單,只需填寫適當的數據。
在這兩種應用場景中,我們可以通過Abode軟件、LibreOffice和其他一些其他攻擊的圖形界面來手動創建表單。
您也可以以編程方式創建一個這樣的表單,但是很少有用例可以使用軟件庫來創建表單或模板,而不是使用帶GUI的工具。不過,我們要試一試。先看下圖5:
上圖中,我們可以看見文本域、單選按鈕、複選框、下拉列表框、多行文本域和一個下壓按鈕。我們看到這些字段,因爲它們由窗口小部件註釋表示。當我們創建一個字段時,這個小部件註釋是隱式創建的。在下面的代碼中,我們先創建另一個PdfAcroForm
對象,第一個參數是PdfDocument
類型的參數,從Document
對象中獲取,第二個參數一個布爾值,表明這個新的表單是否創建,如果沒有已知表單存在的話。因爲我們剛剛創建了Document
的對象,裏面沒有表單,所以我們設置爲True。代碼如下:
PdfAcroForm form = PdfAcroForm.getAcroForm(doc.getPdfDocument(), true);
現在我們可以往裏面添加字段,我們將會使用一個Rectangle
窗口小部件註釋的位置和尺寸。
文本域
我們將從將用於全名(Full Name)的文本字段開始。以下代碼:
PdfTextFormField nameField = PdfTextFormField.createText(
doc.getPdfDocument(), new Rectangle(99, 753, 425, 15), "name", "");
form.addField(nameField);
createText()
方法需要一個PdfDocument
實例、一個Rectangle
、域的名稱、一個默認的值(在這個例子中,默認的值爲一個空的String
)。值得注意的是,文本域的標籤和窗口小部件註釋是不同的。我們使用一個Paragraph
來添加"Full Name"。這個Paragraph
是內容流的一部分。文本域不屬於內容流,它可以用小窗口部件註釋來表示。
單選按鈕
我們創建單選按鈕來選擇語言,值得注意的是,這裏有一個名稱爲language
的radio group,還有五個沒有名稱的按鈕,只有其中一個按鈕會被選中。
PdfButtonFormField group = PdfFormField.createRadioGroup(
doc.getPdfDocument(), "language", "");
PdfFormField.createRadioButton(doc.getPdfDocument(),
new Rectangle(130, 728, 15, 15), group, "English");
PdfFormField.createRadioButton(doc.getPdfDocument(),
new Rectangle(200, 728, 15, 15), group, "French");
PdfFormField.createRadioButton(doc.getPdfDocument(),
new Rectangle(260, 728, 15, 15), group, "German");
PdfFormField.createRadioButton(doc.getPdfDocument(),
new Rectangle(330, 728, 15, 15), group, "Russian");
PdfFormField.createRadioButton(doc.getPdfDocument(),
new Rectangle(400, 728, 15, 15), group, "Spanish");
form.addField(group);
複選框
在下面的代碼段中,我們會引入三個複選按鈕,名稱爲experience0
、experience1
和experience2
:
for (int i = 0; i < 3; i++) {
PdfButtonFormField checkField = PdfFormField.createCheckBox(
doc.getPdfDocument(), new Rectangle(119 + i * 69, 701, 15, 15),
"experience".concat(String.valueOf(i+1)), "Off",
PdfFormField.TYPE_CHECK);
form.addField(checkField);
}
正如大家所見,我們使用createCheckBox()
方法,函數的參數爲:PdfDocument
對象,Rectangle
,check box的名稱,當前的值,選中標記的外觀。
一個check box的值有兩種可能的值:未選中狀態的值必須是"off";選中的值通常是"Yes"(這個值得iText使用的默認值),但是這裏可以使用其他值,看自己的選擇了。
我們可以從列表框或者下拉列表框選擇一個或者多個選項,在PDF術語中,我們稱之爲選擇字段(choice field)。
選擇字段(choice field)
在這裏,我們創建下拉列表框,選擇字段爲的名稱爲"shift"
,並且提供三個選中,其中Any
選項被默認被選中。
String[] options = {"Any", "6.30 am - 2.30 pm", "1.30 pm - 9.30 pm"};
PdfChoiceFormField choiceField = PdfFormField.createComboBox(
doc.getPdfDocument(), new Rectangle(163, 676, 115, 15),
"shift", "Any", options);
form.addField(choiceField);
多行文本框
多行文本框與通常的文本框相比是翔安的,普通文本框
如果添加的內容超出單行能顯示的內容,則此字段中的文本將會只顯示一部分,其餘部分被包裹。
PdfTextFormField infoField = PdfTextFormField.createMultilineText(
doc.getPdfDocument(), new Rectangle(158, 625, 366, 40), "info", "");
form.addField(infoField);
下壓按鈕
在現實的例子中,我們將使用一個提交按鈕,允許人們將他們以表單輸入的數據提交到服務器。這種PDF表格已經變得罕見,因爲HTML演變爲HTML 5和相關技術,引入更的用戶友好的功能來填寫表單。我們通過添加重置按鈕來結束示例,該按鈕將在點擊按鈕時將選定的字段重置爲其初始值。
PdfButtonFormField button = PdfFormField.createPushButton(doc.getPdfDocument(),
new Rectangle(479, 594, 45, 15), "reset", "RESET");
button.setAction(PdfAction.createResetForm(
new String[] {"name", "language", "experience1", "experience2",
"experience3", "shift", "info"}, 0));
form.addField(button);
如果您想使用iText創建一個PDF表單,那麼您現在可以對它的完成情況有一個很好的瞭解。在許多情況下,使用具有圖形用戶界面的工具來手動創建表單是一個更好的主意。然後,您將使用iText自動填寫此表單,例如使用數據庫中的數據。
填充交互式表單
當我們創建完表單,我們可以設置他們的默認的值,如下圖6:
我們一旦創建完表單,我們就可以設置這些字段的值,代碼如下:
Map<String, PdfFormField> fields = form.getFormFields();
fields.get("name").setValue("James Bond");
fields.get("language").setValue("English");
fields.get("experience1").setValue("Off");
fields.get("experience2").setValue("Yes");
fields.get("experience3").setValue("Yes");
fields.get("shift").setValue("Any");
fields.get("info").setValue("I was 38 years old when I became an MI6 agent.");
我們之前都是想PdfAcroForm
對象(form變量)添加了各種各樣的字段,我們通過這個對象來獲得各個字段的Map
,然後我們可以一個一個設置值,當然還有其他更有效的方式,我們這種填充的技術通常應用於預填充一個存在的表單中。
預填充已存在的表單
在這個例子中,我們從一個存在表單的pdf中獲取一個PdfAcroForm
的表單,然後像之前的代碼一樣來進行操作:
PdfDocument pdf = new PdfDocument(
new PdfReader(src), new PdfWriter(dest));
PdfAcroForm form = PdfAcroForm.getAcroForm(pdf, true);
Map<String, PdfFormField> fields = form.getFormFields();
fields.get("name").setValue("James Bond");
fields.get("language").setValue("English");
fields.get("experience1").setValue("Off");
fields.get("experience2").setValue("Yes");
fields.get("experience3").setValue("Yes");
fields.get("shift").setValue("Any");
fields.get("info").setValue("I was 38 years old when I became an MI6 agent.");
pdf.close();
在第二行裏面,PdfReader
一個可以讓iText讀取pdf中不同種類的對象,在這裏src
指向存在的pdf文件路徑。
在iText中,I/O由兩個類來處理:1.
PdfReader
負責輸入 2.PdfWriter
負責輸出
第一行第二行和我們之前創建PdfDocument
的方式不太一樣,在這裏我們同時接收reader
和writer
對象爲參數,然後我們使用getAcroForm()
獲得PdfAcroForm
對象,其餘操作就和上面一樣了。
在這裏表單仍然是互動的:人們仍然根據需要改變相應的值。iText已被用於許多應用程序中以預填寫表單。例如:當用戶登錄登錄在線服務器是,服務器端已經知道很多信息(例如姓名,地址,電話號碼)。當他們需要在線填寫表單時,向他們提供一個空白的文件沒有多大意義,他們必須再次填寫他們的姓名,地址和電話號碼。如果這些值已經存在於表單中,則可以節省大量時間。這可以通過用iText預填寫表單來實現。人們可以檢查信息是否正確,如果不是(例如因爲他們的電話號碼被改變),他們現場仍然可以改變其內容
有的時候我們不想讓終端用戶改變PDF裏面的內容,如果表單是具有特定日期和時間的憑單,則不希望最終用戶更改該日期和時間。在這種情況下,表單就會鎖定
鎖定表單
我們在之前的代碼裏面,添加一個語句,之前的"This file includes fillable form fields"消失了,當你點擊Full name後就不能手動改變裏面的值,如下圖7:
代碼如下,在第12行加入form.flattenFields()
,所有字段會被鎖定,對應的小窗口部件註釋將會被內容所替代。
總結
在本章我們介紹了很多種類的註釋
- 文本註釋
- 線註釋
- 標記註釋(markup annoation)
- 小窗口部件註釋(widget annoation)
在填充和鎖定表單的例子中,我們引入了PdfReader
這個類,在後面的章節中,我們會繼續討論這個類。
上週有的事暫時沒更新,今天補上,希望大家能繼續關注我這系列的文章~ 謝謝大家的支持,給個贊喲