PDF水印工具

PDF水印工具,实现平铺、左上、中上、右上、左中、正中、右中、左下、中下、右下10种水印方式。

使用itext,依赖:

            <dependency>
                <groupId>com.itextpdf</groupId>
                <artifactId>itextpdf</artifactId>
                <version>5.5.13</version>
            </dependency>
            <dependency>
                <groupId>com.itextpdf</groupId>
                <artifactId>itext-asian</artifactId>
                <version>5.2.0</version>
            </dependency>

代码:

/**
 * pdf水印工具类
 *
 * @author : RanPengCheng
 * @date : 2023/3/2 上午9:53
 */
public class PdfWaterMarkUtil2 {

    private PdfWaterMarkUtil2(){}

    /**
     * 水印的横向间隔
     */
    private static final float LEN_OFFSET = 100;
    /**
     * 平铺水印的,上下水印的错位比例
     */
    private static final float TILE_MALPOSITION = 0.3f;

    /** 平铺 */
    public static final int TILE = 0;
    /** 左上 */
    public static final int TOP_LEFT = 1;
    /** 中上 */
    public static final int TOP_CENTER = 2;
    /** 右上 */
    public static final int TOP_RIGHT = 3;
    /** 左中 */
    public static final int CENTER_LEFT = 4;
    /** 正中 */
    public static final int CENTER = 5;
    /** 右中 */
    public static final int CENTER_RIGHT = 6;
    /** 左下 */
    public static final int BOTTOM_LEFT = 7;
    /** 中下 */
    public static final int BOTTOM_CENTER = 8;
    /** 右下 */
    public static final int BOTTOM_RIGHT = 9;

    /** 字体缓存 */
    private static final Map<String, BaseFont> FONT_MAP = new HashMap<>();
    private static final Map<Integer, PdfGState> GS_MAP = new HashMap<>();

    /** 长度(厘米)到像素(px)的换算系数 */
    private static final float LEN_TO_PX = 28.347618466331845f;

    public static void addWaterMark(String srcPdfFile, OutputStream out, List<MarkInfo> markInfoList) throws Exception {
        addWaterMark(new PdfReader(srcPdfFile), out, markInfoList);
    }

    public static void addWaterMark(File pdfFile, OutputStream out, List<MarkInfo> markInfoList) throws Exception {
        addWaterMark(new PdfReader(new FileInputStream(pdfFile)), out, markInfoList);
    }

    public static void addWaterMark(URL pdfUrl, OutputStream out, List<MarkInfo> markInfoList) throws Exception {
        addWaterMark(new PdfReader(pdfUrl), out, markInfoList);
    }


    private static void addWaterMark(PdfReader reader, OutputStream out, List<MarkInfo> markInfoList) throws Exception {
        // 加完水印的文件
        PdfStamper pdfStamper = new PdfStamper(reader, out);
        int pageNumber = reader.getNumberOfPages() + 1;

        // 循环对每页插入水印
        for (int i = 1; i < pageNumber; i++) {
            Rectangle pageSize = reader.getPageSize(i);
            float height = pageSize.getHeight();
            float width = pageSize.getWidth();
            // 水印在之前文本上
            PdfContentByte content = pdfStamper.getOverContent(i);
            // 开始
            content.beginText();
            for (MarkInfo markInfo : markInfoList) {
                // 设置透明度
                content.setGState(getPdfGState(markInfo.getPellucidity()));
                // 设置字体及大小
                content.setFontAndSize(getFont(markInfo.getFontFamily()), markInfo.getFontSize());
                // 设置字体颜色
                content.setColorFill(Color.get(markInfo.getFontColor()));
                // 水印类型
                switch (markInfo.getPositionType()) {
                    case TILE:
                        tile(markInfo, height, width, content);
                        break;
                    case TOP_LEFT:
                        topLeft(markInfo, height, width, content);
                        break;
                    case TOP_CENTER:
                        topCenter(markInfo, height, width, content);
                        break;
                    case TOP_RIGHT:
                        topRight(markInfo, height, width, content);
                        break;
                    case CENTER_LEFT:
                        centerLeft(markInfo, height, width, content);
                        break;
                    case CENTER:
                        center(markInfo, height, width, content);
                        break;
                    case CENTER_RIGHT:
                        centerRight(markInfo, height, width, content);
                        break;
                    case BOTTOM_LEFT:
                        bottomLeft(markInfo, height, width, content);
                        break;
                    case BOTTOM_CENTER:
                        bottomCenter(markInfo, height, width, content);
                        break;
                    case BOTTOM_RIGHT:
                        bottomRight(markInfo, height, width, content);
                        break;
                    default:
                }
            }
            //结束
            content.endText();
        }
        pdfStamper.close();
    }

