< >1.3節(Creating a PDF document in five steps with iText)讀書筆記


背景:

 由於工作的原因要處理和打印一些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應用程序,以下為代碼的運行界面:

2012-6-16 星期六 下午 23-16-32

2012-6-16 星期六 下午 23-17-56

寫的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來查看和編輯里面的數據,下載 點擊這里。

以下為其運行界面:

SQLitE

 其中以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用記事本打開的樣子:

PdfHeader

 

第一行看起來應該是這樣:

%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 讀書筆記。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM