zoukankan      html  css  js  c++  java
  • 使用iTextSharp修改PDF文件

    最近因为工作需要,公司让我写个小程序,对大量的PDF文件(超过2500份)的页眉页脚等区域进行修改。简单地说,就是将一批PDF文档的页眉页脚进行修改,然后生成新的PDF文件。

    这种事情虽然以前没做过,但没办法,谁让我们只是个程序员呢,想继续拿工资,就、就开始工作吧……

    首先,Google一下相关资料,得到了以下一些信息:
    1、Acrobat提供7.0版PDF的SDK;
    2、国内有个由Acrobat公司支持的,专业讨论PDF的论坛:http://www.chinapdf.com/pdfbbs/index.asp
    3、网上有一大堆可以操作PDF文件的库呀、程序呀的,完全收费的(Aspose)、半收费的(PDFLib,对个人使用免费,对商业应用收费)、开源的(iTextSharpPDFBox)都有;
    4、PDF是公开标准的电子文档格式,全世界并不仅仅只有Acrobat公司可以发布相关的产品,其它很多、很多公司都有相关的产品。
    5、国内也有一大批人在研究PDF文件,并有自己的产品,可惜的是好像大部分都是收费的,好像还不便宜。(这是第一次发现,原来国内有这么多人在研究PDF呀,长见识了)

     作为一个程序员,首先当然是试用SDK,从网上DOWN了来,好大呀,60M哦,看看附带的几篇文档,都是近千页的,还是全英文的,看得累呀。难怪网上有人说:如果看SDK的话,没有3个月,基本上写不了什么程序的。
    虽然使用SDK是最正宗、最强大、最能控制每一个细节的方法,但是,这次任务时间相当紧迫(只有3-5天的时间),看来这条路是行不通了。

    第二条路,试用收费的Aspose,这个东东确实不错,可惜过于复杂,对我现的任务也大材小用了。最最重要的是,估计公司不会掏钱买的(只是一次性的任务,这个程序估计以后就没多少机会可以用了),但是公司又不允许使用D版的。

    算了,看开源的吧。

    重量级人物出场了:iTextSharp,鼓掌。。。。。

       这个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的应用就基本上只有对文件进行互相转换,没用过其它太多的功能,没有经验呀。

     好吧,终于要引入正题了,DHL公司将物流面单以pdf文件流的形式返回,就是我们可以保存成Pdf文件,这样就容易处理的多,我们自己的拣货面单,如果也可以保存至Pdf文件,这样就可以将这2个Pdf文件一块打印出来,这算是一个折中的解决方案。

      首先,要将拣货数据(含图片)保存至Pdf文档。技术点如下

        1, 将DataTable导出至Pdf

        2, DataTable中包含图片,也要能导出

        3, Pdf类库,字体的引入(居然需要引用字体的绝对路径,想不通)

      下面分别讲解

        1, 这点就是直接创建iTextSharp中的PdfDataTable对象,直接映射即可。代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    private static pdfText.pdf.PdfPTable CreatePdfPTableToPickupLabel(List<TradeDetailModel> listDetail, iTextSharp.text.Font font)
    {
        pdfText.pdf.PdfPTable pdtTable = new pdfText.pdf.PdfPTable(5);
        <strong>pdtTable.WidthPercentage = 95;      //占宽度百分比:95%(这句很关键,作用是撑满整个面单)</strong>
     
        int[] colWidth = { 2, 4, 2, 2, 2 }; //设置列宽比例
        pdtTable.SetWidths(colWidth);
     
        //此处,先插入首行,即标题
        pdtTable.AddCell(new iTextSharp.text.Phrase("图片", font));
        pdtTable.AddCell(new iTextSharp.text.Phrase("基本信息", font));
        pdtTable.AddCell(new iTextSharp.text.Phrase("单价", font));
        pdtTable.AddCell(new iTextSharp.text.Phrase("数量", font));
        pdtTable.AddCell(new iTextSharp.text.Phrase("备注", font));
     
        //再插入真实拣货数据
        int rowCount = listDetail.Count;
        for (int i = 0; i < rowCount; i++)
        {
            TradeDetailModel modelDetail = listDetail[i];
     
            iTextSharp.text.Image image = PdfUtil.CreatePdfImage(modelDetail.ProductImageBytes);
            pdtTable.AddCell(image);
            pdtTable.AddCell(new iTextSharp.text.Phrase(modelDetail.ProductBase, font));
            pdtTable.AddCell(new iTextSharp.text.Phrase(modelDetail.Price.ToString() + Environment.NewLine + modelDetail.ProductID, font));
            pdtTable.AddCell(new iTextSharp.text.Phrase(modelDetail.Number.ToString() + Environment.NewLine + modelDetail.ProductSpec, font));
            pdtTable.AddCell(new iTextSharp.text.Phrase(modelDetail.Remark, font));
        }
     
        return pdtTable;
    }

        2, 这点要先生成iTextSharp中Image对象才可以,然后再随生成PdfDataTable中,将Image对象插入单元格(注意:图片尺寸需要定义好)。代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    /// <summary>
    /// 创建Pdf所需图像
    /// </summary>
    /// <param name="imageBytes"></param>
    /// <param name="widthS"></param>
    /// <param name="heightS"></param>
    /// <returns></returns>
    private static iTextSharp.text.Image CreatePdfImage(byte[] imageBytes, float widthS = 60f, float heightS = 60f)
    {
        iTextSharp.text.Image image = iTextSharp.text.Image.GetInstance(imageBytes);
     
        //图片大小要求统一80x80,需根据实际图片       
        float perW = (float)Math.Round(widthS / image.Width, 2);
        float perH = (float)Math.Round(heightS / image.Height, 2);
        image.ScalePercent(perW * 100, perH * 100);
     
        //设置Dpi值,能够清晰些
        image.SetDpi(124, 124);
     
        return image;
    }  

        3, 创建字体

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    /// <summary>
    /// 创建Pdf所需字体
    /// </summary>
    /// <returns></returns>
    public static iTextSharp.text.Font CreatePdfFont(float fontSize = 16F)
    {
        //黑体
        string fontPath = @"C:WindowsFontssimhei.ttf";
     
        iTextSharp.text.pdf.BaseFont baseFont = iTextSharp.text.pdf.BaseFont.CreateFont(fontPath,
            iTextSharp.text.pdf.BaseFont.IDENTITY_H,
            iTextSharp.text.pdf.BaseFont.NOT_EMBEDDED);
        iTextSharp.text.Font font = new iTextSharp.text.Font(baseFont, fontSize);
     
        return font;
    }

        下面是调用的代码,仅供参考。注:返回的Pdf文件的绝对路径,用于后续打印

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    /// <summary>
    /// 生成拣货标签pdf文件
    /// </summary>
    /// <param name="modelTrade"></param>
    /// <param name="dhlLabelFolderName"></param>
    /// <returns></returns>
    public static string CreatePickupLabel(TradeModel modelTrade,string dhlLabelFolderName)
    {
        //参考地址
        //https://www.cnblogs.com/yangy1989/p/5300304.html
        //http://blog.csdn.net/lideyuans/article/details/51536676 (设置图片百分比)
     
        //组装,待生成的pdf文件完整路径
        string logisticsNo = modelTrade.R;
        string pdfFileName = dhlLabelFolderName + logisticsNo + "_Label.pdf";
     
        FileStream fs = new FileStream(pdfFileName, FileMode.Create);
        pdfText.Rectangle rect = new pdfText.Rectangle(0f, 0f, 1000f, 1000f);
        pdfText.Document document = new pdfText.Document(rect, 5f, 5f, 5f, 5f);
     
        //创建字体
        iTextSharp.text.Font font = PdfUtil.CreatePdfFont(34F);
     
        pdfText.pdf.PdfWriter pdfWriter = pdfText.pdf.PdfWriter.GetInstance(document, fs);
        document.Open();
     
        //增加拣货单品列表
        pdfText.pdf.PdfPTable pdfpTable = PdfUtil.CreatePdfPTableToPickupLabel(modelTrade.Details, font);
        document.Add(pdfpTable);
     
        //增加品名备注           
        pdfText.Paragraph pgraph1 = new pdfText.Paragraph(modelTrade.ItemsDescription, font);
        document.Add(pgraph1);
     
        //增加:发货期限
        font.Color = pdfText.BaseColor.RED;
        pdfText.Paragraph pgraph2 = new pdfText.Paragraph(modelTrade.ShipLimitTimeText, font);
        document.Add(pgraph2);
     
        document.Close();
        fs.Close();
     
        return pdfFileName;
    }

      

      再将这2个文件,通过C#代码连续打印出来。支持连续打印多个Pdf文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //直接调用cmd命令,实现直接打印
    foreach (string printFile in listPrintFile)
    {
        Process proc = new Process();
        proc.StartInfo.CreateNoWindow = false;
        proc.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
        proc.StartInfo.UseShellExecute = true;
        proc.StartInfo.FileName = printFile;
        proc.StartInfo.Verb = "print";
        proc.Start();
        proc.Close();
    }

      

      好了,最后的最后,看看效果图吧。前2联是DHL返回的Pdf文件,第3联是我本地生成的Pdf文件,合并成一个完整的发货面单

    https://www.oschina.net/p/itextsharp

  • 相关阅读:
    perl 添加主机
    java将阿拉伯数字转换为中文数字
    使用EL调用Java方法
    Perl 检索zabbix 主机
    perl和curl 模拟post 发送json数据
    Caused by: java.lang.ClassNotFoundException: org.eclipse.jetty.continuation.ContinuationThrowable
    3.4. JVM Agent
    perl JMX::Jmx4Perl::Manual 说明
    perl 安装 JMX::Jmx4Perl 需要版本5.10.1
    Chapter 2.Jolokia Architecture 结构:
  • 原文地址:https://www.cnblogs.com/Alex80/p/13701228.html
Copyright © 2011-2022 走看看