You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何使用Apache PDFBox按原顺序提取PDF中的文本与图片?

当然可以!借助Apache PDFBox获取的文本位置信息,完全能实现按文档原有顺序同时提取文本和图片。我来给你梳理具体的思路和实操步骤:

核心思路

PDF文档里的所有内容(文本、图片、图形等)都是按照绘制顺序存储在页面的内容流中的。这意味着,如果你能遍历页面的内容流并捕获每个元素的绘制动作,就能直接按原文档顺序收集文本和图片。如果已经分开提取了文本和图片,也可以通过它们的页面坐标来排序合并——不过遍历内容流的方式会更准确,因为它能直接还原PDF渲染时的原始顺序,避免坐标排序可能出现的层级或重叠问题。

具体实现步骤
  1. 捕获带位置的文本与图片
    继承PDFBox的PDFTextStripper类,重写两个关键方法:

    • writeString:捕获每个文本块的内容和坐标位置,封装成文本对象。
    • processOperator:监听内容流中的Do操作符(用于绘制图片/XObject),提取图片对象并获取其绘制位置(通过当前图形状态的变换矩阵),封装成图片对象。
  2. 按顺序处理内容
    遍历收集到的所有文本和图片对象,它们的顺序就是PDF文档中的原始绘制顺序,直接按此顺序输出或保存即可。如果是分开提取的场景,可以按页面号→y坐标(PDF的y轴从下往上,值越大位置越靠上)→x坐标的优先级排序,也能还原大致顺序。

代码示例

下面是一个自定义的内容提取器,能同时按顺序捕获文本和图片:

import org.apache.pdfbox.cos.COSBase;
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.graphics.PDXObject;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.pdfbox.text.TextPosition;
import org.apache.pdfbox.util.Matrix;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import javax.imageio.ImageIO;

public class OrderedContentExtractor extends PDFTextStripper {
    private final List<ContentItem> contentItems = new ArrayList<>();

    public OrderedContentExtractor() throws IOException {
        super();
    }

    // 捕获文本块及其位置
    @Override
    protected void writeString(String text, List<TextPosition> textPositions) throws IOException {
        TextPosition firstPos = textPositions.get(0);
        contentItems.add(new TextContent(text, firstPos.getPageNumber(), firstPos.getX(), firstPos.getY()));
        super.writeString(text, textPositions);
    }

    // 捕获图片绘制操作
    @Override
    protected void processOperator(Operator operator, List<COSBase> operands) throws IOException {
        String opName = operator.getName();
        if ("Do".equals(opName)) {
            COSName xObjName = (COSName) operands.get(0);
            PDResources resources = getCurrentPage().getResources();
            PDXObject xObject = resources.getXObject(xObjName);
            
            if (xObject instanceof PDImageXObject) {
                PDImageXObject image = (PDImageXObject) xObject;
                Matrix transformMatrix = getGraphicsState().getCurrentTransformationMatrix();
                float imgX = transformMatrix.getTranslateX();
                float imgY = transformMatrix.getTranslateY();
                contentItems.add(new ImageContent(image, getCurrentPage().getNumber(), imgX, imgY));
            }
        }
        super.processOperator(operator, operands);
    }

    public List<ContentItem> getOrderedContent() {
        return contentItems;
    }

    // 抽象内容项基类
    abstract static class ContentItem {
        final int pageNumber;
        final float x;
        final float y;

        ContentItem(int pageNumber, float x, float y) {
            this.pageNumber = pageNumber;
            this.x = x;
            this.y = y;
        }
    }

    // 文本内容项
    static class TextContent extends ContentItem {
        final String text;

        TextContent(String text, int pageNumber, float x, float y) {
            super(pageNumber, x, y);
            this.text = text;
        }
    }

    // 图片内容项
    static class ImageContent extends ContentItem {
        final PDImageXObject image;

        ImageContent(PDImageXObject image, int pageNumber, float x, float y) {
            super(pageNumber, x, y);
            this.image = image;
        }
    }

    // 测试使用
    public static void main(String[] args) {
        try (PDDocument doc = PDDocument.load(new File("your-document.pdf"))) {
            OrderedContentExtractor extractor = new OrderedContentExtractor();
            extractor.setSortByPosition(true);
            extractor.getText(doc);
            
            List<ContentItem> content = extractor.getOrderedContent();
            for (ContentItem item : content) {
                if (item instanceof TextContent) {
                    System.out.println("提取文本:" + ((TextContent) item).text);
                } else if (item instanceof ImageContent) {
                    PDImageXObject img = ((ImageContent) item).image;
                    String imgPath = "extracted-image-" + UUID.randomUUID() + "." + img.getSuffix();
                    ImageIO.write(img.getImage(), img.getSuffix(), new File(imgPath));
                    System.out.println("提取图片:" + imgPath);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
注意事项
  • 变换矩阵处理:有些PDF会对内容进行旋转、缩放等变换,需要通过getCurrentTransformationMatrix获取准确的绘制位置,避免坐标偏移。
  • 文本块合并writeString可能会把连续的文本拆分成多个调用,你可以根据相邻文本块的坐标和间距,手动合并成完整的段落。
  • 层级问题:如果图片被文本覆盖,绘制顺序是先画图片再画文本,遍历内容流的方式会正确捕获这个顺序;而单纯按坐标排序可能会因为y值的问题搞反顺序,所以优先用内容流遍历的方案。

内容的提问来源于stack exchange,提问作者Amine

火山引擎 最新活动