C# html生成PDF遇到的问题,从iTextSharp到Pechkin再到TuesPechkin
我们的网站业务会生成一个报告,用网页展示出来,要有生成pdf并下载的功能,关键是生成pdf。
用内容一段段去拼pdf,想想就很崩溃,所以就去网上找直接把html生成pdf的方法。
网上资料大部分都是用的iTextSharp的XMLWorkerHelper做的(代码我贴在后面),遇到的问题是,它对css样式的支持比较古老或者说简单,所以重新改了一下我的html样式,div大部分都换成了table等,搞定后运行了一段时间没出什么问题。
但是,后来发现它有一种情况会报错。我的html内容是一个订单包含多个小项,每个小项有自己的内容。如果小项的内容稍多,比如几千个字符,生成pdf时在分页那儿会陷入死循环。
由于我的小项一直被当成一整块,如果一页剩下的地方显示不完,它会整个挪到下一页,不会从中间截断,所以我猜测,如果被它判定一段内容是不能分割的,而内容的长度就已经超出了一整页的长度,就会出错。
我试图调整自己的内容的格式等等,让它不被判定为不可分割,没有成功,而且没有看到源码,最后没有解决问题,只好找另外的方式生成pdf。
第二次映入眼帘的是wkhtmltopdf,同时有人提到了Pechkin,是作者在wkhtmltopdf基础上开发的,更方便.NET开放使用,不过原版有个bug,有网友给出了修正版本。
这个的使用更简单,但是需要把几个dll库放到根目录。
这一次接入完成后没有出现内容长了就挂掉的情况,但是也有个毛病:
它的分页非常“硬”,有可能会把一行字从中间拦腰截断,分在上下两页。
两害相权取其轻,只能先用着了,我们经过多次调试确定了行高后也能保证大部分页面不会截断文字了,所以问题不大。
如果能确定内容不会太长,还是iTextSharp比较好。
Pechkin用了几年的时间我们出现了一个新的需求,使得我们需要调整页边距,没找到Pechkin的调整办法(可能有,反正我没查到),找到了另一个基于wkhtmltopdf开发的TuesPechkin,并且这个组件一直有人维护(Pechkin早就停止维护了),所以我们决定改用TuesPechkin。
改完后别的都没啥,唯一的问题就是我们的页面字体“微软雅黑”没应用上,搞了很久才发现是我们的页面代码写的是font-family:'Microsoft YaHei',在Pechkin没问题,在TuesPechkin要写成font-family:'Microsoft YaHei UI'才能识别。
TuesPechkin还有个优点是它会自动把图片识别为整体(不截断),但是如果图片太大一页都装不下,也不会报错会正常截断。
下面是三种方式的使用代码:
iTextSharp:
1 public class PDFHelper 2 { 3 public byte[] ConvertHtmlTextToPDF(string htmlText) 4 { 5 if (string.IsNullOrEmpty(htmlText)) 6 { 7 return null; 8 } 9 //避免当htmlText无任何html tag标签的纯文字时,转PDF时会挂掉,所以一律加上<p>标签 10 htmlText = "<p>" + htmlText + "</p>"; 11 MemoryStream outputStream = new MemoryStream();//要把PDF写到哪个串流 12 byte[] data = Encoding.UTF8.GetBytes(htmlText);//字串转成byte[] 13 MemoryStream msInput = new MemoryStream(data); 14 Document doc = new Document();//要写PDF的文件,建构子没填的话预设直式A4 15 16 PdfWriter writer = PdfWriter.GetInstance(doc, outputStream); 17 PdfDestination pdfDest = new PdfDestination(PdfDestination.XYZ, 0, doc.PageSize.Height, 1f); 18 //开启Document文件 19 doc.Open(); 20 HeaderAndFooterEvent header = new HeaderAndFooterEvent(); 21 header.tpl = writer.DirectContent.CreateTemplate(100, 100); 22 writer.PageEvent = header; 23 24 //使用XMLWorkerHelper把Html parse到PDF档里 25 XMLWorkerHelper.GetInstance().ParseXHtml(writer, doc, msInput, null, Encoding.UTF8, new UnicodeFontFactory()); 26 27 //将pdfDest设定的资料写到PDF档 28 PdfAction action = PdfAction.GotoLocalPage(1, pdfDest, writer); 29 writer.SetOpenAction(action); 30 doc.Close(); 31 msInput.Close(); 32 outputStream.Close(); 33 //回传PDF档案 34 return outputStream.ToArray(); 35 36 } 37 } 38 public class UnicodeFontFactory : FontFactoryImp 39 { 40 41 public override Font GetFont(string fontname, string encoding, bool embedded, float size, int style, BaseColor color, bool cached) 42 { 43 string FontPath = System.Web.Hosting.HostingEnvironment.MapPath("~/Font/"); 44 45 BaseFont bfYaHei = BaseFont.CreateFont(FontPath + "msyh.ttc,1", BaseFont.IDENTITY_H, BaseFont.EMBEDDED); 46 return new Font(bfYaHei, size, style, color); 47 } 48 } 49 50 public class HeaderAndFooterEvent : PdfPageEventHelper, IPdfPageEvent 51 { 52 public PdfTemplate tpl = null; 53 public bool PAGE_NUMBER = true; 54 private int PageCount = 1; 55 private static string FontPath = System.Web.Hosting.HostingEnvironment.MapPath("~/Font/"); 56 private static BaseFont bfYaHei = BaseFont.CreateFont(FontPath + "msyh.ttc,1", BaseFont.IDENTITY_H, BaseFont.EMBEDDED); 57 private static iTextSharp.text.Font font = new Font(bfYaHei, 10, Font.NORMAL, BaseColor.BLACK); 58 59 //重写 关闭一个页面时 60 public override void OnEndPage(PdfWriter writer, Document document) 61 { 62 if (PAGE_NUMBER) 63 { 64 Phrase footer = new Phrase("www.XXXX.com 第" + writer.PageNumber + "页/共 页", font); 65 PdfContentByte cb = writer.DirectContent; 66 67 //模版 显示总共页数 68 cb.AddTemplate(tpl, document.Right - 54 + document.LeftMargin, document.Bottom - 15);//调节模版显示的位置 69 70 71 //页脚显示的位置 72 ColumnText.ShowTextAligned(cb, Element.ALIGN_CENTER, footer, document.Right - 297 + document.LeftMargin, document.Bottom - 14, 0); 73 } 74 } 75 //重写 打开一个新页面时 76 public override void OnStartPage(PdfWriter writer, Document document) 77 { 78 if (PAGE_NUMBER) 79 { 80 PageCount += 1; 81 writer.PageCount = PageCount; 82 } 83 } 84 //关闭PDF文档时 85 public override void OnCloseDocument(PdfWriter writer, Document document) 86 { 87 tpl.BeginText(); 88 tpl.SetFontAndSize(bfYaHei, 10);//生成的模版的字体、颜色 89 tpl.ShowText(PageCount.ToString());//模版显示的内容 90 tpl.EndText(); 91 tpl.ClosePath(); 92 } 93 //定义输出文本 94 public static Paragraph InsertTitleContent(string text) 95 { 96 97 Paragraph paragraph = new Paragraph(text, font);//新建一行 98 99 paragraph.Alignment = Element.ALIGN_CENTER;//居中 100 101 paragraph.SpacingBefore = 5; 102 103 paragraph.SpacingAfter = 5; 104 paragraph.SetLeading(1, 2);//每行间的间隔 105 return paragraph; 106 } 107 }
Pechkin:
1 public static byte[] ConvertHtmlToPdf(string html) 2 { 3 4 try 5 { 6 using (IPechkin pechkin = Factory.Create(new GlobalConfig())) 7 { 8 ObjectConfig oc = new ObjectConfig(); 9 oc.SetPrintBackground(true) 10 .SetLoadImages(true).Footer.SetContentSpacing(30).SetLeftText("www.XXXX.com").SetRightText("[page]/[toPage]"); 11 byte[] pdf = pechkin.Convert(oc, html); 12 return pdf; 13 } 14 } 15 catch (Exception) 16 { 17 18 } 19 return null; 20 }
TuesPechkin:
public static byte[] ConvertHtmlToPdf(string html, string header = "", string footer = "") { try { var document = new HtmlToPdfDocument { GlobalSettings = { ProduceOutline = true, DocumentTitle = "标题", PaperSize =PaperKind.A4, // implicit conversion to pechkinpapersize Margins = { Top=9.5, Bottom=10, Left=0, Right=0 } }, Objects = { new ObjectSettings { HtmlText =html, WebSettings =new WebSettings { DefaultEncoding="utf-8", LoadImages=true, }, FooterSettings=new FooterSettings { CenterText="[page]/[toPage]" } } }, }; byte[] buf = Converter.Convert(document); return buf; } catch (Exception ex) { } return null; } private static IConverter Converter =new ThreadSafeConverter(new RemotingToolset<PdfToolset>(new WinAnyCPUEmbeddedDeployment(new TempFolderDeployment())));
大家若有更好的解决方式,希望赐教。