    private static PdfGState getPdfGState(int pellucidity) {
        PdfGState gs = GS_MAP.get(pellucidity);
        if (gs == null) {
            gs = new PdfGState();
            gs.setFillOpacity((100 - pellucidity) / 100f);
            GS_MAP.put(pellucidity, gs);
        }
        return gs;
    }

    private static void bottomRight(MarkInfo markInfo, float height, float width, PdfContentByte content) {
        final String markText = markInfo.getMarkText();
        final float rotation = markInfo.getRotation();
        float x = getRightX(markInfo, width, content);
        float y = getBottomY(markInfo, height, content);
        content.showTextAligned(Element.ALIGN_LEFT, markText, x, y, rotation);
    }

    private static void bottomCenter(MarkInfo markInfo, float height, float width, PdfContentByte content) {
        final String markText = markInfo.getMarkText();
        final float rotation = markInfo.getRotation();
        float x = getCenterX(markInfo, width, content);
        float y = getBottomY(markInfo, height, content);
        content.showTextAligned(Element.ALIGN_LEFT, markText, x, y, rotation);
    }

    private static void bottomLeft(MarkInfo markInfo, float height, float width, PdfContentByte content) {
        final String markText = markInfo.getMarkText();
        final float rotation = markInfo.getRotation();
        float x = getLeftX(markInfo, width, content);
        float y = getBottomY(markInfo, height, content);
        content.showTextAligned(Element.ALIGN_LEFT, markText, x, y, rotation);
    }

    private static void centerRight(MarkInfo markInfo, float height, float width, PdfContentByte content) {
        final String markText = markInfo.getMarkText();
        final float rotation = markInfo.getRotation();
        float x = getRightX(markInfo, width, content);
        float y = getCenterY(markInfo, height, content);
        content.showTextAligned(Element.ALIGN_LEFT, markText, x, y, rotation);
    }

    private static void centerLeft(MarkInfo markInfo, float height, float width, PdfContentByte content) {
        final String markText = markInfo.getMarkText();
        final float rotation = markInfo.getRotation();
        float x = getLeftX(markInfo, width, content);
        float y = getCenterY(markInfo, height, content);
        content.showTextAligned(Element.ALIGN_LEFT, markText, x, y, rotation);
    }

    private static void topRight(MarkInfo markInfo, float height, float width, PdfContentByte content) {
        final String markText = markInfo.getMarkText();
        final float rotation = markInfo.getRotation();
        float x = getRightX(markInfo, width, content);
        float y = getTopY(markInfo, height, content);
        content.showTextAligned(Element.ALIGN_LEFT, markText, x, y, rotation);
    }

    private static void topCenter(MarkInfo markInfo, float height, float width, PdfContentByte content) {
        final String markText = markInfo.getMarkText();
        final float rotation = markInfo.getRotation();
        float x = getCenterX(markInfo, width, content);
        float y = getTopY(markInfo, height, content);
        content.showTextAligned(Element.ALIGN_LEFT, markText, x, y, rotation);
    }

    private static void topLeft(MarkInfo markInfo, float height, float width, PdfContentByte content) {
        final String markText = markInfo.getMarkText();
        final float rotation = markInfo.getRotation();
        float x = getLeftX(markInfo, width, content);
        float y = getTopY(markInfo, height, content);
        content.showTextAligned(Element.ALIGN_LEFT, markText, x, y, rotation);
    }

