使用iTextSharp修改PDF文件(一)
這個iTextSharp確實是個好東西,可以創建、讀取PDF格式的文檔,雖然我的需求比較簡單,但我首先還是基本上、完整地看完了它的相關文檔,不喜歡英文的同志,可以搜索一篇《用C#制作PDF文件全攻略》(茍安廷),這篇文章是茍先生在使用iTextSharp時的一些心得,里面雖然重點是說明如何創建PDF文件,對讀取、修改PDF文件的方法略過不提,因此,對于我的任務來說,并沒有太大的作用,但在這里,仍然感謝茍先生的無私奉獻。
具體使用iTextSharp的方法,我這里就不細說了,因為非常簡單,仔細看看它的文檔,應該都可以很輕松地創建、讀取PDF文件。我這里就只說說我在使用過程中碰到的一些問題,讓后來的人少走一些彎路:
1、PDF文件從理論上來說,只要創建成功之后,就不能再修改。
因為我需要修改原來的PDF文件,將它的頁眉頁腳去掉,然后換上新的頁眉頁腳。所以,我最開始對怎么只取得原始文件中的內容區域(是去掉了頁眉、頁腳、左邊固定區域、右邊固定區域的一個矩形區域),研究了很久。調用了其中的GetImportedPage方法,得到字符串,然后通過分析該字符串(是極其粗略的分析,因為PDF文件格式的標志太多,后面會有相關說明),去掉其中不需要的部分,再將剩下的其它部分進行保存,生成新的PDF文件。
理論上這種方法是正確的,也比較符合我們的一般邏輯思維(因為我們對已生成的文檔、程序進行修改,大多數情況下都是用類似方法,比如:對某個程序進行解密等等)。我也確實按這種方法得到了符合要求的、新的PDF文件,但隨即就發現了該方法其實不具備通用性,即對某篇文件是有效的,但對另一篇文件卻有可能會造成格式錯位。
因為分析PDF文件的格式是一件非常麻煩的事情,很多明明應該是在內容區域的字節,卻顯示在頁眉處,如果我再分析到里面最細小的、每一個標志位,還不如直接看它的SDK,而且這樣的話,在規定的時間里,這個程序也將完不成了。
解決辦法:
我先研究了Acrobat里的crop,它為什么可以這么精確的剪裁呢?
結果讓我啞然失笑,原來它的crop也不是真正的剪裁,而只是把需要的剪裁掉的區域屏蔽掉了而已,如果再回到crop里,進行上、下、左、右的設置,原來看起來好像被剪裁掉的區域仍然會顯示出來,呵呵,有意思。
好的,現在心里有底了,大概知道怎么做了,這時再仔細看看iTextSharp的文檔,發現有一段話以前沒有注意到:
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.
呵呵,跟我想的一樣,就是用新的區域,把需要剪裁的區域給覆蓋掉。
這就容易多了,先用iTextSharp的Template功能,把自己需要的文字、圖片、表格放到Template里,然后把整個的Template加到合適的位置,即可。
哦,別忘了,得先在Template里加個白色的矩形框,放在最底層。
注:上面提到了PDF文件的格式,其實PDF文件的格式非常有趣,是的,非常有趣。相關的信息,可參考網上的《一個簡單的PDF文件結構的分析》等文章。否則當你看到<BT>、<ET>、/F1、TF時,你會感覺莫明其妙的。
2、PDF文件中的屬性,不是我們一般意義上的文件的屬性。
這一點開始讓我走了一段彎路,我用iTextSharp中的相關函數,在Document.Opent()之前,設置了相關的屬性,如:subject/author/title等等,但奇怪的是,生成新的PDF文件中,我用一般的看某一個文件屬性的方法,卻沒有看到預料中的屬性,都是空的。
后來,經過有經驗的同事提醒,才知道:原來所謂的PDF文件的屬性,是要在Acrobat Reader的某個菜單中才能看到的。
呵呵,以前對Acrobat的應用就基本上只有對文件進行互相轉換,沒用過其它太多的功能,沒有經驗呀。
雖然中間經歷了無數的嘗試、無數的推倒重來。這個小程序后來還是在3天之內完成了,起到了它應有的作用。貼個界面上來:
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", "標題");
16 info.Remove("Author");
17 info.Add("Author", "作者_幻想Zerow");
18 info.Remove("Subject");
19 info.Add("Subject", "主題-修改Pdf元數據_幻想Zerow");
20 info.Remove("Keywords");
21 info.Add("Keywords", "關鍵字");
22
23 reader.Close();
24 PdfStamper stamper = new PdfStamper(reader, new FileStream(pdfName, FileMode.Create, FileAccess.Write));
25 stamper.MoreInfo = info;
26 //設置是否加密
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中相關的概念:
一、Document
這個對象有三個構造函數:
隱藏行號復制代碼?這是一段程序代碼。
public Document();
public Document(Rectangle pageSize);
public Document(Rectangle pageSize,
int marginLeft,
int marginRight,
int marginTop,
int marginBottom);
第一個構造函數以A4頁面作為參數調用第二個構造函數,第二個構造函數以每邊36磅頁邊距為參數調用調用第三個構造函數。
頁面尺寸:
你可以通過指定的顏色和大小創建你自己的頁面,示例代碼0102創建一個細長的淺黃色背景的頁面:
Rectangle pageSize = new Rectangle(144, 720);
pageSize.BackgroundColor = new Color(0xFF, 0xFF, 0xDE);
Document document = new Document(pageSize);
通常,你不必創建這樣的頁面,而可以從下面頁面尺寸中選擇:
A0-A10, LEGAL, LETTER, HALFLETTER, _11x17, LEDGER, NOTE, B0-B5, ARCH_A-ARCH_E, FLSA 和 FLSE
大多數情況下使用縱向頁面,如果希望使用橫向頁面,你只須使用rotate()函數:
Document document = new Document(PageSize.A4.rotate());
詳細代碼見示例代碼0103。
頁邊距:
當創建一個文件時,你還可以定義上、下、左、右頁邊距:
Document document = new Document(PageSize.A5, 36, 72, 108, 180);
說明:
當創建一個矩形或設置邊距時,你可能希望知道該用什么度量單位:厘米、英寸或象素,事實上,默認的度量系統以排版單位磅為基礎得出其他單位的近似值,如1英寸=72磅,如果你想在A4頁面的PDF中創建一個矩形,你需要計算以下數據:
21 厘米 / 2.54 = 8.2677 英寸
8.2677英寸* 72 = 595 磅
29.7 厘米 / 2.54 = 11.6929 英寸
11.6929英寸* 72 = 842 磅
默認邊距為36磅即半英寸。
如果你修改了頁面尺寸,僅僅影響到下一頁,如果你修改了頁邊距,則影響到全部,故慎用。
二、Writer
一旦創建了document,我們可以創建該文檔的多個Writer的實例,所有這些Writer實例均繼承自抽象類“iTextSharp.text.DocWriter”。
同時還有另外一種情況,你可以用iTextSharp.text.pdf.PdfWriter產生文檔PDF文件,如果你想創建一個TeX文檔,你可以使用iTextSharp.text.TeX.TeXWriter包。
Writer類的構造函數是私有的,你只能通過下面的方法創建一個實例:
public static xxxWriter getInstance(Document document, Stream os);(xxx 是 Pdf 或 Xml)
你可以通過下面的方法創建一個實例:
PdfWriter writer = PdfWriter.getInstance(document, new FileStream("Chap01xx.pdf"));
但是你幾乎永遠不會用到Writer實例(除非你想創建高級PDF或者希望用一些非常特殊的函數,如ViewerPreferences或Encryption)。所以通過下面的辦法得到實例已經足夠了: PdfWriter.getInstance(document, new FileStream("Chap01xx.pdf"));
在第一步中創建一個文檔時,第一個參數意義不大,第二個參數可以是任何一種流,到目前為止我們一直使用System.IO.FileStream將Document寫入文件中,示例代碼0105用到了System.IO.MemoryStream(這不是一個獨立的例子,你必須在Servlet Engine中測試這些代碼。
文檔加密:
public void setEncryption(boolean strength, String userPassword, String ownerPassword, int permissions);
· strength 是下面兩個常量之一:
o PdfWriter.STRENGTH40BITS: 40 位
o PdfWriter.STRENGTH128BITS: 128位 (Acrobat Reader 5.0及以上版本支持)
· UserPassword和ownerPassword 可以為空或零長度, 這種情況下, ownerPassword 將被隨機的字符串代替
· 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)是能被添加到文檔的文本的最小單位,塊可以用于構建其他基礎元素如短句、段落、錨點等,塊是一個有確定字體的字符串,要添加塊到文檔中時,其他所有布局變量均要被定義。
四、短句(Phrases)
短句(Phrases)是一系列以特定間距(兩行之間的距離)作為參數的塊,一個短句有一個主字體,但短句中的一些塊具有不同于主字體的字體,你有更多的選擇去創建短句。
五、段落
段落是一系列塊和(或)短句。同短句一樣,段落有確定的間距。用戶還可以指定縮排;在邊和(或)右邊保留一定空白,段落可以左對齊、右對齊和居中對齊。添加到文檔中的每一個段落將自動另起一行。
說明:一個段落有一個且僅有一個間距,如果你添加了一個不同字體的短句或塊,原來的間距仍然有效,你可以通過SetLeading來改變間距,但是段落中所有內容將使用新的中的間距。
更改分割符
通常,當文本不能放在一行時,文本將被分割成不同的部分,iText首先會查找分割符,如果沒有找到,文本將在行尾被截斷。有一些預定的分割符如“ ”空格和“-”連字符,但是你可以使用setSplitCharacter方法來覆蓋這些默認值。
以使用IndentationLeft和IndentationRight,FirstLineIndent屬性設置縮排;
六、錨點(Anchor)
如果你想在文檔中添加一個外部鏈接(例如使用URL鏈接到WEB上的其他文檔),你可以簡單地使用Anchor對象,它派生于Phrase對象,使用方法相同。只有兩種額外方法定義兩種額外變量:setName和 setReference。
外部鏈接示例:
隱藏行號復制代碼?這是一段程序代碼。
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";
如果你想添加內部鏈接,你需要選擇該鏈接不同的名稱,就象你相位在HTML中利用名稱作為錨點一樣。為達到該目的,你需要添加一個“#”。
內部鏈接示例:
隱藏行號復制代碼?這是一段程序代碼。
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)
通過類List 和ListItem,你可以添加列表到PDF文件中,對于列表你還可以選擇是否排序。
排序列表示例:
隱藏行號復制代碼?這是一段程序代碼。
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"));
結果如下:
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
不排序示例如下:
隱藏行號復制代碼?這是一段程序代碼。
List overview = new List(false, 10);
overview.Add(new ListItem("This is an item"));
overview.Add("This is another item");
結果如下:
· This is an item
· This is another item
可以通過SetListSymbol方法來更改列表符號,可以使用圖片或其它對象作為列表符號。
隱藏行號復制代碼?這是一段程序代碼。
// 用字符串作為列表符號
list1.ListSymbol = "*";
// 用Chunk 作為列表符號(包含“•”字符)
list2.ListSymbol = new Chunk("u2022", FontFactory.getFont(FontFactory.HELVETICA, 20));
//用圖片作為列表符號
list3.ListSymbol = new Chunk(Image.getInstance("myBullet.gif"), 0, 0);
還可以使用IndentationLeft和IndentationRight屬性設置縮排,列表符號的縮排使用SymbolIndent屬性,也可以在構造函數中設置。
八、注釋
你可以添加一小段文本到你的文檔中,但它并非文檔內容的一部分,注釋有標題和內容:
Annotation a = new Annotation(
"authors",
"Maybe it's because I wanted to be an author myself that I wrote iText.");
外部鏈接注釋:
你需要指定一個可點擊的矩形和一個字符串(URL描述)或URL對象:
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文件鏈接注釋:
你需要指定一個可點擊的矩形和一個字符串(文件名稱)和目的文件或頁碼。
Annotation annot = new Annotation(100f, 700f, 200f, 800f, "other.pdf", "mark");
Annotation annot = new Annotation(100f, 700f, 200f, 800f, "other.pdf", 2);
指定行為鏈接注釋
你需要指定一個可點擊的矩形和一個指定的行為:
Annotation annot = new Annotation(100f, 700f, 200f, 800f, PdfAction.FIRSTPAGE);
u 應用程序鏈接注釋:
你需要指定一個可點擊的矩形和一個應用程序:
Annotation annot = new Annotation(300f, 700f, 400f, 800f, "C://winnt/notepad.exe", null, null, null);
我們無須在頁面上指定一個位置,iText會內部處理。你能夠看到iText添加文本注釋在頁面上當前位置下面,第一個在段后第一行下面,第二個在短句結束處的下面。
所有其他注釋需要指定想匹配的矩形區域,在示例代碼0304中,我們畫了一些正方形(使用的函數將在第十章中介紹),為每個正方形添加了一些鏈接注釋。
九、頁眉頁腳
在舊版本中,有HeaderFooter對象就可以設置頁眉頁腳,但是新版本中,已經不存在這個對象。
新版本中,使用新的對象PdfWriter中有一個對象:PdfEvent對象,它實現了如下接口:
隱藏行號復制代碼?這是一段程序代碼。
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);
}
可以在這里面實現。
十、章節(Chapter)和區域(Section)
章節的使用就比較少了,并且不太好控制,這就不作說明
十一、書簽
簡單創建書簽,使用如下代碼:
隱藏行號復制代碼?這是一段程序代碼。
protected PdfOutline SetDestination(PdfOutline root, Chunk chk, string name, string destination)
{
chk.SetLocalDestination(destination);
return new PdfOutline(root, PdfAction.GotoLocalPage(destination, false), name);
}
復雜的書簽就要使用Pdfaction,PdfOutline,PdfDestination三個對象來創建了。
十二、中文語言支持
中文語言支持,要加入一些擴展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;
}
}
}
}
上面的兩個dll是注冊中文語言支持,后面是注冊系統下的一些中文字體文件。
十三、文字、表格、圖像混排
在進行文字、表格、圖像混排中,有時比較難控制位置,最好是把文字、表格、圖像分別放到不同的段落中,這樣才能很好控制位置。
十四、表單寫入
讀取表單中的域:
隱藏行號復制代碼?這是一段程序代碼。
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;
}
對表單中的域進行填充:
隱藏行號復制代碼?這是一段程序代碼。
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();
}
}
}
總結
以上是生活随笔為你收集整理的使用iTextSharp修改PDF文件(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 政治面貌分类(政治面貌是怎样区分的?)
- 下一篇: 李念老公多大(李念个人资料老公多大)