zoukankan      html  css  js  c++  java
  • 使用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

    这个对象有三个构造函数:

    隐藏行号 复制代码 ?这是一段程序代码。
    1. public Document();
    2. public Document(Rectangle pageSize);
    3. public Document(Rectangle pageSize,
    4. int marginLeft,
    5. int marginRight,
    6. int marginTop,
    7. 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。

    外部链接示例:

    隐藏行号 复制代码 ?这是一段程序代码。
    1. Anchor anchor = new Anchor("website", FontFactory.getFont(FontFactory.HELVETICA, 12, Font.UNDERLINE, new Color(0, 0, 255)));
    2. anchor.Reference = http://itextsharp.sourceforge.net;
    3. anchor.Name = "website";
      

    如果你想添加内部链接,你需要选择该链接不同的名称,就象你相位在HTML中利用名称作为锚点一样。为达到该目的,你需要添加一个“#”。

    内部链接示例:

    隐藏行号 复制代码 ?这是一段程序代码。
    1. Anchor anchor1 = new Anchor("This is an internal link");
    2. anchor1.Name = "link1";
    3. Anchor anchor2 = new Anchor("Click here to jump to the internal link");
    4. anchor.Reference = "#link1";

    七、列表(List,ListItem)

    通过类List 和ListItem,你可以添加列表到PDF文件中,对于列表你还可以选择是否排序。

    排序列表示例:

    隐藏行号 复制代码 ?这是一段程序代码。
    1. List list = new List(true, 20);
    2. list.Add(new ListItem("First line"));
    3. 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?"));
    4. 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

    不排序示例如下:

    隐藏行号 复制代码 ?这是一段程序代码。
    1. List overview = new List(false, 10);
    2. overview.Add(new ListItem("This is an item"));
    3. overview.Add("This is another item");
      

    结果如下:

    · This is an item

    · This is another item

    可以通过SetListSymbol方法来更改列表符号,可以使用图片或其它对象作为列表符号。

    隐藏行号 复制代码 ?这是一段程序代码。
    1. // 用字符串作为列表符号
    2. list1.ListSymbol = "*";
    3. // 用Chunk 作为列表符号(包含“•”字符)
    4. list2.ListSymbol = new Chunk("u2022", FontFactory.getFont(FontFactory.HELVETICA, 20));
    5. //用图片作为列表符号
    6. 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对象,它实现了如下接口:

    隐藏行号 复制代码 ?这是一段程序代码。
    1. public interface IPdfPageEvent
    2. {
    3.  void OnChapter(PdfWriter writer, Document document, float paragraphPosition, Paragraph title);
    4.         void OnChapterEnd(PdfWriter writer, Document document, float paragraphPosition);
    5.         void OnCloseDocument(PdfWriter writer, Document document);
    6.  void OnEndPage(PdfWriter writer, Document document);
    7.   void OnGenericTag(PdfWriter writer, Document document, Rectangle rect, string text);
    8.      void OnOpenDocument(PdfWriter writer, Document document);
    9.  
    10.         void OnParagraph(PdfWriter writer, Document document, float paragraphPosition);
    11.  
    12.        void OnParagraphEnd(PdfWriter writer, Document document, float paragraphPosition);
    13.  
    14.         void OnSection(PdfWriter writer, Document document, float paragraphPosition, int depth, Paragraph title);
    15.  
    16.        void OnSectionEnd(PdfWriter writer, Document document, float paragraphPosition);
    17.  
    18.         void OnStartPage(PdfWriter writer, Document document);
    19.  
    20.     }

    可以在这里面实现。

    十、章节(Chapter)和区域(Section)

    章节的使用就比较少了,并且不太好控制,这就不作说明

    十一、书签

    简单创建书签,使用如下代码:

    隐藏行号 复制代码 ?这是一段程序代码。
    1. protected PdfOutline SetDestination(PdfOutline root, Chunk chk, string name, string destination)
    2. {
           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是注册中文语言支持,后面是注册系统下的一些中文字体文件。

    十三、文字、表格、图像混排

    在进行文字、表格、图像混排中,有时比较难控制位置,最好是把文字、表格、图像分别放到不同的段落中,这样才能很好控制位置。

    十四、表单写入

    读取表单中的域:

    隐藏行号 复制代码 ?这是一段程序代码。
    1. public static Dictionary<string, string> ReadForm(string pdfTemplate)
      
    2.         {
      
    3.             Dictionary<string, string> dic = new Dictionary<string, string>();
      
    4.             PdfReader pdfReader = null;
      
    5.             try
      
    6.             {
      
    7.                 pdfReader = new PdfReader(pdfTemplate);
      
    8. 
      
    9. 
      
    10.                 AcroFields pdfFormFields = pdfReader.AcroFields;
      
    11.                 foreach (KeyValuePair<string, AcroFields.Item> de in pdfFormFields.Fields)
      
    12.                 {
      
    13.                     dic.Add(de.Key, "");
      
    14.                 }
      
    15. 
      
    16.             }
      
    17.             finally
      
    18.             {
      
    19.                 if (pdfReader != null)
      
    20.                 {
      
    21.                     pdfReader.Close();
      
    22.                 }
      
    23.             }
      
    24. 
      
    25.             return dic;
      
    26.         }
      

    对表单中的域进行填充:

    隐藏行号 复制代码 ?这是一段程序代码。
    1. public static void FillForm(string pdfTemplate, string newFile, Dictionary<string, string> dic)
      
    2.         {
      
    3.             PdfReader pdfReader = null;
      
    4.             PdfStamper pdfStamper = null;
      
    5.             try
      
    6.             {
      
    7.                 pdfReader = new PdfReader(pdfTemplate);
      
    8.                 pdfStamper = new PdfStamper(pdfReader, new FileStream(
      
    9.                  newFile, FileMode.Create));
      
    10. 
      
    11. 
      
    12.                 AcroFields pdfFormFields = pdfStamper.AcroFields;
      
    13.                 foreach (KeyValuePair<string, string> de in dic)
      
    14.                 {
      
    15.                     pdfFormFields.SetField(de.Key, de.Value);
      
    16.                 }
      
    17. 
      
    18.                 pdfStamper.FormFlattening = true;
      
    19.             }
      
    20.             finally
      
    21.             {
      
    22.                 if (pdfReader != null)
      
    23.                 {
      
    24.                     pdfReader.Close();
      
    25.                 }
      
    26. 
      
    27.                 if (pdfStamper != null)
      
    28.                 {
      
    29.                     pdfStamper.Close();
      
    30.                 }
      
    31.             }
      
    32.         }
  • 相关阅读:
    SIEM思考
    PowerDesigner15在生成SQL时报错Generation aborted due to errors detected during the verification of the mod
    Mongo驱动
    RsysLog
    vi全局替换方法
    RPM
    hdu1195 Open the Lock (DFS)
    调制:调幅(AM)与调频(FM)
    调制:调幅(AM)与调频(FM)
    追本溯源 —— 诗词、名言
  • 原文地址:https://www.cnblogs.com/Alex80/p/8116822.html
Copyright © 2011-2022 走看看