1. Freemarker模板引擎
模板语法
2. FlyingSaucer根据模板生成pdf
兼容中文(及中文换行问题)
兼容CSS(绝对、相对定位)
兼容图片
多页输出
(示例代码没有dao、service层,生产环境中自行添加,本示例完整,不坑人)
项目结构截图
org.springframework.boot
spring-boot-starter-freemarker
org.springframework.boot
spring-boot-starter-web
org.xhtmlrenderer
flying-saucer-pdf
9.1.12
PdfUtils.java,方法上有完整注释,思路是利用模板引擎动态处理模板参数,先生成html字符串放在StringWriter中,再用HTML字符串生成Document,再利用FlyingSaucer的ITextRenderer处理Document,最后输出pdf。
package com.suncd.demopdf.Utils;
import com.lowagie.text.pdf.BaseFont;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import org.w3c.dom.Document;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.*;
import java.util.List;
import java.util.Map;
/**
* 功能:pdf处理工具类
*
* @author qust
* @version 1.0 2018/2/23 17:21
*/
public class PdfUtils {
private PdfUtils() {
}
private static final Logger LOGGER = LoggerFactory.getLogger(PdfUtils.class);
/**
* 按模板和参数生成html字符串,再转换为flying-saucer识别的Document
*
* @param templateName freemarker模板名称
* @param variables freemarker模板参数
* @return Document
*/
private static Document generateDoc(FreeMarkerConfigurer configurer, String templateName, Map variables) {
Template tp;
try {
tp = configurer.getConfiguration().getTemplate(templateName);
} catch (IOException e) {
LOGGER.error(e.getMessage(), e);
return null;
}
StringWriter stringWriter = new StringWriter();
try(BufferedWriter writer = new BufferedWriter(stringWriter)) {
try {
tp.process(variables, writer);
writer.flush();
} catch (TemplateException e) {
LOGGER.error("模板不存在或者路径错误", e);
} catch (IOException e) {
LOGGER.error("IO异常", e);
}
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
return builder.parse(new ByteArrayInputStream(stringWriter.toString().getBytes()));
}catch (Exception e){
LOGGER.error(e.getMessage(), e);
return null;
}
}
/**
* 核心: 根据freemarker模板生成pdf文档
*
* @param configurer freemarker配置
* @param templateName freemarker模板名称
* @param out 输出流
* @param listVars freemarker模板参数
* @throws Exception 模板无法找到、模板语法错误、IO异常
*/
private static void generateAll(FreeMarkerConfigurer configurer, String templateName, OutputStream out, List
中文字符坑点:
填坑:
generateAll方法中
//设置字符集(宋体),此处必须与模板中的一致,区分大小写,不能写成汉字"宋体"
ITextFontResolver fontResolver = renderer.getFontResolver();
fontResolver.addFont("simsun.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
①需要拷贝宋体字体文件到resource目录下(字体位置在“c:/Windows/Fonts/simsun.ttc”),方便集成和迁移
②在页面中设置body的样式,必须写成英文,同时大小写敏感,
另外:也有不少文章直接根据操作系统类型取宋体字体文件路径的全路径,如下,显得代码臃肿:
注意: generateAll方法中已经实现了一个模板接收多个参数对象,输出多页到一个pdf文件中,读者可根据自己需要改造
跟编写普通html页面一样,定义2个页面,一个主页面index.ftl,一个pdf模板页面pdfPage.ftl
文件结构:
index.ftl,很简单,一个标题,两个按钮,一个预览功能,一个下载功能,同时预接收一个${title}参数
注:freemarker的语法和原理,读者自行科普
Demo Page PDF
Demo Page ${title}
pdfPage.ftl
Spring Boot Demo - PDF
1.标题-中文
${title}
2.按钮:按钮的边框需要写css渲染
3.普通div
Alice's Adventures in Wonderland
4.图片 绝对定位到左上角(注意:图片必须用全路径或者http://开头的路径,否则无法显示)
5.普通table表格
1
2
2
2
2
1
2
2
2
2
1
2
2
2
2
6.input控件,边框需要写css渲染 (在模板中一般不用input,因为不存在输入操作)
坑点(用户经常有页面尺寸需求,比如纸张类型):
1. 页面尺寸(A3,A4)设置和脚标设置
页面尺寸填坑: 在
2. CSS路径和图片路径
填坑css路径: 引用css文件必须用http://全路径,如上图,可以把css文件单独放到一台服务器上,通过域名或者ip+端口访问.
填坑图片路径: css中引用的图片一样要使用http://全路径,如下图:
写两个Controller,PublicController.java 和 PdfController.java
PublicController.java用来访问主页面, PdfController.java用来接受预览和下载请求
PublicController.java
package com.suncd.demopdf.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
/**
* 功能:公共
*
* @author qust
* @version 1.0 2018/2/23 11:56
*/
@Controller
public class PublicController {
@RequestMapping(value = "/")
public ModelAndView index(ModelAndView modelAndView) {
modelAndView.setViewName("index");
modelAndView.addObject("title", "CGX");
return modelAndView;
}
}
PdfController.java
package com.suncd.demopdf.controller;
import com.suncd.demopdf.Utils.PdfUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 功能:pdf预览、下载
*
* @author qust
* @version 1.0 2018/2/23 9:35
*/
@Controller
@RequestMapping(value = "/pdf")
public class PdfController {
@Autowired
private FreeMarkerConfigurer configurer;
/**
* pdf预览
*
* @param request HttpServletRequest
* @param response HttpServletResponse
*/
@RequestMapping(value = "/preview", method = RequestMethod.GET)
public void preview(HttpServletRequest request, HttpServletResponse response) {
// 构造freemarker模板引擎参数,listVars.size()个数对应pdf页数
List> listVars = new ArrayList<>();
Map variables = new HashMap<>();
variables.put("title","测试预览ASGX!");
listVars.add(variables);
PdfUtils.preview(configurer,"pdfPage.ftl",listVars,response);
}
/**
* pdf下载
*
* @param request HttpServletRequest
* @param response HttpServletResponse
*/
@RequestMapping(value = "/download", method = RequestMethod.GET)
public void download(HttpServletRequest request, HttpServletResponse response) {
List> listVars = new ArrayList<>();
Map variables = new HashMap<>();
variables.put("title","测试下载ASGX!");
listVars.add(variables);
PdfUtils.download(configurer,"pdfPage.ftl",listVars,response,"测试中文.pdf");
}
}
server:
port: 8999
运行项目,访问http://localhost:8999/
点击预览效果如下(有个小坑,就是input控件中的汉字有问题,反正我实际生产中pdf模板不用input控件),其实这个页面已集成了下载和打印功能,这是Chrome自带的pdf预览。
再点击下载,效果如下:
显示已下载,从pdf软件打开该pdf文件效果如下:
大功告成!
1. 中文字体
2. Css路径
3. 图片路径
4. 页面尺寸(纸张大小)
该示例只是为了演示如何利用freemarker模板引擎生成pdf预览、下载,其中数据都为静态数据,在实际项目中调整数据来源可完美达到预期效果,目前支持比较好的是Chrome内核浏览器,为达到更好的浏览器支持,可以用PDF.js来完成兼容。
PdfUtils.java只是对模板操作做了简单封装,可以根据自己的需要进行二次封装,generateAll方法中已经实现了一个模板接收多个参数对象,输出多页到一个pdf文件中,读者可根据自己需要改造(比如把多个不同的模板输出到一个pdf文件中)。
源代码GITHUB地址: https://github.com/QuSongtao/demo-pdf