spring boot使用thymeleaf模板引擎实现html文件转pdf的实现过程

一、相关说明

选择thymeleaf+html的原因:个人觉得thymeleaf有这很好的官方文档做支撑,而且语法更好理解而是用ftl模板语法比较古老很少有人使用,因此本人选择thymeleaf作为模板渲染工具进行代码的开发。

本文主要解决:

  1. html与thymeleaf的集合实现内容的渲染最后由后端生成目标文件
  2. html内容汉字的样式渲染,以及相关坑的说明
  3. 模板页眉和页脚可使用html标签实现自定义样式功能
  4. 实现文字水印和图片水印的方法
  5. 传给前端可以是文件流也可以是上传到文件管理后的文件地址

二、引入相关依赖

 <!--生成pdf依赖 start-->
        <dependency>
            <groupId>org.xhtmlrenderer</groupId>
            <artifactId>flying-saucer-pdf-itext5</artifactId>
            <version>9.4.0</version>
        </dependency>

        <dependency>
            <groupId>org.xhtmlrenderer</groupId>
            <artifactId>flying-saucer-core</artifactId>
            <version>9.4.0</version>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>net.sf.jtidy</groupId>
            <artifactId>jtidy</artifactId>
            <version>r938</version>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf</artifactId>
            <version>3.0.6.RELEASE</version>
            <scope>compile</scope>
        </dependency>

<!--生成pdf依赖  end-->

三、准备模板和相关字体

(1)、静态文件位置参考

注大坑:关于中文汉字的一些表达形式类似于:仿宋、方正小标宋简体下载字体包后文字包的名称也是中文但是linux服务器对汉字是无法识别的所以本地测试好好的一旦上生产就拉闸了,该包名导致字体包无法使用因此要怎么该呢?按照css能够识别的样式来该,具体汉字和英文名称如何对应请参考:

中文与css样式对照表

字体包下载请参考:

官方推荐字体下载地址

在这里引入使用即可,但有些字体可能需要官方授权才能使用(方正小标宋简体),因此后端可能会有问题因此需要准备多款类似的字体作以备用。

(2)、模板准备:

header.html 页眉内容

<div th:fragment="header">
    <table>
        <tr>
            <td><span class="barcode">*TEST BARCODE*</span></td>
            <td class="right"><img class="logo" src="logo.png" alt="logo"></td>
        </tr>
    </table>
</div>

footer.html 页脚

<div th:fragment="footer">
    <hr>
    <div class="page-count" style="font-family: 仿宋_GB2312">
        <span> 第 </span> <span id="pagenumber"></span><span> 页 共 </span><span id="pagecount"></span> <span>页</span>
    </div>
</div>

style.css 样式文件

body {
    font-family: sans-serif;
    font-size: 12px;
}

@page {

    @bottom-left {
        content: element(footer);
        vertical-align: top;
        padding-top: 10px;
    }

    @top-right {
        content: element(header);
        vertical-align: bottom;
        padding-bottom: 10px;
    }

    margin-top: 3.3cm;
    margin-left: 2cm;
    margin-right: 2cm;
    margin-bottom: 3.3cm;

    size: A4 portrait;
}

div.header {
    position: running(header);
}

div.footer {
    display: block;
    margin-top: 0.5cm;
    position: running(footer);
}

#pagenumber:before {
    content: counter(page);
}

#pagecount:before {
    content: counter(pages);
}

.logo-container {
    text-align: right;
}

.page-count {
    font-family: 仿宋_GB2312, Serif;
    text-align: right;
}

.logo {
    width: 275px;
}

h1 {
    font-size: 18px;
    margin-bottom: 40px;
    margin-top: 40px;
}

p {
    font-family: SimSun, Serif;
    font-size: 12px;
}

.footer {
    text-align: center;
}

span{
    font-family: SimSun, Serif;
    font-size: 12px;
}

@font-face {
    font-family: "Bar-Code 39";
    src: url(Code39.ttf) format('truetype');
}

