最近在工作中遇到一个问题,客户要求将系统中的表格全部导出成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:\windows\fonts\simsun.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 里,所有宽度、高度的单位都是磅,这也是排版时要用到的单位。

pdf生成(itextSharp)的更多相关文章

  1. 赞!jsPDF – 基于 HTML5 的强大 PDF 生成工具

    jsPDF 是一个基于 HTML5 的客户端解决方案,用于生成各种用途的 PDF 文档.使用方法很简单,只要引入 jsPDF 库,然后调用内置的方法就可以了.浏览器兼容性: IE 10, Firefo ...

  2. jsPDF – 基于 HTML5 的强大 PDF 生成工具

    jsPDF 是一个基于 HTML5 的客户端解决方案,用于生成各种用途的 PDF 文档. 使用方法很简单,只要引入 jsPDF 库,然后调用内置的方法就可以了. 米扑科技项目用到了HHTML5生成PD ...

  3. 开发笔记:PDF生成文字和图片水印

    背景 团队手里在做的一个项目,其中一个小功能是用户需要上传PDF文件到文件服务器上,都是一些合同或者技术评估文档,鉴于知识版权和防伪的目的,需要在上传的PDF文件打上水印, 这时候我们需要提供能力给客 ...

  4. C#使用iTextSharp+ZXing.Net+FreeSpire.PDF生成和打印pdf文档

    项目需求(Winform)可以批量打印某个模板,经过百度和摸索,使用iTextSharp+ZXing.Net+FreeSpire.PDF三个类库实现了生成pdf.生成条形码和打印pdf功能. 首先在项 ...

  5. create pdf file using Spire.Pdf or iTextSharp or PdfSharp

    Spire.Pdf: 注:pdf 显示中文一定要设置相应的中文字体,其他外文类似.否则显示为乱码( 如果繁体的服务器上生成的中文内容PDF文档,在简体操作系统保存或并传给简体系统上查看,会存在乱码问题 ...

  6. pdf生成

    要用本文的方法生成PDF文件,需要两个控件:itextsharp.dll和ICSharpCode.SharpZipLib.dll,由于示例代码实在太多,我将代码全部整理出来,放在另外一个文件“示例代码 ...

  7. PDF生成类库

    from:https://blog.csdn.net/plean/article/details/8097015 最近忙了两个星期的任务了     iTextSharp.dll是个开源的用于生成pdf ...

  8. .Net 对于PDF生成以及各种转换的操作

    前段时间公司的产品,要做一个新功能,签章(就是把需要的数据整理成PDF很标准的文件,然后在盖上我们在服务器上面的章) 然后我就在百度上找了找,发现搞PDF的类库很少,要么就要钱,要么就有水印,破解版的 ...

  9. PDF 生成插件 flying saucer 和 iText

    最近的项目中遇到了需求,用户在页面点击下载,将页面以PDF格式下载完成供用户浏览,所以上网找了下实现方案. 在Java世界,要想生成PDF,方案不少,所以简单做一个小结吧. 在此之前,先来勾画一下我心 ...

随机推荐

  1. javascript同步和异步的区别与实现方式

    javascript语言是单线程机制.所谓单线程就是按次序执行,执行完一个任务再执行下一个. 对于浏览器来说,也就是无法在渲染页面的同时执行代码. 单线程机制的优点在于实现起来较为简单,运行环境相对简 ...

  2. C#冒泡排序程序

    考虑到很多面试可能会考察冒泡排序的用法,所以特地花时间厘清了一下思路.下面说一下我的思路:冒泡排序核心就是比较方法,冒泡排序的比较方法顾名思义就是像气泡一样,最大(或者最小)的数往上冒.普通比较几个数 ...

  3. 基于 muse-ui 封装一个微信公众号上传插件 实现多图上传

    Vue.component('my-wx-upload', { template: ` <mu-grid-list :cols="3" :cellHeight="9 ...

  4. JS - OOP-继承的最佳实现方式

    如上图,使用第三种方式实现继承最好,也就是加了下划线的. 但是Object.create方法是ES6才支持的,所以,右边就写了一个实现其同样功能的函数.

  5. ls显示前几行或后几行数据

    显示前3行数据 ls -l|head -n 3 显示后3行数据 ls -l|tail -n 3

  6. nginx安装php环境

    1.php下载地址 https://secure.php.net/downloads.php(此次安装版本为7.0.33) 2.安装依赖的包 yum -y install libxml2 yum -y ...

  7. 20190102(多线程,守护线程,线程互斥锁,信号量,JoinableQueue)

    多线程 多进程: 核心是多道技术,本质上就是切换加保存技术. 当进程IO操作较多,可以提高程序效率. 每个进程都默认有一条主线程. 多线程: 程序的执行线路,相当于一条流水线,其包含了程序的具体执行步 ...

  8. Disharmony Trees HDU - 3015

    Disharmony Trees HDU - 3015 One day Sophia finds a very big square. There are n trees in the square. ...

  9. 51nod_1199 树的先跟遍历+区间更新树状数组

    题目是中文,所以不讲题意 做法顺序如下: 使用先跟遍历,把整棵树平铺到一维平面中 使用自己整的区间更新树状数组模板进行相关操作. http://www.cnblogs.com/rikka/p/7359 ...

  10. VBA连接MySQL数据库以及ODBC的配置(ODBC版本和MySQL版本如果不匹配会出现驱动和应用程序的错误)

    db_connected = False '获取数据库连接设置dsn_name = Trim(Worksheets("加载策略").Cells(2, 5).Value)  ---- ...