Always keep a |

玩单机的零度

园龄:2年2个月粉丝:1关注:7

2023-02-02 16:10阅读: 1377评论: 0推荐: 0

IText7将html转换为pdf

 

🧀相关须知

  1. 本文使用自建简单html5进行转换pdf测试,html非模板引擎生成
  2. 相关字体文件使用Windows下微软黑体字体文件,本文使用字体文件来自Windows系统自带字体文件,路径:C:\Windows\Fonts\微软雅黑
  3. 相关pom依赖
    <!-- lombok工具 在父级pom 进行全局引入 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.24</version>
    </dependency>
    
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>1.18.24</version>
    </dependency>
    <!--html转pdf工具 itext7 html2pdf -->
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>html2pdf</artifactId>
        <version>3.0.5</version>
    </dependency>

      

一、实现pdf工具类

package cn.cxyfyf.base.test.pdf.itext.config;

import cn.cxyfyf.base.framework.utils.IdUtils;
import com.itextpdf.html2pdf.ConverterProperties;
import com.itextpdf.html2pdf.HtmlConverter;
import com.itextpdf.io.font.PdfEncodings;
import com.itextpdf.kernel.events.PdfDocumentEvent;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.font.FontProvider;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Itext7转换pdf工具类
 */
@Slf4j
public class PdfUtils {

    // 字体文件路径
    private static final String fontPath = "C:\\Users\\86185\\Desktop\\html-pdf\\front\\msyh.ttc,0";
    // 字体文件 列表
    private static final Set<String> fontSet = new HashSet<>();
    
    static {
        fontSet.add(fontPath);
    }

    /**
     * 浏览器响应pdf
     * @param response 浏览器响应
     * @param htmlStr html字符串
     * @param name 文件名 为空使用UUID
     * @param isOpen true浏览器直接打开,false浏览器附件形式下载
     */
    public static void downloadPdf(HttpServletResponse response, String htmlStr, String name, boolean isOpen) {
        if (StringUtils.isBlank(htmlStr)) htmlStr = "";
        downloadPdf(response, new ByteArrayInputStream(htmlStr.getBytes(StandardCharsets.UTF_8)), name, isOpen);
    }

