使用iTextSharp修改PDF文件(一)
這個(gè)iTextSharp確實(shí)是個(gè)好東西,可以創(chuàng)建、讀取PDF格式的文檔,雖然我的需求比較簡(jiǎn)單,但我首先還是基本上、完整地看完了它的相關(guān)文檔,不喜歡英文的同志,可以搜索一篇《用C#制作PDF文件全攻略》(茍安廷),這篇文章是茍先生在使用iTextSharp時(shí)的一些心得,里面雖然重點(diǎn)是說(shuō)明如何創(chuàng)建PDF文件,對(duì)讀取、修改PDF文件的方法略過(guò)不提,因此,對(duì)于我的任務(wù)來(lái)說(shuō),并沒(méi)有太大的作用,但在這里,仍然感謝茍先生的無(wú)私奉獻(xiàn)。
具體使用iTextSharp的方法,我這里就不細(xì)說(shuō)了,因?yàn)榉浅:?jiǎn)單,仔細(xì)看看它的文檔,應(yīng)該都可以很輕松地創(chuàng)建、讀取PDF文件。我這里就只說(shuō)說(shuō)我在使用過(guò)程中碰到的一些問(wèn)題,讓后來(lái)的人少走一些彎路:
1、PDF文件從理論上來(lái)說(shuō),只要?jiǎng)?chuàng)建成功之后,就不能再修改。
因?yàn)槲倚枰薷脑瓉?lái)的PDF文件,將它的頁(yè)眉頁(yè)腳去掉,然后換上新的頁(yè)眉頁(yè)腳。所以,我最開(kāi)始對(duì)怎么只取得原始文件中的內(nèi)容區(qū)域(是去掉了頁(yè)眉、頁(yè)腳、左邊固定區(qū)域、右邊固定區(qū)域的一個(gè)矩形區(qū)域),研究了很久。調(diào)用了其中的GetImportedPage方法,得到字符串,然后通過(guò)分析該字符串(是極其粗略的分析,因?yàn)镻DF文件格式的標(biāo)志太多,后面會(huì)有相關(guān)說(shuō)明),去掉其中不需要的部分,再將剩下的其它部分進(jìn)行保存,生成新的PDF文件。
理論上這種方法是正確的,也比較符合我們的一般邏輯思維(因?yàn)槲覀儗?duì)已生成的文檔、程序進(jìn)行修改,大多數(shù)情況下都是用類(lèi)似方法,比如:對(duì)某個(gè)程序進(jìn)行解密等等)。我也確實(shí)按這種方法得到了符合要求的、新的PDF文件,但隨即就發(fā)現(xiàn)了該方法其實(shí)不具備通用性,即對(duì)某篇文件是有效的,但對(duì)另一篇文件卻有可能會(huì)造成格式錯(cuò)位。
因?yàn)榉治鯬DF文件的格式是一件非常麻煩的事情,很多明明應(yīng)該是在內(nèi)容區(qū)域的字節(jié),卻顯示在頁(yè)眉處,如果我再分析到里面最細(xì)小的、每一個(gè)標(biāo)志位,還不如直接看它的SDK,而且這樣的話(huà),在規(guī)定的時(shí)間里,這個(gè)程序也將完不成了。
解決辦法:
我先研究了Acrobat里的crop,它為什么可以這么精確的剪裁呢?
結(jié)果讓我啞然失笑,原來(lái)它的crop也不是真正的剪裁,而只是把需要的剪裁掉的區(qū)域屏蔽掉了而已,如果再回到crop里,進(jìn)行上、下、左、右的設(shè)置,原來(lái)看起來(lái)好像被剪裁掉的區(qū)域仍然會(huì)顯示出來(lái),呵呵,有意思。
好的,現(xiàn)在心里有底了,大概知道怎么做了,這時(shí)再仔細(xì)看看iTextSharp的文檔,發(fā)現(xiàn)有一段話(huà)以前沒(méi)有注意到:
If you have an existing PDF file that represents a form, you could copy the pages of this form and paint text at precise locations on this form. You can't edit an existing PDF document, by saying: for instance replace the word Louagie by Lowagie. To achieve this, you would have to know the exact location of the word Louagie, paint a white rectangle over it and paint the word Lowagie on this white rectangle. Please avoid this kind of 'patch' work. Do your PDF editing with an Adobe product.
呵呵,跟我想的一樣,就是用新的區(qū)域,把需要剪裁的區(qū)域給覆蓋掉。
這就容易多了,先用iTextSharp的Template功能,把自己需要的文字、圖片、表格放到Template里,然后把整個(gè)的Template加到合適的位置,即可。
哦,別忘了,得先在Template里加個(gè)白色的矩形框,放在最底層。
注:上面提到了PDF文件的格式,其實(shí)PDF文件的格式非常有趣,是的,非常有趣。相關(guān)的信息,可參考網(wǎng)上的《一個(gè)簡(jiǎn)單的PDF文件結(jié)構(gòu)的分析》等文章。否則當(dāng)你看到<BT>、<ET>、/F1、TF時(shí),你會(huì)感覺(jué)莫明其妙的。
2、PDF文件中的屬性,不是我們一般意義上的文件的屬性。
這一點(diǎn)開(kāi)始讓我走了一段彎路,我用iTextSharp中的相關(guān)函數(shù),在Document.Opent()之前,設(shè)置了相關(guān)的屬性,如:subject/author/title等等,但奇怪的是,生成新的PDF文件中,我用一般的看某一個(gè)文件屬性的方法,卻沒(méi)有看到預(yù)料中的屬性,都是空的。
后來(lái),經(jīng)過(guò)有經(jīng)驗(yàn)的同事提醒,才知道:原來(lái)所謂的PDF文件的屬性,是要在Acrobat Reader的某個(gè)菜單中才能看到的。
呵呵,以前對(duì)Acrobat的應(yīng)用就基本上只有對(duì)文件進(jìn)行互相轉(zhuǎn)換,沒(méi)用過(guò)其它太多的功能,沒(méi)有經(jīng)驗(yàn)呀。
雖然中間經(jīng)歷了無(wú)數(shù)的嘗試、無(wú)數(shù)的推倒重來(lái)。這個(gè)小程序后來(lái)還是在3天之內(nèi)完成了,起到了它應(yīng)有的作用。貼個(gè)界面上來(lái):
1 /// <summary>
2 /// 修改PDF文件屬性
3 /// </summary>
4 /// <param name="pdfName">PDF文件名(比如:D:hello.pdf)</param>
5 private void PdfPropMod(string pdfName)
6 {
7 try
8 {
9 PdfReader reader = new PdfReader(File.ReadAllBytes(pdfName));
10
11 if (!reader.IsEncrypted())
12 {
13 Dictionary<string, string> info = reader.Info;
14 info.Remove("Title");
15 info.Add("Title", "標(biāo)題");
16 info.Remove("Author");
17 info.Add("Author", "作者_(dá)幻想Zerow");
18 info.Remove("Subject");
19 info.Add("Subject", "主題-修改Pdf元數(shù)據(jù)_幻想Zerow");
20 info.Remove("Keywords");
21 info.Add("Keywords", "關(guān)鍵字");
22
23 reader.Close();
24 PdfStamper stamper = new PdfStamper(reader, new FileStream(pdfName, FileMode.Create, FileAccess.Write));
25 stamper.MoreInfo = info;
26 //設(shè)置是否加密
27 //stamper.SetEncryption(PdfWriter.DO_NOT_ENCRYPT_METADATA, null, null, PdfWriter.ALLOW_PRINTING | PdfWriter.ALLOW_COPY);
28 stamper.Close();
29 }
30 }
31 catch (Exception e)
32 {
33 throw e;
34 }
35 }
ITextSharp中相關(guān)的概念:
一、Document
這個(gè)對(duì)象有三個(gè)構(gòu)造函數(shù):
隱藏行號(hào)復(fù)制代碼?這是一段程序代碼。
public Document();
public Document(Rectangle pageSize);
public Document(Rectangle pageSize,
int marginLeft,
int marginRight,
int marginTop,
int marginBottom);
第一個(gè)構(gòu)造函數(shù)以A4頁(yè)面作為參數(shù)調(diào)用第二個(gè)構(gòu)造函數(shù),第二個(gè)構(gòu)造函數(shù)以每邊36磅頁(yè)邊距為參數(shù)調(diào)用調(diào)用第三個(gè)構(gòu)造函數(shù)。
頁(yè)面尺寸:
你可以通過(guò)指定的顏色和大小創(chuàng)建你自己的頁(yè)面,示例代碼0102創(chuàng)建一個(gè)細(xì)長(zhǎng)的淺黃色背景的頁(yè)面:
Rectangle pageSize = new Rectangle(144, 720);
pageSize.BackgroundColor = new Color(0xFF, 0xFF, 0xDE);
Document document = new Document(pageSize);
通常,你不必創(chuàng)建這樣的頁(yè)面,而可以從下面頁(yè)面尺寸中選擇:
A0-A10, LEGAL, LETTER, HALFLETTER, _11x17, LEDGER, NOTE, B0-B5, ARCH_A-ARCH_E, FLSA 和 FLSE
大多數(shù)情況下使用縱向頁(yè)面,如果希望使用橫向頁(yè)面,你只須使用rotate()函數(shù):
Document document = new Document(PageSize.A4.rotate());
詳細(xì)代碼見(jiàn)示例代碼0103。
頁(yè)邊距:
當(dāng)創(chuàng)建一個(gè)文件時(shí),你還可以定義上、下、左、右頁(yè)邊距:
Document document = new Document(PageSize.A5, 36, 72, 108, 180);
說(shuō)明:
當(dāng)創(chuàng)建一個(gè)矩形或設(shè)置邊距時(shí),你可能希望知道該用什么度量單位:厘米、英寸或象素,事實(shí)上,默認(rèn)的度量系統(tǒng)以排版單位磅為基礎(chǔ)得出其他單位的近似值,如1英寸=72磅,如果你想在A4頁(yè)面的PDF中創(chuàng)建一個(gè)矩形,你需要計(jì)算以下數(shù)據(jù):
21 厘米 / 2.54 = 8.2677 英寸
8.2677英寸* 72 = 595 磅
29.7 厘米 / 2.54 = 11.6929 英寸
11.6929英寸* 72 = 842 磅
默認(rèn)邊距為36磅即半英寸。
如果你修改了頁(yè)面尺寸,僅僅影響到下一頁(yè),如果你修改了頁(yè)邊距,則影響到全部,故慎用。
二、Writer
一旦創(chuàng)建了document,我們可以創(chuàng)建該文檔的多個(gè)Writer的實(shí)例,所有這些Writer實(shí)例均繼承自抽象類(lèi)“iTextSharp.text.DocWriter”。
同時(shí)還有另外一種情況,你可以用iTextSharp.text.pdf.PdfWriter產(chǎn)生文檔PDF文件,如果你想創(chuàng)建一個(gè)TeX文檔,你可以使用iTextSharp.text.TeX.TeXWriter包。
Writer類(lèi)的構(gòu)造函數(shù)是私有的,你只能通過(guò)下面的方法創(chuàng)建一個(gè)實(shí)例:
public static xxxWriter getInstance(Document document, Stream os);(xxx 是 Pdf 或 Xml)
你可以通過(guò)下面的方法創(chuàng)建一個(gè)實(shí)例:
PdfWriter writer = PdfWriter.getInstance(document, new FileStream("Chap01xx.pdf"));
但是你幾乎永遠(yuǎn)不會(huì)用到Writer實(shí)例(除非你想創(chuàng)建高級(jí)PDF或者希望用一些非常特殊的函數(shù),如ViewerPreferences或Encryption)。所以通過(guò)下面的辦法得到實(shí)例已經(jīng)足夠了: PdfWriter.getInstance(document, new FileStream("Chap01xx.pdf"));
在第一步中創(chuàng)建一個(gè)文檔時(shí),第一個(gè)參數(shù)意義不大,第二個(gè)參數(shù)可以是任何一種流,到目前為止我們一直使用System.IO.FileStream將Document寫(xiě)入文件中,示例代碼0105用到了System.IO.MemoryStream(這不是一個(gè)獨(dú)立的例子,你必須在Servlet Engine中測(cè)試這些代碼。
文檔加密:
public void setEncryption(boolean strength, String userPassword, String ownerPassword, int permissions);
· strength 是下面兩個(gè)常量之一:
o PdfWriter.STRENGTH40BITS: 40 位
o PdfWriter.STRENGTH128BITS: 128位 (Acrobat Reader 5.0及以上版本支持)
· UserPassword和ownerPassword 可以為空或零長(zhǎng)度, 這種情況下, ownerPassword 將被隨機(jī)的字符串代替
· Permissions 為下列常量之一:
o PdfWriter.AllowPrinting
o PdfWriter.AllowModifyContents
o PdfWriter.AllowCopy
o PdfWriter.AllowModifyAnnotations
o PdfWriter.AllowFillIn
o PdfWriter.AllowScreenReaders
o PdfWriter.AllowAssembly
PdfWriter.AllowDegradedPrinting
三、塊(Chunk)
塊(Chunk)是能被添加到文檔的文本的最小單位,塊可以用于構(gòu)建其他基礎(chǔ)元素如短句、段落、錨點(diǎn)等,塊是一個(gè)有確定字體的字符串,要添加塊到文檔中時(shí),其他所有布局變量均要被定義。
四、短句(Phrases)
短句(Phrases)是一系列以特定間距(兩行之間的距離)作為參數(shù)的塊,一個(gè)短句有一個(gè)主字體,但短句中的一些塊具有不同于主字體的字體,你有更多的選擇去創(chuàng)建短句。
五、段落
段落是一系列塊和(或)短句。同短句一樣,段落有確定的間距。用戶(hù)還可以指定縮排;在邊和(或)右邊保留一定空白,段落可以左對(duì)齊、右對(duì)齊和居中對(duì)齊。添加到文檔中的每一個(gè)段落將自動(dòng)另起一行。
說(shuō)明:一個(gè)段落有一個(gè)且僅有一個(gè)間距,如果你添加了一個(gè)不同字體的短句或塊,原來(lái)的間距仍然有效,你可以通過(guò)SetLeading來(lái)改變間距,但是段落中所有內(nèi)容將使用新的中的間距。
更改分割符
通常,當(dāng)文本不能放在一行時(shí),文本將被分割成不同的部分,iText首先會(huì)查找分割符,如果沒(méi)有找到,文本將在行尾被截?cái)唷S幸恍╊A(yù)定的分割符如“ ”空格和“-”連字符,但是你可以使用setSplitCharacter方法來(lái)覆蓋這些默認(rèn)值。
以使用IndentationLeft和IndentationRight,F(xiàn)irstLineIndent屬性設(shè)置縮排;
六、錨點(diǎn)(Anchor)
如果你想在文檔中添加一個(gè)外部鏈接(例如使用URL鏈接到WEB上的其他文檔),你可以簡(jiǎn)單地使用Anchor對(duì)象,它派生于Phrase對(duì)象,使用方法相同。只有兩種額外方法定義兩種額外變量:setName和 setReference。
外部鏈接示例:
隱藏行號(hào)復(fù)制代碼?這是一段程序代碼。
Anchor anchor = new Anchor("website", FontFactory.getFont(FontFactory.HELVETICA, 12, Font.UNDERLINE, new Color(0, 0, 255)));
anchor.Reference = http://itextsharp.sourceforge.net;
anchor.Name = "website";
如果你想添加內(nèi)部鏈接,你需要選擇該鏈接不同的名稱(chēng),就象你相位在HTML中利用名稱(chēng)作為錨點(diǎn)一樣。為達(dá)到該目的,你需要添加一個(gè)“#”。
內(nèi)部鏈接示例:
隱藏行號(hào)復(fù)制代碼?這是一段程序代碼。
Anchor anchor1 = new Anchor("This is an internal link");
anchor1.Name = "link1";
Anchor anchor2 = new Anchor("Click here to jump to the internal link");
anchor.Reference = "#link1";
七、列表(List,ListItem)
通過(guò)類(lèi)List 和ListItem,你可以添加列表到PDF文件中,對(duì)于列表你還可以選擇是否排序。
排序列表示例:
隱藏行號(hào)復(fù)制代碼?這是一段程序代碼。
List list = new List(true, 20);
list.Add(new ListItem("First line"));
list.Add(new ListItem("The second line is longer to see what happens once the end of the line is reached. Will it start on a new line?"));
list.Add(new ListItem("Third line"));
結(jié)果如下:
1. First line
2. The second line is longer to see what happens once the end of the line is reached. Will it start on a new line?
3. Third line
不排序示例如下:
隱藏行號(hào)復(fù)制代碼?這是一段程序代碼。
List overview = new List(false, 10);
overview.Add(new ListItem("This is an item"));
overview.Add("This is another item");
結(jié)果如下:
· This is an item
· This is another item
可以通過(guò)SetListSymbol方法來(lái)更改列表符號(hào),可以使用圖片或其它對(duì)象作為列表符號(hào)。
隱藏行號(hào)復(fù)制代碼?這是一段程序代碼。
// 用字符串作為列表符號(hào)
list1.ListSymbol = "*";
// 用Chunk 作為列表符號(hào)(包含“•”字符)
list2.ListSymbol = new Chunk("u2022", FontFactory.getFont(FontFactory.HELVETICA, 20));
//用圖片作為列表符號(hào)
list3.ListSymbol = new Chunk(Image.getInstance("myBullet.gif"), 0, 0);
還可以使用IndentationLeft和IndentationRight屬性設(shè)置縮排,列表符號(hào)的縮排使用SymbolIndent屬性,也可以在構(gòu)造函數(shù)中設(shè)置。
八、注釋
你可以添加一小段文本到你的文檔中,但它并非文檔內(nèi)容的一部分,注釋有標(biāo)題和內(nèi)容:
Annotation a = new Annotation(
"authors",
"Maybe it's because I wanted to be an author myself that I wrote iText.");
外部鏈接注釋?zhuān)?/p>
你需要指定一個(gè)可點(diǎn)擊的矩形和一個(gè)字符串(URL描述)或URL對(duì)象:
Annotation annot = new Annotation(100f, 700f, 200f, 800f, new URL("http://www.lowagie.com"));
Annotation annot = new Annotation(100f, 700f, 200f, 800f, "http://www.lowagie.com");
外部PDF文件鏈接注釋?zhuān)?/p>
你需要指定一個(gè)可點(diǎn)擊的矩形和一個(gè)字符串(文件名稱(chēng))和目的文件或頁(yè)碼。
Annotation annot = new Annotation(100f, 700f, 200f, 800f, "other.pdf", "mark");
Annotation annot = new Annotation(100f, 700f, 200f, 800f, "other.pdf", 2);
指定行為鏈接注釋
你需要指定一個(gè)可點(diǎn)擊的矩形和一個(gè)指定的行為:
Annotation annot = new Annotation(100f, 700f, 200f, 800f, PdfAction.FIRSTPAGE);
u 應(yīng)用程序鏈接注釋?zhuān)?/p>
你需要指定一個(gè)可點(diǎn)擊的矩形和一個(gè)應(yīng)用程序:
Annotation annot = new Annotation(300f, 700f, 400f, 800f, "C://winnt/notepad.exe", null, null, null);
我們無(wú)須在頁(yè)面上指定一個(gè)位置,iText會(huì)內(nèi)部處理。你能夠看到iText添加文本注釋在頁(yè)面上當(dāng)前位置下面,第一個(gè)在段后第一行下面,第二個(gè)在短句結(jié)束處的下面。
所有其他注釋需要指定想匹配的矩形區(qū)域,在示例代碼0304中,我們畫(huà)了一些正方形(使用的函數(shù)將在第十章中介紹),為每個(gè)正方形添加了一些鏈接注釋。
九、頁(yè)眉頁(yè)腳
在舊版本中,有HeaderFooter對(duì)象就可以設(shè)置頁(yè)眉頁(yè)腳,但是新版本中,已經(jīng)不存在這個(gè)對(duì)象。
新版本中,使用新的對(duì)象PdfWriter中有一個(gè)對(duì)象:PdfEvent對(duì)象,它實(shí)現(xiàn)了如下接口:
隱藏行號(hào)復(fù)制代碼?這是一段程序代碼。
public interface IPdfPageEvent
{
void OnChapter(PdfWriter writer, Document document, float paragraphPosition, Paragraph title);
void OnChapterEnd(PdfWriter writer, Document document, float paragraphPosition);
void OnCloseDocument(PdfWriter writer, Document document);
void OnEndPage(PdfWriter writer, Document document);
void OnGenericTag(PdfWriter writer, Document document, Rectangle rect, string text);
void OnOpenDocument(PdfWriter writer, Document document);
void OnParagraph(PdfWriter writer, Document document, float paragraphPosition);
void OnParagraphEnd(PdfWriter writer, Document document, float paragraphPosition);
void OnSection(PdfWriter writer, Document document, float paragraphPosition, int depth, Paragraph title);
void OnSectionEnd(PdfWriter writer, Document document, float paragraphPosition);
void OnStartPage(PdfWriter writer, Document document);
}
可以在這里面實(shí)現(xiàn)。
十、章節(jié)(Chapter)和區(qū)域(Section)
章節(jié)的使用就比較少了,并且不太好控制,這就不作說(shuō)明
十一、書(shū)簽
簡(jiǎn)單創(chuàng)建書(shū)簽,使用如下代碼:
隱藏行號(hào)復(fù)制代碼?這是一段程序代碼。
protected PdfOutline SetDestination(PdfOutline root, Chunk chk, string name, string destination)
{
chk.SetLocalDestination(destination);
return new PdfOutline(root, PdfAction.GotoLocalPage(destination, false), name);
}
復(fù)雜的書(shū)簽就要使用Pdfaction,PdfOutline,PdfDestination三個(gè)對(duì)象來(lái)創(chuàng)建了。
十二、中文語(yǔ)言支持
中文語(yǔ)言支持,要加入一些擴(kuò)展dll,加入方法如下所示:
public static void RegisterFont()
{
if (!_isRegisterFont)
{
lock (typeof(TextSharpHelper))
{
if (!_isRegisterFont)
{
BaseFont.AddToResourceSearch("iTextAsian.dll");
BaseFont.AddToResourceSearch("iTextAsianCmaps.dll");
FontFactory.Register(Environment.GetFolderPath(Environment.SpecialFolder.System) +
@"..FontsSTSONG.ttf");
FontFactory.Register(Environment.GetFolderPath(Environment.SpecialFolder.System) +
@"..Fontssimhei.ttf");
FontFactory.Register(Environment.GetFolderPath(Environment.SpecialFolder.System) +
@"..Fontssimsun.ttc");
_isRegisterFont = true;
}
}
}
}
上面的兩個(gè)dll是注冊(cè)中文語(yǔ)言支持,后面是注冊(cè)系統(tǒng)下的一些中文字體文件。
十三、文字、表格、圖像混排
在進(jìn)行文字、表格、圖像混排中,有時(shí)比較難控制位置,最好是把文字、表格、圖像分別放到不同的段落中,這樣才能很好控制位置。
十四、表單寫(xiě)入
讀取表單中的域:
隱藏行號(hào)復(fù)制代碼?這是一段程序代碼。
public static Dictionary<string, string> ReadForm(string pdfTemplate)
{
Dictionary<string, string> dic = new Dictionary<string, string>();
PdfReader pdfReader = null;
try
{
pdfReader = new PdfReader(pdfTemplate);
AcroFields pdfFormFields = pdfReader.AcroFields;
foreach (KeyValuePair<string, AcroFields.Item> de in pdfFormFields.Fields)
{
dic.Add(de.Key, "");
}
}
finally
{
if (pdfReader != null)
{
pdfReader.Close();
}
}
return dic;
}
對(duì)表單中的域進(jìn)行填充:
隱藏行號(hào)復(fù)制代碼?這是一段程序代碼。
public static void FillForm(string pdfTemplate, string newFile, Dictionary<string, string> dic)
{
PdfReader pdfReader = null;
PdfStamper pdfStamper = null;
try
{
pdfReader = new PdfReader(pdfTemplate);
pdfStamper = new PdfStamper(pdfReader, new FileStream(
newFile, FileMode.Create));
AcroFields pdfFormFields = pdfStamper.AcroFields;
foreach (KeyValuePair<string, string> de in dic)
{
pdfFormFields.SetField(de.Key, de.Value);
}
pdfStamper.FormFlattening = true;
}
finally
{
if (pdfReader != null)
{
pdfReader.Close();
}
if (pdfStamper != null)
{
pdfStamper.Close();
}
}
}
總結(jié)
以上是生活随笔為你收集整理的使用iTextSharp修改PDF文件(一)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 政治面貌分类(政治面貌是怎样区分的?)
- 下一篇: 李念老公多大(李念个人资料老公多大)