背景:
由於工作的原因要處理和打印一些pdf文檔,目前的實現方式是FOP,園子里有這方面的介紹:Pdf 解決方案——fop。但項目中打印的pdf文檔較大,每次用戶打印文檔都要run很長一段時間,因此老大希望將FOP轉換為iTextSharp來處理。iText是java中處理pdf文檔很出名的一個開源類庫,其NET版本的是iTextSharp,大家可以從這里下載源代碼和dll文件,具體使用的時候引用dll即可。
iText是開源的類庫,文檔可以參考其首頁推薦的書:iText In Action 2nd。這書是iText的創建人Bruno寫的,但里面的實例都是java寫的。不過國外還是有人寫了iTextSharp相關的一些文檔,博客園的能人很多,Careyson同學就翻譯了這一系列:使用iTextSharp在Asp.Net中操作PDF系列文章 目錄。
資源下載:
當然了最好的資料還是上面提到的書::iText In Action 2nd,書可以在這里下載。這本書我斷斷續續的看了大部分,也將看過的大部分java代碼轉換為C#版本,於是希望寫一些博客記錄一下。書中自帶的代碼都是命令行模式,為了方便我寫成了一個winform應用程序,以下為代碼的運行界面:
寫的winform沒什么技術含量,大家雙擊右側ListView中某個Item就會打開對應的pdf文檔。本機開發環境為win7 64bit,VS2010,NET 4.0,代碼大家可以點擊這里下載:chapter01 。
代碼的solution如下圖:
整個工程就只有兩個類庫和一個winform,但引用了三個第三方的類庫,這些dll已經放到代碼中的Resource文件夾下。如上圖所示:我為每一節都創建了一個文件夾,大家目前下載的只有chapter01目錄下有類文件,后續我也只需放出對應章節的類文件即可,大家也只要放入到對應的文件夾然后將其加入到工程中即可。
大家要注意的是System.Data.SQLite.dll在win 32bit和win 64bit有不同的版本,如果是32位的機子,大家去 官方網站 找到對應的下載。由於用到了SqLite來存儲數據,Resource文件夾下面還有一個Movie.db3文件,我本機用的是SQLite Expert Professional來查看和編輯里面的數據,下載 點擊這里。
以下為其運行界面:
其中以FILM和FESTIVAL開頭的表是iText In Action書中demo要用到的一些數據,最后一個表iTextSharpExample我存儲的是winform中listview對應的item數據。 iText in Action書中有用到一些resource文件(比如說圖片和xml文件),所以代碼中也需引用resource文件,大家先下載 Source文件,解壓之后可以在SourceCodeiText\itext-book\book\resources文件夾中找到資源文件,然后修改下winform的app.config文件使其引用正確的目錄。
最后在app.config文件中還需配置下SQLiteConnStr節點,對應的value就是上面提到的Movie.db3文件,要注意的是最好配置絕對路徑。以下為我本機的app.config文件內容:
<appSettings> <add key="SQLiteConnStr" value="data source=D:\Movie.db3"/> <add key="ResourceFont" value="D:\MyFolder\SourceCodeiText\itext-book\book\resources\fonts\"/> <add key="ResourcePosters" value="D:\MyFolder\SourceCodeiText\itext-book\book\resources\posters\{0}.jpg"/> <add key="ResourceCalendar" value="D:\MyFolder\SourceCodeiText\itext-book\book\resources\calendar\{0}.jpg"/> <add key="ResourceImage" value="D:\MyFolder\SourceCodeiText\itext-book\book\resources\img\"/> <add key="ResourcePdfs" value="D:\MyFolder\SourceCodeiText\itext-book\book\resources\pdfs\"/> <add key="ResourceTxt" value="D:\MyFolder\SourceCodeiText\itext-book\book\resources\txt\"/> <add key="ResourceJS" value="D:\MyFolder\SourceCodeiText\itext-book\book\resources\js\"/> <add key="ResourceXml" value="D:\MyFolder\SourceCodeiText\itext-book\book\resources\xml\"/> </appSettings>
Creating a PDF document in five steps with iText
好了,說來很多,我們現在就直奔主題。這一節的標題就是五步創建pdf文檔,典型的代碼如下:
HelloWorld.cs
// step 1 Document document = new Document(); using (document) { // step 2 PdfWriter.GetInstance(document, new FileStream(fileName, FileMode.Create)); // step 3 document.Open(); // step 4 document.Add(new Paragraph("Hello World")); //step 5 document.Close(); }
- 第一步:創建Document類的實例
- 第二步:獲取一個PdfWriter的實例
- 第三步:打開文檔
- 第四步:添加內容
- 第五步:關閉文檔
以上五步還是比較容易看懂的,下面就對具體的每一步進行介紹:
Step1:創建Document的實例
Document類我們可以理解為一個容器,因此可以往里面添加一些高層次(high-level)的類。高層次的意思是高度抽象了,里面沒有涉及到pdf語法等相關等操作,和我們平常創建的類類似。高層次的類一般有:Chunk、Pharse、Paragraph等,這一節中我們只使用Paragraph類。那我們先看下Document類的三個構造器:
public Document(); public Document(Rectangle pageSize); public Document(Rectangle pageSize, float marginLeft, float marginRight, float marginTop, float marginBottom);
從形參最長的構造器中可以得知Document要有頁面大小(PageSize)和頁邊距,因此我們也可以使用以下代碼:
HelloWorldNarrow.cs
Rectangle pagesize = new Rectangle(216f, 720f); Document document = new Document(pagesize, 36f, 72f, 108f, 180f);
上面的代碼好懂,要注意的是度量單位,在pdf中度量單位是用戶單位(user unit)。換算的公式是 1英寸=25.4mm=72 user units≈72pt(磅)。老外的計量單位一般都是英寸,大家仔細看上面的代碼,里面的數字都是36的倍數,也就是0.5英寸。但在iText中,默認的度量單位是pt不是user uint。因為pt和user unit基本上是相等的,而且pt也是比較常用的度量單位。不過我們也可以修改他們之間的對應關系,代碼如下:
HelloWorldMaximum.cs
// step 1 // maximum page size Document document = new Document(new Rectangle(14400, 14400)); using (document) { // step 2 PdfWriter writer = PdfWriter.GetInstance(document, new FileStream(fileName, FileMode.Create)); // changes the user unit writer.Userunit = 75000f; // step 3 document.Open(); // step 4 document.Add(new Paragraph("Hello World")); }
通過以上代碼生成的pdf文檔其頁面大小是15000000(200*75000)英寸*15000000英寸,因為1user unit對應了75000pt。
一般來講,我們生成pdf文檔是都會選用一些標准頁面大小。為了方便生成標准頁面大小,iText中提供一個PageSize類,其中包含了大量標准頁面大小,有B0到B10,A0到A10還有美國的標准頁面:LETTER,LEGAL等。因此我們也可以按照以下代碼設置頁面大小:
Document document = new Document(PageSize.LETTER);
在上面創建的頁面大小中,都是長度小余高度的。但也可以創建長度大於高度的文檔,以下為代碼:
HelloWorldLandscape1.cs
// step 1 Document document = new Document(PageSize.LETTER.Rotate());
或者
HelloWorldLandscape2.cs
Document document = new Document(new Rectangle(792, 612));
通過以上兩張方法生成的pdf文檔打開看的話是沒有什么區別。只是在pdf文檔內部,第一種的頁面大小是長度小余高度,但有一個90度的頁面旋轉,而第二種的頁面大小是長度大於高度沒有頁面的旋轉。這些都是一些細微的差別,在操作和處理已經存在的文檔時我們就需要考慮這些細節。
在Document類中還有SetPageSize()方法和SetMargins()方法,我們可以在創建文檔過程中的任意時刻調用這些方法,但這些方法不會影響當前頁面的設置,只會影響到下一頁。
這里要注意的是如果調用的Document的無參構造器創建的頁面大小就是A4,頁邊距全是36pt。但我們還是建議顯示的設置頁面大小和頁邊距。
頁邊距在文檔雙面打印的時候要注意一些細節:如果文檔要裝訂成冊,那么我們就會希望在裝訂的一邊設置大一點的頁邊距,比如說平常的書左邊裝訂,那么第一頁的左邊距要大一點,而第二頁的右邊距要和第一頁的左邊距一樣。總而言之頁邊距要對稱。這些在iText中可以這樣設置:
HelloWorldMirroredMargins.cs
PdfWriter writer = PdfWriter.GetInstance(document, new FileStream(fileName, FileMode.Create)); document.SetPageSize(PageSize.A5); document.SetMargins(36, 72, 108, 180); document.SetMarginMirroring(true);
這樣設置頁面的左邊距和右邊距就對稱了,但還有一些書是在頁面的上部或者下部裝訂,因此就需要頁面的上邊距和下邊距對稱,代碼如下:
HelloWorldMirroredMarginsTop.cs
document.SetMarginMirroringTopBottom(true);
Step2:獲取一個PdfWriter的實例
上面介紹Document類時說過其可以理解為一個容器,我們為其添加一些high-level的對象。但具體負責寫入pdf文檔的是PdfWriter類,一般都是通過一下代碼獲取PdfWriter實例:
PdfWriter.GetInstance(document, new FileStream(fileName, FileMode.Create));
以上的代碼有兩個作用
- 將Document類的實例和PdfWriter實例關聯起來,這樣PdfWriter就會監聽Docuemnt,並將加入到Document類中的high-level對象轉換為相應的pdf語句。
- 告訴PdfWriter實例要將轉換好的pdf語句寫入到那個輸出流。
這里的代碼設置的都是文件流FileStream,但也可以設置為其它的Stream:
HelloWorldMemory.cs
public override void CreateFile(string fileName) { // step 1 Document document = new Document(); MemoryStream ms = new MemoryStream(); using (document) { // step 2 // we'll create the file in memory PdfWriter.GetInstance(document, ms); // step 3 document.Open(); // step 4 document.Add(new Paragraph("Hello World")); } // let's write the file in memory to a file anyway FileStream fs = new FileStream(fileName, FileMode.Create); using (fs) { BinaryWriter w = new BinaryWriter(fs); using (w) { w.Write(ms.ToArray()); } } }
Step3:打開文檔
當文檔被打開時,進行了大量的初始化的設置,其中包括將pdf文件頭信息寫入到輸出流中。下圖就是你創建的第一個pdf文件:hello.pdf用記事本打開的樣子:
第一行看起來應該是這樣:
%PDF-1.4
%âãÏÓ
以上就是pdf文檔的header,Pdf文檔是由header,body,cross-reference table和footer組成,更加詳細的信息可以參考書的chapter13。大家從上也猜到pdf的版本是1.4,現在大部分的pdf文檔基本上都是1.4版本,其它的版本可以這樣設置:
HelloWorldVersion_1_7.cs
// Creating a PDF 1.7 document PdfWriter writer = PdfWriter.GetInstance(document, new FileStream(fileName, FileMode.Create)); writer.PdfVersion = PdfWriter.VERSION_1_7;
Step4:添加內容
以上啰啰嗦嗦的將了很多,其實我們要關注的主要就是添加內容。
添加內容也有兩個方法
- 往Document類中添加high-level的對象
- 直接用PdfWriter實例添加比較low-level的數據。
以下代碼就是一個看起來有點復雜,但可以給大家一個關於iText內部PDF創建進程的大概印象。
HelloWorldDirect.cs
PdfWriter writer = PdfWriter.GetInstance(document, new FileStream(fileName, FileMode.Create)); // step 3 document.Open(); // step 4 PdfContentByte canvas = writer.DirectContent; writer.CompressionLevel = 0; canvas.SaveState(); //q canvas.BeginText(); //BT canvas.MoveText(36, 788); //36 788 Td canvas.SetFontAndSize(BaseFont.CreateFont(), 12); //F1 12 Tf canvas.ShowText("Hello world"); //(Hello world)Tj canvas.EndText(); // ET canvas.RestoreState();//Q
和以前代碼不同的是:我們將生成的PdfWriter實例保存到局部變量writer中。因為后續要通過此writer來獲取canvas畫線畫圖。代碼中通過設置CompressionLevel屬性為0可以避免對輸出流的壓縮,我們也可以通過記事本來查看pdf的語法語句。以上canvas的每個方法后面的注釋對應具體的pdf語法,大家用記事本打開就可以看到。但代碼看起來就比較復雜,而且只是簡單的一個hello world,如果要寫更多的內容,那代碼可能就更加難懂。
因此iText提供了一些便利的方法來調用:
HelloWorldColumn.cs
PdfWriter writer = PdfWriter.GetInstance(document, new FileStream(OnePdfFile, FileMode.Create)); // step 3 document.Open(); // step 4 // we set the compression to 0 so that we can read the PDF syntax writer.CompressionLevel = 0; // writes something to the direct content using a convenience method Phrase hello = new Phrase("Hello World"); PdfContentByte canvas = writer.DirectContent; ColumnText.ShowTextAligned(canvas, Element.ALIGN_LEFT, hello, 36, 788, 0);
這樣代碼看起來就比較親民一些,而且用Adobe Reader打開以上的兩份Pdf文檔,看起來是一樣的。如果用記事本打開,里面會有一些細微的差別。這里按照作者的說法就是PDF內置的特點,而且如果你用相同的一份代碼生成兩份pdf文檔,也會有一些差別。
Step5:關閉文檔
關閉的時候iText會在pdf文檔中寫入EOF(End of File)標記,在iTextSharp中,Document實現了IDisposable接口,因此就用了using語句。要注意的是在關閉Document的時候,設置的輸出流也會被自動關閉,但有時候我們希望輸出流不被自動關閉。
HelloZip.cs
// creating a zip file with different PDF documents ZipOutputStream zip = new ZipOutputStream(new FileStream(fileName, FileMode.Create)); for (int i = 0; i < 4; i++) { ZipEntry entry = new ZipEntry("hello_" + i + ".pdf"); zip.PutNextEntry(entry); // step 1 Document document= new Document(); // step 2 PdfWriter writer = PdfWriter.GetInstance(document, zip); writer.CloseStream = false; // step 3 document.Open(); // step 4 document.Add(new Paragraph("Hello " + i)); // step 5 document.Close(); zip.CloseEntry(); } zip.Close();
以上代碼會產生一個壓縮包,包中包含了4個pdf文件,因為設置的是ZipOutpuStream,我們不希望在中間生成pdf文檔時輸出流被自動關閉,這里只要設置PdfWriter的ClosedStream屬性為true即可。
總結
通過五步我們學會了創建pdf文檔,每一步也都有了基本的了解。但我們最需要關注的就是第四步:添加內容。其它的步驟大家有個概念並知道一些配置就可以了。在后續的章節中,我們會為pdf文檔添加一些有實際意義的內容,大家也會關注一些常用high-level對象的操作。
同步
此文章已同步到目錄索引:iText in Action 2nd 讀書筆記。