0%

【工作技能】PDF处理-FreeMarker&xhtmlrenderer

关于使用freemarker和xhtmlrenderer来实现动态生成pdf


1 PDF处理

1.1 数据准备

  • 将生产的PDF所需数据封装为Map集合中
  • 例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    public class MetaData {

    //类
    public static class Person {
    public String name;
    public String sex;

    public Person(String name, String sex) {
    this.name = name;
    this.sex = sex;
    }

    public String getName() {
    return name;
    }
    public void setName(String name) {
    this.name = name;
    }
    public String getSex() {
    return sex;
    }
    public void setSex(String sex) {
    this.sex = sex;
    }
    }

    /**
    * 获取数据
    * @return Map<String, Object>
    */
    public static Map<String, Object> getData() {
    Map<String, Object> data = new HashMap<>();

    data.put("single", "普通数据");

    data.put("person", new Person("莱特雷", "男"));

    List<String> list = new ArrayList<>();
    list.add("Hello");
    list.add("World");
    list.add("!");
    data.put("lists", list);

    return data;
    }
    }

1.2 模板准备

  • (1)创建html文件,编写pdf模板
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8"/> <!--注意:在meta这里补充一个'/',默认生成没有-->
    <title>pdf模板测试</title>
    <style>
    @page {
    /* 页面大小:示例中设置为A4纸的大小 */
    size: 210mm 297mm;
    margin: 15mm 15mm 15mm 15mm;
    }

    body {
    width: 178mm;
    font-family: SimSun; /* 设置字体(宋体) */
    font-size: 18px;
    }

    table {
    width: 100%;
    border-collapse: collapse;
    }

    thead th {
    border: 1px solid #000;
    padding: 5px 5px;
    }

    tr td {
    border: 1px solid #000;
    padding: 5px 5px;
    }
    </style>
    </head>
    <body>
    <h2>PDF模板测试</h2>

    <p>
    这里是Map中key的数据:
    <span>${single}</span>
    </p>

    <p>
    Map中对象数据:
    <span>${person.name}</span>
    ,
    <span>${person.sex}</span>
    </p>

    <table>
    <thead>
    <tr>
    <th>序号</th>
    <th>数据</th>
    </tr>
    </thead>
    <tbody>
    <#list lists as item> <!-- 被遍历的数据需要用此进行封装, lists为map中的key,item为遍历的元素' -->
    <tr>
    <td>${item_index + 1}</td> <!--需要获取遍历数据的下标,可以通过xxx_index来获取-->
    <td>${item}</td>
    </tr>
    </#list>
    </tbody>
    </table>
    </body>
    </html>
  • (2)模板准备好,就修改后缀名为ftl

1.3 工具类准备

  • (1)依赖导入
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <!-- freemarker -->
    <dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.31</version>
    </dependency>

    <!-- 将HTML文件转化为PDF文件 -->
    <dependency>
    <groupId>org.xhtmlrenderer</groupId>
    <artifactId>flying-saucer-pdf</artifactId>
    <version>9.1.20</version>
    </dependency>
  • (2)准备字体文件
  • (3)工具类代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    /**
    * FreeMarker - Pdf工具类
    * @author letere
    * @create 2021-05-14 15:28
    */
    public class PdfUtil {
    //编码格式
    private static final String ENCODING = "UTF-8";
    //ftl模板位置
    private static final String TEMPLATE_PATH = "/ftl/templates/pdf";
    //字体(宋体)文件路径
    private static final String FONT = "/ftl/font/simsun.ttc";
    //FreeMarker配置
    private static final Configuration CFG = new Configuration(Configuration.VERSION_2_3_22);


    static {
    //设置模板所在文件夹
    CFG.setClassForTemplateLoading(PdfUtil.class, TEMPLATE_PATH);
    // setEncoding这个方法一定要设置国家及其编码,不然在ftl中的中文在生成html后会变成乱码
    CFG.setEncoding(Locale.getDefault(), ENCODING);
    // 设置异常处理器,这样的话就可以${a.b.c.d}即使没有属性也不会出错
    CFG.setTemplateExceptionHandler(TemplateExceptionHandler.IGNORE_HANDLER);
    }

    /**
    * 创建PDF文件
    * @param data 数据
    * @param templateFileName 模板名
    * @param outputStream 输出流
    */
    public static void create(Map<String, Object> data, String templateFileName, OutputStream outputStream) throws Exception{
    //将数据写进模板,得到模板字符串
    String templateStr = writeTemplate(data, templateFileName, outputStream);

    //渲染字符串形成pdf
    renderPDF(templateStr, outputStream);
    }

    /**
    * 将数据动态写进pdf模板
    * @param data 数据
    * @param templateFileName 模板名
    * @param outputStream 输出流
    * @return String
    */
    private static String writeTemplate(Map<String, Object> data, String templateFileName, OutputStream outputStream) throws Exception{
    //获取ftl模板
    Template template = CFG.getTemplate(templateFileName, ENCODING);

    //将数据写入到ftl模板中
    StringWriter writer = new StringWriter();
    template.process(data, writer);
    writer.flush();

    //返回写入后的模板字符串
    return writer.toString();
    }

    /**
    * 渲染pdf模板形成PDF
    * @param templateStr 模板字符串
    * @param outputStream 输出流
    */
    private static void renderPDF(String templateStr, OutputStream outputStream) throws Exception{
    //防止依赖冲突,导致找不到TransformerFactory,设置TransformerFactory位置
    System.setProperty("javax.xml.transform.TransformerFactory", "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");

    ITextRenderer renderer = new ITextRenderer();

    //设置字体样式
    renderer.getFontResolver().addFont(FONT, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);

    //渲染模板字符串
    renderer.setDocumentFromString(templateStr);
    renderer.layout();

    //生成pdf
    renderer.createPDF(outputStream, false);
    renderer.finishPDF();
    outputStream.flush();
    }
    }

1.4 测试

  • (1)测试代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     @Test
    public void pdfUtilTest() throws Exception{
    //获取输出流
    String fileName = "./src/main/resources/template/test.pdf";
    OutputStream outputStream = new FileOutputStream(fileName);

    //获取数据
    Map<String, Object> data = MetaData.getData();

    //创建PDF
    PdfUtil.create(data, "pdfTemplate.ftl", outputStream);
    }
  • (2)测试效果