前言
在前面的章節中我們知道一個PdfStamper對象只能對應一個PdfReader對象。因此當我們將多個文檔組裝或者編輯時就需要用到另一個類:PdfCopy。PdfCopy繼承PdfWriter,因此在五步創建文檔過程中可以用PdfCopy代替PdfWriter,就如以下代碼所示:
listing 6.20 SelectPages.cs
int n = reader.NumberOfPages; Document document = new Document(); PdfCopy copy = new PdfCopy(document, new FileStream(result2, FileMode.Create)); document.Open(); for (int i = 1; i <= n; i++) { PdfImportedPage page = copy.GetImportedPage(reader, i); copy.AddPage(page); } document.Close();
PdfCopy和PdfWriter有很大的區別:使用PdfCopy類添加內容只能通過AddPage方法。以上代碼中我們只操作了一個文檔,接下來我們在此基礎上擴展並將兩個文檔連接起來。
Concatenating and splitting PDF documents
在第二節時我們創建一個包含了鏈接的電影列表文檔,然后還自動創建了這些電影的歷史信息,現在我們將這兩個文檔連接起來:
listing 6.21 Concatenate.cs
string[] files = { movieLink1, movieHistory }; // step 1 Document document = new Document(); using (document) { // step 2 PdfCopy copy = new PdfCopy(document, new FileStream(fileName, FileMode.Create)); // step 3 document.Open(); // step 4 PdfReader reader; int n; for (int i = 0; i < files.Length; i++) { reader = new PdfReader(files[i]); n = reader.NumberOfPages; for (int page = 0; page < n; ) { copy.AddPage(copy.GetImportedPage(reader, ++page)); } } }
movieLink1有34頁,movieHistory有26頁,最后連接的文檔一共有60頁。PdfCopy的AddPage還有一個重載的方法:
public void AddPage(Rectangle rect, int rotation);
通過以上方法會添加一個空的頁面,或者添加一個PdfImportedPage對象。
PRESERVATION OF INTERACTIVE FEATURES
在前面的列子我們使用已經使用PdfWriter和PdfStamper類獲取PdfImportedPage對象,然后將其旋轉,縮放等一系列操作。但通過PdfCopy獲取的PdfImportedPage類有很大的區別:我們只能按照其以前的大小添加新的文檔中。這是一個很大的限制但也伴隨一個很大的優點:頁面大部分交互的特性也被保存了。如果使用PdfWriter或者PdfStamper那么movieLink1中鏈接就會全部lost,但使用PdfCopy則完好無損。鏈接是一種特殊的注釋(annotation),我們在第7節會有詳細說明,這里大家知道通過PdfCopy鏈接還可以保存即可。但movieHistory中的書簽全部不見了,我們會在下一節中解決這個問題。
ADDING CONTENT WITH PDFCOPY
在前面的章節中我們知道PdfImportedPage是PdfTemplate的一個只讀子類,我們不能在其中添加內容,但我們也學習了如何通過PdfWriter和PdfStamper在導出頁面的上面或者下面添加內容。使用PdfCopy也有類似的功能:
listing 6.22 ConcatenateStamp.cs
// step 1 Document document = new Document(); using (document) { // step 2 PdfCopy copy = new PdfCopy(document, new FileStream(fileName, FileMode.Create)); // step 3 document.Open(); // step 4 // reader for document 1 PdfReader reader1 = new PdfReader(movieLink1); int n1 = reader1.NumberOfPages; // reader for document 2 PdfReader reader2 = new PdfReader(movieHistory); int n2 = reader2.NumberOfPages; // initializations PdfImportedPage page; PdfCopy.PageStamp stamp; // Loop over the pages of document 1 for (int i = 0; i < n1; ) { page = copy.GetImportedPage(reader1, ++i); stamp = copy.CreatePageStamp(page); ColumnText.ShowTextAligned(stamp.GetUnderContent(), Element.ALIGN_CENTER, new Phrase(string.Format("page {0} of {1}", i, n1 + n2)), 297.5f, 28, 0); stamp.AlterContents(); copy.AddPage(page); } // Loop over the pages of document 2 for (int i = 0; i < n2; ) { page = copy.GetImportedPage(reader2, ++i); stamp = copy.CreatePageStamp(page); ColumnText.ShowTextAligned(stamp.GetUnderContent(), Element.ALIGN_CENTER, new Phrase(string.Format("page {0} of {1}", i + n1, n1 + n2)), 297.5f, 28, 0); stamp.AlterContents(); copy.AddPage(page); }
在以上代碼中我們通過PdfCopy.PageStamp對象將內容添加到PdfImportedPage中,而PdfCopy.PageStamp對象則通過PdfCopy的CreatePageStamp方法獲取,其他的就和PdfWriter和PdfStamper的操作一致,只是最好要格外添加AlterContent方法。
SPLITTING A PDF
PdfCopy和PdfStamper不一樣,PdfCopy可以重復使用同一個PdfReader類。在第三節中我們構建了一個Timeable的pdf文檔,現在我們會將每一頁分成單獨的一個文檔,在Pdf的技術中這個又叫做PDF bursting,具體的如下代碼:
listing 6.23 Burst.cs
// Create a reader PdfReader reader = new PdfReader(movieTemplates); // We'll create as many new PDFs as there are pages Document document; PdfCopy copy; int n = reader.NumberOfPages; // loop over all the pages in the original PDF for (int i = 0; i < n; ) { // step 1 document = new Document(); using (document) { // step 2 string fileName = string.Format(result, ++i); copy = new PdfCopy(document, new FileStream(fileName, FileMode.Create)); // step 3 document.Open(); // step 4 copy.AddPage(copy.GetImportedPage(reader, i)); } }
原始的timeable文檔一共有8頁,大小大概15kb,分成單個文檔之后每個文檔大小大概4kb,4*8=32kb比原始的文檔要大很多,主要原因是:在原始文檔中我們可以共用一些資源,但分開之后這些共用的資源被單獨的copy的每一份中。這是大家可以會想如果我們將有大量重復內容的文檔連接起來會有什么樣的結果?
PdfCopy versus PdfSmartCopy
在前一節中我們填充了一些pdf表單,但生產了大量的文檔,現在我們會將其連接成一個文檔:
listing 6.24 DataSheets1.cs
public void CreateFile(string fileName) { // step 1 Document document = new Document(); using (document) { // step 2 PdfCopy copy = new PdfCopy(document, new FileStream(fileName, FileMode.Create)); // step 3 document.Open(); // step 4 AddDataSheets(copy); } }
public void AddDataSheets(PdfCopy copy) { string connStr = ConfigurationManager.AppSettings["SQLiteConnStr"]; SQLiteConnection conn = new SQLiteConnection(connStr); using (conn) { conn.Open(); List<Movie> movies = PojoFactory.GetMovies(conn); PdfReader reader; PdfStamper stamper; MemoryStream ms; // Loop over all the movies and fill out the data sheet foreach (Movie movie in movies) { reader = new PdfReader(datasheet); ms = new MemoryStream(); stamper = new PdfStamper(reader, ms); Fill(stamper.AcroFields, movie); stamper.FormFlattening = true; stamper.Close(); reader = new PdfReader(ms.ToArray()); copy.AddPage(copy.GetImportedPage(reader, 1)); } } }
以上代碼中我們使用PdfStamper來填充表單,然后在通過PdfCopy將頁面導出並最終構建一個文檔。
最后生成的文檔如果打開看的話好像挺完美,但如果你仔細觀察文檔的大小:原始的datasheet.pdf只有60kb,但我們最終的文檔大概有5MB。最終的文檔有120頁,每一頁基本上是相同的,只不過文檔上有不同的信息,但使用PdfCopy時其會將每一頁所必需的資源全部copy過去。這顯然大大增加了文檔的大小,要解決這個問題,可以通過PdfSmartCopy類來代替PdfCopy類:
listing 6.25 DataSheets2.cs
Document document = new Document(); using (document) { PdfSmartCopy copy = new PdfSmartCopy(document, new FileStream(fileName, FileMode.Create)); document.Open(); AddDataSheets(copy); }
通過以上代碼生成的文檔大小只有300KB,對比之下還有有很大的提升。PdfSmartCopy類繼承PdfCopy,但其會仔細檢查每一頁看是否有相同的內容,這會減少最終文檔的大小但由於要格外的檢查因此要耗費更多的cpu和內存,所以大家可以權衡這兩個類自己選擇:要文檔的大小還是更多考慮cpu和內存消耗。如果文檔之間沒什么太多的相同點那使用PdfCopy是個好選擇,但如果文檔都有相同的logo或者水印那么使用PdfSmartCopy去檢查logo就應該是我們的選擇。在這個列子我們連接已經flatter的表單,但如果我們希望連接原始的表單呢?我們不需要嘗試因此那是不work的,雖然PdfCopy和PdfSmartCopy將一些表單交互的特性保留了,但如果連接多個表單那么表單的功能就會失效,所以我們最后的選擇是使用PdfCopyFields類。
Concatenating forms
假設我們希望創建一個有兩頁或者多頁的film data form,這很容易實現,只要一下四行代碼即可:
listing 6.26 ConcatenateForms1.cs
PdfCopyFields copy = new PdfCopyFields(new FileStream(fileName, FileMode.Create)); copy.AddDocument(new PdfReader(datasheet)); copy.AddDocument(new PdfReader(datasheet)); copy.Close();
通過以上代碼現在的表單有兩頁,但估計不是我們想要的效果:我們希望在第一頁的表單中輸入信息然后在第二頁表單中輸入其他的信息,這在以上代碼生成的文檔是無法實現,如果你在第一頁輸入一些信息你會發現輸入的信息在第二頁相同的字段中也出現了。因為在表單中每一個字段都應該有單獨的一個名稱,因為我們直接連接了兩個文檔,因此其字段都有兩個,輸入的信息也會影響兩個字段。所以我們在連接表單時要對字段重命名一下:
listing 6.27 ConcatenateForms2.cs
private byte[] RenameFiledsIn(string datasheet, int i) { MemoryStream ms = new MemoryStream(); // Create the stamper PdfStamper stamper = new PdfStamper(new PdfReader(datasheet), ms); // Get the fields AcroFields form = stamper.AcroFields; // Loop over the fields List<string> fields = new List<string>(form.Fields.Keys); foreach (string key in fields) { // rename the fields form.RenameField(key, string.Format("{0}{1}", key, i)); } stamper.Close(); return ms.ToArray(); }
通過以上代碼的重命名,每一個字段就有自己單獨的名稱,效果也就OK了。
總結
到這里第六節也就全部結束了,對應pdf文檔的操作介紹了很多不同的類,每個類有自己不同的方法和缺點,以下的table是一個總結:
| PdfReader | 讀取pdf文檔的類,我們可以將其作為參數傳給其他操作pdf的類。 |
| PdfImportedPage | PdfTemplate的只讀子類,其代表一個導出頁面,一般通過GetImportedPage方法獲取。 |
| PdfWriter | 從頭創建文檔的一個類,也可以從其他文檔導出頁面,最大的缺點:導出頁面所有的交互特性(annotation,bookmark,field etct)全部lost。 |
| PdfStamper | 只能操作一個文檔,而且可以為文檔添加內容或者格外的頁面還可以填充表單。所有交互特性被保留除非我們顯示將其移除。 |
| PdfCopy | 能夠從現有的多個文檔導出頁面,最大的缺點:不能檢測到多余的內容,而且不能連接表單。 |
| PdfSmartCopy | 也能夠從現有多個文檔中導出頁面但可以檢查多余的內容,但相應會消耗更多的cpu和內存。 |
| PdfCopyFields | 能夠消除連接表單而產生的一些問題,但同樣也會消耗更多的內存。 |
在接下來的章節中我們主要使用PdfStamper對象,然后會介紹annotation的概念並知道表單字段是一種特殊的annotation,最后就是這一節的代碼下載。
同步
此文章已同步到目錄索引:iText in Action 2nd 讀書筆記。
