SpringBoot Thymeleaf企业级真实应用:使用Flying Saucer结合iText5将HTML界面数据转换为PDF输出(三) 给pdf加水印及水印图片、页眉页脚、页眉logo

接上一篇 SpringBoot Thymeleaf企业级真实应用:使用Flying Saucer结合iText5将HTML界面数据转换为PDF输出(二) 设置多字体, 以及中文不显示的问题

HtmlToPDFUtil 类进行修改升级加入封装方法



import com.itextpdf.text.*;
import com.itextpdf.text.Font;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import org.springframework.stereotype.Component;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.annotation.Resource;
import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

/**
 * @Author:wangdi
 * @Date:2023/6/9 14:58
 * @Des: HtmlToPdfUtil 转换pdf工具类
 */
@Component
public class HtmlToPDFUtil {


    @Resource
    private TemplateEngine templateEngine;


    /**
     * 使用 Thymeleaf 渲染 HTML
     *
     * @param template HTML模板路径
     * @param params   渲染的参数
     * @return 返回渲染后的html代码
     * @throws Exception
     */
    public String render(String template, Map<String, Object> params) {
        Context context = new Context();
        if (params.size() > 0) {
            context.setVariables(params);
        }
        //将数据填充到模板里,开始处理模板
        return templateEngine.process(template, context);
    }