@font-face {
    font-family: FangSong_GB2312;
    src: url(FangSong_GB2312.ttf) format("truetype");
}
@font-face {
    font-family: FZXiaoBiaoSong-B05S;
    src: url(FZXiaoBiaoSong-B05S.TTF) format("truetype");
}

.barcode {
    font-family: "Bar-Code 39";
    font-size: 26px;
}

.right {
    text-align: right;
}

.left {
    text-align: left;
}

.bottom {
    vertical-align: bottom;
}

.address-block {
    height: 100px;
}

table {
    width: 100%;
}

hr {
    background-color: #000000;
    border: dashed #000000 0.5px;
    height: 1px;
}

.page-break {
    page-break-after: always;
}

.next-page {
    page-break-before: always
}

template.html 主体部分模板

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">

    <head>
        <meta charset="UTF-8">
        <link rel="stylesheet" type="text/css" media="all" href="style.css"/>
    </head>

    <body>
        <div class='header' th:include="header::header"></div>
<!--        页脚一定要放在正文上面不然第一页页脚加载不上-->
        <div class='footer' th:include="footer::footer"></div>
        <p >良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
        <p>良渚实验室教授委员会主要职责包括:审定良渚实验室人才引进、人才待遇体系、绩效评估等;审定良渚实验室学术相关的事项;审定良渚实验室师德师风相关的事项等。</p>
<!--        <div th:utext="${data.name}"></div>-->

    </body>

</html>

注意:这里的内容暂时写死用于测试效果如果想直接从后端捞数据则请参考thymeleaf语法

请参考:thymeleaf基本语法

四、后端生成pdf方法实现

package com.zjhc.lzlab.utils;

import com.zjhc.lzlab.constants.CommonConstants;
import org.springframework.stereotype.Component;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
import org.w3c.tidy.Tidy;
import org.xhtmlrenderer.pdf.ITextRenderer;
import org.thymeleaf.context.Context;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;

import static com.itextpdf.text.pdf.BaseFont.EMBEDDED;
import static com.itextpdf.text.pdf.BaseFont.IDENTITY_H;
import static org.thymeleaf.templatemode.TemplateMode.HTML;

/**
 * @ClassName: Template2PdfUtils
 * @Description: html模板转为pdf
 * @Author: TXW
 * @Date: 2024-01-12
 */
@Component
public class Template2PdfUtils {