    private static void center(MarkInfo markInfo, float height, float width, PdfContentByte content) {
        final String markText = markInfo.getMarkText();
        final float rotation = markInfo.getRotation();
        float x = getCenterX(markInfo, width, content);
        float y = getCenterY(markInfo, height, content);
        content.showTextAligned(Element.ALIGN_LEFT, markText, x, y, rotation);
    }

    private static float getBottomY(MarkInfo markInfo, float height, PdfContentByte content) {
        final String markText = markInfo.getMarkText();
        float textLength = content.getEffectiveStringWidth(markText, false);
        final float rotation = markInfo.getRotation();
        float bottomMargin = markInfo.getYMargin() * LEN_TO_PX;
        if (rotation >= 0) {
            return bottomMargin;
        }
        return bottomMargin - textLength * sin(rotation);
    }


    private static float getCenterY(MarkInfo markInfo, float height, PdfContentByte content) {
        final String markText = markInfo.getMarkText();
        float textLength = content.getEffectiveStringWidth(markText, false);
        final float rotation = markInfo.getRotation();
        final float yLen = textLength * sin(rotation);

        final float halfSize = markInfo.getFontSize() / 2f;
        return  (height - yLen) / 2 + halfSize * (1 - cos(rotation));
    }

    private static float getTopY(MarkInfo markInfo, float height, PdfContentByte content) {
        final String markText = markInfo.getMarkText();
        float textLength = content.getEffectiveStringWidth(markText, false);
        final int fontSize = markInfo.getFontSize();
        final float rotation = markInfo.getRotation();
        float topMargin = markInfo.getYMargin() * LEN_TO_PX;
        if (rotation >= 0) {
            return height -topMargin - fontSize * cos(rotation) - textLength * sin(rotation);
        }

        return height - topMargin - fontSize * cos(rotation);
    }


    private static float getRightX(MarkInfo markInfo, float width, PdfContentByte content) {
        final String markText = markInfo.getMarkText();
        float textLength = content.getEffectiveStringWidth(markText, false);
        final int fontSize = markInfo.getFontSize();
        final float rotation = markInfo.getRotation();
        final float rightMargin = markInfo.getXMargin() * LEN_TO_PX;
        if (rotation >= 0) {
            return width - rightMargin - textLength * cos(rotation);
        }

        return width - rightMargin - textLength * cos(rotation) + fontSize * sin(rotation);
    }

    private static float getCenterX(MarkInfo markInfo, float width, PdfContentByte content) {
        final String markText = markInfo.getMarkText();
        float textLength = content.getEffectiveStringWidth(markText, false);
        final float halfSize = markInfo.getFontSize() / 2f;
        final float rotation = markInfo.getRotation();
        final float xLen = textLength * cos(rotation);
        return (width - xLen) / 2 + halfSize * sin(rotation);
    }

    private static float getLeftX(MarkInfo markInfo, float width, PdfContentByte content) {
        final int fontSize = markInfo.getFontSize();
        final float rotation = markInfo.getRotation();
        float leftMargin = markInfo.getXMargin() * LEN_TO_PX;
        if (rotation <= 0 ) {
            return leftMargin;
        }
        return leftMargin + fontSize * sin(rotation);
    }

    /**
     * 平铺
     * 1、先找出为旋转的时候平铺的点坐标,再绕中心点旋转
     * 2、文档正中心始终有一个水印
     */
    private static void tile(MarkInfo markInfo, float height, float width, PdfContentByte content) {
        float textLength = content.getEffectiveStringWidth(markInfo.getMarkText(), false);
        final int fontSize = markInfo.getFontSize();

        // 正中心的条水印的坐标,所有其他水印的坐标由该坐标推算出来
        float x = (width - textLength) / 2;
        float y = (height - fontSize) / 2;

        // 文档外切圆的半径
        final float radius = (float) Math.sqrt(height * height + width * width) / 2f;
        // 相邻水印的x坐标偏移量
        final float xOffset = textLength + LEN_OFFSET;
        // 相邻水印的y坐标偏移量
        final float yOffset = markInfo.getLineSpacing() * fontSize;
        final float centerX = width / 2;
        float textCenterX = x + textLength / 2;
        float relativeX = Math.abs(textCenterX - centerX);
        while (relativeX < radius) {
            x -= xOffset;
            textCenterX = x + textLength / 2;
            relativeX = Math.abs(textCenterX - centerX);
        }

        diffusion(x, y, 1, 1, markInfo, height, width, content);
        diffusion(x - xOffset * TILE_MALPOSITION, y - yOffset, 1, -1, markInfo, height, width, content);
    }

