如何彻底移除PDF中Acrobat创建的XObject水印并检测水印文本?
问题解决方案
一、彻底移除Acrobat创建的水印对象
Acrobat生成的水印可能以页面注释(Stamp)、Form XObject或直接的内容流指令三种形式存在,仅用PdfContentStreamEditor修改内容流无法覆盖所有场景,需结合以下操作:
1. 移除水印类页面注释
遍历页面注释集合,识别并删除水印对应的Stamp注释:
PDDocument document = PDDocument.load(new File("input.pdf")); for (PDPage page : document.getPages()) { // 复制注释集合避免遍历时修改原集合引发异常 List<PDAnnotation> annotations = new ArrayList<>(page.getAnnotations()); for (PDAnnotation annot : annotations) { if (annot instanceof PDAnnotationStamp) { PDAnnotationStamp stamp = (PDAnnotationStamp) annot; // 根据注释内容、外观或Subtype判断是否为目标水印 if (stamp.getContents() != null && stamp.getContents().contains("EBSCO")) { page.getAnnotations().remove(annot); } } } }
2. 删除水印对应的Form XObject
部分水印会被封装为Form XObject存储在页面资源中,需先识别再移除,并同步修改内容流跳过其调用指令:
for (PDPage page : document.getPages()) { PDResources resources = page.getResources(); if (resources != null && resources.getXObjects() != null) { Map<String, PDXObject> xObjects = resources.getXObjects(); List<String> watermarkXObjKeys = new ArrayList<>(); // 遍历XObject,标记包含水印内容的对象 for (Map.Entry<String, PDXObject> entry : xObjects.entrySet()) { PDXObject xobj = entry.getValue(); if (xobj instanceof PDFormXObject) { PDFormXObject form = (PDFormXObject) xobj; if (hasWatermarkText(form.getContents())) { watermarkXObjKeys.add(entry.getKey()); } } } // 移除标记的XObject for (String key : watermarkXObjKeys) { xObjects.remove(key); } } }
3. 结合PdfContentStreamEditor跳过水印相关指令
在重写的processOperator方法中,不仅跳过文本绘制指令,还要跳过水印XObject的调用指令:
@Override protected void processOperator(Operator operator, List<COSBase> operands) throws IOException { String opName = operator.getName(); // 跳过水印文本绘制指令 if (("Tj".equals(opName) || "TJ".equals(opName)) && isWatermarkText(extractText(operands))) { return; } // 跳过水印XObject的调用指令 if ("Do".equals(opName)) { COSName xobjName = (COSName) operands.get(0); PDXObject xobj = getResources().getXObject(xobjName); if (xobj instanceof PDFormXObject && hasWatermarkText(((PDFormXObject)xobj).getContents())) { return; } } super.processOperator(operator, operands); }
二、检测水印对象中的文本
可通过解析内容流文本、结合文本属性两种方式识别水印:
1. 解析内容流提取文本匹配关键词
使用PDFTextStripper提取内容流中的文本,匹配水印特征关键词:
private boolean hasWatermarkText(PDStream contentStream) throws IOException { // 临时创建包含目标内容流的页面,用于文本提取 PDPage tempPage = new PDPage(contentStream); PDDocument tempDoc = new PDDocument(); tempDoc.addPage(tempPage); PDFTextStripper stripper = new PDFTextStripper(); String contentText = stripper.getText(tempDoc); tempDoc.close(); // 根据实际水印文本设置匹配规则,比如EBSCO相关水印 return contentText.contains("EBSCO") || contentText.contains("仅供学术使用"); } // 提取文本指令中的内容 private String extractText(List<COSBase> operands) { if (operands.isEmpty()) return ""; COSBase base = operands.get(0); if (base instanceof COSString) { return ((COSString) base).getString(); } else if (base instanceof COSArray) { // 处理TJ指令的数组内容 StringBuilder sb = new StringBuilder(); COSArray array = (COSArray) base; for (COSBase item : array) { if (item instanceof COSString) { sb.append(((COSString) item).getString()); } } return sb.toString(); } return ""; }
2. 结合文本位置与样式筛选
若水印文本有明显样式特征(如固定位置、浅灰色、大字体),可重写PDFTextStripper的writeString方法,记录文本的位置和样式信息:
class WatermarkDetectStripper extends PDFTextStripper { private boolean foundWatermark = false; @Override protected void writeString(String text, List<TextPosition> textPositions) throws IOException { for (TextPosition pos : textPositions) { // 示例:判断文本是否在页面底部(水印常见位置)、颜色为浅灰色 if (pos.getY() < 50 && pos.getFillColor().getRGB() > 0xeeeeee) { if (text.contains("EBSCO")) { foundWatermark = true; break; } } } super.writeString(text, textPositions); } public boolean isWatermarkFound() { return foundWatermark; } }
内容的提问来源于stack exchange,提问作者menteith
相关产品推荐
相关产品推荐

