HtmlToPdfUtils [请参照码云上 https://gitee.com/bbevis/html-to-pdf 最新版]

<!-- freemarker依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>

        <!-- web基础依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>
<!-- FlyingSaucer依赖
        https://mvnrepository.com/artifact/org.xhtmlrenderer/flying-saucer-pdf -->
        <dependency>
            <groupId>org.xhtmlrenderer</groupId>
            <artifactId>flying-saucer-pdf</artifactId>
            <version>9.1.12</version>
        </dependency>
/*
 * 
 * 
 */
package cn.com.utils;

import com.lowagie.text.pdf.BaseFont;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import org.w3c.dom.Document;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.*;
import java.util.List;
import java.util.Map;

/**
 * 功能:pdf处理工具类
 *
 * @author qust
 * @version 1.0 2018/2/23 17:21
 */
public class PdfUtils {
    private PdfUtils() {
    }

    private static final Logger LOGGER = LoggerFactory.getLogger(PdfUtils.class);

    /**
     * 按模板和参数生成html字符串,再转换为flying-saucer识别的Document
     *
     * @param templateName freemarker模板名称
     * @param variables    freemarker模板参数
     * @return Document
     */
    private static Document generateDoc(FreeMarkerConfigurer configurer, String templateName, Map<String, Object> variables)  {
        Template tp;
        try {
            tp = configurer.getConfiguration().getTemplate(templateName);
        } catch (IOException e) {
            LOGGER.error(e.getMessage(), e);
            return null;
        }

        StringWriter stringWriter = new StringWriter();
        try(BufferedWriter writer = new BufferedWriter(stringWriter)) {
            try {
                tp.process(variables, writer);
                writer.flush();
            } catch (TemplateException e) {
                LOGGER.error("模板不存在或者路径错误", e);
            } catch (IOException e) {
                LOGGER.error("IO异常", e);
            }
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();

              // 防止XXE攻击
              String FEATURE = null;
              FEATURE = "http://javax.xml.XMLConstants/feature/secure-processing";
              documentBuilderFactory.setFeature(FEATURE, true);
              FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
              documentBuilderFactory.setFeature(FEATURE, false);
              FEATURE = "http://xml.org/sax/features/external-parameter-entities";
              documentBuilderFactory.setFeature(FEATURE, false);
              FEATURE = "http://xml.org/sax/features/external-general-entities";
              documentBuilderFactory.setFeature(FEATURE, false);
              FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
              documentBuilderFactory.setFeature(FEATURE, false);
              documentBuilderFactory.setXIncludeAware(false);
              documentBuilderFactory.setExpandEntityReferences(false);
              // 防止XXE攻击


            DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder();
            byte[] bytes = stringWriter.toString().getBytes();
            try(ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes)) {
                Document document = builder.parse(byteArrayInputStream);
                return document;
            } catch (Exception e) {
                LOGGER.error(e.getMessage(), e);
                return null;
            }
        }catch (Exception e){
            LOGGER.error(e.getMessage(), e);
            return null;
        }
    }

    /**
     * 核心: 根据freemarker模板生成pdf文档
     *
     * @param configurer   freemarker配置
     * @param templateName freemarker模板名称
     * @param out          输出流
     * @param listVars     freemarker模板参数
     * @throws Exception 模板无法找到、模板语法错误、IO异常
     */
    private static void generateAll(FreeMarkerConfigurer configurer, String templateName, OutputStream out, List<Map<String, Object>> listVars) throws Exception {
        if (CollectionUtils.isEmpty(listVars)) {
            LOGGER.warn("警告:freemarker模板参数为空!");
            return;
        }

        ITextRenderer renderer = new ITextRenderer();
        Document doc = generateDoc(configurer, templateName, listVars.get(0));
        renderer.setDocument(doc, null);
        //设置字符集(宋体),此处必须与模板中的<body style="font-family: SimSun">一致,区分大小写,不能写成汉字"宋体"
        ITextFontResolver fontResolver = renderer.getFontResolver();
        //加载linux系统中文字体
        //fontResolver.addFontDirectory("/usr/share/fonts/chinese", BaseFont.NOT_EMBEDDED); 

// 默认路径 srcmain esourcessimsun.ttf fontResolver.addFont("simsun.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); //展现和输出pdf renderer.layout(); renderer.createPDF(out, false); //根据参数集个数循环调用模板,追加到同一个pdf文档中 //(注意:此处从1开始,因为第0是创建pdf,从1往后则向pdf中追加内容) for (int i = 1; i < listVars.size(); i++) { Document docAppend = generateDoc(configurer, templateName, listVars.get(i)); renderer.setDocument(docAppend, null); renderer.layout(); renderer.writeNextDocument(); //写下一个pdf页面 } renderer.finishPDF(); //完成pdf写入 } /** * pdf下载 * * @param configurer freemarker配置 * @param templateName freemarker模板名称(带后缀.ftl) * @param listVars 模板参数集 * @param response HttpServletResponse * @param fileName 下载文件名称(带文件扩展名后缀) */ public static void download(FreeMarkerConfigurer configurer, String templateName, List<Map<String, Object>> listVars, HttpServletResponse response, String fileName) { // 设置编码、文件ContentType类型、文件头、下载文件名 response.setCharacterEncoding("utf-8"); response.setContentType("multipart/form-data"); try { String finalFileName = ""; if(StringUtils.isNotBlank(fileName)) { finalFileName = new String(fileName.getBytes("gb2312"), "ISO8859-1"); } if(StringUtils.isBlank(finalFileName)) { throw new RuntimeException("下载文件名fileName为空!"); } response.setHeader("Content-Disposition", "attachment;fileName=" + finalFileName); } catch (Exception e) { LOGGER.error(e.getMessage(), e); } try (ServletOutputStream out = response.getOutputStream()) { generateAll(configurer, templateName, out, listVars); out.flush(); } catch (Exception e) { LOGGER.error(e.getMessage(), e); } } /** * pdf预览 * * @param configurer freemarker配置 * @param templateName freemarker模板名称(带后缀.ftl) * @param listVars 模板参数集 * @param response HttpServletResponse */ public static void preview(FreeMarkerConfigurer configurer, String templateName, List<Map<String, Object>> listVars, HttpServletResponse response) { try (ServletOutputStream out = response.getOutputStream()) { generateAll(configurer, templateName, out, listVars); out.flush(); } catch (Exception e) { LOGGER.error(e.getMessage(), e); } } /** * pdf转换为File * * @param configurer freemarker配置 * @param templateName freemarker模板名称(带后缀.ftl) * @param listVars 模板参数集 * @param file file */ public static void toFile(FreeMarkerConfigurer configurer, String templateName, List<Map<String, Object>> listVars, File file) { try (OutputStream out = FileUtils.openOutputStream(file)) { generateAll(configurer, templateName, out, listVars); out.flush(); } catch (Exception e) { LOGGER.error(e.getMessage(), e); } } }
package cn.com.utils;

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;

public class PDFToImgUtil {
    private static Logger logger = LoggerFactory.getLogger(PDFToImgUtil.class);


    /**
     * 获取PDF总页数
     * @throws IOException
     */
    public static int getPDFNum(String fileUrl) throws IOException {
        PDDocument pdDocument = null;
        int pages = 0;
        try {
            pdDocument = getPDDocument(fileUrl);
            pages = pdDocument.getNumberOfPages();
        } catch (Exception e) {
            e.printStackTrace();
            logger.error(e.getMessage(),e);
        } finally {
            if (pdDocument != null) {
                pdDocument.close();
            }
        }
        return pages;
    }


    /**
     * PDF转图片 根据页码一页一页转
     * @throws IOException
     * imgType:转换后的图片类型 jpg,png
     */
    public static void PDFToImg(OutputStream sos, String fileUrl, int page, String imgType) throws IOException {
        PDDocument pdDocument = null;
        /* dpi越大转换后越清晰,相对转换速度越慢 */
        int dpi = 100;
        try {
            pdDocument = getPDDocument(fileUrl);
            PDFRenderer renderer = new PDFRenderer(pdDocument);
            int pages = pdDocument.getNumberOfPages();
            if (page <= pages && page > 0) {
                BufferedImage image = renderer.renderImageWithDPI(page,dpi);
                ImageIO.write(image, imgType, sos);
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.error(e.getMessage(),e);
        } finally {
            if (pdDocument != null) {
                pdDocument.close();
            }
        }

    }
    /**
     * 转换全部的pdf
     * @param fileAddress 文件地址
     * @param filename PDF文件名
     * @param type 图片类型
     */
    public static void pdf2png(String fileAddress,String filename,String type,int pageCount) {
        // 将pdf装图片 并且自定义图片得格式大小
        File file = new File(fileAddress + "\" + filename + ".pdf");
        try {
            PDDocument doc = PDDocument.load(file);
            PDFRenderer renderer = new PDFRenderer(doc);
            for (int i = 0; i < pageCount; i++) {
                BufferedImage image = renderer.renderImageWithDPI(i, 140); // Windows native DPI
                // BufferedImage srcImage = resize(image, 240, 240);//产生缩略图
                ImageIO.write(image, type, new File(fileAddress + "\" + filename + "_" + (i + 9) + "." + type));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    private static PDDocument getPDDocument(String fileUrl) throws IOException {
        File file = new File(fileUrl);
        FileInputStream inputStream = new FileInputStream(file);
        return PDDocument.load(inputStream);
    }


}
/*
 *
 * 
 */
package com.controller;

import com.Utils.PdfUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 功能:pdf预览、下载
 *
 * @author 
 * @version 
 */
@Controller
@RequestMapping(value = "/pdf")
public class PdfController {

    @Autowired
    private FreeMarkerConfigurer configurer;

    /**
     * pdf预览
     *
     * @param request  HttpServletRequest
     * @param response HttpServletResponse
     */
    @RequestMapping(value = "/preview", method = RequestMethod.GET)
    public void preview(HttpServletRequest request, HttpServletResponse response) {
        // 构造freemarker模板引擎参数
        List<Map<String,Object>> listVars = new ArrayList<>();
        Map<String,Object> variables = new HashMap<>();
        variables.put("userName","小明");
        listVars.add(variables);
// 默认路径 srcmain esources emplatese-prop-pdf.ftl PdfUtils.preview(configurer,
"e-prop-pdf.ftl",listVars,response); } /** * pdf下载 * * @param request HttpServletRequest * @param response HttpServletResponse */ @RequestMapping(value = "/download", method = RequestMethod.GET) public void download(HttpServletRequest request, HttpServletResponse response) { List<Map<String,Object>> listVars = new ArrayList<>(); Map<String,Object> variables = new HashMap<>(); variables.put("title","测试下载ASGX!"); listVars.add(variables);
// 默认路径 srcmain esources emplatese-prop-pdf.ftl PdfUtils.download(configurer,
"e-prop-pdf.ftl",listVars,response,"测试.pdf"); } }
<!DOCTYPE html>
<html>
<head lang="en">
    <title>Spring Boot Demo - PDF</title>
    <link href="http://localhost:8999/css/index.css" rel="stylesheet" type="text/css"/>
    <link href="http://localhost:8999/css/pdf.css" rel="stylesheet" type="text/css"/>
    <style>
        @page {
            size: 210mm 297mm; /*设置纸张大小:A4(210mm 297mm)、A3(297mm 420mm) 横向则反过来*/
            margin: 0.25in;
            padding: 1em;
            @bottom-center{
                content:"XXXXX © 版权所有";
                font-family: SimSun;
                font-size: 12px;
                color:red;
            };
            @top-center { content: element(header) };
            @bottom-right{
                content:"第" counter(page) "页  共 " counter(pages) "页";
                font-family: SimSun;
                font-size: 12px;
                color:#000;
            };
        }
    </style>
</head>
<#-- 这样配置不中文不会显示 -->
<#--<body style="font-family: 宋体">-->
<body style="font-family: 'SimSun'">
<div>1.标题-中文</div>
<h2>${title}</h2>

<div>2.按钮:按钮的边框需要写css渲染</div>
<button class="a" style="border: 1px solid #000000"> click me t-p</button>
<div id="divsub"></div>

<div>3.普通div</div>
<div id="myheader">Alice's Adventures in Wonderland</div>

<div>4.图片 绝对定位到左上角(注意:图片必须用全路径或者http://开头的路径,否则无法显示)</div>
<div id="signImg"></div>

<div>5.普通table表格</div>
<div>
    <table>
        <tr>
            <td>1</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
        </tr>
        <tr>
            <td>1</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
        </tr>
        <tr>
            <td>1</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
            <td>2</td>
        </tr>
    </table>

</div>

<div>6.input控件,边框需要写css渲染 (在模板中一般不用input,因为不存在输入操作)</div>
<div>
    <label>姓名:</label>
    <input id="input1" aria-label="dasdasd" type="text" value="123你是"/>
</div>
</body>
</html>
.trialPresentation .pdfContent .clientLetter {
      height: 42px;
      line-height: 42px;
      background: url(data:image/png;base64,iVBORw0KGgoAAAA...=) center no-repeat;
      border-radius: 21px;
      font-size: 22px;
      margin: 0 auto;
      color: #fff;
      width: 240px;
      text-align: center;
      position: relative;
    }
/*解决自动分页  分页符在元素上 造成样式错乱问题*/
div {page-break-after:auto;}
原文地址:https://www.cnblogs.com/bevis-byf/p/12432952.html