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

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

2、頁面css樣式的採集chrome

  看過不少篇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和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+'}'; } 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; } });

 

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標籤有</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){ }); }); });

 4、後臺代碼

   項目中引入中文字體,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("&lt;","<").replaceAll( "&gt;",">").replaceAll("<br/>","\n|\r\n|\r" ) .replaceAll("&nbsp;"," "); 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 = 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() { } @Override public void remove(Element arg0) {} @Override public void setFormSubmissionListener(FormSubmissionListener arg0) {} }

 

個人頁面

 

 

導出的pdf效果,自動分頁,而且分頁不會強制裁剪圖片區域。

相關文章
相關標籤/搜索