Java通过Html(ftl模板)生成PDF实战, 可支持商用

news2024/11/14 19:52:08

Java通过Html(freemarker模板)生成PDF实战, 可支持商用

技术架构

springboot + freemarker + [pdfbox] + flying-saucer-pdf

生成流程:

  1. freemarker: 根据数据填充ftl模板文件,得到包含有效数据的html文件(包含页眉页脚页码的处理,和解决中文渲染等问题)。
  2. flying-saucer-pdf: 将html转换成PDF文件。
  3. pdfbox: 操作PDF文件,完成加解密等操作。

依赖包

<!-- springboot版本, 2.2.x也是支持的 -->
<dependency>
   	<groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>3.3.0</version>
</dependency>
<!-- load freemarker template file -->
 <dependency>
	 <groupId>org.freemarker</groupId>
	 <artifactId>freemarker</artifactId>
	 <version>2.3.30</version>
 </dependency>
<!-- convert html to pdf -->
 <dependency>
     <groupId>org.xhtmlrenderer</groupId>
     <artifactId>flying-saucer-pdf</artifactId>
     <version>9.4.1</version>
 </dependency>
 <dependency>
     <groupId>org.xhtmlrenderer</groupId>
     <artifactId>flying-saucer-core</artifactId>
     <version>9.4.1</version>
 </dependency>
 <!-- operate pdf, such as encypt/decypt,不做加解密可不引用 -->
 <dependency>
     <groupId>org.apache.pdfbox</groupId>
     <artifactId>pdfbox</artifactId>
     <version>2.0.24</version>
 </dependency>
 <!-- encrypt/decrypt zip -->
 <dependency>
     <groupId>net.lingala.zip4j</groupId>
     <artifactId>zip4j</artifactId>
     <version>2.11.5</version>
 </dependency>
PS:网上很多文章使用 itext5/7来生成PDF的,用于个人学习或者开源项目确实没问题,但是不方便用于公司商业项目,因为itext是基于AGPL 的开源协议,商用是需要收费的,当然贵司愿意付费也是okay的。

1. 准备PDF的模板文件(freemarker文件)

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <title>PDF Demo Title</title>
    <style>
        @page {
            size: A4;
            margin: 35mm 10mm 23mm 10mm;
            @top-center {
                content: element(headerTop);
            }
            @bottom-center {
                content: element(footerBottom);
            }
        }
        #pagenumber:before {
            content: counter(page);
        }
        #pagecount:before {
            content: counter(pages);
        }
        .headerTop {
            position: running(headerTop);
            color: white;
        }
        .footerBottom {
            color: #777E90;
            position: running(footerBottom);
            margin-top: 10mm;
        }
        * {
            padding: 0;
            margin: 0;
        }

        html,
        body {
            /*优先加载 Poppins英文字体,无法渲染则使用PingFang中文字体*/
            font-family: Poppins-Medium, PingFang, sans-serif;
            margin: 0 auto;
        }
        .content {
            color: #23262F;
            font-size: 8px;
            padding: 0 0;
            margin-top: 0;
        }
        .size16 {
            font-size: 16px;
        }
        .size12 {
            font-size: 12px;
        }
        .size10 {
            font-size: 10px;
        }
        .size8 {
            font-size: 8px;
        }
        .lineHeight18 {
            line-height: 18px;
        }
        .weight600 {
            font-weight: 600;
        }
        .weight500 {
            font-weight: 500;
        }
        .padding18 {
            padding: 18px;
        }
        .marginBottom8 {
            margin-bottom: 8px;
        }
        .marginLeft6 {
            margin-left: 6px;
        }
        .marginLeft24 {
            margin-left: 24px;
        }
        .marginLeft16 {
            margin-left: 16px;
        }
        .marginLeft13 {
            margin-left: 13px;
        }
        .marginLeft4 {
            margin-left: 4px;
        }
        .marginTop4 {
            margin-top: 4px;
        }
        .marginTop8 {
            margin-top: 8px;
        }
        .width140 {
            width: 140px;
        }
        .widthFull {
            width: 100%;
        }
        .heightFull {
            height: 100%;
        }
        .textAlignCenter {
            text-align: center;
        }
        .inlineCenter {
            display: inline-block;
            vertical-align: top;
        }
        .backGray {
            background-color: #F7F7F7;
        }
        .backBlue {
            background-color: #0E59F0;
        }
        .border {
            border: 1px solid #E5E5E5;
        }
        .colorBlue {
            color: #6E9BF6;
        }
        .colorDark {
            color: #777E90;
        }
        .colorBlack {
            color: #23262F;
        }
        .colorBottomLine {
            background-color: #6E9BF6;
        }
        .colorGray {
            color: #838CA4;
        }
        .table {
            border-spacing: 0;
            /*跨页表格标题*/
            -fs-table-paginate: paginate;
        }
        .table>thead>tr>th,
        .table>tbody>tr>td {
            padding: 16px 4px;
            text-align: left;
            word-break: break-all;
            word-wrap: break-word;
            white-space: normal;
            page-break-inside: avoid;
            page-break-after: auto;
        }
        .table>thead>tr>th {
            color: #838CA4;
            font-weight: 400;
            size: 12px;
        }
        .positionAbsolute {
            position: absolute;
        }
        .rightBottom8 {
            top: 80px;
            right: 30px;
        }
        .height8 {
            height: 8px;
        }
        .height6 {
            height: 6px;
        }
        .height16 {
            height: 16px;
        }
        .right0 {
            right: 0;
        }
        .lineGary {
            height: 1px;
            background-color: #E5E5E5;
        }
    </style>
