xhtmlrenderer 将html转换成pdf,完美css,带图片,手动分页,解决内容断开的问题

来源:xhtmlrenderer 将html转换成pdf,完美css,带图片,手动分页,解决内容断开的问题 - 煮过的花朵 - 博客园

之前用itext7将html导出为pdf,比较方便,代码较少,而且支持base64的图片。但是itext7是收费的,所以换成了xhtmlrenderer。

xhtmlrenderer自动引入依赖包itext2.0.8,而且不能再引入其他版本的itext,因为itext2.0.8是已经被废弃的,里面的很多方法在新版本已经没有了。

itext导出pdf最重要的4个难点:

1.css样式

2.中文不显示

3.图片(itext7支持比较好,不过要收费)

4.分页时内容断开的问题(itext7不会出现这种问题,不过要收费)

一、首先引入包

只需要这个就够了,它会自动引入itext2.0.8

<dependency><groupId>org.xhtmlrenderer</groupId><artifactId>core-renderer</artifactId><version>R8</version>
</dependency>

二、页面css样式的采集

  看过很多篇itext的文章,都没有达到想象中要求。大多是说将css路径改为绝对路径,或者将css写在页面中,这都不现实。真正的项目中,你的项目经理是不会让你这么做的。

所以我找到一个能将页面所有css采集起来的js方法。传入你的标签的id,返回一个包含该id的区域的所有css样式 ,加上html,head和body标签,组成一个html的字符串。将字符串传给后台去生成pdf。值得注意的是我加了这个字体body{font-family: SimSun;},这个字符是中文字体,后端必须与前端一致。且看后面。

function getElementChildrenAndStyles(selector) {var html = $(selector).prop("outerHTML");selector = selector.split(",").map(function(subselector){return subselector + "," + subselector + " *";}).join(",");elts = $(selector);var rulesUsed = [];//文档的所有样式表sheets = document.styleSheets;for(var c = 0; c < sheets.length; c++) {// rules 和 cssRules 的计数方法也是不一样的!rules 是第几个选择器;cssRules 是第几条规则,// 分别用于IE7和chromevar rules = sheets[c].rules || sheets[c].cssRules;for(var r = 0; r < rules.length; r++) {//selectorText: $节点var selectorText = rules[r].selectorText;var matchedElts = $(selectorText);//找到dom节点里所有节点,并将其push到数组里for (var i = 0; i < elts.length; i++) {if (matchedElts.index(elts[i]) != -1) {rulesUsed.push(rules[r]); break;}   }}}//重组stylevar style = rulesUsed.map(function(cssRule){if (cssRule.style) {var cssText = cssRule.selectorText+'{'+cssRule.style.cssText.toLowerCase()+'}';} else {var cssText = cssRule.selectorText+'{'+cssRule.cssText+'}';}return cssText;}).join("\n");return "<html><head><meta charset='UTF-8'/> <style>\n"           + style           +"\n td{background:white!important;}"          +"\n body{font-family: SimSun;} \n</style>\n\n</head><body>"           + html+"</body></html>";}

 今天解决了分页的时候会断开内容的问题,解决方案就是手动分页,用js计算高度然后超过页面高度的就换页,这样就不会出现自动换页的时候内容断开了。

1.我将需要显示的元素都添加class= ‘pdf-page-range’    

2. class='pageNext'             .pageNext{page-break-after: always;} 这个css表示下一个元素将会换页,转pdf的时候itext会自动识别。

3.在前面的基础上插入以下代码即可,需要图片转换之后执行,

注意:这个修改了网页内容,如果想保留原网页内容,自行想办法 -。-!

//后端低版本的itext对分页的处理非常不友好,所以前端页面强制分页。//我将需要显示的元素都添加class= ‘pdf-page-range’//class='pageNext'   .pageNext{page-break-after: always;}  这个css表示下一个元素将会换页。function pdfPageRange(){var heigth= 0;   $(".pdf-page-range").each(function(){var $this = $(this);var $table = $this.find('table');var $next = $this.next();var $prevPage = $this.prev('.pageNext');index = $(".pageNext").length;var tagName = $this[0].tagName;var element_tag;if($table&&$table.length>0){element_tag = $table[0];}if(tagName=='table'||tagName=='TABLE'){element_tag = $this[0];}if(element_tag){heigth = tablePage($(element_tag),heigth)return true;}//不是table的处理heigth +=  $this[0].offsetHeight;if(heigth>1000){$this.before("<div class='pageNext' ></div> ");heigth = $this[0].offsetHeight;}});}//table单独算高度function tablePage($table,heigth){var $trList = $table.find('tr');var $thead = $table.find('tr.thead');$trList.each(function(){heigth += $(this)[0].offsetHeight;if(heigth>1000){$(this).before($thead.prop("outerHTML"));$thead_add = $(this).prev().prev();$thead_add.addClass('pageNext');heigth = $(this)[0].offsetHeight+$thead_add[0].offsetHeight;}});return heigth;}
});

