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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

服务器软件架构演进

服务器软件架构演进 背景介绍阶段一&#xff1a;单机部署阶段二&#xff1a;应用与数据分离部署阶段三&#xff1a;启用缓存优化阶段四&#xff1a;启用应用服务器集群阶段五&#xff1a;数据库读写分离阶段六&#xff1a;启用反向代理及CDN加速阶段七&#xff1a;启用分布式文…

论文阅读--GroupViT

视觉之前做无监督分割的时候&#xff0c;经常使用grouping方法&#xff1a;如果有一些聚类的中心点&#xff0c;从这写点开始发散&#xff0c;把周围相似的点逐渐扩充成一个group&#xff0c;这个group就相当是一个segmentation mask 右边是grouping block&#xff0c;左边的两…

【Java】IdentityHashMap 的使用场景

文章目录 前言1. Druid 应用场景2. IdentityHashMap 特性3. IdentityHashMap 同步化4. IdentityHashMap 处理key为空值后记 前言 最近有兴趣看一下 Druid 连接池怎么做连接管理的&#xff0c;看到一个类 IdentityHashMap &#xff0c;这里记录一下使用场景。 1. Druid 应用场…

MySQL数据库语法(二)

一、数据库的创建 创建数据库CRATE DATABASE语法&#xff1a;CREATE DATABASE [IF NOT EXISTS]数据库名;功能&#xff1a;用给定的名字创建一个数据库如果数据库已经存在&#xff0c;发生一个错误。查看创建数据库&#xff1a;SHOW CREATE DATABASE <数据库名>&#xff…

通过键值对访问字典

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 在Python中&#xff0c;如果想将字典的内容输出也比较简单&#xff0c;可以直接使用print()函数。例如&#xff0c;要想打印dictionary字典&#xff…

【Redis】Widows 和 Linux 下使用 Redis

Redis 简述 1.缓存 缓存就是将数据存放在距离计算最近的位置以加快处理速度。缓存是改善软件性能的第一手段,现代 CPU 越来越快的一个重要因素就是使用了更多的缓存,在复杂的软件设计中,缓存几乎无处不在。大型网站架构设计在很多方面都使用了缓存设计。 2.Redis Redis …

神龙秘籍 无极神功 无极管理 真正的力量来自于自我内心。每个人都有潜力成为伟大的,只需要相信自己并发现内在的力量。

功夫熊猫中神龙秘籍的含义 在动画电影《功夫熊猫》中&#xff0c;神龙秘籍&#xff08;Dragon Scroll&#xff09;是一个具有重要象征意义的物品。影片通过神龙秘籍传达了几个深刻的主题和教训。 内在力量与自我发现&#xff1a;当阿宝&#xff08;Po&#xff09;最终打开神龙…

【物联网实战项目】STM32C8T6+esp8266/mqtt+dht11+onenet+uniapp

一、实物图 前端uniapp效果图&#xff08;实现与onenet同步更新数据&#xff09; 首先要确定接线图和接线顺序&#xff1a; 1、stm32c8t6开发板连接stlinkv2下载线 ST-LINK V2STM323.3V3.3VSWDIOSWIOSWCLKSWCLKGNDGND 2、ch340串口连接底座&#xff08;注意RXD和TXD的连接方式…

如何理解 Java 类和对象

Java 中的类和对象是学习 Java 编程的基础之一。类是 Java 中的核心概念之一&#xff0c;它提供了一种组织和封装数据以及相关行为的方式。对象是类的实例&#xff0c;它是在运行时创建的&#xff0c;具有特定的状态和行为。 类和对象的概念 1. 类&#xff08;Class&#xff…

访问构造方法(反射)

文章目录 前言一、反射是什么&#xff1f;二、访问构造方法 1.Constructor对象的获取方法2.Constructor方法的使用总结 前言 Java的反射机制可以实现访问、检测和修改Java对象本身信息的功能&#xff0c;在java.lang.reflect包下提供此功能。可以使程序员更加深入地控制程序的运…

