项目图片返回的是text html类型,xhtmlrenderer 将html转换成pdf,完美css,带图片,手动分页,解决内容断开的问题...

以前用itext7将html导出为pdf,比较方便,代码较少,并且支持base64的图片。可是itext7是收费的,因此换成了xhtmlrenderer。css

xhtmlrenderer自动引入依赖包itext2.0.8,并且不能再引入其余版本的itext,由于itext2.0.8是已经被废弃的,里面的不少方法在新版本已经没有了。html

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

1.css样式java

2.中文不显示node

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

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

1、首先引入包web

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

org.xhtmlrenderer

core-renderer

R8

2、页面css样式的采集chrome

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

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

functiongetElementChildrenAndStyles(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和chrome

var 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;

}

}

}

}//重组style

var style = rulesUsed.map(function(cssRule){if(cssRule.style) {var cssText = cssRule.selectorText+'{'+cssRule.style.cssText.toLowerCase()+'}';

}else{var cssText = cssRule.selectorText+'{'+cssRule.cssText+'}';

}returncssText;

}).join("\n");return "

\n\n"

+ 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表示下一个元素将会换页。

functionpdfPageRange(){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;varelement_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("

heigth= $this[0].offsetHeight;

}

});

}//table单独算高度

functiontablePage($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;

}

});returnheigth;

}

});

3、图片的支持

项目中有不少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标签有或,浏览器最后显示仍是,  因此我用一个字符串代替“/”, 后台再用“/”代替这个字符串,你也能够前端就替换。(请看代码)

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 (" ")

}

}

);

canvasArray.push(canvasIndex);

});

Promise.all(canvasArray).then(function() {var str = getElementChildrenAndStyles('#basket');

$.post("/ecloud/sa/saerrorquestions/exportpdf.do",{"str":str },function(r){

});

});

});

4、后台代码

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

packagecn.myc.ykt3.util;importjava.io.FileOutputStream;importjava.io.OutputStream;importorg.xhtmlrenderer.pdf.ITextFontResolver;importorg.xhtmlrenderer.pdf.ITextRenderer;importcom.lowagie.text.pdf.BaseFont;public classItextHtmlTopdf {/***

*@paramhtmlStr html字符串

*@return*@throwsException*/

public String exportpdf(String htmlStr ) throwsException {if(StringUtils.isBlank(htmlStr)) {return null;

}

htmlStr= htmlStr.trim().replaceAll("<","").replaceAll("
","\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.8

ITextRenderer renderer = newITextRenderer();

OutputStream os= new FileOutputStream("C:/Users/Administrator/Desktop/createSamplePDF3.pdf");//若是携带图片则加上如下两行代码,将图片标签转换为Itext本身的图片对象,Base64ImgReplacedElementFactory为图片处理类

renderer.getSharedContext().setReplacedElementFactory(newBase64ImgReplacedElementFactory());

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图片处理类

packagecn.myc.ykt3.util;importjava.io.IOException ;importorg.w3c.dom.Element ;importorg.xhtmlrenderer.extend.FSImage ;importorg.xhtmlrenderer.extend.ReplacedElement ;importorg.xhtmlrenderer.extend.ReplacedElementFactory ;importorg.xhtmlrenderer.extend.UserAgentCallback ;importorg.xhtmlrenderer.layout.LayoutContext ;importorg.xhtmlrenderer.pdf.ITextFSImage ;importorg.xhtmlrenderer.pdf.ITextImageElement ;importorg.xhtmlrenderer.render.BlockBox ;importorg.xhtmlrenderer.simple.extend.FormSubmissionListener ;importcom.lowagie.text.BadElementException ;importcom.lowagie.text.Image ;importcom.lowagie.text.pdf.codec.Base64 ;public class Base64ImgReplacedElementFactory implementsReplacedElementFactory {/*** 实现createReplacedElement 替换html中的Img标签

*

*@paramc 上下文

*@parambox 盒子

*@paramuac 回调

*@paramcssWidth css宽

*@paramcssHeight css高

*@returnReplacedElement*/

publicReplacedElement createReplacedElement(LayoutContext c, BlockBox box, UserAgentCallback uac,int cssWidth, intcssHeight) {

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 newITextImageElement(fsImage);

}

}return null;

}/*** 将base64编码解码并生成itext图像

*

*@paramsrcAttr 属性

*@paramuac 回调

*@returnFSImage

*@throwsIOException io异常

*@throwsBadElementException BadElementException*/

protected FSImage buildImage(String srcAttr, UserAgentCallback uac) throwsIOException,

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= newITextFSImage(Image.getInstance(decodedBytes));

}else{

fsImage=uac.getImageResource(srcAttr).getImage();

}returnfsImage;

}/*** 实现reset*/

public voidreset() {

}

@Overridepublic voidremove(Element arg0) {}

@Overridepublic voidsetFormSubmissionListener(FormSubmissionListener arg0) {}

}

个人页面

7e9095770127e4d24e1fbe596258b443.png

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

7b08b0a0af1274e43848798eda8692bb.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值