    /**
     * @Param tempName:模板名称,contentData 模板数据
     * */
    public File generatePdf(String fondPath,String tempName, Map<String, String> contentData) throws Exception {
        ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
        templateResolver.setPrefix("/templates/");
        templateResolver.setSuffix(".html");
        templateResolver.setTemplateMode(HTML);
        templateResolver.setCharacterEncoding(CommonConstants.UTF_8);

        TemplateEngine templateEngine = new TemplateEngine();
        templateEngine.setTemplateResolver(templateResolver);

//        Map<String, String> data = new HashMap<>();
//        data.put("name", "<ul> <li class=\"ql-indent-1\"><strong><u>三生三世 三生三世</u></strong><img src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAqUAAADHCAYAAAAgeypKAAAgAElEQVR4nO3dCZAU5f3G8ZdDYQFBEfBAwEh2vRJPymiMmqgEZRLjkSBGIpqopZDSJTEYi3iEqEmMCUsQKtEY77MSFP8ZiG4JibckXnjvekBERUUQlCAR2P887fbaLNMzPTPd/XbPfD9VUyzbszM9uzPdT7/H7+3SlmMAAAAAi7ra3gEAAACAUAoAAADrCKUAAACwjlAKAAAA6wilAAAAsI5QCgAAAOsIpQAAALCOUAoAAADrCKUAAACwjlAKAAAA6wilAAAAsI5QCgAAAOsIpQAAALCOUAoAAADrCKUAAACwjlAKAAAA6wilAAAAsI5QCgAAAOsIpQAAALCOUAoAAADrCKUAAACwjlAKAAAA6wilAAAAsI5QCgAAAOsIpQAAALCOUAoAAADrCKUAAACwjlAKAAAA6wilAAAAsI5QCgAAAOsIpQAAALCOUAoAAADrCKUAAACwjlAKAAAA6wilAAAAsI5QCgAAAOsIpQAAALCOUAoAAADrutveAcDP+oMOsr0LABBI90cftb0LQOrRUgoAAADrCKUAAACwjlAKAAAA6wilAAAAsI5QCgAAAOsIpQAAALCOklBIJcqvAIgbZeqAaNFSCgAAAOsIpQAAALCOUAoAAADrCKUAAACwjlAKAAAA6wilAAAAsI5QCgAAAOsIpQAAIJEefvhhM27cOOemr1HdKJ4PAAAS58wzzzTXXHNNx/9vueUWc9JJJ5lbb73V4l4hSrSUAgCARHnooYc2CaSu2267zQwdOpRW0ypFKAUAAIly2WWX+W574403zFe+8hVz/PHHx7hHiAOhFAAAJIqCZzF33XWXOeigg8wHH3wQwx4hDoRSAACQKH379g10v8cee8wMHjzY3HHHHRHvEeJAKAUAAInSs2fPwPf973//a8aOHetMjEK6MfseQFVSl969995r7rzzTrN27Vqz6667mq233rpj28svv+yc+M444wxz9NFHW95bAJXSxCh9zn/84x/b3hWUiVAKoCrMmzfPXHXVVWbp0qVm5cqVm41J0/Z8NC6td+/eZsyYMearX/2qOeaYYzrCK4B0Oe+888zAgQPNKaecYntXUAZCKYDUUtC89NJLzaJFi8xHH31U9uOsWbPGXHfddc5Ntt9+e7Pbbrs5j33wwQeHtbsAYjB+/HizfPly86Mf/cj2rqBEjCkFkCqqT3j44YebrbbayowePdo88sgjFQXSfJYtW2b+8Y9/OGVnDj30UGb3AjF74YUXfLcFmQSlLnxKRqUPoRRAamgig4LiggULQg+ifh588EGz8847m6lTp8byfECt++53v2veeecd3+0aYhMkcGpozhe+8AWzZMmSMHcPEerSlmN7J9JOb3iVpcg3oaIztbi4H5Bhw4YVHLvmnYyh+x577LEd2/Rze++9d7gvJGHWH3SQ77bujz4a454gCX72s58VLKgdh8997nOmqanJOSmi9nBMip5WcjrkkEMK3kcXiro4/d3vfhdoUtM222zjfG4ZZ5p8hNISqevw8ssvN2+//bbp0aOHWbhwodm4caPt3XJOlgqurl122cXZR9H4mgMPPHCT7WnACQCuzmtg21ZfX2+mT5/OrP0awzEpekOGDHEmK/pRo89LL73U8f8bb7zRnHrqqSZIlDnuuOPMn//8ZyYyJhihtAQnnHCCmT17tu3dqIhmF++zzz5OiFVLq25J/YByAoAEaTnxs9NOO5lBgwY5N01c6tevn/N99UI888wz5vXXX6+oa+/kk082N998c9k/j3ThmBStUlpJvYK2mMq2225rZs6caU488cSy9xPRIZQGoG75H/7wh+a9996zvSuRUNeGwqnC6r777uu0qB522GG2d4sTABxHHHGEmT9/fqD79urVy3zxi190Cmmr9STIBZcC6pw5c5yJTfqsqxB3KVRKitVkagPHpGh9+9vfNn/96199tw8YMMD3PFxqb4rqE1999dUl7yOiRSj1oW76yZMnm6effrrkk1S1cIcEqHXV27IaF04A0dH7+4YbbnDKpuiiRHU9xR3i4Y5lVmuC7aEfmmn74Ycf+m5XjVFNZjj99NOdW6UUTM8//3yzePHiwD/z/e9/3/z2t79NbK8DwsExKVrf+MY3TDab9d1+0kknmVtvvdV3uy4OJ06caN5///1Az3fllVdSaD9hCKV5VEM3fZS83f/6WjOTowirnACiUe74TIXTdevWOTU8p0yZEkv9zrlz55pMJuO7fcKECU5XXBTuueceM2nSJPPaa68Fuv9+++1n7r//foJpFeOYFK2f//zn5pJLLvHdnq/rvjP1fPzgBz8IfA7XxTkToJKDUOqhQtxq8VCNQpROoUVvp/79+4cyuYoTQPgq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\"></img></li><li class=\"ql-indent-1\"><img src=\"data:image/png;base64,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\"></img></li><li class=\"ql-indent-1\">2222222测22222222222222222222222222222222222</li><li class=\"ql-indent-1\">22222222222222222222222222222222222222222222222222222222222222</li></ul>");
//
        Context context = new Context();
        context.setVariable("data", contentData);
//        File tempFile = new File("test.pdf"); //本地生成查看结果用(项目中结构中会生成)
        File tempFile = File.createTempFile("redFile", ".pdf");
        String renderedHtmlContent = templateEngine.process(tempName, context);
        String xHtml = convertToXhtml(renderedHtmlContent);

        String baseUrl = FileSystems.getDefault().getPath("src", "main", "resources")
                .toUri().toURL().toString();

        ITextRenderer renderer = new ITextRenderer();
//        String path =  "D:\\data\\font\\Code39.ttf";
//        String path1 = "D:\\data\\font\\ARIALUNI.TTF";
//        String path2 = "D:\\data\\font\\MSYH.ttc";
//        String path3 = "D:\\data\\font\\MSYHBD.ttc";
//        String path4 = "D:\\data\\font\\MSYHL.ttc";
//        String path5 = "D:\\data\\font\\SIMHEI.TTF";
//        String path6 = "D:\\data\\font\\simsun.ttc";
//        String path7 = "D:\\data\\font\\FangSong_GB2312.ttf";
//        String path8 = "D:\\data\\font\\FZXiaoBiaoSong-B05S.TTF";

        String path = fondPath + "/Code39.ttf";
        String path1 = fondPath+"/ARIALUNI.TTF";
        String path2 = fondPath+"/MSYH.ttc";
        String path3 = fondPath+"/MSYHBD.ttc";
        String path4 = fondPath+"/MSYHL.ttc";
        String path5 = fondPath+"/SIMHEI.TTF";
        String path6 = fondPath+"/simsun.ttc";
        String path7 = fondPath+"/FangSong_GB2312.ttf";
        String path8 = fondPath+"/FZXiaoBiaoSong-B05S.TTF";

        renderer.getFontResolver().addFont(path, IDENTITY_H, EMBEDDED);
        renderer.getFontResolver().addFont(path1, IDENTITY_H, EMBEDDED);
        renderer.getFontResolver().addFont(path2, IDENTITY_H, EMBEDDED);
        renderer.getFontResolver().addFont(path3, IDENTITY_H, EMBEDDED);
        renderer.getFontResolver().addFont(path4, IDENTITY_H, EMBEDDED);
        renderer.getFontResolver().addFont(path5, IDENTITY_H, EMBEDDED);
        renderer.getFontResolver().addFont(path6, IDENTITY_H, EMBEDDED);
        renderer.getFontResolver().addFont(path7, IDENTITY_H, EMBEDDED);
        renderer.getFontResolver().addFont(path8, IDENTITY_H, EMBEDDED);
        renderer.setDocumentFromString(xHtml, baseUrl);
        renderer.layout();
        OutputStream outputStream = new FileOutputStream(tempFile);
        renderer.createPDF(outputStream);

        return tempFile;


        /**
         *
         * // 1. 读取临时文件内容都到 response
         *             try (FileInputStream fis = new FileInputStream(tempFile);
         *                  ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
         *                 byte[] buffer = new byte[1024];
         *                 int len;
         *                 while ((len = fis.read(buffer)) > 0) {
         *                     baos.write(buffer, 0, len);
         *                 }
         *                 baos.flush(); // ensure all data is written to the byte array
         *                 // 2. 将字节数组作为响应的主体返回给前端
         *                 byte[] fileBytes = baos.toByteArray();
         *                 response.setContentType("application/octet-stream"); // 设置响应内容类型为二进制流
         *                 response.setContentLength(fileBytes.length); // 设置响应内容长度
         *                 try (OutputStream os = response.getOutputStream()) {
         *                     os.write(fileBytes); // write bytes to output stream
         *                     os.flush(); // flush the stream to ensure all bytes are written out
         *                 } catch (IOException e) {
         *                     e.printStackTrace();
         *                 }
         *                 outputStream.close();
         *             } catch (IOException e) {
         *                 e.printStackTrace();
         *             }
         * */
    }


