Bootstrap

SpringBoot 集成 html2Pdf

一、概述:

        1. springboot如何生成pdf,接口可以预览可以下载

        2. vue下载通过bold如何下载

        3. 一些细节:页脚、页眉、水印、每一页得样式添加

二、直接上代码【主要是一个记录下次开发更快】

模板位置

1. 导入pom包

<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>html2pdf</artifactId>
    <version>5.0.5</version>
</dependency>
<!-- 中文字体支持 -->
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>font-asian</artifactId>
    <version>7.2.1</version>
</dependency>

2. 写工具类

[1] 页眉工具类

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.properties.TextAlignment;
import org.springframework.util.StringUtils;
import java.net.MalformedURLException;
import java.net.URL;

public class HeaderMarkerEventHandler implements IEventHandler {

    /**
     * pdf字体
     */
    private final PdfFont pdfFont;

    /**
     * 页眉显示
     */
    private final String title;

    /**
     * logo地址
     */
    private String logoUrl;

    public HeaderMarkerEventHandler(PdfFont pdfFont, String title) {
        this.pdfFont = pdfFont;
        this.title = title;
    }

    public HeaderMarkerEventHandler(PdfFont pdfFont, String title, String logoUrl) {
        this.pdfFont = pdfFont;
        this.title = title;
        this.logoUrl = logoUrl;
    }

    @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);
        float x = pageSize.getRight() - 60;
        float y = pageSize.getTop() - 32;
        Paragraph p = new Paragraph(title)
                .setFontSize(9)
                .setFont(pdfFont);
        // 页眉字体显示得位置
        canvas.showTextAligned(p, x, y, TextAlignment.RIGHT);
        // 加载图片
        if (!StringUtils.isEmpty(logoUrl)) {
            try {
                URL url = new URL(logoUrl);
                Image logo = new Image(ImageDataFactory.create(url));
                logo.scaleAbsolute(100, 20);
                logo.setMarginLeft(30);
                logo.setMarginTop(15);
                canvas.add(logo);
            } catch (MalformedURLException e) {
                System.out.println("logo 无法解析: " + logoUrl);
            }
        }
        canvas.close();
    }
}

[2] 水印工具类


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.font.PdfFontFactory;
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.properties.TextAlignment;
import com.itextpdf.layout.properties.VerticalAlignment;
import java.io.IOException;

/**
 * 生成 pdf 水印
 */
public class WaterMarkEventHandler implements IEventHandler {

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

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

    /**
     * 一页中每列有多少水印
     */
    private final int waterMarkY;

    public WaterMarkEventHandler(String waterMarkContent) {
        this(waterMarkContent, 5, 5);
    }

    public WaterMarkEventHandler(String waterMarkContent, int waterMarkX, int waterMarkY) {
        this.waterMarkContent = waterMarkContent;
        this.waterMarkX = waterMarkX;
        this.waterMarkY = waterMarkY;
    }

    @Override
    public void handleEvent(Event event) {
        PdfDocumentEvent documentEvent = (PdfDocumentEvent) event;
        PdfDocument document = documentEvent.getDocument();
        PdfPage page = documentEvent.getPage();
        Rectangle pageSize = page.getPageSize();
        PdfFont pdfFont = null;
        try {
            pdfFont = PdfFontFactory.createFont("STSongStd-Light", "UniGB-UCS2-H");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        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);

        for (int i = 0; i < waterMarkX; i++) {
            for (int j = 0; j < waterMarkY; j++) {
                canvas.showTextAligned(waterMark, (150 + i * 300), (160 + j * 150), document.getNumberOfPages(),
                        TextAlignment.CENTER, VerticalAlignment.BOTTOM, 120);
            }
        }
        canvas.close();
    }
}

[3]添加页脚工具类


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.properties.TextAlignment;

/**
 * 生成 pdf 页码
 */
public class PageEventHandler implements IEventHandler {

    /**
     * pdf字体
     */
    private final PdfFont pdfFont;

    public PageEventHandler(PdfFont pdfFont) {
        this.pdfFont = pdfFont;
    }

