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>