You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 09:45:03