</head>

<body>
<!-- 页眉 -->
<div class="headerTop">
    <img class="widthFull" style="margin-left: -1px" width="716px"
         src="data:image/jpeg;base64,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" />
    <div class="positionAbsolute rightBottom8 size12 weight500">${headerDate}</div>
</div>

<!-- 页脚 -->
<div class="footerBottom">
    <div class="height6 colorBottomLine"></div>
    <div class="marginTop8 size10">
        <div class="inlineCenter">footer name
            <span class="marginLeft4 colorBlack">
                    <![CDATA[${footerName}]]>
                </span>
        </div>
        <!-- 页码 -->
        <div class="inlineCenter colorBlack positionAbsolute right0">
            <span id="pagenumber"></span>/<span id="pagecount"></span>
        </div>
    </div>
</div>

<div class="content">
    <!-- 用户信息 -->
    <div class="border padding18">
        <div class="colorBlue marginBottom8 weight500 size16">
            <![CDATA[${userName}]]>
        </div>
        <div class="">
            <span>Account Name</span>
            <span class="colorBlue marginLeft6 weight500">
                    <![CDATA[${accountName}]]>
                </span>

            <span class="marginLeft24">Account ID</span>
            <span class="colorBlue marginLeft6 weight500">
                    <![CDATA[${accountId}]]>
                </span>
        </div>
    </div>

    <div class="height8"></div>

    <!-- 表格 -->
    <section style="display: ${display}">
        <div class="height16"></div>

        <div class="">
            <span class="colorBlack size16 weight600 marginLeft4 inlineCenter">Deposit</span>
        </div>

        <div class="marginTop8 weight500 colorBlack size10">Deposit History</div>

        <div class="height16"></div>
        <div class="lineGary"></div>

        <div class="marginTop8">
            <table class="widthFull table">
                <thead>
                <tr class="backGray">
                    <th>Currency</th>
                    <th>Request ID</th>
                    <th>Bank Name</th>
                    <th>Bank Number</th>
                    <th>Amount</th>
                    <th>Time</th>
                </tr>
                </thead>
                <tbody>
                <#if list?? && (list?size> 0)>
                    <#list list as detail>
                        <tr>
                            <td>${detail.tokenCode}</td>
                            <td>${detail.orderNo}</td>
                            <td>
                                <#if detail.bankName??>
                                    <![CDATA[${detail.bankName}]]>
                                <#else>-
                                </#if>
                            </td>
                            <td>
                                <#if detail.bankNumber??>
                                    <#-- 处理特殊字符的渲染 -->
                                    <![CDATA[${detail.bankNumber}]]>
                                <#else>-
                                </#if>
                            </td>
                            <td>
                                <#if detail.qty??>${detail.qty}<#else>-</#if>
                            </td>
                            <td>
                                <#if detail.updateTime??>${detail.updateTime}<#else>-</#if>
                            </td>
                        </tr>
                    </#list>
                </#if>
                </tbody>
            </table>
        </div>

        <div class="height16"></div>
        <div class="lineGary"></div>
    </section>
