• pdf生成(itextSharp)


    最近在工作中遇到一个问题,客户要求将系统中的表格全部导出成PDF格式。经过搜索,基本是三种思路:

    1. 直接用byte写PDF文件。(算你狠,霸王硬上弓)
    2. 通过Com组件转换。以Adobe Acrobat为典型代表,先转换为PS文件再通过虚拟打印机生成PDF。
    3. 通过现有的组件,其中以iTextSharp为代表(不过我也没有找到其他的组件,汗一个……)。

    基本上第一个方法是行不通的,不但需要研究PDF格式,而且就算写成了,也基本相当于重复发明了轮子。第二种方法稍好,不过正规的方法是要去买一套正版装在服务器上,但是费用的问题是需要和客户讲清楚的。暂时也不考虑。第三种方法,看起来最可行。iTextSharp是一个移植自Java平台iText的项目,采用GPL许可证发布,如果直接采用其DLL而不使用源代码的话,应该不用公开我们自己的代码(哪位看官对GPL许可证比较有研究的还望指点指点)。下面是其简介。

    iText# (iTextSharp) is a port of the iText open source java library written entirely in C# for the .NET platform. iText# is a library that allows you to generate PDF files on the fly. It is implemented as an assembly.

    经过一段时间的研究,略有心得,写出来一是备忘,另一方面,与大家分享,共同讨论。

    在我的应用场景中,有以下几点需求:

    1. 中文支持。毕竟是在中国,汉字输出的问题不可避免,而这点是个前提条件,如果不能正确输出汉字,那也就没有使用的价值了。
    2. 表格。我遇到的需求就是把所有的Excel表格导出成PDF。这也是个前提条件,如果不能原样输出表格的话,也就没法使用了。
    3. 图片。表格中可能有图片,需要将图片显示在正确的单元格里。
    4. 页面设置。包括打印的纸张大小、纵打横打设置、页边距设置等。这点最好能够满足,如果不能,就只有看客户的心情了,影响还是比较大。
    5. PDF文件本身的设置。如能不能修改等安全设置已经PDF文件的信息。这点的影响就不大了。
    6. 其他一些细微的内容,就不再赘述了。

    我的目标,就是要封装iTextSharp,为系统提供一个简单的接口生成PDF文件。我所使用的dll,最初是4.1.X,后来直接升级到5.0.4版本。至于原因,在后文会提到。

    要想生成一份PDF文档,首先要创建一个Document对象。

    Document pdf = new Document(PageRectangle,MarginLeft,MarginRight,MarginTop,MarginBottom);

    从这里我们可以得到几个信息。

    一是,我们创建了一个 Document 对象,为什么不是常理的PDFDocument类似的对象呢?这就是设计上的抽象。从源代码可以看出,在iTextSharp.text.pdf命名空间下确实有 PdfDocument 类,而这个类确实是从 Document 类继承来的。但,它的构造函数却是internal的,也就是不能从组件外访问,只能在组件内访问。这样做的原因在于,iTextSharp 不仅能生成 PDF 文件,也能生成HTML、RTF、XML文件。要生成这么多种格式,如果在每种生成方式中都要保留一份 Document 的话,如果需要同时生成多种格式的文档,就需要将生成过程重复多次,而且,如果要再加入一种文档格式,就有可能要对Document类进行修改,以加入新格式所需的一些内容,而这种操作极有可能会影响到已有文档格式的代码。还有一个原因是,如果一份文件需要根据一定的条件(比如权限)显示不同的内容,那么就需要创建更多的文档内容拷贝。所以,iTextSharp采用了这样一种模式。Document 记录着所有的 Writer 所持有的 Document,Document 中增加元素的时候,并没有实际的内容,只是将增加的操作转发给所有的 Writer, 通过不同Writer,就可以生成不同格式的文档,加入新的文档格式,也只需要加入新的Writer,对Document的修改也不会影响已有的Writer。

    二是,Document 的内容是限制在一个 Rectangle 中,这个 Rectangle 就是纸张的大小。组件内置了几种常用的纸张大小,如A3、A4等都是内置的。这点也就解决了前面提到的第四个要求中提到的内容。默认情况是纵向打印,如果要求横打,只需要调用 Rectangle 的Rotate 方法即可。

    有了 Document,接下来要创建一个Writer,在此,我需要一个 PdfWriter。

    PdfWriter writer = PdfWriter.GetInstance(pdf,this._stream);

    如果我需要需要根据一定的条件(比如权限)显示不同的内容,就可以创建多个 Writer,在特定的条件下让特定的 Writer Pause(组件提供了Pause方法),然后在合适的时机让它 Resume 即可。在Pause后Document中增加的内容就不会被增加到Stream中。Writer 构造函数中的第二个参数,是一个 Stream,很显然,写入流中好处多多。但要注意,如果流是由组件外部传入的,那么需要设置 writer.CloseStream = false,不要随意关闭外部传入的流。

    接下来,可以给 Writer 设置一些属性,如 PDF 的版本号、PDF 文件的密码和访问权限、甚至是阅读器的参数。

    //1.6版本
    writer.PdfVersion = PdfWriter.VERSION_1_6;
    //没有密码,但只能打开和打印
    writer.SetEncryption(null,null,PdfWriter.AllowCopy|PdfWriter.AllowPrinting,true);
    //设置阅读器的参数。单列显示,不显示大纲和缩略图
    writer.ViewerPreferences = PdfWriter.FitWindow
                        | PdfWriter.PageLayoutOneColumn
                        | PdfWriter.PageModeUseNone;

    接下来,可以继续设置一些 PDF 文件的属性,如公司、文档标题等。之后就可以调用 Document.Open 方法打开文档,向里面写内容了。要注意的是,设置文档和 Writer 的属性一定要在 Document Open 之前做(好像只有设置 Document 的属性是必须在 Open 之前做,我不太记得了,不过为了保险起见,都先设置好了再 Open 吧)。这点可能是为了满足 PDF 格式的要求而又要照顾生成的速度才做出的一个限制。

    Open 之后,就可以向 Document 中填充内容了,文字、表格、图片等内容都可以。填充完内容之后,要调用 Document.Close 方法,之后各个Writer 就会生成指定的文档。

    好了,大致的流程就是这样。接下来重点说一下如何增加一个表格、增加图片以及如何输出中文。

    先说说如何输出中文。之所以按照普通方法增加的中文在 PDF 文件中无法显示,是因为缺省的字体中缺少相应的中文字库。所以解决的方法也很简单,使用包含中文的字体就可以了。目前有两种思路,一种是使用 iTextSharp 提供的包含中文字体的DLL,一种是直接指定字体文件。我采用的是第二种方法,因为我需要不同的字体。

    BaseFont basefont = BaseFont.CreateFont(@“c:windowsfontssimsun.ttc,1”, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);

    iTextSharp.text.Font font = new iTextSharp.text.Font(font,size,style,color);

    这里不但指定了使用的字体,也同时设置了大小、样式、颜色。这就可以完全满足要求了。使用该 font 输出的中文一切正常。如果需要换不同的字体,指定不同的字体文件即可。需要指出的是,BaseFont.CreateFont 的第一个参数,在字体文件后不一定需要跟上“,1”。这取决于使用的字体文件是否支持多字体。例子中的 simsun.ttc 包含了“宋体”和“新宋体”两套字体,那么如果要使用这个字体文件,就必须指明使用哪一套字体,序号从0开始。但如果使用的字体文件只有一套字体,则一定不能加“,0”。否则,无论是多加还是少加,都会报错。要想知道字体文件是否是多套字体,只需双击打开字体文件,如果有导航的>>和<<按钮,则是有多套字体,否则就不是。

    接下来的任务是增加表格。在4.1.X版本的 iTextSharp 中,和表格有关的有三个类,Table、PdfTable、PdfPTable。根据网上搜索到的“唯一”一篇价值较高的文章(真的几乎是唯一的一篇,几乎所有人都在转载那篇文章),Table 最简单,PdfPtable 最复杂。而我的需求貌似比较复杂,所以我直接使用了PdfPTable。升级到5.0.4版本中,已经没有其他的两个类了(幸亏啊……)。在4.1.X版本中,有个非常无语的Bug,造成了一直等待其修正此Bug的情况。假如有下面的一个表格:

    1.1 1.2 1.3 1.4 1.5
    2.1 2.2 2.3 2.5
    3.1 3.2 3.5
    4.1 4.2 4.3 4.4 4.5
    5.1 5.2 5.3 5.4 5.5

    如上的表格在呈现的时候,3.5单元格就会被呈现在2.3单元格的右下角,也就是占据了3.4的位置,进而也就导致从4.1开始的单元格全部左移一位。无论如何修改PdfPTable和PdfPCell 的属性都没有任何改善。原因在于,在 PdfPtable 中,是没有 Row 的概念的,它的行是靠增加的单元格来计算得到的,而不像 HTML 的 Table 那样先增加一行再在行内增加单元格。在这种思路下,很明显这应该是增加单元格在计算 Row 时候的一个 Bug。这个 Bug 在5.0.4版中已经被修复了(之前的版本是否修复了我不清楚)。这个问题搞定了以后,表格的使用基本上就没什么问题了,都是别人封装好的功能,直接就可以拿来用了。

    首先生成一个表格:

    PdfPTable pdftable = new PdfPTable(n);

    注意参数,生成表格时必须指定有多少列。原因也是上面提到的,只有单元格的概念没有行的概念,行是计算得到的。指定了列以后,就可以计算有多少行。

    通过 PdfTable 的 DefaultCell 属性,就可以设置一些默认的样式,如对齐方式等。不过好像不起什么作用。接下来可以设置表格的宽度。PdfPTable 的宽度分为三种:绝对宽度、相对宽度、百分比宽度。绝对宽度要设置TotalWidth属性,相对宽度要调用SetWidths方法,百分比宽度要设置WidthPercentage属性。其中设置相对宽度需要传入各列宽度的数组,要注意的是传入的实际是个各列的相对宽度,也就是以某个值为基数的比值,当然可以直接传入各列的绝对宽度,PdfPTable 会自动计算各列的百分比。绝对宽度和百分比宽度应该是只有一个起作用,但目前还没看出来以哪个宽度为准,可能是在最后生成的时候吧。

    接下来就要生成一个个的单元格并加入到表格中了。下面就是生成一个文字单元格的代码:

    Phrase text1 = new Phrase(text,font);
    PdfPCell cell = new PdfPCell(text1);
    cell.VerticalAlignment = VA;
    cell.HorizontalAlignment = HA;
    cell.Padding = 0.5F;
    //cell.FixedHeight = height;
    cell.MinimumHeight = height;

    要注意的是注释掉的一句,它如果被取消注释了,那么就意味着,如果单元格不足以容下其内容,也不会换行,不够显示的内容就被隐藏了。

    如果想在单元格中放一个图片,可以像下面这样做:

    iTextSharp.text.Image img1 = iTextSharp.text.Image.GetInstance(img,iTextSharp.text.BaseColor.WHITE);
    if (img.Width>cell.Width || img.Height>cell.Height)
    {
        img1.ScaleToFit(cell.Width, cell.Height);
    }
    cell.FixedHeight = cell.MinimumHeight;
    cell.Image = img1;

    GetInstance 的 img 参数是 System.Drawing 中的 Image。如果你不希望单元格被撑大,就要设置其 FixedHeigh。如果还想看整个图片,就需要调用 ScaleToFit 方法。

    有了 cell,直接调用PdfPTable.AddCell 方法就可以了。

    最后再说一点,ITextSharp 里,所有宽度、高度的单位都是磅,这也是排版时要用到的单位。

  • 相关阅读:
    OutputCache 缓存key的创建 CreateOutputCachedItemKey
    Asp.net Web Api源码调试
    asp.net mvc源码分析DefaultModelBinder 自定义的普通数据类型的绑定和验证
    Asp.net web Api源码分析HttpParameterBinding
    Asp.net web Api源码分析HttpRequestMessage的创建
    asp.net mvc源码分析ActionResult篇 RazorView.RenderView
    Asp.Net MVC 项目预编译 View
    Asp.net Web.config文件读取路径你真的清楚吗?
    asp.net 动态创建TextBox控件 如何加载状态信息
    asp.net mvc源码分析BeginForm方法 和ClientValidationEnabled 属性
  • 原文地址:https://www.cnblogs.com/bit-by-bit/p/3975056.html
Copyright © 2020-2023  润新知