HtmlToPdfUtils [请参照码云上 https://gitee.com/bbevis/html-to-pdf 最新版]
<!-- freemarker依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency> <!-- web基础依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> </dependency> <!-- FlyingSaucer依赖 https://mvnrepository.com/artifact/org.xhtmlrenderer/flying-saucer-pdf --> <dependency> <groupId>org.xhtmlrenderer</groupId> <artifactId>flying-saucer-pdf</artifactId> <version>9.1.12</version> </dependency>
/* * * */ package cn.com.utils; import com.lowagie.text.pdf.BaseFont; import freemarker.template.Template; import freemarker.template.TemplateException; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; 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<String, Object> 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); } DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
// 防止XXE攻击
String FEATURE = null;
FEATURE = "http://javax.xml.XMLConstants/feature/secure-processing";
documentBuilderFactory.setFeature(FEATURE, true);
FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
documentBuilderFactory.setFeature(FEATURE, false);
FEATURE = "http://xml.org/sax/features/external-parameter-entities";
documentBuilderFactory.setFeature(FEATURE, false);
FEATURE = "http://xml.org/sax/features/external-general-entities";
documentBuilderFactory.setFeature(FEATURE, false);
FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
documentBuilderFactory.setFeature(FEATURE, false);
documentBuilderFactory.setXIncludeAware(false);
documentBuilderFactory.setExpandEntityReferences(false);
// 防止XXE攻击
DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder(); byte[] bytes = stringWriter.toString().getBytes(); try(ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes)) { Document document = builder.parse(byteArrayInputStream); return document; } catch (Exception e) { LOGGER.error(e.getMessage(), e); return null; } }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<Map<String, Object>> listVars) throws Exception { if (CollectionUtils.isEmpty(listVars)) { LOGGER.warn("警告:freemarker模板参数为空!"); return; } ITextRenderer renderer = new ITextRenderer(); Document doc = generateDoc(configurer, templateName, listVars.get(0)); renderer.setDocument(doc, null); //设置字符集(宋体),此处必须与模板中的<body style="font-family: SimSun">一致,区分大小写,不能写成汉字"宋体" ITextFontResolver fontResolver = renderer.getFontResolver(); //加载linux系统中文字体 //fontResolver.addFontDirectory("/usr/share/fonts/chinese", BaseFont.NOT_EMBEDDED);
// 默认路径 \src\main\resources\simsun.ttf fontResolver.addFont("simsun.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); //展现和输出pdf renderer.layout(); renderer.createPDF(out, false); //根据参数集个数循环调用模板,追加到同一个pdf文档中 //(注意:此处从1开始,因为第0是创建pdf,从1往后则向pdf中追加内容) for (int i = 1; i < listVars.size(); i++) { Document docAppend = generateDoc(configurer, templateName, listVars.get(i)); renderer.setDocument(docAppend, null); renderer.layout(); renderer.writeNextDocument(); //写下一个pdf页面 } renderer.finishPDF(); //完成pdf写入 } /** * pdf下载 * * @param configurer freemarker配置 * @param templateName freemarker模板名称(带后缀.ftl) * @param listVars 模板参数集 * @param response HttpServletResponse * @param fileName 下载文件名称(带文件扩展名后缀) */ public static void download(FreeMarkerConfigurer configurer, String templateName, List<Map<String, Object>> listVars, HttpServletResponse response, String fileName) { // 设置编码、文件ContentType类型、文件头、下载文件名 response.setCharacterEncoding("utf-8"); response.setContentType("multipart/form-data"); try { String finalFileName = ""; if(StringUtils.isNotBlank(fileName)) { finalFileName = new String(fileName.getBytes("gb2312"), "ISO8859-1"); } if(StringUtils.isBlank(finalFileName)) { throw new RuntimeException("下载文件名fileName为空!"); } response.setHeader("Content-Disposition", "attachment;fileName=" + finalFileName); } catch (Exception e) { LOGGER.error(e.getMessage(), e); } try (ServletOutputStream out = response.getOutputStream()) { generateAll(configurer, templateName, out, listVars); out.flush(); } catch (Exception e) { LOGGER.error(e.getMessage(), e); } } /** * pdf预览 * * @param configurer freemarker配置 * @param templateName freemarker模板名称(带后缀.ftl) * @param listVars 模板参数集 * @param response HttpServletResponse */ public static void preview(FreeMarkerConfigurer configurer, String templateName, List<Map<String, Object>> listVars, HttpServletResponse response) { try (ServletOutputStream out = response.getOutputStream()) { generateAll(configurer, templateName, out, listVars); out.flush(); } catch (Exception e) { LOGGER.error(e.getMessage(), e); } } /** * pdf转换为File * * @param configurer freemarker配置 * @param templateName freemarker模板名称(带后缀.ftl) * @param listVars 模板参数集 * @param file file */ public static void toFile(FreeMarkerConfigurer configurer, String templateName, List<Map<String, Object>> listVars, File file) { try (OutputStream out = FileUtils.openOutputStream(file)) { generateAll(configurer, templateName, out, listVars); out.flush(); } catch (Exception e) { LOGGER.error(e.getMessage(), e); } } }
package cn.com.utils; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.rendering.PDFRenderer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.OutputStream; public class PDFToImgUtil { private static Logger logger = LoggerFactory.getLogger(PDFToImgUtil.class); /** * 获取PDF总页数 * @throws IOException */ public static int getPDFNum(String fileUrl) throws IOException { PDDocument pdDocument = null; int pages = 0; try { pdDocument = getPDDocument(fileUrl); pages = pdDocument.getNumberOfPages(); } catch (Exception e) { e.printStackTrace(); logger.error(e.getMessage(),e); } finally { if (pdDocument != null) { pdDocument.close(); } } return pages; } /** * PDF转图片 根据页码一页一页转 * @throws IOException * imgType:转换后的图片类型 jpg,png */ public static void PDFToImg(OutputStream sos, String fileUrl, int page, String imgType) throws IOException { PDDocument pdDocument = null; /* dpi越大转换后越清晰,相对转换速度越慢 */ int dpi = 100; try { pdDocument = getPDDocument(fileUrl); PDFRenderer renderer = new PDFRenderer(pdDocument); int pages = pdDocument.getNumberOfPages(); if (page <= pages && page > 0) { BufferedImage image = renderer.renderImageWithDPI(page,dpi); ImageIO.write(image, imgType, sos); } } catch (Exception e) { e.printStackTrace(); logger.error(e.getMessage(),e); } finally { if (pdDocument != null) { pdDocument.close(); } } } /** * 转换全部的pdf * @param fileAddress 文件地址 * @param filename PDF文件名 * @param type 图片类型 */ public static void pdf2png(String fileAddress,String filename,String type,int pageCount) { // 将pdf装图片 并且自定义图片得格式大小 File file = new File(fileAddress + "\\" + filename + ".pdf"); try { PDDocument doc = PDDocument.load(file); PDFRenderer renderer = new PDFRenderer(doc); for (int i = 0; i < pageCount; i++) { BufferedImage image = renderer.renderImageWithDPI(i, 140); // Windows native DPI // BufferedImage srcImage = resize(image, 240, 240);//产生缩略图 ImageIO.write(image, type, new File(fileAddress + "\\" + filename + "_" + (i + 9) + "." + type)); } } catch (IOException e) { e.printStackTrace(); } } private static PDDocument getPDDocument(String fileUrl) throws IOException { File file = new File(fileUrl); FileInputStream inputStream = new FileInputStream(file); return PDDocument.load(inputStream); } }
/* * * */ package com.controller; import com.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.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 * @version */ @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模板引擎参数 List<Map<String,Object>> listVars = new ArrayList<>(); Map<String,Object> variables = new HashMap<>(); variables.put("userName","小明"); listVars.add(variables);
// 默认路径 src\main\resources\templates\e-prop-pdf.ftl PdfUtils.preview(configurer,"e-prop-pdf.ftl",listVars,response); } /** * pdf下载 * * @param request HttpServletRequest * @param response HttpServletResponse */ @RequestMapping(value = "/download", method = RequestMethod.GET) public void download(HttpServletRequest request, HttpServletResponse response) { List<Map<String,Object>> listVars = new ArrayList<>(); Map<String,Object> variables = new HashMap<>(); variables.put("title","测试下载ASGX!"); listVars.add(variables);
// 默认路径 src\main\resources\templates\e-prop-pdf.ftl PdfUtils.download(configurer,"e-prop-pdf.ftl",listVars,response,"测试.pdf"); } }
<!DOCTYPE html> <html> <head lang="en"> <title>Spring Boot Demo - PDF</title> <link href="http://localhost:8999/css/index.css" rel="stylesheet" type="text/css"/> <link href="http://localhost:8999/css/pdf.css" rel="stylesheet" type="text/css"/> <style> @page { size: 210mm 297mm; /*设置纸张大小:A4(210mm 297mm)、A3(297mm 420mm) 横向则反过来*/ margin: 0.25in; padding: 1em; @bottom-center{ content:"XXXXX © 版权所有"; font-family: SimSun; font-size: 12px; color:red; }; @top-center { content: element(header) }; @bottom-right{ content:"第" counter(page) "页 共 " counter(pages) "页"; font-family: SimSun; font-size: 12px; color:#000; }; } </style> </head> <#-- 这样配置不中文不会显示 --> <#--<body style="font-family: 宋体">--> <body style="font-family: 'SimSun'"> <div>1.标题-中文</div> <h2>${title}</h2> <div>2.按钮:按钮的边框需要写css渲染</div> <button class="a" style="border: 1px solid #000000"> click me t-p</button> <div id="divsub"></div> <div>3.普通div</div> <div id="myheader">Alice's Adventures in Wonderland</div> <div>4.图片 绝对定位到左上角(注意:图片必须用全路径或者http://开头的路径,否则无法显示)</div> <div id="signImg"></div> <div>5.普通table表格</div> <div> <table> <tr> <td>1</td> <td>2</td> <td>2</td> <td>2</td> <td>2</td> </tr> <tr> <td>1</td> <td>2</td> <td>2</td> <td>2</td> <td>2</td> </tr> <tr> <td>1</td> <td>2</td> <td>2</td> <td>2</td> <td>2</td> </tr> </table> </div> <div>6.input控件,边框需要写css渲染 (在模板中一般不用input,因为不存在输入操作)</div> <div> <label>姓名:</label> <input id="input1" aria-label="dasdasd" type="text" value="123你是"/> </div> </body> </html>
.trialPresentation .pdfContent .clientLetter { height: 42px; line-height: 42px; background: url(data:image/png;base64,iVBORw0KGgoAAAA...=) center no-repeat; border-radius: 21px; font-size: 22px; margin: 0 auto; color: #fff; width: 240px; text-align: center; position: relative; }
/*解决自动分页 分页符在元素上 造成样式错乱问题*/ div {page-break-after:auto;}