</div>
</body>
</html>
freemarker模板注意事项:
  1. 图片需要通过base64的方式加载,直接加载图片路径可能无法渲染
  2. 字体名称需要和Java代码中加载的字体名称保持一致,中文无法渲染可能是没有设置别名
  3. 替换的变量,如果有null值需要在模板中判断
  4. 如果填充的变量中存在特殊字符,通过<![CDATA[${变量名}]]> 方式设置
  5. 部分高级的CSS样式或者标签可能不支持
  6. 页眉页脚采用running的方式处理

2. Java Code

2.1 FreeMarkerUtils

import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Map;

@Slf4j
public class FreeMarkerUtils {

    private static Template getTemplate(String templateFileName) {
        Configuration configuration = new Configuration(Configuration.VERSION_2_3_29);
        Template template = null;
        try {
            configuration.setObjectWrapper(new DefaultObjectWrapper());
            //设置编码格式
            configuration.setDefaultEncoding("UTF-8");
            //模板文件
            configuration.setClassForTemplateLoading(FreeMarkerUtils.class, "/templates");
            template = configuration.getTemplate(templateFileName + ".ftl", StandardCharsets.UTF_8.toString());
        } catch (IOException e) {
            e.printStackTrace();
            log.error("get template file failed, fileName:{}", templateFileName);
        }
        return template;
    }

