itext7學習筆記——第4章

前言

itext4-1

    在之前的章節中,我們創建了PDF文件,並往裏面添加了內容。不管我們使用的是高級api(例如Paragraph)或者低級api(例如lineTO(),MoveTo,stroke()),iText會把這些api轉換成pdf的語法,這些pdf語法會被寫入內容流(content stream)。在本章,我們會介紹一種不同特性的內容————註解(如上圖1)。註解並不屬於內容流(content stream),他們通常被放在已經存在內容的上面。註解的種類有很多,大多數的註解可以允許用戶交互。

添加註解

    我們從簡單的例子開始講起,如下圖1,我們首先添加了一個Paragraph類型的文本,然後在這文本之前添加了綠色的註釋。

itext4-2

圖1. 一個文本註釋

    這個例子的大部分代碼都和第一章的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,我們創建了一個可視的註釋,如果你鼠標停留在附近的話就會顯示一個原始網站,我們可以通過點擊這個文本來打開這個鏈接,這就是鏈接註釋。

itext4-3

圖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是線註釋:

itext4-4

圖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:

itext4-5

圖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技術應用於以下兩種應用場景:

  1. 當表單相當於數字紙(digital paper):在某些情況下,對錶單有嚴格的形式要求。重要的是數字文檔是相應表單的精確副本。填寫的每個表單都需要符合相同的正式要求。如果是這種情況,那麼使用PDF格式比HTML表格更好。
  2. 當表單不用於數據收集,但作爲模板:例如:您有一個表單代表一個優惠券或一個活動的入場券。在這個表單上,你有不同的字段,例如誰買的票,事件的日期和時間,和座位號等等。當人們買票時,您不需要重新生成完整的憑證,您可以使用表單,只需填寫適當的數據。

    在這兩種應用場景中,我們可以通過Abode軟件、LibreOffice和其他一些其他攻擊的圖形界面來手動創建表單。
    您也可以以編程方式創建一個這樣的表單,但是很少有用例可以使用軟件庫來創建表單或模板,而不是使用帶GUI的工具。不過,我們要試一試。先看下圖5:

itext4-6

圖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);

複選框

    在下面的代碼段中,我們會引入三個複選按鈕,名稱爲experience0experience1experience2:

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:

itext4-7

圖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的方式不太一樣,在這裏我們同時接收readerwriter對象爲參數,然後我們使用getAcroForm()獲得PdfAcroForm對象,其餘操作就和上面一樣了。

在這裏表單仍然是互動的:人們仍然根據需要改變相應的值。iText已被用於許多應用程序中以預填寫表單。例如:當用戶登錄登錄在線服務器是,服務器端已經知道很多信息(例如姓名,地址,電話號碼)。當他們需要在線填寫表單時,向他們提供一個空白的文件沒有多大意義,他們必須再次填寫他們的姓名,地址和電話號碼。如果這些值已經存在於表單中,則可以節省大量時間。這可以通過用iText預填寫表單來實現。人們可以檢查信息是否正確,如果不是(例如因爲他們的電話號碼被改變),他們現場仍然可以改變其內容

    有的時候我們不想讓終端用戶改變PDF裏面的內容,如果表單是具有特定日期和時間的憑單,則不希望最終用戶更改該日期和時間。在這種情況下,表單就會鎖定

鎖定表單

    我們在之前的代碼裏面,添加一個語句,之前的"This file includes fillable form fields"消失了,當你點擊Full name後就不能手動改變裏面的值,如下圖7:

itext4-8

圖7. 一個鎖定的表單

    代碼如下,在第12行加入form.flattenFields(),所有字段會被鎖定,對應的小窗口部件註釋將會被內容所替代。

總結

    在本章我們介紹了很多種類的註釋

  • 文本註釋
  • 線註釋
  • 標記註釋(markup annoation)
  • 小窗口部件註釋(widget annoation)

    在填充和鎖定表單的例子中,我們引入了PdfReader這個類,在後面的章節中,我們會繼續討論這個類。

上週有的事暫時沒更新,今天補上,希望大家能繼續關注我這系列的文章~ 謝謝大家的支持,給個贊喲

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章