    private static String convertToXhtml(String html) throws UnsupportedEncodingException {
        Tidy tidy = new Tidy();
        tidy.setInputEncoding(CommonConstants.UTF_8);
        tidy.setOutputEncoding(CommonConstants.UTF_8);
        tidy.setXHTML(true);
        ByteArrayInputStream inputStream = new ByteArrayInputStream(html.getBytes(CommonConstants.UTF_8));
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        tidy.parseDOM(inputStream, outputStream);
        return outputStream.toString(CommonConstants.UTF_8);
    }
}

 注意:在这里引入服务器 的字体和本地字体位置不一样在上车生产和测试的时候注意切换(我用的是nacos所以配置文件使用的是生产的所以可以在yml文件中注意切换)

tempFile:文件打开生成结果会在项目目录中可点开查看,在模板中使用的所有字体需要再这里引入否则文件中的字体还是无法显示。

如果是给前端传流的话可以将文件写入流中,否则将文件返回出去后使用文件上传的方法将文件上传到文件管理系统再将文件地址返回到前端可以仿照代码:

/**这是一个impl方法*/
public AjaxResult getRedFilePdfShow(HttpServletResponse response, CompositeManageRedFileVo redFileVo){
        Map<String,String> fileMsg = new HashMap<>();
        //设置pdf文件内容
        Map<String,String> data = new HashMap<>();
        data.put("title",redFileVo.getFileTitle());
        data.put("fileNo",redFileVo.getFileNo());
        data.put("signDept",redFileVo.getSignDept());
        try {
            data.put("content", URLDecoder.decode(redFileVo.getContent(),"UTF-8"));
            Template2PdfUtils template2PdfUtils = new Template2PdfUtils();
            File file = template2PdfUtils.generatePdf(fontPath, TemplatesFileEnum.RED_FILE.getTempName(), data);
            String filePath = FileUploadUtils.uploadFileByFile(basePath, file);

            fileMsg.put("fileName",file.getName());
            fileMsg.put("filePath",ip + filePath);
        } catch (Exception e) {
          e.printStackTrace();
        }
        return AjaxResult.success(fileMsg);
    }