    public static String generateHtmlStr(Map<String, Object> variables, String templateFileName) {
        Template template = getTemplate(templateFileName);
        StringWriter stringWriter = new StringWriter();
        template.setEncoding("UTF-8");
        try (BufferedWriter writer = new BufferedWriter(stringWriter)) {
            template.process(variables, writer);
            String htmlStr = stringWriter.toString();
            writer.flush();
            return htmlStr;
        } catch (TemplateException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 删除xml无法识别的非法字符
     * @param content
     * @return
     */
    public static String removeIllegalChar(String content) {
        return content.replaceAll("[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f]", "");
    }
}

2.2 PdfTest Code


import com.janche.pdf.utils.FileUtil;
import com.janche.pdf.utils.FreeMarkerUtils;
import com.janche.pdf.vo.PdfVo;
import com.lowagie.text.pdf.BaseFont;
import freemarker.cache.ClassTemplateLoader;
import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.encryption.AccessPermission;
import org.apache.pdfbox.pdmodel.encryption.StandardProtectionPolicy;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.IntStream;

/**
 * @Description:
 * @Auther: lirong
 * @Date: 2024/04/16
 */
@Slf4j
public class PdfTest {

    public static void main(String[] args) {

        ITextRenderer renderer = new ITextRenderer();
        try {
            addPdfFont(renderer);
            String htmlStr = FreeMarkerUtils.generateHtmlStr(loadPdfData(), "pdfTemplate");
            renderer.setDocumentFromString(FreeMarkerUtils.removeIllegalChar(htmlStr));
            renderer.layout();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        File pdfFile = new File("output/demo.pdf");
        File encryptPdfFile = new File("output/demo_encrypt.pdf");
        File zipFile = new File("output/demo.zip");

        try (OutputStream os = new FileOutputStream(pdfFile)) {
            renderer.createPDF(os);

            // encrypt pdf, if needn't encrypt pdf file, can remove pdfBox dependency
            PDDocument document = PDDocument.load(pdfFile);
            StandardProtectionPolicy policy = new StandardProtectionPolicy("123456", "1234", new AccessPermission());

            policy.setEncryptionKeyLength(128);
            policy.setPermissions(new AccessPermission());
            document.protect(policy);
            document.save(encryptPdfFile);
            document.close();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        log.info("PDF file generated successfully!");
    }

    private static void addPdfFont(ITextRenderer renderer) throws IOException {
        // add English font
        ClassTemplateLoader classTemplateLoader = new ClassTemplateLoader(FreeMarkerUtils.class, "/static/font");
        String enFontPath = classTemplateLoader.getBasePackagePath() + "Poppins-Medium.ttf";
        ITextFontResolver fontResolver = renderer.getFontResolver();
        // the first font needn't set alias
        fontResolver.addFont(enFontPath, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
        // add Chinese font, the second font must set alias
        String chFontPath = classTemplateLoader.getBasePackagePath() + "PingFang-Regular.ttf";
        fontResolver.addFont(chFontPath, "PingFang", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED, null);
    }

    public static Map<String, Object> loadPdfData() {
        Map<String, Object> data = new HashMap<>();
        // 启用
        data.put("display", "block");
        // 隐藏
//        data.put("display", "none");
        data.put("footerName", "footer-龍");
        data.put("userName", "龍年發财");
        data.put("accountName", "Jackson-龍");
        data.put("accountId", "234324");
        LocalDateTime now = LocalDateTime.now();
        DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        data.put("headerDate", now.format(dateFormatter));

        ArrayList<PdfVo> tableList = new ArrayList<>();
        IntStream.range(1, 20).forEach(i -> {
            LocalDateTime dateTime = now.plusDays(i);
            PdfVo pdfVo = PdfVo.builder()
                    .tokenCode("USD" + i)
                    .bankName("HK Bank" + i)
                    .bankNumber("&23**94&" + i)
                    .qty(BigDecimal.TEN.add(new BigDecimal(i)))
                    .orderNo("ab123445" + i)
                    .updateTime(dateTime.format(dateFormatter))
                    .build();
            tableList.add(pdfVo);
        });
        data.put("list", tableList);
        return data;
    }
}

PS:
  1. 关于中文字体不显示的问题: 一般是字体未正确加载,或者读取时字体名称不正确,完全不需要去更改什么源码class文件,高版本的flying-saucer-pdf 早已支持。
  2. 加载多个字体时,后面的字体需要设置别名,ftl模板中也需要使用设置的别名。
字体文件:(可在文章底部的github项目中获取)
  1. PingFang-Regular.ttf 中文字体
  2. Poppins-Medium.ttf 英文字体

3. PDF展示

在这里插入图片描述

Github源码下载:https://github.com/Janche/springboot-html2pdf-demo

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1701854.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Delft3D水动力-富营养化模型(水质模型)教程

原文链接&#xff1a;Delft3D水动力-富营养化模型&#xff08;水质模型&#xff09;教程https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247605459&idx5&sn105e94f09f0589172835ce8204519971&chksmfa821d34cdf59422b4f6c39b243373a23836d79841a1fcd19f9…

超越中心化:Web3的去中心化应用探索

随着区块链技术的迅速发展&#xff0c;Web3作为其最前沿的应用之一&#xff0c;正引领着互联网进入了一个新的时代。Web3不仅仅是技术的进步&#xff0c;更是一种全新的思维方式和社会模式&#xff0c;其核心理念是去中心化、自治和透明&#xff0c;这与传统的中心化互联网模式…

关于栈的简单讲解

哈喽&#xff0c;小伙伴们大家好呀&#xff0c;今天给大家带来栈、队列的那些知识点。 栈的概念 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。 进行数据插入和删除操作的一端 称为栈顶&#xff0c;另一端称为栈底。 总结 一种线性…

springboot+minio 文件上传

前期准备 需要先安装minio文件服务器&#xff0c;请参考我上一篇文章 pom.xml 版本 本次使用的是springboot2.7.16 版本&#xff0c; minio 版本是8.2.2 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-pare…

1071.字符串的最大公因子

对于字符串s和t&#xff0c;只有在s t t t ... t t&#xff08;t自身连接1次或多次&#xff09;时&#xff0c;我们才认定 “t能除尽s”。 给定两个字符串str1和str2。返回最长字符串x&#xff0c;要求满足x能除尽str1且x能除尽str2 。 示例 1&#xff1a; 输入&#xf…

上位机图像处理和嵌入式模块部署(f103 mcu原理图)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 从linux卡片电脑转到mcu领域,第一个需要适应的估计就是原理图。之前学习linux的时候,我们大概只需要知道一些接口就好了,比如电源键、usb、网口、hdmi口、音频口、tf卡槽等等,整…

使用Java 读取PDF表格数据并保存到TXT或Excel

目录 导入相关Java库 Java读取PDF表格数据并保存到TXT Java读取PDF表格数据并保存到Excel 在日常工作中&#xff0c;我们经常需要处理来自各种来源的数据。其中&#xff0c;PDF 文件是常见的数据来源之一。这类文件通常包含丰富的信息&#xff0c;其中可能包含重要的表格数据…

冯喜运:5.27黄金暴跌大阴后出现“暂定符”今日黄金原油操作策略

【黄金消息面分析】&#xff1a;金价虽然有大阴线暴跌&#xff0c;但依然属于超买后的调整而非熊市&#xff0c;对中长线投资者来说只是市场洗牌。因此&#xff0c;在出现企稳迹象之后&#xff0c;随时关注反弹时机的启动。未来几日&#xff0c;黄金空头可能在进一步发力之前需…

pdf拆分成多个文件 pdf拆分成一页一页

pdf拆分成多个文件的方法。在现代办公环境中&#xff0c;PDF文件因其跨平台、保持格式一致等特性&#xff0c;成为了广泛使用的文件格式。然而&#xff0c;有时我们需要对PDF文件进行拆分&#xff0c;以便更好地管理和使用其中的内容。本文将详细介绍PDF拆分的方法和步骤&#…

智能改写工具,一键改写文章效率非常高

在当今快节奏的时代&#xff0c;效率成为了人们追求的重要目标之一。在文章创作领域&#xff0c;智能改写工具的出现为我们带来了极大的便利&#xff0c;它的高效率一键改写优势可以帮助创作者在几秒的时间里完成文章改写的工作&#xff0c;从此&#xff0c;也让创作者们实现了…

小明同学的考试分数统计:总分、平均分与方差计算进阶

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、原始方法介绍与问题发现 原始方法存在的问题 二、优化方案&#xff1a;使用列表简化代…

【OpenVINO™】在C#中使用 OpenVINO™ 部署 YOLOv10 模型实现目标

文章目录 1. 前言1.1 OpenVINO™ C# API1.2 YOLOv10 2. 模型获取2.1 源码下载2.2 配置环境2.3 下载模型 3. Yolov10 项目配置3.1 项目创建与环境配置3.2 定义模型预测方法3.2.1 定义目标检测模型方法3.2.2 使用OpenVINO™ 预处理接口编译模型 3.2 模型预测方法调用 4. 项目运行…

Java通过Html(ftl模板)生成PDF实战

Java通过Html(freemarker模板)生成PDF实战, 可支持商用 技术架构 springboot freemarker [pdfbox] flying-saucer-pdf 生成流程&#xff1a; freemarker: 根据数据填充ftl模板文件&#xff0c;得到包含有效数据的html文件&#xff08;包含页眉页脚页码的处理&#xff0c…

深入剖析—【服务器硬件】与【Nginx配置】:从基础到实战

服务器硬件部分&#xff1a; Processor (CPU)&#xff1a;服务器的计算核心&#xff0c;负责处理数据和执行程序。Memory (RAM)&#xff1a;用于暂时存储和快速访问数据&#xff0c;决定了系统的运行速度和并发处理能力。Storage (HDD/SSD)&#xff1a;长期存储数据的设备&…

高性能、超小型的电源模块——TP2L-3W系列,3W 1.5KVDC 3KVDC 隔离宽范围输入,单、双输出 DC/DC 电源模块

TP2L-3W系列是一款高性能、超小型的电源模块&#xff0c;宽范围2:1,4:1输入&#xff0c;输出有稳压和连续短路保护功能&#xff0c;隔离电压为1.5KVDC、3KVDC工作温度范围为–40℃到85℃。特别适合对输出电压的精度有严格要求的地方&#xff0c;外部遥控功能对您的设计又多一项…

行业分析---造车新势力之小鹏汽车

1 背景 在之前的博客中&#xff0c;笔者分析了苹果《行业分析---我眼中的Apple Inc.》&#xff0c;随后也分析了电动汽车公司特斯拉《行业分析---马斯克的Tesla》和蔚来汽车《行业分析---造车新势力之蔚来汽车》&#xff0c;看到有不少读者朋友对此类文章也有些兴趣。 那么本篇…

基于springboot实现旅游管理系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现旅游管理系统演示 摘要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本旅游管理系统就是在这样的大环境下诞生&#xff0c;其可以帮助使用者在…

Docker基础篇之入门使用

文章目录 1. Docker的基本组成2. Docker平台架构3. 阿里云镜像加速4. Docker的Hello World入门案例5. 总结 1. Docker的基本组成 Docker的基本组成主要是有四部分&#xff0c;分别是镜像、容器和仓库。 镜像&#xff1a;Docker镜像就是一个只读的模版&#xff0c;镜像可以用来…

huggingface笔记: accelerate estimate-memory 命令

探索可用于某一机器的潜在模型时&#xff0c;了解模型的大小以及它是否适合当前显卡的内存是一个非常复杂的问题。为了缓解这个问题&#xff0c;Accelerate 提供了一个 命令行命令 accelerate estimate-memory。 accelerate estimate-memory {MODEL_NAME} --library_name {LIBR…

kettle学习之表的输入输出

需求 把表A里的数据传送到表B中&#xff0c;在此之前&#xff0c;清空表B内的数据 表输入 执行SQL脚本 表输出