    @Override
    public void handleEvent(Event event) {
        PdfDocumentEvent documentEvent = (PdfDocumentEvent) event;
        PdfDocument document = documentEvent.getDocument();
        PdfPage page = documentEvent.getPage();
        Rectangle pageSize = page.getPageSize();
        PdfCanvas pdfCanvas = new PdfCanvas(page.getLastContentStream(), page.getResources(), document);
        Canvas canvas = new Canvas(pdfCanvas, pageSize);
        float x = (pageSize.getLeft() + pageSize.getRight()) / 2;
        float y = pageSize.getBottom() + 15;
        Paragraph paragraph =
                new Paragraph(""+document.getPageNumber(page) )
                        .setFontSize(10)
                        .setFont(pdfFont);
        canvas.showTextAligned(paragraph, x, y, TextAlignment.CENTER);
        canvas.close();
    }
}

[4]工具类;重点说一下这个字体:

        1. 本文第一个图里面font得字体路径在Windows电脑:C:\Windows\Fonts;看中哪个字体就直接拷贝进去进会自动生成ttc文件;我拷贝得是宋体和微软雅黑,主要解决加粗

        2.前端 

font-family: "MicrosoftYaHei-Bold";

这个字体得名字日下图:

import com.itextpdf.html2pdf.ConverterProperties;
import com.itextpdf.html2pdf.HtmlConverter;
import com.itextpdf.kernel.events.PdfDocumentEvent;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
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 org.apache.velocity.Template;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.context.Context;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
import java.io.*;
import java.nio.charset.StandardCharsets;

/**
 * PDF工具
 *
 * @author ppp
 * @date 2022/8/5
 */
public class Html2PdfUtil {

    static {
        Velocity.setProperty(RuntimeConstants.INPUT_ENCODING, StandardCharsets.UTF_8);
        Velocity.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath");
        Velocity.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName());
        Velocity.init();
    }

    /**
     * 据模板生成pfd格式文件
     *
     * @param context      上下文对象
     * @param template     pdf模板
     * @param outputStream 生成ofd文件输出流
     */
    public static void pdfFile(Context context, String template, OutputStream outputStream, String watermarkText) throws IOException {
        PdfWriter pdfWriter = null;
        PdfDocument pdfDocument = null;
        StringWriter writer = null;
        try {
           pdfWriter = new PdfWriter(outputStream);
            pdfDocument = new PdfDocument(pdfWriter);
            PageSize pageSize = new PageSize(842.0F,595.0F);
            pdfDocument.setDefaultPageSize(pageSize);
            ConverterProperties properties = new ConverterProperties();
            // 添加字体
            FontProvider fontProvider = new FontProvider();
            // 字体设置,解决中文不显示问题
            PdfFont sysFont = PdfFontFactory.createFont("STSongStd-Light", "UniGB-UCS2-H");
            fontProvider.addFont(sysFont.getFontProgram(), "UniGB-UCS2-H");
//           添加宋体,html中使用SimSun,不指定则默认为第一个引入的字体
            sysFont = PdfFontFactory.createFont("font/simsun.ttc,0", PdfEncodings.IDENTITY_H);
            fontProvider.addFont(sysFont.getFontProgram(), PdfEncodings.IDENTITY_H);
            // 添加微软雅黑,html中使用MicrosoftYaHei
            sysFont = PdfFontFactory.createFont("font/msyh.ttc,0", PdfEncodings.IDENTITY_H);
            fontProvider.addFont(sysFont.getFontProgram(), PdfEncodings.IDENTITY_H);
            // 添加微软雅黑粗体,html中使用MicrosoftYaHei-Bold
            sysFont = PdfFontFactory.createFont("font/msyhbd.ttc,0", PdfEncodings.IDENTITY_H);
            fontProvider.addFont(sysFont.getFontProgram(), PdfEncodings.IDENTITY_H);
            // 处理微软雅黑及粗体描述符错乱问题
            processYaHeiFontDescriptor(fontProvider);
            properties.setFontProvider(fontProvider);
            // 添加页眉
//            pdfDocument.addEventHandler(PdfDocumentEvent.START_PAGE, new HeaderMarkerEventHandler(sysFont, "学校"));
            // 添加水印
            pdfDocument.addEventHandler(PdfDocumentEvent.INSERT_PAGE, new WaterMarkEventHandler(watermarkText));
            // 添加页脚
            pdfDocument.addEventHandler(PdfDocumentEvent.END_PAGE, new PageEventHandler(sysFont));
            // 读取html模板和赋值
            Template pfdTemplate = Velocity.getTemplate(template, "UTF-8");
            writer = new StringWriter();
            pfdTemplate.merge(context, writer);
            // 构建带有样式的 HTML 字符串
            StringBuilder htmlWithStyles = new StringBuilder();
            htmlWithStyles.append("<html><head><style>");
            // 如果有边距参数,则添加到样式中
            htmlWithStyles.append("@page { ");
            htmlWithStyles.append("margin-bottom: ").append(30).append("mm; ");
            htmlWithStyles.append("}");
            htmlWithStyles.append("</style></head><body>").append("</body></html>");
            // html转换PDF
            HtmlConverter.convertToPdf(writer.toString(), pdfDocument, properties);
        } catch (Exception e) {
            throw new RuntimeException("PFD文件生成失败", e);
        }finally {
            pdfDocument.close();
            writer.close();
            pdfWriter.close();
        }
    }
}