你还可能会用到:File to MultipartFile

    public static String uploadFileByFile (String path,File file) throws IOException {
        DiskFileItem item = new DiskFileItem("file"
                , MediaType.MULTIPART_FORM_DATA_VALUE
                , true
                , file.getName()
                , (int)file.length()
                , file.getParentFile());
        try {
            OutputStream os = item.getOutputStream();
            os.write(FileUtils.readFileToByteArray(file));
        } catch (IOException e) {
            e.printStackTrace();
        }
        MultipartFile commonsMultipartFile = new CommonsMultipartFile(item);
        return upload(path, commonsMultipartFile);
    }

五、给目标文件pdf文件添加水印

下列都是实例方法可以直接将方法复制到上面的Utils方法里面进行使用

1、添加文字水印方法

/**
     * 添加文字水印
     * @param pdfPath 需要添加水印的pdf文件  例如:XXXX.pdf
     * @param outFilePath 最终生成含有水印的文件地址 例如:XXXX_waterMark.pdf
     * @param fontPath 文字的字体设置  例如:XXX/XXX/XXX.TTF
     * @param text 水印内容
     * */
    public void addTextWatermark(String pdfPath,String outFilePath, String fontPath,String text) throws IOException, DocumentException {
        // 打开现有的PDF
        PdfReader pdfReader = new PdfReader(pdfPath);
        // 创建一个新的PDF文件名用于输出带水印的PDF,初始化PdfStamper用于添加水印
        PdfStamper pdfStamper = new PdfStamper(pdfReader, new FileOutputStream(outFilePath));
        //获取pdf的总页数
        int n = pdfReader.getNumberOfPages();
        // 设置水印字体
        //BaseFont baseFont = BaseFont.createFont(BaseFont.HELVETICA, BaseFont.WINANSI, BaseFont.EMBEDDED);
        BaseFont baseFont = BaseFont.createFont(fontPath, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);

        // 在PDF中添加水印
        for (int i = 1; i <= n; i++) {
            PdfContentByte overContent = pdfStamper.getOverContent(i); // 获取每一页的内容
            // 设置水印透明度
            PdfGState gs = new PdfGState();
            gs.setFillOpacity(0.6f); // 设置透明度
            overContent.setGState(gs);
            // 计算水印位置和间隔
            float xInterval = 150;
            float yInterval = 100;
            float xStart = 40;
            float yStart = 50;
            float x, y;
            int rotation = 45; // 水印旋转角度
            // 在PDF中添加重复水印
            for (x = xStart; x < PageSize.A4.getWidth(); x += xInterval) {
                for (y = yStart; y < PageSize.A4.getHeight(); y += yInterval) {
                    overContent.beginText();
                    overContent.setFontAndSize(baseFont, 50);
                    overContent.setColorFill(BaseColor.PINK);
                    overContent.showTextAligned(Element.ALIGN_CENTER, text, x, y, rotation);
                    overContent.endText();
                }
            }
        }
        // 关闭PdfStamper
        pdfStamper.close();
        pdfReader.close();
    }