缓存降级

当Redis缓存出现问题或者无法正常工作时,需要有一种应对措施,避免直接访问数据库而导致整个系统瘫痪。缓存降级就是这样一种机制。 主要的缓存降级策略包括: 本地缓存降级 当Redis缓存不可用时,可以先尝试使用本地进程内缓存,如Guava Cache或Caffeine等。这样可以减少对Redis…

如何在工信部教考中心官网查询PG证书

1.第一步&#xff1a;进入工业和信息化部教育与考试中心官网 2.第二步&#xff1a;点击最右边“证书查询” 3.点击“工业和信息化部教育与考试中心培训评价证书查询” 4.在该页面按照如下方式进行证书查询&#xff1a;输入您的证件号码和您的证书号码以及姓名&#xff0c;点…

十四天学会Vue——Vue核心(理论+实战)中篇(第二天)

声明&#xff1a;是接着上篇讲的哦&#xff0c;感兴趣可以去看一看~ 这里一些代码就不写了&#xff0c;为了缩减代码量&#xff0c;大家知道就可以了&#xff1a; Vue.config.productionTip false //阻止 vue 在启动时生成生产提示。热身小tips&#xff0c;可以安装这个插件&…

免费wordpress中文主题

免费大图wordpress主题 首页是一张大图的免费wordpress主题模板。简洁实用&#xff0c;易上手。 https://www.jianzhanpress.com/?p5857 免费WP模板下载 顶部左侧导航条的免费WP模板&#xff0c;后台简洁&#xff0c;新手也可以下载使用。 https://www.jianzhanpress.com/…

idea改了代码,但是需要紧急切换分支,需要把改动的保存到本地

但是如果有冲突&#xff0c;你没有合并&#xff0c;那也会丢哦&#xff01; 改完那个分支&#xff0c;回到这个分支然后弹出来再。

WGCLOUD使用下发指令重启安卓设备

wgcloud的下发命令很好使&#xff0c;可以下发很多命令&#xff0c;最好的是可以选择很多主机同时下发命令 这里我想重启下我的安卓设备&#xff0c;只需要下发一个命令&#xff1a; bash reboot 就好啦 如下图

微信公众号完成自动回复,自定义菜单

微信公众号完成自动回复&#xff0c;自定义菜单 首先要获取到微信公众号的开发者权限&#xff0c;这一步省略&#xff0c;可以自行百度 微信公众号对接自己的服务器 首先第一步需要有自己的服务器和固定的ip&#xff0c; 其中&#xff0c;80/443端口需要有其中一个&#xff0…

按尺寸筛选轮廓图中的轮廓

1.按短边筛选 原始轮廓图&#xff1a; import cv2 import numpy as np# 读取轮廓图 contour_image cv2.imread(..\\IMGS\\pp_edge.png, cv2.IMREAD_GRAYSCALE)# 使用cv2.findContours()函数获取所有轮廓 contours, _ cv2.findContours(contour_image, cv2.RETR_EXTERNAL, cv2…

据阿谱尔APO Research调研显示,2023年全球绝缘栅双极晶体管(IGBT)市场销售额约为89.9亿美元

根据阿谱尔 (APO Research&#xff09;的统计及预测&#xff0c;2023年全球绝缘栅双极晶体管&#xff08;IGBT&#xff09;市场销售额约为89.9亿美元&#xff0c;预计在2024-2030年预测期内将以超过14.7%的CAGR&#xff08;年复合增长率&#xff09;增长。 由于各行业对电力电子…

XSKY CTO 在英特尔存储技术峰会的演讲:LLM 存储,架构至关重要

5 月 17 日&#xff0c;英特尔存储技术峰会在北京顺利举办。作为英特尔长期的合作伙伴&#xff0c;星辰天合受邀参加了此次峰会。星辰天合 CTO 王豪迈作为特邀嘉宾之一&#xff0c;作了主题为《LLM 存储&#xff1a;架构至关重要》的演讲&#xff0c;分享了大语言模型&#xff…