    /**
     * 根据html生成pdf的base64格式
     *
     * @param html
     * @return
     */
    public static String getPDFBase64ByHtml(String html) throws IOException, DocumentException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();//构建字节输出流
        ITextRenderer renderer = new ITextRenderer();
        ITextFontResolver fontResolver = renderer.getFontResolver();
        // html中设置的字体样式需要参考此处debug后fontResolver的fontFamily的数据的key
        //指定文件字体添加到PDF库,指定字体不作为内部字体,而是外部字体被加载
        fontResolver.addFont("font/SimSun.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); // SimSun
        fontResolver.addFont("font/微软雅黑.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); // Microsoft YaHei
        fontResolver.addFont("font/simhei.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); // SimHei
        fontResolver.addFont("font/simkai.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); // KaiTi
        renderer.setDocumentFromString(html);
        renderer.layout();
        renderer.createPDF(baos);

        return new BASE64Encoder().encode(baos.toByteArray());
    }


    /**
     * 根据pdf的base64格式和路径生成pdf文件
     *
     * @param base64 pdf的base64格式
     * @param path   生成pdf的路径
     * @return
     */
    public static String base64ToPDF(String base64, String path) {


        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        String fileAdd = sdf.format(new Date());
        //先判断文件是否存在
        path = path + "/" + fileAdd;
        String fileName = path + "/" + System.currentTimeMillis() + ".pdf";//新的文件名

        BufferedInputStream bin = null;
        FileOutputStream fout = null;
        BufferedOutputStream bout = null;
        BASE64Decoder decoder = new BASE64Decoder();
        try {
            byte[] bytes = decoder.decodeBuffer(base64);

            ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
            // 创建从底层输入流中读取数据的缓冲输入流对象
            bin = new BufferedInputStream(bais);

            //获取文件夹路径
            File file = new File(path);
            //如果文件夹不存在则创建
            if (!file.exists() && !file.isDirectory()) {
                file.mkdirs();
            }
            // 创建到指定文件的输出流
            fout = new FileOutputStream(fileName);
            // 为文件输出流对接缓冲输出流对象
            bout = new BufferedOutputStream(fout);
            byte[] buffers = new byte[1024];
            int len = bin.read(buffers);
            while (len != -1) {
                bout.write(buffers, 0, len);
                len = bin.read(buffers);
            }
            // 刷新此输出流并强制写出所有缓冲的输出字节,必须这行代码,否则有可能有问题
            bout.flush();
            //返回存储的路径
            return fileName;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                bin.close();
                fout.close();
                bout.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return "";
    }



     /**
     * 合并添加水印 页眉和页脚 水印的方法 (可根据需求自行改动封装)
     *
     * @param pdfBase64        PDF的Base64格式
     * @param isAddWatermark   是否添加水印
     * @param waterMarkName    水印文字
     * @param isAddPageNumbers 是否添加页眉
     * @param headerText       页眉名称
     * @param isAddLogoImg     是否添加Logo
     * @param logoPath         logo图片路径
     * @param newWidth         图片大小宽度如 141
     * @param newHeight        图片大小高度如30
     * @param absoluteX        图片相对位置 如20
     * @param absoluteY        图片相对位置 如-30
     * @return
     */
    public static String pdfAddWaterHeaderLogo(String pdfBase64
            , boolean isAddWatermark, String waterMarkName
            , boolean isAddPageNumbers, String headerText
            , boolean isAddLogoImg, String logoPath, float newWidth, float newHeight, float absoluteX, float absoluteY
            , boolean isAddWatermarkLogo, String watermarkLogoPath
    ) {
        try {
            byte[] bytes = new BASE64Decoder().decodeBuffer(pdfBase64);
            /**
             * PdfReader是iText库中的PDF解析器,用于读取和解析PDF文件。通过PdfReader对象,可以获取PDF文件的各种属性和内容,
             * 如总页数、页面尺寸、文本内容等。它提供了方法来打开和关闭PDF文件,并可以从文件路径、InputStream或字节数组等多种方式加载PDF文件。
             *
             * PdfStamper是iText库中的PDF编辑器,用于修改和编辑PDF文件。通过PdfStamper对象,
             * 可以在PDF文件中添加文本、图片、表单域等元素,修改页面内容、添加注释、加密文档等操作。它基于已解析的PdfReader对象,可以将修改后的内容写入新的PDF文件或覆盖原始文件。
             */
            PdfReader reader = new PdfReader(bytes);
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            PdfStamper stamper = new PdfStamper(reader, bos);
            int total = reader.getNumberOfPages();

            if (isAddLogoImg) {
                addLogoImg(stamper, reader, logoPath, newWidth, newHeight, absoluteX, absoluteY);
            }


            if (isAddPageNumbers) {
                addPageNumbers(stamper, reader, headerText);
            }

            // 添加水印放到最后执行 否则会将页眉页脚进行透明度设置
            if (isAddWatermark) {
                addWatermark(stamper, reader, waterMarkName);
            }

            if (isAddWatermarkLogo) {
                addWatermarkImage(stamper, reader, watermarkLogoPath);
            }


            stamper.close();
            reader.close();
            return new BASE64Encoder().encode(bos.toByteArray());
        } catch (IOException | DocumentException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 添加水印
     *
     * @param stamper       PdfStamper对象
     * @param reader        PdfReader对象
     * @param waterMarkName 水印文字
     * @throws DocumentException
     * @throws IOException
     */
    private static void addWatermark(PdfStamper stamper, PdfReader reader, String waterMarkName) throws DocumentException, IOException {
        BaseFont baseFont = BaseFont.createFont("font/SimSun.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
        Rectangle pageRect;
        PdfGState gs = new PdfGState();
        gs.setFillOpacity(0.1f); // 填充的透明度
        gs.setStrokeOpacity(0.1f); // 描边的透明度

        JLabel label = new JLabel();
        FontMetrics metrics;
        int textH = 0;
        int textW = 0;
        label.setText(waterMarkName);
        metrics = label.getFontMetrics(label.getFont());
        textH = metrics.getHeight();
        textW = metrics.stringWidth(label.getText());

        PdfContentByte under;
        for (int i = 1; i <= reader.getNumberOfPages(); i++) {
            pageRect = reader.getPageSizeWithRotation(i);
            under = stamper.getOverContent(i);
            under.saveState();
            under.setGState(gs);
            under.beginText();
            under.setFontAndSize(baseFont, 20);
            under.setColorFill(BaseColor.BLACK);//水印颜色

            // 水印文字成30度角倾斜
            for (int height = -5 + textH; height < pageRect.getHeight(); height = height + textH * 4) {
                for (int width = -5 + textW; width < pageRect.getWidth() + textW; width = width + textW * 3) {
                    under.showTextAligned(Element.ALIGN_LEFT, waterMarkName, width - textW, height - textH, 30);
                }
            }
            // 添加水印文字
            under.endText();
        }
    }

    /**
     * 添加页眉 (页脚)
     *
     * @param stamper PdfStamper对象
     * @param reader  PdfReader对象
     * @throws DocumentException
     * @throws IOException
     */
    private static void addPageNumbers(PdfStamper stamper, PdfReader reader, String headerText) throws DocumentException, IOException {
        int total = reader.getNumberOfPages();
        for (int i = 1; i <= total; i++) {
            Rectangle pageRect = reader.getPageSizeWithRotation(i);
            PdfContentByte content = stamper.getOverContent(i);

            BaseFont baseFont = BaseFont.createFont("font/SimSun.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
            content.setFontAndSize(baseFont, 20);

            ColumnText.showTextAligned(content, Element.ALIGN_CENTER, new Phrase(headerText, new Font(baseFont, 10)),
                    pageRect.getWidth() / 2, pageRect.getTop() - 20, 0);

            ColumnText.showTextAligned(content, Element.ALIGN_CENTER, new Phrase("页码 " + i + " / " + total, new Font(baseFont, 10)),
                    (pageRect.getLeft() + pageRect.getRight()) / 2, pageRect.getBottom() + 10, 0);


        }
    }



    /**
     * 添加图片logo
     *
     * @param stamper   PdfStamper对象
     * @param reader    PdfReader对象
     * @param logoPath  图片路径
     * @param newWidth  图片大小宽度如 141
     * @param newHeight 图片大小高度如30
     * @param absoluteX 图片相对位置 如20
     * @param absoluteY 图片相对位置 如-30
     * @throws DocumentException
     * @throws IOException
     */
    private static void addLogoImg(PdfStamper stamper, PdfReader reader, String logoPath, float newWidth, float newHeight, float absoluteX, float absoluteY) throws DocumentException, IOException {
        for (int i = 1; i <= reader.getNumberOfPages(); i++) {
            Rectangle pageRect = reader.getPageSizeWithRotation(i);
            PdfContentByte content = stamper.getOverContent(i);

            BaseFont baseFont = BaseFont.createFont("font/SimSun.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
            content.setFontAndSize(baseFont, 20);

            // 添加Logo图片到页眉
            Image logoImage = Image.getInstance(logoPath);
//            logoImage.scaleAbsolute(141, 30);
            logoImage.scaleAbsolute(newWidth, newHeight);
            logoImage.setAbsolutePosition(pageRect.getLeft() + absoluteX, pageRect.getTop() + absoluteY);
            content.addImage(logoImage);

        }
    }
	 /**
     * 添加水印图片
     *
     * @param stamper       PdfStamper对象
     * @param reader        PdfReader对象
     * @param watermarkPath 水印图片路径
     * @throws DocumentException
     * @throws IOException
     */
    private static void addWatermarkImage(PdfStamper stamper, PdfReader reader, String watermarkPath) throws DocumentException, IOException {
        Image watermarkImage = Image.getInstance(watermarkPath);
        watermarkImage.scaleToFit(400, 400); // 调整水印图片大小

        /**
         * .scaleAbsolute    .scaleToFit 这两个方法 什么区别
         * scaleAbsolute和scaleToFit是iText库中用于缩放图像的两个方法。
         *
         * scaleAbsolute(float width, float height):
         * 该方法用于将图像缩放到指定的绝对宽度和高度。
         * 参数width和height分别指定了缩放后的宽度和高度。
         * 图像将按照指定的宽度和高度进行缩放,可能会导致图像的比例失调。
         *
         * scaleToFit(float width, float height):
         * 该方法用于将图像缩放以适应指定的矩形框的大小。
         * 参数width和height指定了矩形框的宽度和高度。
         * 图像将按照比例缩放,以适应指定的矩形框,同时保持其宽高比。
         * 简而言之,scaleAbsolute方法按照指定的宽度和高度进行缩放,而scaleToFit方法会按照比例缩放以适应指定的矩形框大小。选择使用哪个方法取决于你的需求和图像的要求。
         */

        for (int i = 1; i <= reader.getNumberOfPages(); i++) {
            Rectangle pageRect = reader.getPageSizeWithRotation(i);
            PdfContentByte content = stamper.getOverContent(i);

            // 创建PdfGState对象并设置透明度
            PdfGState gState = new PdfGState();
            gState.setFillOpacity(0.1f); // 水印图片透明度 (0.0f - 1.0f,0.0f 表示完全透明,1.0f 表示完全不透明)

            content.saveState();
            content.setGState(gState);

            // 添加水印图片到页面
            float x = (pageRect.getLeft() + pageRect.getRight() - watermarkImage.getScaledWidth()) / 2;
            float y = (pageRect.getBottom() + pageRect.getTop() - watermarkImage.getScaledHeight()) / 2;
            content.addImage(watermarkImage, watermarkImage.getScaledWidth(), 0, 0, watermarkImage.getScaledHeight(), x, y);

            content.restoreState();
        }
    }

}

使用

String base64 = HtmlToPDFUtil.getPDFBase64ByHtml(html);

String base64O = HtmlToPDFUtil.pdfAddWaterHeaderLogo(base64
                , true, "水印测试123"
                , true, "页眉测试123"
                , true, pathResolver.getResourcePath("classpath:static/img/logo-0acbf217.png"), 141, 30, 20, -30
                , true, pathResolver.getResourcePath("classpath:static/img/waterLogo_b.png")

        );

String pdfNumberF = HtmlToPDFUtil.base64ToPDF(base64O, "D:\\______________________________________IDEA\\__SpringBoot\\spring20_pdf\\src\\main\\resources");
System.out.println(pdfNumberF);

接下一篇 SpringBoot Thymeleaf企业级真实应用:使用Flying Saucer结合iText5将HTML界面数据转换为PDF输出(四) 表格中断问题

你可能感兴趣的:(spring,boot,html,pdf)