效果展示:

2、添加图片水印的方法

/**
     * 添加图片水印
     * @param pdfPath 需要添加水印的pdf文件  例如:XXXX.pdf
     * @param outFilePath 最终生成含有水印的文件地址 例如:XXXX_waterMark.pdf
     * @param imagePath 图片路径 XXX.png
     * */
    public void addImageWatermark(String pdfPath,String outFilePath, String imagePath) throws IOException, DocumentException {
        // 打开现有的PDF
        PdfReader pdfReader = new PdfReader(pdfPath);
        // 初始化PdfStamper用于添加水印
        PdfStamper pdfStamper = new PdfStamper(pdfReader, new FileOutputStream(outFilePath));

        int n = pdfReader.getNumberOfPages(); // 获取PDF的总页数

// 加载水印图片
        Image image = Image.getInstance(imagePath); // 替换为您的图片路径
        image.setAbsolutePosition(200, 400); // 设置图片的位置
        image.scaleToFit(250, 250); // 设置图片的大小
        image.setRotationDegrees(-45); // 设置图片的旋转角度

// 循环每一页,添加水印
        for (int i = 1; i <= n; i++) {
            PdfContentByte content = pdfStamper.getOverContent(i); // 获取每一页的内容

            // 设置水印透明度
            PdfGState gs = new PdfGState();
            gs.setFillOpacity(0.2f); // 设置透明度
            content.setGState(gs);

            // 计算水印位置和间隔
            float xInterval = 200; // 水印的水平间隔
            float yInterval = 100; // 水印的垂直间隔
            float x, y;

            // 在PDF中添加重复水印
            for (x = 0; x < PageSize.A4.getWidth(); x += xInterval) {
                for (y = 0; y < PageSize.A4.getHeight(); y += yInterval) {
                    Image watermarkImage = Image.getInstance(image);
                    watermarkImage.setAbsolutePosition(x, y);
                    content.addImage(watermarkImage);
                }
            }
        }

        // 关闭PdfStamper
        pdfStamper.close();
        pdfReader.close();
    }

