信息发布→ 登录 注册 退出

Java给PDF加水印并合并多个文件

发布时间:2026-01-11

点击量:
目录
  • 前言
  • 准备环境
  • 代码
    • 添加依赖
    • 工具类
  • 测试
    • 测试加水印与背景
    • 测试pdf合并
  • 总结

    前言

    本文基于itext7实现pdf加水印和合并的操作。实际上在我们实际项目应用中,对于pdf的操作也是比较常见的,我上一个项目中就有将结果转成pdf导出的需求。

    准备环境

    jdk8,idea2025.1.1,maven3

    代码

    添加依赖

    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.5.4</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>itext7-core</artifactId>
        <version>7.1.16</version>
    </dependency>
    

    工具类

    图片水印实体

    import lombok.Data;
    import lombok.ToString;
    
    /**
     * 文字水印
     *
     * @author mrcode
     */
    @Data
    @ToString
    public class ImageWatermark {
        /**
         * 图片所在绝对路径
         */
        private String path;
    
        /**
         * 透明度 0-1(完全透明-不透明)
         */
        private float opacity = 0.5F;
    }
    

    文字水印实体

    import com.itextpdf.io.font.constants.StandardFonts;
    import lombok.Data;
    import lombok.ToString;
    
    /**
     * 文字水印
     *
     * @author mrcode
     */
    @Data
    @ToString
    public class TextWatermark {
        /**
         * 文字水印,多行可使用 \n 换行
         */
        private String text;
    
        /**
         * 透明度 0-1(完全透明-不透明)
         */
        private float opacity = 0.5F;
        /**
         * 颜色:只支持 RGB; 为空则默认为黑色;比如 0,0,0;
         * <pre>
         *     建议使用 rgba 提供用户选择,后面的 a 的数值用于透明度的设置,展示的颜色和水印效果类似
         * </pre>
         */
        private String color;
    
        /**
         * 旋转角度
         */
        private float radAngle = 0F;
    
        /**
         * 字体文件路径;如果为空,则使用标准的英文字体 StandardFonts.HELVETICA
         * <pre>
         *     支持: afm、pfm、ttf、otf、woff、woff2
         * </pre>
         *
         * @see StandardFonts#HELVETICA
         */
        private String fontPath;
        /**
         * 字号大小,
         */
        private int fontSize = 30;
    
        /**
         * 文本平铺方式: 1:文本水平垂直居中 2:页面平铺
         */
        private int tileMode = 1;
        /**
         * 页面平铺:文字水平间隔;默认为 50
         */
        private Integer pageModeOfHorizontalInterval;
        /**
         * 页面平铺:文字垂直间隔; 建议至少为字体大小(默认为字体大小),如果有旋转,则合理的高度是 (文字个数 * 文字高度)
         */
        private Integer pageModeOfVerticalInterval;
    }
    

    添加水印和背景的工具类

    import com.itextpdf.io.font.PdfEncodings;
    import com.itextpdf.io.font.constants.StandardFonts;
    import com.itextpdf.io.image.ImageData;
    import com.itextpdf.io.image.ImageDataFactory;
    import com.itextpdf.kernel.colors.DeviceRgb;
    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.PdfReader;
    import com.itextpdf.kernel.pdf.PdfWriter;
    import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
    import com.itextpdf.kernel.pdf.extgstate.PdfExtGState;
    import com.itextpdf.layout.Document;
    import com.itextpdf.layout.element.Paragraph;
    import com.itextpdf.layout.property.TextAlignment;
    import com.itextpdf.layout.property.VerticalAlignment;
    
    import java.io.IOException;
    
    /**
     * PDF 水印添加
     *
     * @author mrcode
     * @date 2025/10/22 21:27
     */
    public class PdFWatermarkUtil {
        /**
         * 添加文字水印; 默认为居中添加
         *
         * @param watermark
         * @param srcPath   原始 PDF 文件绝对路径
         * @param destPath  添加完水印后的 PDF 存放路径
         */
        public static void addWatermark(TextWatermark watermark, String srcPath, String destPath) throws IOException {
            PdfDocument pdfDoc = new PdfDocument(new PdfReader(srcPath), new PdfWriter(destPath));
            Document doc = new Document(pdfDoc);
            PdfFont font = getPdfFont(watermark.getFontPath());
    
            // 设置文字水印样式
            final String text = watermark.getText();
            final int fontSize = watermark.getFontSize();
            Paragraph paragraph = new Paragraph(text)
                    .setFont(font)
    //                 .setFontColor(new DeviceRgb(0, 0, 0))
                    .setOpacity(watermark.getOpacity()) // 字体透明度 0-1 完全透明~不透明
                    .setFontSize(fontSize); // 字体大小
            final String color = watermark.getColor();
            // 设置 RGB 颜色
            if (color != null) {
                final String[] rgbs = color.split(",");
                final DeviceRgb deviceRgb = new DeviceRgb(
                        Integer.parseInt(rgbs[0].trim()),
                        Integer.parseInt(rgbs[1].trim()),
                        Integer.parseInt(rgbs[2].trim()));
                paragraph.setFontColor(deviceRgb);
            }
    
            // 获取水印文字宽度
            final float textWidth = font.getWidth(text, fontSize);
            // 文字高度则是字体大小
            final float textHeight = fontSize;
            final int tileMode = watermark.getTileMode();
            final int pageModeOfHorizontalInterval = watermark.getPageModeOfHorizontalInterval() == null ? 50 : watermark.getPageModeOfHorizontalInterval();
            final int pageModeOfVerticalInterval = watermark.getPageModeOfVerticalInterval() == null ? (int) textHeight : watermark.getPageModeOfVerticalInterval();
    
            for (int i = 1; i <= pdfDoc.getNumberOfPages(); i++) {
                PdfPage pdfPage = pdfDoc.getPage(i);
                // 获取页面大小,考虑页面旋转
                Rectangle pageSize = pdfPage.getPageSizeWithRotation();
                // 当页面有旋转时,内容自动旋转
                pdfPage.setIgnorePageRotationForContent(true);
    
                // 水印水平垂直居中
                if (tileMode == 1) {
                    // 计算添加的位置坐标:这里使用居中位置,水平垂直居中
                    float x = (pageSize.getLeft() + pageSize.getRight()) / 2;
                    float y = (pageSize.getTop() + pageSize.getBottom()) / 2;
    
                    // 参数分别为:文本、x 坐标、y 坐标、添加到底几页、文本水平对齐方式、文本垂直对齐方式、旋转弧度
                    doc.showTextAligned(paragraph,
                            x, // 文本所在 x y 坐标,文字将围绕这个点进行对齐或则旋转
                            y,
                            i, // 添加到 PDF 第几页
                            TextAlignment.CENTER,   // 文本水平对齐方式
                            VerticalAlignment.TOP, // 文本垂直对齐方式
                            // 将 角度 转换为 弧度
                            (float) Math.toRadians(watermark.getRadAngle()));
                } else {
                    // 水印按照设置平铺页面
    
                    // 注意这里的坐标点是 文字中心点,所以宽度需要增加文字自己的宽度,否则会重合在一起
                    for (float posX = 0f; posX < pageSize.getWidth(); posX = posX + textWidth + pageModeOfHorizontalInterval) {
                        for (float posY = pageModeOfVerticalInterval; posY < pageSize.getHeight(); posY = posY + pageModeOfVerticalInterval) {
                            // 参数分别为:文本、x 坐标、y 坐标、添加到底几页、文本水平对齐方式、文本垂直对齐方式、旋转弧度
                            doc.showTextAligned(paragraph,
                                    posX, // 文本所在 x y 坐标,文字将围绕这个点进行对齐或则旋转
                                    posY,
                                    i, // 添加到 PDF 第几页
                                    TextAlignment.CENTER,   // 文本水平对齐方式
                                    VerticalAlignment.TOP, // 文本垂直对齐方式
                                    // 将 角度 转换为 弧度
                                    (float) Math.toRadians(watermark.getRadAngle()));
                        }
                    }
                }
            }
            doc.close();
        }
    
        /**
         * 获取字体,支持 afm、pfm、ttf、otf、woff、woff2 字体,ttc 目前直接报错
         *
         * @param fontPath 字体文件绝对路径,如果为空则返回默认的英文字体
         * @return
         */
        private static PdfFont getPdfFont(String fontPath) throws IOException {
            if (fontPath == null) {
                return PdfFontFactory.createFont(StandardFonts.HELVETICA);
            }
            return PdfFontFactory.createFont(
                    fontPath,
                    // 水平书写
                    PdfEncodings.IDENTITY_H,
                    // 是否将字体嵌入到目标文档中: 如果可能,嵌入字体
                    PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED);
        }
    
        /**
         * 添加图片水印; 默认为居中添加
         *
         * @param watermark
         * @param srcPath   原始 PDF 文件绝对路径
         * @param destPath  添加完水印后的 PDF 存放路径
         */
        public static void addWatermark(ImageWatermark watermark, String srcPath, String destPath) throws IOException {
            PdfDocument pdfDoc = new PdfDocument(new PdfReader(srcPath), new PdfWriter(destPath));
            Document doc = new Document(pdfDoc);
    
            // 创建图片
            ImageData img = ImageDataFactory.create(watermark.getPath());
            float w = img.getWidth();
            float h = img.getHeight();
    
            // 设置透明度
            PdfExtGState gs1 = new PdfExtGState().setFillOpacity(watermark.getOpacity());
    
            // 循环添加到每一页的 PDF 中
            for (int i = 1; i <= pdfDoc.getNumberOfPages(); i++) {
                PdfPage pdfPage = pdfDoc.getPage(i);
                // 获取页面大小,考虑页面旋转
                Rectangle pageSize = pdfPage.getPageSizeWithRotation();
                // 当页面有旋转时,内容自动旋转
                pdfPage.setIgnorePageRotationForContent(true);
    
                // 计算添加的位置坐标:这里使用居中位置,水平垂直居中
                float x = (pageSize.getLeft() + pageSize.getRight()) / 2;
                float y = (pageSize.getTop() + pageSize.getBottom()) / 2;
    
                // 添加图片水印使用
                PdfCanvas over = new PdfCanvas(pdfDoc.getPage(i));
                over.saveState();
                over.setExtGState(gs1);
    
                // 添加图片水印:位置水平垂直居中
                over.addImageWithTransformationMatrix(img, w, 0, 0, h, x - (w / 2), y - (h / 2), false);
                over.restoreState();
            }
            doc.close();
        }
    }
    

    文件合并添加页码工具类

    import com.itextpdf.io.font.PdfEncodings;
    import com.itextpdf.io.font.constants.StandardFonts;
    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.PdfReader;
    import com.itextpdf.kernel.pdf.PdfWriter;
    import com.itextpdf.kernel.utils.PdfMerger;
    import com.itextpdf.layout.Document;
    import com.itextpdf.layout.element.Paragraph;
    import com.itextpdf.layout.property.TextAlignment;
    import com.itextpdf.layout.property.VerticalAlignment;
    
    import java.io.IOException;
    import java.util.List;
    
    import cn.hutool.core.util.StrUtil;
    
    /**
     * @author mrcode
     * @date 2025/1/21 20:27
     */
    public class PDFUtil {
        /**
         * 合并多个 PDF 文件为一个
         *
         * @param list   要合并的 PDF 文件绝对路径列表
         * @param toFile 合并后的 PDF 文件绝对路径
         * @throws IOException
         */
        public static void merge(List<String> list, String toFile) throws IOException {
            final PdfDocument toPdf = new PdfDocument(new PdfWriter(toFile));
            PdfMerger merger = new PdfMerger(toPdf);
            for (String file : list) {
                final PdfDocument pdfDocument = new PdfDocument(new PdfReader(file));
                merger.merge(pdfDocument, 1, pdfDocument.getNumberOfPages());
                pdfDocument.close();
            }
            merger.close();
            toPdf.close();
        }
    
        /**
         * 添加页码; 在右下角添加
         *
         * @param fontSize 文字大小,一般为 15 比较合适
         * @param srcPath  原始 PDF 文件绝对路径
         * @param destPath 添加完水印后的 PDF 存放路径
         */
        public static void addPageNumber(int fontSize, String srcPath, String destPath) throws IOException {
            PdfDocument pdfDoc = new PdfDocument(new PdfReader(srcPath), new PdfWriter(destPath));
            Document doc = new Document(pdfDoc);
            PdfFont font = getPdfFont(null);
            // 文字高度则是字体大小
            final float textHeight = fontSize;
            final int numberOfPages = pdfDoc.getNumberOfPages();
            for (int i = 1; i <= numberOfPages; i++) {
                PdfPage pdfPage = pdfDoc.getPage(i);
                // 获取页面大小,考虑页面旋转
                Rectangle pageSize = pdfPage.getPageSizeWithRotation();
                // 当页面有旋转时,内容自动旋转
                pdfPage.setIgnorePageRotationForContent(true);
    
                // 构建页码
                final String text = StrUtil.format("{}/{}", i, numberOfPages);
                Paragraph paragraph = new Paragraph(text)
                        .setFont(font)
                        .setFontSize(fontSize);
                // 获取文字宽度
                final float textWidth = font.getWidth(text, fontSize);
                // 计算添加的位置坐标
                // 定位到水平垂直居中
                // float x = (pageSize.getLeft() + pageSize.getRight()) / 2;
                // 定位到右侧:根据文字宽度减少宽度,能动态的根据文字宽度调整,让文字不会超出屏幕外面
                float x = pageSize.getRight() - textWidth;
                // bottom 是 0,+ 20 就是底部往上 20
                // 定位到底部:根据文字高度动态往上调整,不会超出屏幕外面;
                float y = pageSize.getBottom() + textHeight + 10;
    
                // 参数分别为:文本、x 坐标、y 坐标、添加到底几页、文本水平对齐方式、文本垂直对齐方式、旋转弧度
                doc.showTextAligned(paragraph,
                        x, // 文本所在 x y 坐标,文字将围绕这个点进行对齐或则旋转
                        y,
                        i, // 添加到 PDF 第几页
                        TextAlignment.CENTER,   // 文本水平对齐方式
                        VerticalAlignment.TOP, // 文本垂直对齐方式
                        // 将 角度 转换为 弧度
                        (float) Math.toRadians(0));
            }
            doc.close();
        }
    
        /**
         * 获取字体,支持 afm、pfm、ttf、otf、woff、woff2 字体,ttc 目前直接报错
         *
         * @param fontPath 字体文件绝对路径,如果为空则返回默认的英文字体
         * @return
         */
        private static PdfFont getPdfFont(String fontPath) throws IOException {
            if (fontPath == null) {
                return PdfFontFactory.createFont(StandardFonts.HELVETICA);
            }
            return PdfFontFactory.createFont(
                    fontPath,
                    // 水平书写
                    PdfEncodings.IDENTITY_H,
                    // 是否将字体嵌入到目标文档中: 如果可能,嵌入字体
                    PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED);
        }
    }
    

    测试

    测试加水印与背景

    package com.example.pdf;
    
    import com.itextpdf.io.image.ImageData;
    import com.itextpdf.io.image.ImageDataFactory;
    import com.itextpdf.kernel.geom.Rectangle;
    import com.itextpdf.kernel.pdf.PdfDocument;
    import com.itextpdf.kernel.pdf.PdfPage;
    import com.itextpdf.kernel.pdf.PdfReader;
    import com.itextpdf.kernel.pdf.PdfWriter;
    import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
    import com.itextpdf.kernel.pdf.extgstate.PdfExtGState;
    import com.itextpdf.layout.Document;
    import com.itextpdf.layout.element.Paragraph;
    import com.itextpdf.layout.property.TextAlignment;
    import com.itextpdf.layout.property.VerticalAlignment;
    
    public class Demo {
        public static final String DEST = "C:\\Users\\zhbcm\\Desktop\\readme1.pdf";
        public static final String IMG = "E:\\图片\\th.jpg";
        public static final String SRC = "C:\\Users\\zhbcm\\Desktop\\readme.pdf";
    
        public static void main(String[] args) throws Exception {
            new Demo().manipulatePdf(DEST);
        }
    
    
        protected void manipulatePdf(String dest) throws Exception {
            PdfDocument pdfDoc = new PdfDocument(new PdfReader(SRC), new PdfWriter(dest));
            Document doc = new Document(pdfDoc);
    //        PdfFont font = PdfFontFactory.createFont(StandardFonts.HELVETICA);
            // 这个实体是宋体中文字体,只有 4, m
            // 字体文件源码中只支持 afm、pfm、ttf、otf、woff、woff2,等你提供不支持的格式就会报错,点进去看源码就知道了
            // ttc 看到源码中也支持,但是提供 ttc 就报错,暂时没深入看源码如何报错
            // final PdfFont font = PdfFontFactory.createFont("C:\\temp\\songti.ttf",
            //         // 水平书写
            //         PdfEncodings.IDENTITY_H,
            //         // 是否将字体嵌入到目标文档中
            //         // 该参数被  PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED 代替,true 对应  PREFER_EMBEDDED,如果可能,嵌入字体
            //         true);
            // 多行文本使用 \n 换行
            Paragraph paragraph = new Paragraph("My watermark (中国强text) \n 中国")
                    // .setFont(font)
                    .setOpacity(0.1F) // 字体透明度 0-1 完全透明~不透明
                    .setFontSize(30); // 字体大小
            ImageData img = ImageDataFactory.create(IMG);
    
            float w = img.getWidth();
            float h = img.getHeight();
    
            // 用于添加图片水印,设置图片水印透明度
            PdfExtGState gs1 = new PdfExtGState().setFillOpacity(0.5f);
    
            // Implement transformation matrix usage in order to scale image
            for (int i = 1; i <= pdfDoc.getNumberOfPages(); i++) {
    
                PdfPage pdfPage = pdfDoc.getPage(i);
                // 获取页面大小,考虑页面旋转
                Rectangle pageSize = pdfPage.getPageSizeWithRotation();
    
                // 当页面有旋转时,内容自动旋转
                pdfPage.setIgnorePageRotationForContent(true);
    
                // 计算添加的位置坐标
                float x = (pageSize.getLeft() + pageSize.getRight()) / 2;
                float y = (pageSize.getTop() + pageSize.getBottom()) / 2;
    
                // 添加图片水印使用
                PdfCanvas over = new PdfCanvas(pdfDoc.getPage(i));
                over.saveState();
                over.setExtGState(gs1);
    
                if (i % 2 == 1) {
                    // 添加文本水印
                    // 参数分别为:文本、x 坐标、y 坐标、添加到第几页、文本水平对齐方式、文本垂直对齐方式、旋转弧度
                    doc.showTextAligned(paragraph, x, y, i, TextAlignment.CENTER, VerticalAlignment.TOP, 0);
                } else {
                    // 添加图片水印
                    over.addImageWithTransformationMatrix(img, w, 0, 0, h, x - (w / 2), y - (h / 2), false);
                }
                over.restoreState();
            }
    
            doc.close();
        }
    }
    

    水印效果图

    背景效果图

    测试pdf合并

    合并前

    合并后

    总结

    在线客服
    服务热线

    服务热线

    4008888355

    微信咨询
    二维码
    返回顶部
    ×二维码

    截屏,微信识别二维码

    打开微信

    微信号已复制,请打开微信添加咨询详情!