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 命令行基本…

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

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

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…

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;输入…

干货|十大产业方向深度解析!《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…

展望2021年:智能机器人可监督工业机器人干活,效率提升30%

来源&#xff1a;极客网会帮我们吸地板、在公共场所担任导引员或是拆除炸弹的机器人呢可能感觉比较有趣&#xff0c;但那些负责组装汽车以及在工厂生产在线帮忙拾取物品的机器人&#xff0c;在整体价值上要高得多&#xff0c;而且也有越来越多的工/商业或消费性应用产品是由这种…

ASP。NET的设计思想

自从有了html与http&#xff0c;就有了浏览器与Web服务器&#xff0c;并有了Web应用&#xff0c;最初的交互模式是这样的&#xff1a; 该模式很好地运行了很多年。然而&#xff0c;随着计算机应用的发展&#xff0c;人们越来越不满足于只有静态内容的页面&#xff0c;而由某种机…

Kali linux 渗透测试技术之搭建WordPress Turnkey Linux及检测WordPress 应用程序漏洞

From&#xff1a;https://bbs.ichunqiu.com/thread-15716-1-1.html 怎样用 WPScan&#xff0c;Nmap 和 Nikto 扫描和检查一个 WordPress 站点的安全性&#xff1a;https://www.cnblogs.com/chayidiansec/p/7989274.html 为了收集用于测试的应用程序&#xff0c; Turnkey Linux…

AI芯片格局分布

来源&#xff1a;中国科学院自动化研究所 作者&#xff1a; 吴军宁如果说2016年3月份AlphaGo与李世石的那场人机大战只在科技界和围棋界产生较大影响的话&#xff0c;那么2017年5月其与排名第一的世界围棋冠军柯洁的对战则将人工智能技术推向了公众视野。阿尔法狗&#xff08;…

科学互驳:大脑细胞活到老,长到老?

来源&#xff1a;中国生物技术网 作者&#xff1a;格格科学家发现&#xff0c;人脑中与学习、记忆和情感相关的区域在成年后依然会持续产生新的神经元。这与过去的理论恰恰相反&#xff0c;即青春期之后大脑停止产生新的神经元。这项发现有助于我们开发治疗神经系统疾病的新方…

AI综述专栏 | 朱松纯教授浅谈人工智能:现状、任务、构架与统一

作者&#xff1a;朱松纯来源&#xff1a;人工智能前沿讲习班导读本文作者&#xff1a;朱松纯&#xff0c;加州大学洛杉矶分校UCLA统计学和计算机科学教授&#xff0c;视觉、认知、学习与自主机器人中心主任。文章前四节浅显探讨什么是人工智能和当前所处的历史时期&#xff0c;…

联想linux笔记本评测,联想(lenovo)G460AL-ITH Linux笔记本电脑接口评测-ZOL中关村在线...

模具和外观的“革新”让我们见识到不一样的联想G460&#xff0c;而在整机的接口扩展能力方面依旧主打实用性。机身左侧从左至右依次是安全锁孔、散热孔、RJ-45以太网接口、VGA视频输出接口、USB2.0接口、e-SATA接口(兼容USB2.0)、Express Card卡槽和HDMI高清视频输出接口。与前…

联合国召开会议讨论“杀手机器人”问题

来源&#xff1a;中国科学报 作者&#xff1a;赵熙熙来自29个国家的57位科学家日前呼吁联合抵制一所韩国大学&#xff0c;因为设立在该校的一个新的中心旨在利用人工智能强化国家安全。人工智能科学家表示&#xff0c;该大学正在开发自主武器&#xff0c;又称“杀手机器人”&a…

【转】近期Coolite控件的技术点总结

1。Coolite下如何自定义控件样式表Code1<style type"text/css"> 2 .x-grid3-td-fullName .x-grid3-cell-inner {}{ 3 font-family:tahoma, verdana; 4 display:block; 5 font-weight:normal; 6 font-style…