如何使用PDFBox提取PDF斜向水印并保留对齐提取表单域内容
解决方案
1. 斜向文本(含水印)提取修复
原有代码的核心问题是角度匹配逻辑和文本筛选逻辑不兼容,调整方案如下:
- 优化角度计算逻辑,避免浮点精度误差导致的角度漏匹配:将角度计算结果按1度精度取整,同时允许±1度的匹配误差
- 移除
FilteredTextStripper的固定0度筛选逻辑,改为动态传入当前提取的目标角度 - 避免同一文本多次提取:每次旋转页面后只提取和当前旋转角度匹配的文本
修改后的核心代码:
// 优化后的角度计算方法 static int getAngle(TextPosition text) { Matrix m = text.getTextMatrix().clone(); m.concatenate(text.getFont().getFontMatrix()); double angle = Math.toDegrees(Math.atan2(m.getShearY(), m.getScaleY())); // 处理接近360/0度的边界情况 int roundedAngle = (int) Math.round(angle); return (roundedAngle + 360) % 360; } // 可调目标角度的文本提取器 class AngleFilteredTextStripper extends PDFTextStripper { private int targetAngle; private static final int ANGLE_TOLERANCE = 1; // 允许1度误差 public AngleFilteredTextStripper(int targetAngle) throws IOException { this.targetAngle = targetAngle; } @Override protected void processTextPosition(TextPosition text) { int currentAngle = getAngle(text); // 匹配目标角度,处理0度和359度相邻的边界 if (Math.abs(currentAngle - targetAngle) <= ANGLE_TOLERANCE || (targetAngle == 0 && currentAngle >= 359) || (targetAngle == 359 && currentAngle <= 1)) { super.processTextPosition(text); } } } // 提取逻辑调整 public void extractPages(int startPage, int endPage, PDDocument document, Writer output) throws IOException { for (int p = startPage; p <= endPage; ++p) { PDPage page = document.getPage(p - 1); // 先提取注释内容 for (var annot : getAnnots(page)) { output.write(annot); } int originalRotation = page.getRotation(); page.setRotation(0); // 收集页面所有文本角度 AngleCollector angleCollector = new AngleCollector(); angleCollector.setStartPage(p); angleCollector.setEndPage(p); angleCollector.writeText(document, new NullWriter()); // 用空输出避免冗余内容 // 按角度分别提取文本 for (int angle : angleCollector.getAngles()) { // 旋转页面将目标角度文本转为0度 try (PDPageContentStream cs = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.PREPEND, false)) { cs.transform(Matrix.getRotateInstance(-Math.toRadians(angle), 0, 0)); } // 只提取当前角度的文本 AngleFilteredTextStripper stripper = new AngleFilteredTextStripper(angle); stripper.setStartPage(p); stripper.setEndPage(p); stripper.writeText(document, output); // 移除临时旋转变换 ((COSArray) page.getCOSObject().getItem(COSName.CONTENTS)).remove(0); } page.setRotation(originalRotation); } }
如果需要单独筛选水印,可以额外增加判断条件:比如文本的透明度<0.5、文本在页面中重复出现3次以上、渲染模式为填充且无描边等特征,过滤正常正文内容。
2. 表单域内容提取并保留对齐格式
原有代码直接调用flatten()会将表单域转为普通页面内容,丢失对齐属性,正确处理逻辑如下:
AcroForm表单处理
不要提前flatten表单,先遍历所有表单域获取对齐属性和内容,再根据坐标排序后输出:
public List<FieldInfo> extractAcroFormFields(PDDocument document) throws IOException { PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm(); List<FieldInfo> fields = new ArrayList<>(); if (acroForm == null) return fields; for (PDField field : acroForm.getFields()) { if (field instanceof PDTextField textField) { int alignment = textField.getQ(); // 0=左对齐,1=居中,2=右对齐 String value = textField.getValueAsString(); PDRectangle rect = field.getWidgets().get(0).getRectangle(); // 存储域的坐标、对齐方式、内容,后续按y坐标倒序、x坐标正序排序 fields.add(new FieldInfo(rect.getLowerLeftY(), rect.getLowerLeftX(), alignment, value)); } } // 按页面位置排序 fields.sort((f1, f2) -> { int yCompare = Float.compare(f2.getY(), f1.getY()); return yCompare != 0 ? yCompare : Float.compare(f1.getX(), f2.getX()); }); return fields; } // 存储表单域信息的辅助类 class FieldInfo { float y; float x; int alignment; String content; // 构造方法、getter省略 }
输出时可以根据对齐属性,按固定宽度补空格实现对齐,也可以输出为HTML表格等带格式的载体保留对齐效果。
XFA表单处理
XFA表单的内容存储在XML数据包中,直接解析XML即可获取完整的内容和样式属性:
public String extractXfaContent(PDDocument document) throws IOException { PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm(); if (acroForm == null || acroForm.getXFA() == null) return ""; return acroForm.getXFA().getDocument().getTextContent(); // 后续可以用DOM或SAX解析XML获取每个域的内容、对齐、位置等属性 }
内容的提问来源于stack exchange,提问作者Sahib Yar
相关产品推荐
相关产品推荐