    private static void diffusion(float x, float y, int xDirection, int yDirection, MarkInfo markInfo, float height, float width, PdfContentByte content) {
        final float rotation = markInfo.getRotation();
        final float sin = sin(rotation);
        final float cos = cos(rotation);
        float textLength = content.getEffectiveStringWidth(markInfo.getMarkText(), false);
        final int fontSize = markInfo.getFontSize();
        final float radius = (float) Math.sqrt(height * height + width * width) / 2f;
        final float xOffset = textLength + LEN_OFFSET;
        final float yOffset = markInfo.getLineSpacing() * fontSize;
        final float centerX = width / 2;
        final float centerY = height / 2;

        final float xLen = textLength * cos;
        final float yLen = textLength * sin;

        float textCenterX = x + textLength / 2;
        float textCenterY = y + fontSize / 2f;
        float relativeY = Math.abs(textCenterY - centerY);
        if (relativeY > radius) {
            return;
        }
        float relativeX = Math.abs(textCenterX - centerX);
        while (relativeX > radius) {
            x += xOffset * xDirection;
            textCenterX = x + textLength / 2;
            relativeX = Math.abs(textCenterX - centerX);
        }
        while (relativeX < radius) {
            float newX = (x - centerX) * cos - (y - centerY) * sin + centerX;
            float newY = (x - centerX) * sin + (y - centerY) * cos + centerY;

            x += xOffset * xDirection;
            textCenterX = x + textLength / 2;
            relativeX = Math.abs(textCenterX - centerX);

            boolean outOfRange = (newX < 0 && newX + xLen < 0)
                    || (newX > width && (newX + xLen) > width)
                    || (newY < 0 && (newY + yLen < 0))
                    || (newY > height && ((newY + yLen) > height));
            if (outOfRange) {
                continue;
            }
            content.showTextAligned(Element.ALIGN_LEFT, markInfo.getMarkText(), newX, newY, rotation);
        }

        x += xOffset * TILE_MALPOSITION * yDirection;
        xDirection *= -1;
        y += yOffset * yDirection;

        diffusion(x, y, xDirection, yDirection, markInfo, height, width, content);
    }

    private static float sin(float rotation) {
        return (float) Math.sin(Math.toRadians(rotation));
    }

    private static float cos(float rotation) {
        return (float) Math.cos(Math.toRadians(rotation));
    }

