如何用PDFBox 2.0.27移除内容上方水印?求可用实现代码
移除PDF内容上方水印的PDFBox实现(2.0.27版本)
当然可以实现移除内容上方的水印,PDF页面的内容是按绘制顺序渲染的——内容上方的水印(前景水印)是在正文之后绘制的,只要解析页面内容流,识别并过滤掉水印对应的绘制指令,就能生成干净的PDF文件。
下面是两种常见水印场景的实现代码,你可以根据自己的水印类型调整:
场景1:移除已知文本内容的前景水印
如果你的水印是固定文本(比如"CONFIDENTIAL"),用这段代码:
import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.common.PDStream; import org.apache.pdfbox.contentstream.operator.Operator; import org.apache.pdfbox.contentstream.PDFStreamEngine; import org.apache.pdfbox.contentstream.operator.state.*; import org.apache.pdfbox.contentstream.operator.text.*; import org.apache.pdfbox.contentstream.operator.graphics.*; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.util.List; public class RemoveForegroundTextWatermark { // 替换成你要移除的水印文本 private static final String TARGET_WATERMARK = "CONFIDENTIAL"; public static void main(String[] args) { try (PDDocument doc = PDDocument.load(new File("你的输入文件路径.pdf"))) { for (PDPage page : doc.getPages()) { PDStream newContent = new PDStream(doc); try (PDPageContentStream contentStream = new PDPageContentStream(doc, page, PDPageContentStream.AppendMode.OVERWRITE, true, true)) { WatermarkFilterEngine engine = new WatermarkFilterEngine(contentStream); engine.processPage(page); } page.setContents(newContent); } doc.save("你的输出文件路径.pdf"); } catch (IOException e) { e.printStackTrace(); } } // 自定义流引擎,过滤水印文本 private static class WatermarkFilterEngine extends PDFStreamEngine { private final PDPageContentStream outputStream; private boolean skipWatermark = false; public WatermarkFilterEngine(PDPageContentStream outputStream) { this.outputStream = outputStream; // 注册必要的操作符处理器 registerOperatorProcessor("BT", new BeginText()); registerOperatorProcessor("ET", new EndText()); registerOperatorProcessor("Tj", new ShowText()); registerOperatorProcessor("TJ", new ShowTextAdjusted()); registerOperatorProcessor("Tf", new SetFontAndSize()); registerOperatorProcessor("q", new SaveGraphicsState()); registerOperatorProcessor("Q", new RestoreGraphicsState()); registerOperatorProcessor("Do", new Invoke()); } @Override protected void processOperator(Operator operator, List<?> operands) throws IOException { String opName = operator.getName(); // 判断当前文本是否是水印 if ("Tj".equals(opName) || "TJ".equals(opName)) { String text = getTextFromOperand(operands, opName); if (text.contains(TARGET_WATERMARK)) { skipWatermark = true; return; // 跳过水印绘制 } } else if ("ET".equals(opName)) { skipWatermark = false; // 文本块结束,重置标记 } // 非水印指令写入新流 if (!skipWatermark) { outputStream.addOperand(operator); operands.forEach(outputStream::addOperand); outputStream.endOperand(); } } private String getTextFromOperand(List<?> operands, String opName) { if ("Tj".equals(opName)) { return operands.get(0).toString(); } else { StringBuilder sb = new StringBuilder(); for (Object obj : (List<?>) operands.get(0)) { if (obj instanceof String) sb.append(obj); } return sb.toString(); } } } }
场景2:移除图像类型的前景水印
如果水印是图片,可以通过尺寸、位置等特征过滤:
import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDResources; import org.apache.pdfbox.pdmodel.common.PDStream; import org.apache.pdfbox.pdmodel.graphics.PDXObject; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.apache.pdfbox.util.Matrix; import org.apache.pdfbox.contentstream.operator.Operator; import org.apache.pdfbox.contentstream.PDFStreamEngine; import org.apache.pdfbox.contentstream.operator.state.*; import org.apache.pdfbox.contentstream.operator.graphics.*; import java.io.File; import java.io.IOException; import java.util.List; public class RemoveForegroundImageWatermark { // 替换成你水印图片的实际尺寸(允许误差) private static final float WATERMARK_WIDTH = 220f; private static final float WATERMARK_HEIGHT = 110f; private static final float ERROR_RANGE = 15f; public static void main(String[] args) { try (PDDocument doc = PDDocument.load(new File("你的输入文件路径.pdf"))) { for (PDPage page : doc.getPages()) { PDStream newContent = new PDStream(doc); try (PDPageContentStream contentStream = new PDPageContentStream(doc, page, PDPageContentStream.AppendMode.OVERWRITE, true, true)) { ImageFilterEngine engine = new ImageFilterEngine(contentStream, page); engine.processPage(page); } page.setContents(newContent); } doc.save("你的输出文件路径.pdf"); } catch (IOException e) { e.printStackTrace(); } } private static class ImageFilterEngine extends PDFStreamEngine { private final PDPageContentStream outputStream; private final PDPage page; private Matrix currentMatrix; public ImageFilterEngine(PDPageContentStream outputStream, PDPage page) { this.outputStream = outputStream; this.page = page; // 记录当前变换矩阵(用于计算图像实际显示尺寸) registerOperatorProcessor("cm", new ConcatenateMatrix() { @Override public void process(Operator operator, List<?> operands) throws IOException { super.process(operator, operands); currentMatrix = getGraphicsState().getCurrentTransformationMatrix(); } }); // 过滤水印图像 registerOperatorProcessor("Do", new Invoke() { @Override public void process(Operator operator, List<?> operands) throws IOException { COSName xObjName = (COSName) operands.get(0); PDResources resources = page.getResources(); PDXObject xObj = resources.getXObject(xObjName); if (xObj instanceof PDFormXObject) { // 递归处理嵌套的表单对象 processFormXObject((PDFormXObject) xObj); } else if (xObj instanceof org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject) { org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject image = (org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject) xObj; float displayWidth = image.getWidth() * currentMatrix.getScaleX(); float displayHeight = image.getHeight() * currentMatrix.getScaleY(); // 判断是否是目标水印图像 if (Math.abs(displayWidth - WATERMARK_WIDTH) < ERROR_RANGE && Math.abs(displayHeight - WATERMARK_HEIGHT) < ERROR_RANGE) { return; // 跳过水印绘制 } } // 非水印图像写入新流 outputStream.addOperand(operator); outputStream.addOperand(xObjName); outputStream.endOperand(); } }); // 注册其他必要操作符处理器 registerOperatorProcessor("BT", new BeginText()); registerOperatorProcessor("ET", new EndText()); registerOperatorProcessor("Tj", new ShowText()); registerOperatorProcessor("q", new SaveGraphicsState()); registerOperatorProcessor("Q", new RestoreGraphicsState()); } private void processFormXObject(PDFormXObject form) throws IOException { saveGraphicsState(); concatenateMatrix(form.getMatrix()); processStream(form, form.getResources(), form.getContentStream()); restoreGraphicsState(); } @Override protected void processOperator(Operator operator, List<?> operands) throws IOException { if (!"Do".equals(operator.getName())) { outputStream.addOperand(operator); operands.forEach(outputStream::addOperand); outputStream.endOperand(); } } } }
使用说明
- 确保项目引入PDFBox 2.0.27依赖:
<dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pdfbox</artifactId> <version>2.0.27</version> </dependency>
- 根据水印类型选择对应代码,修改水印文本或尺寸参数
- 替换代码中的输入输出文件路径,运行即可生成处理后的PDF
注意事项
- 如果水印是嵌套在表单对象里的,代码已经做了递归处理
- 若文本水印有旋转、缩放变形,可以结合变换矩阵做更精准的判断
- 复杂水印可能需要增加颜色、字体等特征识别逻辑,你可以在代码中扩展判断条件
内容的提问来源于stack exchange,提问作者lizepengg
相关产品推荐
相关产品推荐