    /**
     * 浏览器响应pdf
     * @param response 浏览器响应
     * @param inputStream 输入流
     * @param name 文件名 为空使用UUID
     */
    @SneakyThrows
    public static void downloadPdf(HttpServletResponse response, InputStream inputStream, String name, boolean isOpen) {
        Objects.requireNonNull(response);
        String suffix = ".pdf";
        // 文件名 为空使用UUID
        if (StringUtils.isBlank(name)) name = IdUtils.simpleUUID();
        // 设置响应文件类型
        response.setContentType(Files.probeContentType(Path.of(suffix)));
        // 设置编码UTF_8
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        // 文件名转ISO_8859_1,不然会乱码
        String filename = new String(name.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
        // 若文件不存在后缀,拼接后缀名
        if (!filename.contains(suffix)) {
            filename+=suffix;
        }
        // 设置浏览器附件形式下载
        if (!isOpen) {
            response.setHeader("Content-Disposition","attachment;filename=" + filename);
        }
        // 转换pdf数据
        PdfUtils.convertHtmlToPdf(inputStream, PageSize.A4, response.getOutputStream());
        response.flushBuffer();
    }

    /**
     * html转 pdf 默认A4纸张
     * @param inputStream  输入流
     * @param outputStream 输出流
     */
    public static void convertHtmlToPdf(InputStream inputStream, OutputStream outputStream) {
        convertHtmlToPdf(inputStream, "", "", PageSize.Default, fontSet, outputStream);
    }

    /**
     * html转 pdf 默认A4纸张
     * @param htmlStr  html 字符串文本
     * @param outputStream 输出流
     */
    public static void convertHtmlToPdf(String htmlStr, OutputStream outputStream) {
        convertHtmlToPdf(new ByteArrayInputStream(htmlStr.getBytes(StandardCharsets.UTF_8)), "", "", PageSize.Default, fontSet, outputStream);
    }
    /**
     * html转 pdf
     * @param htmlStr  html 字符串文本
     * @param pageSize 纸张大小
     * @param outputStream 输出流
     */
    public static void convertHtmlToPdf(String htmlStr, PageSize pageSize, OutputStream outputStream) {
        convertHtmlToPdf(new ByteArrayInputStream(htmlStr.getBytes(StandardCharsets.UTF_8)), "", "", pageSize, fontSet, outputStream);
    }

    /**
     * html转 pdf
     * @param inputStream  输入流
     * @param pageSize 纸张大小
     * @param outputStream 输出流
     */
    public static void convertHtmlToPdf(InputStream inputStream, PageSize pageSize, OutputStream outputStream) {
        convertHtmlToPdf(inputStream, "", "", pageSize, fontSet, outputStream);
    }

    /**
     * html转 pdf
     * @param inputStream  输入流
     * @param headerText    页眉
     * @param waterMark    水印
     * @param pageSize 纸张大小
     * @param fontPathList  字体路径列表,ttc后缀的字体需要添加<b>,0<b/> 例:C:\front\msyh.ttc,0
     * @param outputStream 输出流
     */
    public static void convertHtmlToPdf(InputStream inputStream, String headerText, String waterMark, PageSize pageSize,
                                        Set<String> fontPathList, OutputStream outputStream) {

        // 验空
        Objects.requireNonNull(inputStream);
        Objects.requireNonNull(outputStream);

        PdfWriter pdfWriter = new PdfWriter(outputStream);
        PdfDocument pdfDocument = new PdfDocument(pdfWriter);
        //设置纸张大小
        pdfDocument.setDefaultPageSize(pageSize);

        //添加中文字体支持
        ConverterProperties properties = new ConverterProperties();
        FontProvider fontProvider = new FontProvider();

        //添加自定义字体,例如微软雅黑
        if (CollectionUtils.isNotEmpty(fontPathList)) {
            fontPathList.forEach(e -> fontProvider.addFont(e, PdfEncodings.IDENTITY_H));
        }

        PdfFont pdfFont = fontProvider.getFontSet()
                .getFonts()
                .stream()
                .findFirst()
                .map(fontProvider::getPdfFont)
                .orElse(null);

        // 添加页眉
        pdfDocument.addEventHandler(PdfDocumentEvent.START_PAGE, new PdfHeaderMarkerEventHandler(pdfFont, headerText));
        // 添加水印
        pdfDocument.addEventHandler(PdfDocumentEvent.INSERT_PAGE, new PdfWaterMarkEventHandler(pdfFont, waterMark));
        // 添加页脚
        pdfDocument.addEventHandler(PdfDocumentEvent.END_PAGE, new PdfPageMarkerEventHandler(pdfFont));

        properties.setFontProvider(fontProvider);
        // 读取Html文件流,查找出当中的&nbsp;或出现类似的符号空格字符
        inputStream = readInputStrem(inputStream);

        try {
            // 生成pdf文档
            HtmlConverter.convertToPdf(inputStream, pdfDocument, properties);
        } catch (IOException e) {
            e.printStackTrace();
            log.error("错误信息:pdf转换失败{}", e.getMessage());
        } finally {
            try {
                pdfWriter.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            pdfDocument.close();
        }
    }

    /**
     * 读取HTML 流文件,并查询当中的&nbsp;或类似符号直接替换为空格
     *
     * @param inputStream 输入流
     * @return 去掉特殊标记的输入流
     */
    private static InputStream readInputStrem(InputStream inputStream) {
        // 定义一些特殊字符的正则表达式 如:
        String regEx_special = "\\&[a-zA-Z]{1,10};";
        try(ByteArrayOutputStream baos = new ByteArrayOutputStream();) {
            // 创建缓存大小
            byte[] buffer = new byte[1024]; // 1KB
            // 每次读取到内容的长度
            int len = -1;
            // 开始读取输入流中的内容
            while ((len = inputStream.read(buffer)) != -1) { //当等于-1说明没有数据可以读取了
                baos.write(buffer, 0, len);   //把读取到的内容写到输出流中
            }
            // 把字节数组转换为字符串 设置utf-8字符编码
            String content = baos.toString(StandardCharsets.UTF_8);
            // 关闭输入流和输出流
            inputStream.close();
            // 判断HTML内容是否具有HTML的特殊字符标记
            Pattern compile = Pattern.compile(regEx_special, Pattern.CASE_INSENSITIVE);
            Matcher matcher = compile.matcher(content);
            String replaceAll = matcher.replaceAll("");
            // 将字符串转化为输入流返回
            return getStringStream(replaceAll);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("错误信息:pdf字符串格式化特殊字符失败{}", e.getMessage());
            return null;
        }
    }

    /**
     * 将一个字符串转化为输入流
     * @param sInputString 字符串
     * @return 字符串对应的输入流
     */
    public static InputStream getStringStream(String sInputString) {
        if (sInputString != null && !sInputString.trim().equals("")) {
            try {
                return new ByteArrayInputStream(sInputString.getBytes(StandardCharsets.UTF_8)); // 设置utf-8字符编码
            } catch (Exception e) {
                e.printStackTrace();
                log.error("错误信息:pdf字符串转输入流失败,{}", e.getMessage());
            }
        }
        return null;
    }


}

二、页眉、页脚、水印实现类

页眉

页眉通常用来显示公司等标志性信息

package cn.cxyfyf.base.test.pdf.itext.config;

import com.itextpdf.kernel.events.Event;
import com.itextpdf.kernel.events.IEventHandler;
import com.itextpdf.kernel.events.PdfDocumentEvent;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.layout.Canvas;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.property.TextAlignment;
import org.apache.commons.lang3.StringUtils;

/**
 * 页眉实现类
 */
public class PdfHeaderMarkerEventHandler implements IEventHandler {

    /**
     * pdf字体
     */
    private final PdfFont pdfFont;
    /**
     * 页眉显示
     */
    private final String title;

    public PdfHeaderMarkerEventHandler(PdfFont pdfFont, String title) {
        this.pdfFont = pdfFont;
        this.title = title;
    }
 
    @Override
    public void handleEvent(Event event) {

        if (StringUtils.isEmpty(title)) return;

        PdfDocumentEvent docEvent = (PdfDocumentEvent) event;
        PdfDocument pdf = docEvent.getDocument();
        PdfPage page = docEvent.getPage();
        Rectangle pageSize = page.getPageSize();
        PdfCanvas pdfCanvas = new PdfCanvas(page.getLastContentStream(), page.getResources(), pdf);
        Canvas canvas = new Canvas(pdfCanvas, pageSize);
        float x = (pageSize.getLeft() + pageSize.getRight()) / 2;
        float y = pageSize.getTop() - 25;
        Paragraph p = new Paragraph(title)
                .setFontSize(12)
                .setFont(pdfFont);
        // 显示在顶部中间位置
        canvas.showTextAligned(p, x, y, TextAlignment.CENTER);
        canvas.close();
    }
 
}

页脚

页脚通常用来显示页码

package cn.cxyfyf.base.test.pdf.itext.config;

import com.itextpdf.kernel.events.Event;
import com.itextpdf.kernel.events.IEventHandler;
import com.itextpdf.kernel.events.PdfDocumentEvent;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.layout.Canvas;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.property.TextAlignment;

/**
 * 页脚(页码)实现类
 */
public class PdfPageMarkerEventHandler implements IEventHandler {

    /**
     * pdf字体
     */
    private final PdfFont pdfFont;
 
    public PdfPageMarkerEventHandler(PdfFont pdfFont) {
        this.pdfFont = pdfFont;
    }
 
    @Override
    public void handleEvent(Event event) {
        PdfDocumentEvent docEvent = (PdfDocumentEvent) event;
        PdfDocument pdf = docEvent.getDocument();
        PdfPage page = docEvent.getPage();
        int pageNumber = pdf.getPageNumber(page);
        Rectangle pageSize = page.getPageSize();
        PdfCanvas pdfCanvas = new PdfCanvas(page.getLastContentStream(), page.getResources(), pdf);
        Canvas canvas = new Canvas(pdfCanvas, pageSize);
        float x = (pageSize.getLeft() + pageSize.getRight()) / 2;
        float y = pageSize.getBottom() + 15;
        Paragraph p = new Paragraph("第" + pageNumber + "页")
                .setFontSize(12)
                .setFont(pdfFont);
        // 绘制到底部中间位置
        canvas.showTextAligned(p, x, y, TextAlignment.CENTER);
        canvas.close();
    }
 
}

水印

 水印实现类,具体显示效果可以自行改造 

package cn.cxyfyf.base.test.pdf.itext.config;

import com.itextpdf.kernel.colors.WebColors;
import com.itextpdf.kernel.events.Event;
import com.itextpdf.kernel.events.IEventHandler;
import com.itextpdf.kernel.events.PdfDocumentEvent;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.layout.Canvas;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.property.TextAlignment;
import com.itextpdf.layout.property.VerticalAlignment;
import org.apache.commons.lang3.StringUtils;

/**
 * pdf水印
 */
public class PdfWaterMarkEventHandler implements IEventHandler {

    /**
     * 水印内容
     */
    private final String waterMarkContent;

    /**
     * 一页中有几行水印
     */
    private final int waterMarkX;

    /**
     * 一页中每列有多少水印
     */
    private final int waterMarkY;
    /**
     * Pdf字体
     */
    private final PdfFont pdfFont;

    /**
     * 默认水印效果5行5列
     * @param pdfFont pdf字体
     * @param waterMarkContent 水印内容
     */
    public PdfWaterMarkEventHandler(PdfFont pdfFont, String waterMarkContent) {
        this(pdfFont, waterMarkContent, 5, 5);
    }

    /**
     * 水印效果
     *
     * @param pdfFont          pdf字体
     * @param waterMarkContent 水印内容
     * @param waterMarkX       一页中有多少行水印
     * @param waterMarkY       一页中有多少列水印
     */
    public PdfWaterMarkEventHandler(PdfFont pdfFont, String waterMarkContent, int waterMarkX, int waterMarkY) {
        this.waterMarkContent = waterMarkContent;
        this.waterMarkX = waterMarkX;
        this.waterMarkY = waterMarkY;
        this.pdfFont = pdfFont;
    }

    @Override
    public void handleEvent(Event event) {

        if (StringUtils.isEmpty(waterMarkContent)) return;

        // 获取pdf对象、页面属性
        PdfDocumentEvent documentEvent = (PdfDocumentEvent) event;
        PdfDocument document = documentEvent.getDocument();
        PdfPage page = documentEvent.getPage();
        Rectangle pageSize = page.getPageSize();

        // 设置画布属性、水印属性
        PdfCanvas pdfCanvas = new PdfCanvas(page.newContentStreamAfter(), page.getResources(), document);
        Paragraph waterMark = new Paragraph(waterMarkContent).setOpacity(0.5f);
        Canvas canvas = new Canvas(pdfCanvas, pageSize)
            .setFontColor(WebColors.getRGBColor("lightgray")) // 水印颜色效果
            .setFontSize(16)
            .setFont(pdfFont);

        float y = pageSize.getHeight(); // 页面高度
        float x = pageSize.getWidth(); // 页面宽度

        // 根据行列进行绘制
        for (int i = 0; i < waterMarkX; i++) {
            float width = (x / waterMarkX)  * i + 50;
            for (int j = waterMarkY; j > 0; j--) {
                float height = (y / waterMarkY)  * j - 50;
//                System.out.println("waterMarkX: "+width+" waterMarkY: "+height);
                canvas.showTextAligned(waterMark, width, height, document.getNumberOfPages(), TextAlignment.CENTER, VerticalAlignment.MIDDLE, 120);
            }
        }
        canvas.close();
    }


}

水印(图片)

图片水印和固定文字位置水印效果

package cn.cxyfyf.base.test.pdf.itext.config;

import com.itextpdf.io.image.ImageData;
import com.itextpdf.io.image.ImageDataFactory;
import com.itextpdf.kernel.events.Event;
import com.itextpdf.kernel.events.IEventHandler;
import com.itextpdf.kernel.events.PdfDocumentEvent;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.layout.Canvas;
import com.itextpdf.layout.element.Image;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.property.TextAlignment;
import com.itextpdf.layout.property.VerticalAlignment;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.ClassPathResource;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * 添加图片水印
 */
public class PdfWaterMarker implements IEventHandler {
 
    private final PdfFont pdfFont;
    private final String waterContent;
    private final String waterImgPath;
 
    public PdfWaterMarker(PdfFont pdfFont, String waterContent, String waterImgPath) {
        this.pdfFont = pdfFont;
        this.waterContent = waterContent;
        this.waterImgPath = waterImgPath;
    }
 
    @Override
    public void handleEvent(Event event) {
 
        PdfDocumentEvent docEvent = (PdfDocumentEvent) event;
        PdfDocument pdf = docEvent.getDocument();
        PdfPage page = docEvent.getPage();
        Rectangle pageSize = page.getPageSize();
        PdfCanvas pdfCanvas = new PdfCanvas(page.getLastContentStream(), page.getResources(), pdf);
        Canvas canvas = new Canvas(pdfCanvas, pageSize);
        Paragraph waterMarker = new Paragraph(waterContent)
                .setFont(pdfFont)
                .setOpacity(.5f)
                .setFontSize(13);
        // 右下角位置
        canvas.showTextAligned(waterMarker, pageSize.getRight() - 150, pageSize.getBottom() + 15, pdf.getNumberOfPages(), TextAlignment.LEFT, VerticalAlignment.BOTTOM, 0);
 
        // 水印图片
        Image waterImg = null;
        if (StringUtils.isNotBlank(waterImgPath)) {
            try {
                InputStream inputStream = returnInputStream(waterImgPath);
                ImageData waterImgData = ImageDataFactory.create(toByteArray(inputStream));
                waterImg = new Image(waterImgData);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        // 图片水印
        if (waterImg != null) {
            int length = waterContent.length();
            // 设置坐标 X Y
            waterImg.setFixedPosition(pdf.getNumberOfPages(), pageSize.getRight() - (168 + length), pageSize.getBottom() + 12);
            // 设置等比缩放
            waterImg.scaleAbsolute(20, 20);// 自定义大小
            // 写入图片水印
            canvas.add(waterImg);
        }
 
        canvas.close();
    }
 
    public static byte[] toByteArray(InputStream input) throws IOException {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        byte[] buffer = new byte[4096];
        int n = 0;
        while (-1 != (n = input.read(buffer))) {
            output.write(buffer, 0, n);
        }
        return output.toByteArray();
    }
 
    public static InputStream returnInputStream(String filePath) throws IOException {
        return new ClassPathResource(filePath).getInputStream();
    }
 
}

三、测试类

package cn.cxyfyf.base.test.pdf.itext;

import cn.cxyfyf.base.test.pdf.itext.config.SignUtils;
import lombok.extern.slf4j.Slf4j;

import java.io.*;

@Slf4j
public class Test {

    public static void main(String[] args) throws IOException {
        long startTime = System.currentTimeMillis();
        // html文件所在绝对路径
        String htmlFile = "C:\\Users\\86185\\Desktop\\html-pdf\\ceshi.html";
        // pdf文件存储绝对路径
        String pdfFile = "C:\\Users\\86185\\Desktop\\html-pdf\\ceshi.pdf";
        try(InputStream inputStream = new FileInputStream(htmlFile);OutputStream singOutputStream = new FileOutputStream(pdfFile);) {
            PdfUtils.convertHtmlToPdf(inputStream, outputStream);
        }

        log.info("转换结束,耗时:{}ms",System.currentTimeMillis()-startTime);
    }


}

浏览器下载方式

package cn.cxyfyf.base.controller.sys;

import cn.cxyfyf.base.framework.utils.IdUtils;
import cn.cxyfyf.base.test.pdf.itext.PdfUtils;
import com.itextpdf.kernel.geom.PageSize;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.FileInputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;

/**
 * test Controller
 *
 * @author fyf
 * @date 2022-11-01
 */
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/test")
public class TestController {

    /**
     * 导出pdf
     */
    @SneakyThrows
    @PostMapping("/downloadPdf")
    public void downloadPdf(HttpServletResponse response, @RequestBody Map<String, Object> map){
        // html文件所在绝对路径
        String htmlFile = "C:\\Users\\86185\\Desktop\\html-pdf\\ceshi.html";
        PdfUtils.downloadPdf(response, new FileInputStream(htmlFile), "小和系统", false);
    }

}

使用Freemarker模板引擎生成html字符串

需要引入依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
    <version>2.6.1</version>
</dependency>

需要在项目resource目录下创建templates目录,并创建template.html文件

 template.html源码

<html>
<head>
    <style>
        .center_div {
            border: 1px solid #404e94;
            margin-left: auto;
            margin-right: auto;
            background-color: #f6d0ed;
            text-align: left;
            padding: 8px;
        }
        table {
            width: 100%;
            border: 1px solid black;
        }
        th, td {
            border: 1px solid black;
        }
        body,html,input{font-family:"msyh";}
    </style>
</head>
<body>
<div class="center_div">
    <h1>Hello java North!</h1>
    <div>
        <p>convert html to pdf.</p>
    </div>
	<img src="https://www.baidu.com/img/flexible/logo/pc/result.png">
    <div>
        <table style="repeat-header:yes;repeat-footer:yes;">
            <thead>
                <th>${bt1}</th>
                <th>${bt2}</th>
                <th>${bt3}</th>
            </thead>
            <tbody>
                <#list list as item>
                <tr>
                    <td>${item.name}</td>
                    <td>${item.sex}</td>
                    <td>${item.age}</td>
                </tr>
                </#list>
            </tbody>
			<tfoot>
				<th>表尾1</th>
				<th>表尾2</th>
				<th>表尾3</th>
			</tfoot>
        </table>
    </div>
</div>
</body>
</html>

工具类

package cn.cxyfyf.base.test.pdf.itext.config;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import lombok.SneakyThrows;
import org.springframework.core.io.ClassPathResource;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Map;
 
/**
 * html模板工具类
 */
public class FreemarkerUtils {
 
    private FreemarkerUtils() {
    }
 
    private volatile static Configuration configuration;
    private final static String path = "/templates/";

    static {
        if (configuration == null) {
            synchronized (FreemarkerUtils.class) {
                if (configuration == null) {
                    configuration = new Configuration(Configuration.VERSION_2_3_28);
                }
            }
        }
    }
 
    /**
     * freemarker 通过流的方式 引擎渲染 html
     *
     * @param templateName html 模板文件相对路径(相对于 resources路径,路径 + 文件名),之后通过 BufferedReader 流来读取模板 "templates/template.html"
     * @param dataMap     传入 html 模板的 Map 数据
     * @return 渲染后的html字符串
     */
    public static String getTemplateStr(String templateName, Map<String, Object> dataMap) {
        // 初始化Configuration
        configuration.setDefaultEncoding("UTF-8");
        configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
        configuration.setLogTemplateExceptions(false);
        configuration.setWrapUncheckedExceptions(true);

        try(Writer out = new StringWriter();) {
            // 获取模板对象
            Template template = new Template("", getReaderStream(path + templateName), configuration);
            // 渲染模板
            template.process(dataMap, out);
            out.flush();
            return out.toString();
        } catch (IOException | TemplateException e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * 根据文件相对路径返回一个 BufferedReader 流
     *
     * @param filePath 项目resource目录下文件路径
     * @return BufferedReader
     */
    @SneakyThrows
    public static BufferedReader getReaderStream(String filePath) {
        return new BufferedReader(new InputStreamReader(new ClassPathResource(filePath).getInputStream(), StandardCharsets.UTF_8));
    }

}

 根据参数渲染模板界面示例:

package cn.cxyfyf.base.test.pdf.itext;

import cn.cxyfyf.base.test.pdf.itext.config.FreemarkerUtils;
import cn.cxyfyf.base.test.pdf.itext.config.PdfUtils;
import com.google.common.collect.ImmutableMap;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 描述
 * 渲染模板界面示例
 * @author fyf
 * Create by 2023/2/3 16:39
 */

public class TemplateTest {
    public static void main(String[] args) {
        // 制造假数据
        List<Map<String, Object>> list = new ArrayList<>();
        for (int i = 0; i < 50; i++) {
            list.add(ImmutableMap.of("name", "小明" + i, "sex", i%2==0?"男":"女", "age", 10 + i));
        }
        Map<String, Object> map = ImmutableMap.of("bt1", "表头1", "bt2", "表头2", "bt3", "表头3", "list", list);

        // 渲染模板
        String template = FreemarkerUtils.getTemplateStr("template.html", map);
        // 导出文件的路径
        String outFilePath = "C:/Users/86185/Desktop/html-pdf/template2.pdf";
        try(OutputStream outputStream = new FileOutputStream(outFilePath)) {
            PdfUtils.convertHtmlToPdf(template, outputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

HTML自建文件(表头、表尾控制)

注意表头表尾的控制取决于style样式跟标签

如下文:

table标签内需要有以下样式:
repeat-header:yes代表表头重复
repeat-footer:yes代表表尾重复
style="repeat-header:yes;repeat-footer:yes;"
表头对应标签thead,表尾对应标签tfoot

HTML文件源码

<html>
<head>
    <style>
        .center_div {
            border: 1px solid #404e94;
            margin-left: auto;
            margin-right: auto;
            background-color: #f6d0ed;
            text-align: left;
            padding: 8px;
        }
        table {
            width: 100%;
            border: 1px solid black;
        }
        th, td {
            border: 1px solid black;
        }
        body,html,input{font-family:"msyh";}
    </style>
</head>
<body>
<div class="center_div">
    <h1>Hello java North!</h1>
    <div>
        <p>convert html to pdf.</p>
    </div>
	<img src="https://www.baidu.com/img/flexible/logo/pc/result.png">
    <div>
        <table style="repeat-header:yes;repeat-footer:yes;">
            <thead>
                <th>ROLE</th>
                <th>NAME</th>
                <th>TITLE</th>
            </thead>
            <tbody>
                <tr>
                    <td>MARKSMAN</td>
                    <td>ASHE</td>
                    <td>THE FROST ARCHER</td>
                </tr>
                <tr>
                    <td>MAGES</td>
                    <td>ANNIE</td>
                    <td>THE DARK CHILD</td>
                </tr>
                <tr>
                    <td>射手</td>
                    <td>凯特琳</td>
                    <td>皮城女警</td>
                </tr>
				<tr>
                    <td>MARKSMAN</td>
                    <td>ASHE</td>
                    <td>THE FROST ARCHER</td>
                </tr>
                <tr>
                    <td>MAGES</td>
                    <td>ANNIE</td>
                    <td>THE DARK CHILD</td>
                </tr>
                <tr>
                    <td>射手</td>
                    <td>凯特琳</td>
                    <td>皮城女警</td>
                </tr>
				<tr>
                    <td>MARKSMAN</td>
                    <td>ASHE</td>
                    <td>THE FROST ARCHER</td>
                </tr>
                <tr>
                    <td>MAGES</td>
                    <td>ANNIE</td>
                    <td>THE DARK CHILD</td>
                </tr>
                <tr>
                    <td>射手</td>
                    <td>凯特琳</td>
                    <td>皮城女警</td>
                </tr>
				<tr>
                    <td>MARKSMAN</td>
                    <td>ASHE</td>
                    <td>THE FROST ARCHER</td>
                </tr>
                <tr>
                    <td>MAGES</td>
                    <td>ANNIE</td>
                    <td>THE DARK CHILD</td>
                </tr>
                <tr>
                    <td>射手</td>
                    <td>凯特琳</td>
                    <td>皮城女警</td>
                </tr>
				<tr>
                    <td>MARKSMAN</td>
                    <td>ASHE</td>
                    <td>THE FROST ARCHER</td>
                </tr>
                <tr>
                    <td>MAGES</td>
                    <td>ANNIE</td>
                    <td>THE DARK CHILD</td>
                </tr>
                <tr>
                    <td>射手</td>
                    <td>凯特琳</td>
                    <td>皮城女警</td>
                </tr><tr>
                    <td>MARKSMAN</td>
                    <td>ASHE</td>
                    <td>THE FROST ARCHER</td>
                </tr>
                <tr>
                    <td>MAGES</td>
                    <td>ANNIE</td>
                    <td>THE DARK CHILD</td>
                </tr>
                <tr>
                    <td>射手</td>
                    <td>凯特琳</td>
                    <td>皮城女警</td>
                </tr>
				<tr>
                    <td>MARKSMAN</td>
                    <td>ASHE</td>
                    <td>THE FROST ARCHER</td>
                </tr>
                <tr>
                    <td>MAGES</td>
                    <td>ANNIE</td>
                    <td>THE DARK CHILD</td>
                </tr>
                <tr>
                    <td>射手</td>
                    <td>凯特琳</td>
                    <td>皮城女警</td>
                </tr>
				<tr>
                    <td>MARKSMAN</td>
                    <td>ASHE</td>
                    <td>THE FROST ARCHER</td>
                </tr>
                <tr>
                    <td>MAGES</td>
                    <td>ANNIE</td>
                    <td>THE DARK CHILD</td>
                </tr>
                <tr>
                    <td>射手</td>
                    <td>凯特琳</td>
                    <td>皮城女警</td>
                </tr>
				<tr>
                    <td>MARKSMAN</td>
                    <td>ASHE</td>
                    <td>THE FROST ARCHER</td>
                </tr>
                <tr>
                    <td>MAGES</td>
                    <td>ANNIE</td>
                    <td>THE DARK CHILD</td>
                </tr>
                <tr>
                    <td>射手</td>
                    <td>凯特琳</td>
                    <td>皮城女警</td>
                </tr>
				<tr>
                    <td>MARKSMAN</td>
                    <td>ASHE</td>
                    <td>THE FROST ARCHER</td>
                </tr>
                <tr>
                    <td>MAGES</td>
                    <td>ANNIE</td>
                    <td>THE DARK CHILD</td>
                </tr>
                <tr>
                    <td>射手</td>
                    <td>凯特琳</td>
                    <td>皮城女警</td>
                </tr><tr>
                    <td>MARKSMAN</td>
                    <td>ASHE</td>
                    <td>THE FROST ARCHER</td>
                </tr>
                <tr>
                    <td>MAGES</td>
                    <td>ANNIE</td>
                    <td>THE DARK CHILD</td>
                </tr>
                <tr>
                    <td>射手</td>
                    <td>凯特琳</td>
                    <td>皮城女警</td>
                </tr><tr>
                    <td>MARKSMAN</td>
                    <td>ASHE</td>
                    <td>THE FROST ARCHER</td>
                </tr>
                <tr>
                    <td>MAGES</td>
                    <td>ANNIE</td>
                    <td>THE DARK CHILD</td>
                </tr>
                <tr>
                    <td>射手</td>
                    <td>凯特琳</td>
                    <td>皮城女警</td>
                </tr><tr>
                    <td>MARKSMAN</td>
                    <td>ASHE</td>
                    <td>THE FROST ARCHER</td>
                </tr>
                <tr>
                    <td>MAGES</td>
                    <td>ANNIE</td>
                    <td>THE DARK CHILD</td>
                </tr>
                <tr>
                    <td>射手</td>
                    <td>凯特琳</td>
                    <td>皮城女警</td>
                </tr>
				<tr>
                    <td>MARKSMAN</td>
                    <td>ASHE</td>
                    <td>THE FROST ARCHER</td>
                </tr>
                <tr>
                    <td>MAGES</td>
                    <td>ANNIE</td>
                    <td>THE DARK CHILD</td>
                </tr>
                <tr>
                    <td>射手</td>
                    <td>凯特琳</td>
                    <td>皮城女警</td>
                </tr>
            </tbody>
			<tfoot>
				<th>表尾1</th>
				<th>表尾2</th>
				<th>表尾3</th>
			</tfoot>
        </table>
    </div>
</div>
</body>
</html>

 

本文作者:玩单机的零度

本文链接:https://www.cnblogs.com/cxyfyf/p/17086376.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   玩单机的零度  阅读(1377)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起