java使用Freemarker、Flying sauser生成pdf,中文不顯示、設定頁首、換頁、紅色中文字型不能加粗顯示、中文不能換行解決
因為做這個很坑,花了幾天時間,終於爬出來了,為了實現功能,借鑑了很多程式碼,找起來很麻煩,現整合一下,方便使用,所以記錄下。
首先上兩個效果圖:
需求圖1:demo圖2:
做了個demo匯出pdf,demo的看不出換行,懶得去加資料了,所以把自己用的圖發出來。
本人使用的jar包:
freemarker-2.3.22.jar,flying-saucer-core-9.1.16.jar,flying-saucer-pdf-9.1.16.jar,itext-4.2.0.jar,
serializer-2.7.1.jar(遇到java.lang.NoClassDefFoundError: org/apache/xml/serializer/TreeWalker,丟失這個jar,可以加上去)
jar包地址:maven repository下載
因為工作上需要,第一次做匯出pdf,word,然後入了Freemarker的坑。開始做pdf匯出,由於專案中已經有了類似匯出,我就用itextpdf來匯出生成pdf,後面又需要生成word,在系統上沒有發現類似功能,一直複製別人寫的程式碼早就想練練手了,所以查閱了下生成word的幾種方式,後面選了Freemarker,這是最簡單的方式,也是很快就把word生成了。後面使用者說這個pdf樣式不合格,被懟了回來, 完成的比較匆忙,可能有很多itext 的操作都沒有發現吧!自己匯出的pdf有點問題,pdf的每一行都是需要設定高度,字數多了不顯示,設定行太高了,資料少了樣式又顯瘦了,後面網上查了很久,不想查了決定直接重新生成個pdf。
為了生成pdf,我首先想到的word轉pdf,可能是懶吧!原本已經寫好word直接轉的就不會這麼多坑了,我是這麼認為的。然後我用了aspose-words來轉,很順利地轉成了pdf,如果不是會生成個水印就完美了,收費的東西,需要破解,不想用,目前網上的lisense破解檔案都沒用,哈哈哈。後面還有docx4j,poi等網上的都用了,都沒實現,後面發現,原來Freemarker生成的world是doc格式本質是一個xml,docx是個壓縮檔案,網上的大部分支援的是docx轉pdf。
在卡了一陣子後,我突然想到自己為什麼不直接生成pdf呢!後面又開始研究起Freemarker了。
專案結構:
上程式碼:
PdfUtils.java
1 package com.dcampus.job.util; 2 3 import java.io.File; 4 import java.io.FileInputStream; 5 import java.io.FileNotFoundException; 6 import java.io.FileOutputStream; 7 import java.io.IOException; 8 import java.io.InputStream; 9 import java.io.OutputStream; 10 import java.io.StringWriter; 11 import java.net.URLEncoder; 12 import java.util.ArrayList; 13 import java.util.HashMap; 14 import java.util.List; 15 import java.util.Map; 16 17 import javax.servlet.ServletOutputStream; 18 import javax.servlet.http.HttpServletRequest; 19 import javax.servlet.http.HttpServletResponse; 20 21 import org.xhtmlrenderer.pdf.ITextRenderer; 22 23 import com.lowagie.text.DocumentException; 24 import com.lowagie.text.pdf.BaseFont; 25 26 import freemarker.template.Configuration; 27 import freemarker.template.Template; 28 import freemarker.template.TemplateException; 29 30 public class PdfUtils { 31 private PdfUtils() { 32 throw new AssertionError(); 33 } 34 public static Configuration configurationPdf = null; 35 //這裡注意的是利用PdfUtils類獲得模板檔案的位置 36 private static final String templateFolder = PdfUtils.class.getResource("/").getPath(); 37 private static final String ROOTPATH =new File(new File(templateFolder).getParent()).getParentFile().toString(); 38 private static String templatePath = null; 39 static { 40 System.out.println("ROOTPATH"+ROOTPATH); 41 templatePath = new File(templateFolder).getParent()+"/template/"; 42 System.out.println(new File(new File(templateFolder).getParent()).getParentFile()); 43 configurationPdf = new Configuration(); 44 configurationPdf.setDefaultEncoding("utf-8"); 45 try { 46 configurationPdf.setDirectoryForTemplateLoading(new File(templatePath)); 47 } catch (IOException e) { 48 e.printStackTrace(); 49 } 50 } 51 public static void exportMillCertificatePdf(HttpServletRequest request, HttpServletResponse response, Map map,String title,String ftlFile) throws IOException, DocumentException { 52 Template freemarkerTemplate = configurationPdf.getTemplate(ftlFile,"UTF-8"); 53 File file = null; 54 InputStream fin = null; 55 ServletOutputStream out = null; 56 String outPath = ROOTPATH+"/upload/bigMeetingDoc/sellPlan"+System.currentTimeMillis() +".pdf"; 57 OutputStream pdfStream = new FileOutputStream(outPath); 58 String html = null; 59 InputStream finPdf = null; 60 File pdfFile=null; 61 62 63 StringWriter writer = new StringWriter(); 64 try { 65 out = response.getOutputStream(); 66 pdfFile = cratePDF(map, freemarkerTemplate); 67 finPdf = new FileInputStream(pdfFile); 68 response.setCharacterEncoding("utf-8"); 69 response.setContentType("application/pdf"); 70 // 設定瀏覽器以下載的方式處理該檔名 71 String fileName = title+System.currentTimeMillis() + ".pdf"; 72 response.setHeader("Content-Disposition", "attachment;filename=" 73 .concat(String.valueOf(URLEncoder.encode(fileName, "UTF-8")))); 74 75 byte[] buffer = new byte[512]; // 緩衝區 76 int bytesToRead = -1; 77 // 通過迴圈將讀入的Word檔案的內容輸出到瀏覽器中 78 while((bytesToRead = finPdf.read(buffer)) != -1) { 79 out.write(buffer, 0, bytesToRead); 80 } 81 } catch (Exception e) { 82 // TODO Auto-generated catch block 83 e.printStackTrace(); 84 }finally { 85 if(fin != null) fin.close(); 86 if(finPdf != null) 87 finPdf.close(); 88 if(out != null) out.close(); 89 if(pdfFile != null) 90 pdfFile.delete(); // 刪除臨時檔案 91 if(file != null) 92 file.delete(); // 刪除臨時檔案 93 } 94 95 96 } 97 public static ITextRenderer getRender() throws DocumentException, IOException { 98 99 ITextRenderer render = new ITextRenderer(); 100 //新增字型,以支援中文 101 render.getFontResolver().addFont(templatePath + "font/arialuni.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); 102 render.getFontResolver().addFont(templatePath + "font/simsun.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); 103 render.getFontResolver().addFont(templatePath + "font/stzhongs.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); 104 return render; 105 } 106 107 public static File cratePDF(Map<?, ?> dataMap, Template template) throws FileNotFoundException { 108 String outPath = ROOTPATH+"/upload/bigMeetingDoc/企業海報"+System.currentTimeMillis() +".pdf"; 109 OutputStream pdfStream = new FileOutputStream(outPath); 110 String html = null; 111 File pdfFile=null; 112 113 StringWriter writer = new StringWriter(); 114 try { 115 template.process(dataMap, writer); 116 writer.flush(); 117 html = writer.toString(); 118 ITextRenderer render=null; 119 render = getRender(); 120 try { 121 render.setDocumentFromString(html); 122 }catch(Exception e){ 123 e.printStackTrace(); 124 } 125 try { 126 render.layout(); 127 render.createPDF(pdfStream); 128 render.finishPDF(); 129 }catch(Exception e){ 130 e.printStackTrace(); 131 } 132 render = null; 133 pdfFile = new File(outPath); 134 }catch (Exception ex) { 135 ex.printStackTrace(); 136 throw new RuntimeException(ex); 137 } 138 return pdfFile; 139 } 140 141 }
這裡需要設定好模板目錄,上傳目錄,字型目錄
測試Animal.java:
1 package com.test.ftl; 2 3 public class Animal { 4 private String name; 5 private int price; 6 7 public String getName() { 8 return name; 9 } 10 11 public void setName(String name) { 12 this.name = name; 13 } 14 15 public int getPrice() { 16 return price; 17 } 18 19 public void setPrice(int price) { 20 this.price = price; 21 } 22 23 }
測試頁面:
index2.jsp
<%@page import="com.test.ftl.PdfUtils"%> <%@page import="com.test.ftl.Animal"%> <%@page import="com.test.ftl.JobList"%> <%@page import="java.util.ArrayList"%> <%@page import="java.util.List"%> <%@page import="java.io.IOException"%> <%@page import="com.test.ftl.WordUtils"%> <%@page import="java.util.HashMap"%> <%@page import="java.util.Map"%> <%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> <% Animal a1 = new Animal(); a1.setName("小狗"); a1.setPrice(88); Animal a2 = new Animal(); a2.setName("小喵"); a2.setPrice(80); List<Animal> list = new ArrayList<Animal>(); for(int i=0;i<5;i++){ list.add(a1); list.add(a2); } Map<String, Object> map = new HashMap<String, Object>(); map.put("user", "冉冉"); map.put("score", 13); map.put("team", "一班,二班"); map.put("jobList", list); map.put("EntName", "張三"); map.put("Synopsis", "asas14122d"); map.put("bmName", "招聘會"); System.out.println("1231" + application.getRealPath(request.getRequestURI())); try { //WordUtils.exportMillCertificateWordToPdf(request, response, map, "方案", "companyTemplate.ftl"); PdfUtils.exportMillCertificatePdf(request, response, map, "企業海報", "bigMeetingForPdf.ftl"); //PdfUtils.exportMillCertificatePdf(request, response, map, "企業海報", "tpl.ftl"); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } %> <!DOCTYPE html> <html> <head> <meta charset="ISO-8859-1"> <title>Insert title here</title> </head> <body> </body> </html>
模板:
bigMeetingForPdf.ftl
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="Content-Style-Type" content="text/css" /> <title></title> <style type="text/css">
/**頁首**/ div.header-top {display: none} @media print { div.header-top { display: block; position: running(header-top); margin-top: 20px; } }
/**a4分頁**/ @page { size:a4; margin: 10%; /**頁首**/ @top-center { content: element(header-top) } } } </style> </head> <body> <div><!--***************頁首*****************--> <div id="header-top" class="header-top" align="left"> <p style="margin:0pt; orphans:0;font-family:SimSun; font-size:12px;text-align:center; widows:0">${EntName} </p> <hr style="margin-left:-35px;width:110%;"/> </div> <p style="margin:0pt; orphans:0; text-align:center; widows:0"> <span style="color:#ff0000; font-family:STZhongsong;border:none; font-size:29px;">${EntName}</span></p> <p style="font-size:12pt; line-height:150%; margin:0pt; orphans:0; text-align:center; widows:0"> <span style="color:#ff0000; font-family:SimSun; font-size:12pt"> </span></p> <p style="font-size:18px; line-height:150%; margin:0pt; orphans:0; text-indent:20pt; widows:0"> <span style="font-family:SimSun; font-size:14pt">${Synopsis}</span></p> <p style="font-size:29px; line-height:150%; margin:0pt; orphans:0; text-align:center; widows:0"> <span style="color:#0070c0; font-family:SimSun; font-size:29px">誠 聘</span></p> <p style="font-size:12pt; line-height:150%; margin:0pt; orphans:0; text-align:center; widows:0"> <span style="color:#ff0000; font-family:SimSun; font-size:12pt"> </span></p> <#list jobList! as job> <p style="font-size:18px; line-height:150%; margin:0pt; orphans:0; widows:0"> <span style="color:#ff0000; font-family:SimSun; font-size:18px">崗位名稱:${job.name!}</span> <span style="color:#ff0000; font-family:SimSun; font-size:18px">(${job.price!}人) </span></p> <p style="font-size:18px; line-height:150%; margin:0pt; orphans:0; widows:0"> <span style="font-family:SimSun; font-size:18px">崗位要求:${job.name!}</span></p> <p style="font-size:18px; line-height:150%; margin:0pt; orphans:0; widows:0"> <span style="color:#ff0000; font-family:SimSun; font-size:18px">待遇:月薪${job.price!}-${job.price!}元/月</span></p> <p style="font-size:12pt; line-height:150%; margin:0pt; orphans:0; text-align:center; widows:0"> <span style="color:#ff0000; font-family:SimSun; font-size:12pt"> </span></p> </#list> </div> </body> </html>
模板是由word轉成html,然後調整樣式改為.ftl字尾,ftl的簡單語法比較簡單的,可以自行上網檢視。儲存之後發現了第一個坑,突然看到原本word裡面的也沒不見了。這個網上看了很久最後才發現了一個解決的辦法。在上面標註了主要程式碼,順便附上一個網址:https://www.w3.org/TR/css-page-3/#margin-boxes,在這裡我知道了css設定分頁和頁首得具體語法。後面出現匯出的pdf不分頁還有中文不顯示的問題,分頁上面交代了,而中文不顯示也是個噁心的問題,freemark都不支援中文,需要下載字型才能顯示,這裡使用的字型有:simsun.ttf、stzhongs.ttf、arialuni.ttf,可在這裡下載,開始我參照網上說的設定body的樣式,如下:
但是實際發現並不能顯示中文,後來採用了在每個需要顯示中文的標籤寫入行內設定字型就可以顯示中文了,後面想到自已用的是宋體,然後有個標題加粗,發現加粗後的不能正常顯示,原來是字型的問題沒有對應粗體,後面只能換字型替代,找了很多最後發現和宋體差不多的華文中體(stzhongs.ttf),解決了這個問題,感覺有點過關斬將了,差不多好了,後面部署到之後突然發現一大坑,發現了中文不能換行,差點掀桌子了,文字都水平顯示一行,過長的文字被遮蓋,後面找到方法使用table,並給他加樣式
table {
border-collapse: collapse;
table-layout: fixed;
word-break:break-all;
font-size: 10px;
width: 100%;
text-align: center;
margin-top: 10px;
}
td {
width: 42px;
word-break:break-all;
word-wrap : break-word;
}
我真實使用的模板檔案,這裡有解決中文換行的問題。
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="Content-Style-Type" content="text/css" /> <title></title> <style type="text/css"> div.header-top {display: none} @media print { div.header-top { display: block; position: running(header-top); margin-top: 20px; } } .document_body{ width: 100%; /*可以修改此處的寬度百分比來適應頁面大小*/ margin: 0 auto; position: relative; } .document_body p{ text-align: justify; text-justify: inter-word; } table { border-collapse: collapse; table-layout: fixed; word-break:break-all; font-size: 10px; width: 100%; text-align: center; margin-top: 10px; } td { width: 42px; word-break:break-all; word-wrap : break-word; } @page { size:a4; margin: 10%; @top-center { content: element(header-top) } } } </style> </head> <body> <div class="document_body"> <div class="document_main"> <div id="header-top" class="header-top" align="left"> <p style="margin:0pt; orphans:0;font-family:SimSun; font-size:12px;text-align:center; widows:0">${bmName!}</p> <hr style="margin-left:-50px;width:110%;" /></div> <table> <tr> <td> <p style="margin:0pt; orphans:0; text-align:center; widows:0"> <span style="color:#ff0000; font-family:STZhongsong;border:none; font-size:29px;">${EntName!}</span></p> </td> </tr> <tr> <td><p>  </p> </td> </tr> <tr> <td> <p style="font-size:18px; line-height:150%; orphans:0; text-indent:40px; widows:0"> <span style="font-family:SimSun; font-size:14pt">${Synopsis!}</span></p> </td> </tr> </table> <p style="font-size:29px; line-height:150%; margin:0pt; orphans:0; text-align:center; widows:0"> <span style="color:#0070c0; font-family:SimSun; font-size:29px">誠 聘</span></p> <p style="font-size:12pt; line-height:150%; margin:0pt; orphans:0; text-align:center; widows:0"> <span style="color:#ff0000; font-family:SimSun; font-size:12pt"> </span></p> <table> <#list jobList! as job> <tr> <td> <p style="font-size:18px;font-family:SimSun; line-height:150%; margin:0pt; orphans:0; widows:0"> <span style="color:#ff0000;font-family:SimSun; font-size:18px">崗位名稱:</span> <span style="font-family:SimSun; font-size:18px">${job.jobName!}(${job.num!}人) </span></p> </td> </tr> <tr> <td> <p style="font-size:18px; line-height:150%; font-family:SimSun; margin:0pt; orphans:0; widows:0"> <span style="font-family:SimSun; font-size:18px">崗位要求:${job.jobRequirement!}</span></p> </td> </tr> <tr> <td> <p style="font-size:18px;font-family:SimSun; line-height:150%; margin:0pt; orphans:0; widows:0"> <span style=" font-family:SimSun; font-size:18px">待遇:月薪${job.internshipSalary?c!}-${job.formalSalary?c!}元/月</span></p> </td> </tr> <tr> <td> <p> </p> </td> </tr> </#list> </table> </div> </div> </body> </html>