3.html模板,里面那个分页,就是新开一页,估计有点问题

        【1】、td 换行得自己加

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8"/>
</head>
<body>
<div class="contianer">
    <div>
        <div class="fourth_title">
            <h4>2-2 专业群建设总目标</h4>
        </div>
        <div class="third_con">
            <p  class="pFontCon">
                $!{xxxxmb.zyqjszmb}

            </p>
        </div>
    </div>

    <div>
        #foreach($html in $canVasHtml)
            <div class="page-break">
                $html
            </div>
        #end

    </div>

</div>
</body>
<style>
    h1,h2 {
        font-style: italic;
        font-family: "MicrosoftYaHei-Bold";
        text-align: center;
        font-weight: bold; /* 加粗文本 */
    }
    td{
        white-space:normal;
        word-wrap:break-word;
        word-break:break-all;
    }
    @media print {
        .page-break {
            page-break-before: always; /* 在元素之前插入分页 */
        }
    }
/* 每次新开一个分页 */
    .page-break {
        page-break-before: always; /* 在元素之前插入分页 */
    }
</style
</html>


4. Service 只留了一部分代码

@Override
public void downloadAchievementsByPdf( HttpServletResponse response, HttpServletRequest request) throws IOException {
    OutputStream outputStream = null;
    try {
        response.reset();
        String fileName = schooleInfo.getXxmc();
//      这个是直接下载
        response.setHeader("Content-Type", "application/pdf");
        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName + ".pdf", "utf-8"));
        // 这个是预览,访问接口页面打开得是一个FPD预览得页面
//       response.setContentType("application/pdf");
//       response.addHeader("Content-Disposition", "inline; filename=" + fileName);
        VelocityContext context = new VelocityContext();
        setBaseInfo(context, schooleInfo);
        context.put("zyqxxList", zyqjbxxTS);
        context.put("xxxxmb", zyqjbxxTInfoOne);
        // 获取指标数据
        List<Map<String, Object>> maps = (List<Map<String, Object>>) treeWithCanvas(schoolBookId, isTask, taskId, schoolId).get("data");
        List<String> canVasHtml = PdfTemplate.getCanVasHtml(maps);
        context.put("canVasHtml", canVasHtml);
        outputStream = response.getOutputStream();
        Html2PdfUtil.pdfFile(context, "templates/jxzpbPdf.html", outputStream, schooleInfo.getXxmc());
    } catch (Exception e) {
        e.printStackTrace();
    }finally {
        outputStream.close();
    }
}

private void setBaseInfo(VelocityContext context, XxbcxxT schooleInfo) {
    // 1. 查询学校基本信息
    context.put("xmdw", schooleInfo.getXxmc());
    context.put("xmmc", schooleInfo.getXmmc());
    context.put("tbrq", DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD));
    context.put("jbdw", schooleInfo.getJbdw());
    context.put("szsf", schooleInfo.getSfbm() == null ? "" : ProvinceJson.getProName(schooleInfo.getSfbm()));
}

5. VUE代码

  return request(url, {
    ...params,
    method: 'GET',
    responseType: 'blob'
  }).then((data) => {
    const aLink = document.createElement('a');
    const blob =  new Blob([data],{type: 'application/pdf'});  
    aLink.style.display = 'none';
    aLink.href = URL.createObjectURL(blob);
    aLink.setAttribute('download', fileName); 
    document.body.appendChild(aLink);
    aLink.click();
    URL.revokeObjectURL(aLink.href); // 清除引用
    document.body.removeChild(aLink);
  });

三、最后还有一个加粗得问题。思路就是需要引入字体文件

;