    private static BaseFont getFont(String fontFamily) throws IOException, DocumentException {
        BaseFont baseFont = FONT_MAP.get(fontFamily);
        if (baseFont == null) {
            String fontName = "./fonts/SimSun.ttf";
            switch (fontFamily) {
                case "NSimSun":
                    fontName = "./fonts/simsun_new.ttf";
                    break;
                case "SimHei":
                    fontName = "./fonts/SIMHEI.TTF";
                    break;
                case "KaiTi":
                    fontName = "./fonts/simkai.ttf";
                    break;
                default:
            }

            baseFont = BaseFont.createFont(PdfWaterMarkUtil2.class.getClassLoader().getResource(fontName).getPath(), BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
            FONT_MAP.put(fontFamily, baseFont);
        }
        return baseFont;
    }

    public static void main(String[] args) throws Exception {
        try (final FileInputStream is = new FileInputStream(new File("/Users/ranpengcheng/Desktop/1.pdf"));
             FileOutputStream out = new FileOutputStream(new File("/Users/ranpengcheng/Desktop/test444.pdf"))
        ) {
            String s = "测试水印";
            final PdfReader reader = new PdfReader(is);

            MarkInfo m1 = new MarkInfo();
            m1.setPellucidity(45);
            m1.setPositionType(TILE);
            m1.setFontColor(Color.BLUE);
            m1.setFontFamily("KaiTi");
            m1.setMarkText(s);
            m1.setFontSize(18);
            m1.setRotation(30);
            m1.setXMargin(1f);
            m1.setYMargin(0.1f);

            List<MarkInfo> list = new ArrayList<>();
            list.add(m1);
            addWaterMark(reader, out, list);
        }
    }

    public static class Color {
        public static final int BLANK = 1;
        public static final int RED = 2;
        public static final int BLUE = 3;

        public static BaseColor get(int fontColor) {
            switch (fontColor) {
                case BLANK:
                    return BaseColor.BLACK;
                case RED:
                    return BaseColor.RED;
                default:
                    return BaseColor.BLUE;
            }

        }
    }

    @Data
    public static class MarkInfo {

        /**
         * 水印文字
         */
        private String markText;

        /**
         * 字体
         */
        private String fontFamily = "SimSun";

        /**
         * 字号
         */
        private int fontSize = 12;

        /**
         * 字体颜色
         */
        private int fontColor = 1;

        /**
         * 透明度(0-100,0全完不透明,100完全透明)
         */
        private int pellucidity = 30;

        /**
         * 水印位置类型
         */
        private int positionType = 0;

        /**
         * 倾斜度(-90至90)
         */
        private float rotation = 45;

        /**
         * 左/右边距(厘米),最大15
         */
        private float xMargin = 3;
        /**
         * 上/下边距(厘米),最大15
         */
        private float yMargin = 2;

        /**
         * 行距(字体倍数)
         */
        private int lineSpacing = 5;

    }

}
### 如何去除PDF文件上的水印 要去除PDF文件上的水印,可以采用多种方法和技术手段。以下是几种常见的解决方案: #### 方法一:使用专业的PDF编辑器 通过专门的PDF编辑工具能够轻松完成水印移除的任务。这些工具通常提供直观的界面和强大的功能支持,使得用户无需具备编程技能即可快速解决需求[^1]。 #### 方法二:借助Python脚本自动处理 利用Python语言编写程序可实现批量自动化地对多个PDF文档执行去水印操作。这种方法尤其适合技术背景较强的使用者,在已有基础之上扩展更多自定义选项成为可能[^2]。 ```python from PyPDF2 import PdfReader, PdfWriter def remove_watermark(input_path, output_path): reader = PdfReader(input_path) writer = PdfWriter() for page in reader.pages: # Remove annotations and other elements that may contain watermark information. if "/Annots" in page: del page["/Annots"] writer.add_page(page) with open(output_path, 'wb') as f_out: writer.write(f_out) remove_watermark('input_with_watermark.pdf', 'output_without_watermark.pdf') ``` 此代码片段展示了如何使用`PyPDF2`库读取并写入不带注释的新版PDF副本,从而间接达到清除某些类型的嵌入式标记效果的目的。 #### 方法三:基于Java开发定制化应用 如果倾向于企业级部署或者希望深入控制整个流程,则可以选择依靠像iText这样的开源框架构建专属应用程序来进行复杂场景下的pdf内容修改工作。下面给出了一段简单的例子说明创建含特定文字作为覆盖层形式存在的新版本文件过程[^3]: ```java import com.itextpdf.kernel.geom.PageSize; import com.itextpdf.kernel.pdf.PdfDocument; import com.itextpdf.kernel.pdf.PdfReader; import com.itextpdf.kernel.pdf.PdfWriter; import com.itextpdf.layout.Document; import com.itextpdf.layout.element.Paragraph; public class WatermarkRemover { public static void main(String[] args) throws Exception{ String src = "original_file.pdf"; String dest = "file_after_removal.pdf"; PdfReader reader = new PdfReader(src); PdfWriter writer = new PdfWriter(dest); PdfDocument pdfDoc = new PdfDocument(reader, writer); Document document = new Document(pdfDoc); // Add your custom logic here to manipulate pages document.close(); } } ``` 以上三种方式各有优劣之处,具体选择取决于实际应用场景和个人偏好等因素综合考量之后决定最为合适的一种方案实施下去。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值