三、图片的支持

  项目中有很多Echarts做的图表,这个生成的图表都是canvas标签,而itext是不支持canvas标签的。所以要把图表全部换成base64的img标签。这里引入一个js。

html2canvas.js,它能将制定区域截图。请看以下。

注意:

1.html2canvas()方法返回的是Promise类型,为什么要将所有 html2canvas()方法的返回值集中起来然后使用Promise.all(canvasArray).then()方法。因为html2canvas()是异步的,你的下面的js已经处理完了,它可能还没截图完成。Promise.all(canvasArray).then()方法,会在所有截图已经完成之后执行。所以我把ajax请求放在里面。(请看代码)

2. img标签闭合的问题,img标签是自闭合标签。正常情况下,浏览器不会去识别你的img的闭合标签,即使你的img标签有</img>或<img  src=""  />,浏览器最后显示还是<img>,  所以我用一个字符串代替“/”, 后台再用“/”代替这个字符串,你也可以前端就替换。(请看代码) 

3.必须给img加上宽度和高度,不然被后台转换之后尺寸会变得很小。

$("#itextpdf").click(function(){var canvasArray = [];$(".charts").each(function(){var $this=$(this);var canvasIndex = html2canvas($this,{   scale: 5,background: '#FFFFFF',onrendered:function(canvas){var imgBase64 = canvas.toDataURL('image/jpeg', 1.0);$this.html("");//  标签被jquery获取后,自定义属性closingtags会变成closingtags="",你可以加个css将图片隐藏起来,然后在html字符串里面再加一个显示的css。$this.append ("<img  class=“hidden” alt='' src='"+ imgBase64+"' closingtags  > ") } });canvasArray.push(canvasIndex);});       Promise.all(canvasArray).then(function () {var str = getElementChildrenAndStyles('#basket');$.post("/ecloud/sa/saerrorquestions/exportpdf.do",{"str":str  },function(r){});   });});
$("#itextpdf").click(function(){var canvasArray = [];$(".charts").each(function(){var $this=$(this);var canvasIndex = html2canvas($this,{   scale: 5,background: '#FFFFFF',onrendered:function(canvas){var imgBase64 = canvas.toDataURL('image/jpeg', 1.0);$this.html("");//  标签被jquery获取后,自定义属性closingtags会变成closingtags="",你可以加个css将图片隐藏起来,然后在html字符串里面再加一个显示的css。$this.append ("<img  class=“hidden” alt='' src='"+ imgBase64+"' closingtags  > ") } });canvasArray.push(canvasIndex);});       Promise.all(canvasArray).then(function () {var str = getElementChildrenAndStyles('#basket');$.post("/ecloud/sa/saerrorquestions/exportpdf.do",{"str":str  },function(r){});   });});

 四、后台代码

   项目中引入中文字体,html字符串中也必须引入。我的字体css是     body{font-family: SimSun;}

package cn.myc.ykt3.util;import java.io.FileOutputStream;
import java.io.OutputStream;import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;import com.lowagie.text.pdf.BaseFont;public class ItextHtmlTopdf  {/*** * @param htmlStr html字符串* @return* @throws Exception*/public String exportpdf(String htmlStr ) throws  Exception  {if (StringUtils.isBlank(htmlStr)) {return null;}htmlStr = htmlStr.trim().replaceAll("<","<").replaceAll( ">",">").replaceAll("<br/>","\n|\r\n|\r" ).replaceAll(" "," ");htmlStr= htmlStr.replace("closingtags=\"\"", "/");String classpath = this.getClass().getResource("/").getPath().replaceFirst("/", "");String webappRoot = classpath.replaceAll("/target/classes", "/src/main/webapp");//-----版本2.0.8ITextRenderer renderer = new ITextRenderer();OutputStream os = new FileOutputStream("C:/Users/Administrator/Desktop/createSamplePDF3.pdf");// 如果携带图片则加上以下两行代码,将图片标签转换为Itext自己的图片对象,Base64ImgReplacedElementFactory为图片处理类renderer.getSharedContext().setReplacedElementFactory(new Base64ImgReplacedElementFactory());renderer.getSharedContext().getTextRenderer().setSmoothingThreshold(1);renderer.setDocumentFromString(htmlStr);ITextFontResolver fontResolver = renderer.getFontResolver();// 解决中文支持问题,参数为字体的路径,html页面也必须引入字体fontResolver.addFont(webappRoot+"static/sanalysis/simsun.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);renderer.layout();renderer.createPDF(os);os.close();return null;}
}

Base64ImgReplacedElementFactory图片处理类

package cn.myc.ykt3.util;import java.io.IOException ;
import org.w3c.dom.Element ;
import org.xhtmlrenderer.extend.FSImage ;
import org.xhtmlrenderer.extend.ReplacedElement ;
import org.xhtmlrenderer.extend.ReplacedElementFactory ;
import org.xhtmlrenderer.extend.UserAgentCallback ;
import org.xhtmlrenderer.layout.LayoutContext ;
import org.xhtmlrenderer.pdf.ITextFSImage ;
import org.xhtmlrenderer.pdf.ITextImageElement ;
import org.xhtmlrenderer.render.BlockBox ;
import org.xhtmlrenderer.simple.extend.FormSubmissionListener ;
import com.lowagie.text.BadElementException ;
import com.lowagie.text.Image ;
import com.lowagie.text.pdf.codec.Base64 ;public class Base64ImgReplacedElementFactory implements ReplacedElementFactory {/*** 实现createReplacedElement 替换html中的Img标签* * @param c 上下文* @param box 盒子* @param uac 回调* @param cssWidth css宽* @param cssHeight css高* @return ReplacedElement*/public ReplacedElement createReplacedElement(LayoutContext c, BlockBox box, UserAgentCallback uac,int cssWidth, int cssHeight) {Element e = box.getElement();if (e == null) {return null;}String nodeName = e.getNodeName();// 找到img标签if (nodeName.equals("img")) {String attribute = e.getAttribute("src");FSImage fsImage;try {// 生成itext图像fsImage = buildImage(attribute, uac);} catch (BadElementException e1) {fsImage = null;} catch (IOException e1) {fsImage = null;}if (fsImage != null) {// 对图像进行缩放if (cssWidth != -1 || cssHeight != -1) {fsImage.scale(cssWidth, cssHeight);}return new ITextImageElement(fsImage);}}return null;}/*** 将base64编码解码并生成itext图像* * @param srcAttr 属性* @param uac 回调* @return FSImage* @throws IOException io异常* @throws BadElementException BadElementException*/protected FSImage buildImage(String srcAttr, UserAgentCallback uac) throws IOException,BadElementException {FSImage fsImage;if (srcAttr.startsWith("data:image/")) {String b64encoded = srcAttr.substring(srcAttr.indexOf("base64,") + "base64,".length(),srcAttr.length());// 解码byte[] decodedBytes = Base64.decode(b64encoded);fsImage = new ITextFSImage(Image.getInstance(decodedBytes));} else {fsImage = uac.getImageResource(srcAttr).getImage();}return fsImage;}/*** 实现reset*/public void reset() {}@Overridepublic void remove(Element arg0) {}@Overridepublic void setFormSubmissionListener(FormSubmissionListener arg0) {}
}

我的页面

导出的pdf效果,自动分页,并且分页不会强制裁剪图片区域。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/496192.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

商汤科技宣布C轮战略融资6亿美元 阿里领投苏宁跟投

来源&#xff1a;雷帝网 人工智能平台公司商汤科技SenseTime宣布完成6亿美元C轮融资&#xff0c;由阿里巴巴集团领投&#xff0c;新加坡主权基金淡马锡、苏宁等投资机构和战略伙伴跟投。商汤科技联合创始人、CEO徐立表示&#xff1a;商汤科技C轮融资将进一步夯实公司在人工智能…

MongoDB Shell和Robo3T使用以及与SQL语法比较

From&#xff1a;MongoDB Shell 了解使用 - 大葱哥 - 博客园 MongoDB基本管理命令&#xff1a;MongoDB基本管理命令_千与的专栏-CSDN博客_mongo查询命令 MongoDB常用操作命令大全&#xff1a;MongoDB常用操作命令大全_piaocoder-CSDN博客_mongodb常用命令 mongodb 命令行基本…

PowerDesiGner数据库设计

原文地址&#xff1a;http://hi.baidu.com/shunkunl/blog/item/871c75ef8596faeace1b3e00.htmlPowerDesign&#xff1a;PowerDesign是 Sybase推出的主打数据库设计工具。PowerDesign致力于采用基于Entiry-Relation的数据模型&#xff0c;分别从概念数据模型 (Conceptual Data M…

mipony linux客户端,Mipony网盘下载工具

Mipony是一个特别设计的下载管理器下载的免费网盘如Rapidshare&#xff0c;Megaupload&#xff0c;Hotfiles&#xff0c;Gigasize&#xff0c;Filefactory&#xff0c;Mediafire&#xff0c;Netload。简介&#xff1a;支援“HJSplit”档案合并功能&#xff0c;还可以自动侦测网…

2018全球100个最有价值的科技品牌 18个中国品牌上榜

来源&#xff1a;全球企业动态英国品牌评估机构Brand Finance发布“2018全球100个最有价值的科技品牌榜”(Top 100 most valuable tech brands 2018)&#xff0c;前五位都是美国品牌。美国上榜品牌总价值9590亿美元&#xff0c;占百强品牌总价值14673亿美元的65%。亚马逊跃升至…

Effective Java~2.Builder代替多参数Constructor

Builder模式 // Builder Pattern public class NutritionFacts {private final int servingSize;private final int servings;private final int calories;private final int fat;private final int sodium;private final int carbohydrate;public static class Builder {// R…

Alpine Linux 使用简介

From&#xff1a;https://www.aliyun.com/jiaocheng/137717.html Alpine Linux、CoreOS、RancherOS、Red Hat 原子项目、 VMware光子操作系统比较https://blog.csdn.net/hxpjava1/article/details/78482987 Alpine Linux配置使用技巧&#xff1a;https://www.aliyun.com/jiao…

实例学习SSIS(五)--理论介绍SSIS

导读&#xff1a; 实例学习SSIS&#xff08;一&#xff09;--制作一个简单的ETL包 实例学习SSIS&#xff08;二&#xff09;--使用迭代 实例学习SSIS&#xff08;三&#xff09;--使用包配置 实例学习SSIS&#xff08;四&#xff09;--使用日志记录和错误流重定向 实例学习SSIS…

MIT教授Tomaso Poggio演讲与专访:智能背后的科学与工程 | 腾讯AI Lab学术论坛

来源&#xff1a;腾讯AI实验室腾讯AI Lab第二届学术论坛在深圳举行&#xff0c;聚焦人工智能在医疗、游戏、多媒体内容、人机交互等四大领域的跨界研究与应用。全球30位顶级AI专家出席&#xff0c;对多项前沿研究成果进行了深入探讨与交流。腾讯AI Lab还宣布了2018三大核心战略…

linux捕捉信号sigint失败,为shell布置陷阱:trap捕捉信号方法论

本文目录&#xff1a;1.1 信号说明1.2 trap布置陷阱1.3 布置完美陷阱必备知识家里有老鼠&#xff0c;快消灭它&#xff01;哎&#xff0c;又给跑了。老鼠这小东西跑那么快&#xff0c;想直接直接消灭它还真不那么容易。于是&#xff0c;老鼠药、老鼠夹子或老鼠笼就派上用场了&a…

Effective Java~3. 私有Constructor 或Enum 强化单例

1、私有化构造器 // Singleton with static factory public class Elvis {private static final Elvis INSTANCE new Elvis();private Elvis() { ... }public static Elvis getInstance() { return INSTANCE; }public void leaveTheBuilding() { ... } } 可序列化对象单例&a…

VMware虚拟机软件

http://www.xuniji.com/vmware/ 虚拟机之家 http://www.ccw.com.cn/ 如何配置VMware虚拟机网络环境全程图解 nyytwngmail.com 8 转载于:https://www.cnblogs.com/hnytwn/archive/2009/10/28/1591044.html

linux 内置ssh,Linux ssh内置sftp配置说明

centos7 环境下已验证首先建立两个用户&#xff0c;用于sftp访问使用。eg:useradd -d /opt/sftp -s /bin/nologin sftp说明 -s /bin/nologin 禁止ssh登录服务器,其实如果将用户设置为用于sftp的话,不做此设置也是无法登录的会提示:This service allows sftp connections onl…

Trade Stages - The Trade Path

Gieno Trade Stages - The Trade Path STARTS OFF “greed orientated.”Loses because:1 Market problemsNot a zero sum game, a “very negative” sum gameMarket psychology – doing the wrong thing at the wrong timeThe majority is always wrongMarket exists on ch…

win10、oneplus7pro 使用 Kali

1、Windows 10 使用 Kali Linux子系统 微软为 Windows Subsystem for Linux (WSL) 带来了著名的 Kali Linux &#xff0c;无虚拟机&#xff0c;无Docker实现Windows 和 Kali Linux 交互。 window 开启 wsl 功能&#xff1a; 1.打开控制面板&#xff08; winR&#xff0c;输入…

Effective Java~9. try-with-resource 优先于 try-catch

try-finally 语句是保证资源正确关闭的最佳方式&#xff0c;但是当添加多个资源时&#xff0c;情况会变糟 // try-finally is ugly when used with more than one resource! static void copy(String src, String dst) throws IOException {InputStream in new FileInputStre…

linux安装汉语输入法,在linux中安装google拼音输入法

载自&#xff1a; 并修改了一些内容installhow to build and install scim-googlepinyin* Introduction* grab the source* build depends* runtime depends* build and installIntroductionThis document is intended to help you to build and install scim-googlepinyin fro…

干货|十大产业方向深度解析!《2020科技产业趋势报告》

来源&#xff1a;机器人大讲堂报告下载&#xff1a;https://pan.baidu.com/s/1BKf2rINXx0CVLhgokfYrgQ未来智能实验室是人工智能学家与科学院相关机构联合成立的人工智能&#xff0c;互联网和脑科学交叉研究机构。未来智能实验室的主要工作包括&#xff1a;建立AI智能系统智商评…

交互式数据包处理程序 Scapy 用法

From&#xff1a;https://www.cnblogs.com/hongxueyong/p/5641475.html Scapy 用法官方文档&#xff1a;http://scapy.readthedocs.io/en/latest/#starting-scapyAbout ScapyScapy is a Python program that enables the user to send, sniff and dissect and forge network pa…

top、postop、scrolltop、scrollHeight、offsetHeight

网页可见区域宽&#xff1a;document.body.clientWidth; 网页可见区域高&#xff1a;document.body.clientHeight; 网页可见区域高&#xff1a;document.body.offsetWeight: 网页可见区域高&#xff1a;document.body.offsetHeight; 网页正文全文宽&#xff1a;documen…