效果展示:

以上水印的方法中可能会存在文字或者透明度以及相关参数需要进行调整因此按照客户需求修改相关的值即可 

六、项目效果展示:

模板代码:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="UTF-8">
</head>
<style>
    @page {
        @bottom-center {
            content: counter(page);
            font-family: SimSun;
            vertical-align: middle;
            font-size: 21px;
            padding-bottom: 10px;
        };

/*        @bottom-right {
            content: "第 " counter(page) " 页 共 " counter(pages) " 页 ";
            font-family: SimSun;
            font-size: 21px;
            color: red;

            //自定义页脚
           @bottom-left {
            content: element(footer);
            font-family: SimSun;
            vertical-align: middle;
            font-size: 21px;
            color: red;
            padding-bottom: 10px;
        };
        }*/
        margin-top: 2cm;
        margin-left: 2cm;
        margin-right: 2cm;
        margin-bottom: 3.3cm;

        size: A4 portrait;
    }

    @font-face {
        font-family: FangSong_GB2312;
        src: url(FangSong_GB2312.ttf) format("truetype");
    }
    @font-face {
        font-family: FZXiaoBiaoSong-B05S;
        src: url(FZXiaoBiaoSong-B05S.TTF) format("truetype");
    }

    #pagenumber:before {
        content: counter(page);
    }

    #pagecount:before {
        content: counter(pages);
    }

    .title {
        text-align: center;
        color: red;
        font-family: FZXiaoBiaoSong-B05S,serif;
        font-size: 41px;
    }

    .fileNo {
        font-family: FangSong_GB2312,serif;
        font-size: 21px;
        text-align: center;
    }

    span {
        font-family: 'Times New Roman', serif;
    }

    img {
        display: block;
        margin-left: auto;
        margin-right: auto;
    }

    div.footer {
        position: running(footer);
    }
    .footer {
        position: running(footer);
    }

    .bottom {
        vertical-align: bottom;
    }

    /*判断是否结束*/
    .page-break {
        page-break-after: always;
    }

    /*下一页*/
    .next-page {
        page-break-before: always
    }

</style>
<body>

<!--标题-->
<h1 class="title" style="font-family: FZXiaoBiaoSong-B05S, serif" th:text="${data.title}"></h1>
<br/>
<!--文号-->
<p class="fileNo" style="font-family: FangSong_GB2312, serif" th:text="${data.fileNo}"></p>
<hr style="background-color: red;border-style: none;height: 2px;"/>
<!--页脚一定要放在正文的前面,先加载页脚在加载正文不然页脚无法显示(TMD大坑)-->
<div class='footer' th:include="footer::footer"></div>
<!--正文-->
<div style="font-family: FangSong_GB2312, serif" th:utext="${data.content}"></div>

<div style="position: relative;bottom: 0;" >
    <hr>
    <table style="width: 100%;">
        <tr>
            <td style="text-align: left;font-family: 'NSimSun', serif;" th:text="${data.signDept}"></td>
            <td style="text-align: right;font-family: 'NSimSun', serif;" th:text="${#dates.format(#dates.createNow(), 'yyyy年MM月dd日')} + '印发'"></td>
        </tr>
    </table>
    <hr>
</div>
<!--        <div th:utext="${data.name}"></div>-->

</body>

</html>

生成效果:

 

希望能对看到这篇文章的你有帮助!

------------------------------------------------------------------------------

后续问题请看评论区,本人有回复部分问题的解决方案!

后续问题二:

在加载css文件的时,测试和生产可能不互通,因此css文件可能无法加载成功导致接口超时!

解决方案:

<link rel="stylesheet" type="text/css" th:href="@{http://localhost/data/files/edit_css1.css}"/>

 解决测试和生产环境的css文件加载切换的问题!

前提条件在测试和生产端必须有该文件,否则还是会加载失败

  • 21
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值