SpringBoot 通过 freemarker iText 渲染 html 模板导出 PDF
maven依赖

 
            
                org.freemarker
                freemarker
                ${freemarker.version}
            
            
            
                org.xhtmlrenderer
                flying-saucer-pdf
                ${flying-saucer-pdf.version}
            
            
            
                com.itextpdf
                itextpdf
                ${itextpdf.version}
            

2.3.28
9.1.16
5.5.13

PDF 导出需要 freemarker 的渲染模板 ftl 文件

关于 ftl 模板的一些信息: ftl 模板使用和 html 一致的标签,和 html 不同的是 ftl 模板文件可以添加独有的逻辑元算标签, 如 if-else, for 等,逻辑运算标签具体可参考 freemarker 官方文档:

https://freemarker.apache.org/docs/ref_directives.html

package com.shopping.rbac.util;

import com.lowagie.text.DocumentException;
import com.lowagie.text.pdf.BaseFont;
import com.shopping.rbac.entity.ShoppingOrderDetailEntity;
import com.shopping.rbac.rest.app.order.response.GetShoppingOrderRes;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;

import java.io.*;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 *  
* * @author hanqy
* @version 1.0
* @CreateDate 2020/6/18 8:26 上午
*/ public class PDFUtil { private static final String fontFile = "/Users/hanqy/Desktop/素材/SIMSUN.TTC"; private static final String pdfFile = "/Users/hanqy/Desktop/素材/pdfdemo.pdf"; private static final String htmlTmpFile = "/Users/hanqy/Desktop/素材/orderPrepare.ftl"; public static void main(String[] args) { Map dataMap = new HashMap<>(); GetShoppingOrderRes getShoppingOrderRes = new GetShoppingOrderRes(); getShoppingOrderRes.setId(123); getShoppingOrderRes.setPrice(new BigDecimal(50)); getShoppingOrderRes.setWxName("test一号"); getShoppingOrderRes.setNum(10); getShoppingOrderRes.setOrderTime(DateHelper.formatDate(new Date(), DateHelper.DATE_FORMAT_YYYYMMDDHHMMSS)); List shoppingOrderDetailEntities = new ArrayList<>(); BigDecimal countSum = new BigDecimal(0); for (int i = 1; i <= 5; i++) { ShoppingOrderDetailEntity shoppingOrderDetailEntity = new ShoppingOrderDetailEntity(); shoppingOrderDetailEntity.setNum(5); shoppingOrderDetailEntity.setPrice(new BigDecimal(1)); shoppingOrderDetailEntity.setName("商品收到就好卡拉收到货" + i); shoppingOrderDetailEntity.setSum(shoppingOrderDetailEntity.getPrice().multiply(new BigDecimal(shoppingOrderDetailEntity.getNum()))); countSum = countSum.add(countSum.add(shoppingOrderDetailEntity.getSum())); shoppingOrderDetailEntities.add(shoppingOrderDetailEntity); } dataMap.put("order", getShoppingOrderRes); dataMap.put("details", shoppingOrderDetailEntities); dataMap.put("countsum", countSum); dataMap.put("discount", "0"); dataMap.put("actuallypaid", countSum); String pdfData = freemarkerRender(dataMap, htmlTmpFile); createPDF(pdfData, fontFile, pdfFile); } /** * 使用 Freemarker 渲染 html * * @param dataMap 传入 thml 模板的 Map 数据 * @param htmlTmpFile thml 模板文件(路径+文件名) */ public static String freemarkerRender(Map dataMap, String htmlTmpFile) { Configuration cfg = new Configuration(); cfg.setDefaultEncoding("UTF-8"); Writer out = new StringWriter(); try { // 解决跨系统文件路径问题 int num = htmlTmpFile.lastIndexOf('/'); if (num == -1) { num = htmlTmpFile.lastIndexOf('\\'); } // 获取模板 cfg.setDirectoryForTemplateLoading(new File(htmlTmpFile.substring(0, num))); Template template = cfg.getTemplate(htmlTmpFile.substring(num)); // 将数据写入模板 template.process(dataMap, out); out.flush(); return out.toString(); } catch (IOException e) { e.printStackTrace(); } catch (TemplateException e) { e.printStackTrace(); } finally { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } return null; } /** * 使用 Itext 生成 PDF 文档 * * @param htmlTmpStr html 模板文件字符串 * @param fontFile 所需字体文件(路径+文件名) * @param pdfFile 生成的 PDF 文件(路径+文件名) */ public static void createPDF(String htmlTmpStr, String fontFile, String pdfFile) { File pdf = new File(pdfFile); try { OutputStream out = new FileOutputStream(pdf); ITextRenderer renderer = new ITextRenderer(); renderer.setDocumentFromString(htmlTmpStr); ITextFontResolver fontResolver = renderer.getFontResolver(); // 解决中文支持问题,需要所需字体(ttc)文件 fontResolver.addFont(fontFile, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); renderer.layout(); renderer.createPDF(out); if (out != null) { out.close(); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (DocumentException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }

上面一切正常的时候。需要注意一个问题,
在macOS 15.4版本的系统中,使用macOS系统自带的字体生成pdf的时候会报错,具体原因未知。
解决办法去Windows系统里面复制一个中文的字体过来就可以了。