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

提取PDF文本时能否获取行号?Java中用pdfbox等库如何逐行提取?

从PDF逐行提取文本并获取行号的Java实现方法

PDF本身没有原生的"行"概念,文本位置由坐标定义,所以需要通过字符/文本块的Y坐标来分组判断行。以下是你提到的三个Java库的具体实现方案:

1. Apache PDFBox

通过自定义PDFTextStripper子类,跟踪文本的Y坐标来分组行并记录行号:

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.pdfbox.text.TextPosition;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class LineNumberPDFStripper extends PDFTextStripper {
    private List<String> lines = new ArrayList<>();
    private float currentLineY = -1;
    private static final float Y_TOLERANCE = 2f; // 根据PDF字体大小调整阈值

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

    @Override
    protected void writeString(String text, List<TextPosition> textPositions) throws IOException {
        float y = textPositions.get(0).getY();
        // 判断是否为新行:Y坐标差异超过阈值
        if (currentLineY == -1 || Math.abs(y - currentLineY) > Y_TOLERANCE) {
            currentLineY = y;
            lines.add("行" + (lines.size() + 1) + ": " + text);
        } else {
            // 同一行,追加文本
            String lastLine = lines.get(lines.size() - 1);
            lines.set(lines.size() - 1, lastLine.replaceAll(": .*$", ": ") + text);
        }
    }

    public List<String> getLinesWithNumbers() {
        return lines;
    }

    public static void main(String[] args) throws IOException {
        try (PDDocument document = PDDocument.load(new File("input.pdf"))) {
            LineNumberPDFStripper stripper = new LineNumberPDFStripper();
            stripper.setStartPage(1);
            stripper.setEndPage(document.getNumberOfPages());
            stripper.getText(document);
            
            for (String line : stripper.getLinesWithNumbers()) {
                System.out.println(line);
            }
        }
    }
}

2. iText 7

利用LocationTextExtractionStrategy的文本块(TextChunk)分组,通过Y坐标判断行:

import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.canvas.parser.PdfTextExtractor;
import com.itextpdf.kernel.pdf.canvas.parser.listener.LocationTextExtractionStrategy;
import com.itextpdf.kernel.pdf.canvas.parser.listener.TextChunk;

import java.io.File;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class LineNumberExtractor {
    public static List<String> extractLinesWithNumbers(String pdfPath) throws Exception {
        List<String> lines = new ArrayList<>();
        try (PdfDocument pdfDoc = new PdfDocument(new PdfReader(pdfPath))) {
            for (int pageNum = 1; pageNum <= pdfDoc.getNumberOfPages(); pageNum++) {
                LocationTextExtractionStrategy strategy = new LocationTextExtractionStrategy();
                PdfTextExtractor.getTextFromPage(pdfDoc.getPage(pageNum), strategy);
                
                // 获取文本块并按Y坐标降序排序(PDF坐标原点在左下角)
                List<TextChunk> chunks = new ArrayList<>(strategy.getResultantTextChunks());
                chunks.sort(Comparator.comparing(TextChunk::getLocation).reversed());
                
                float currentLineY = -1;
                StringBuilder currentLine = new StringBuilder();
                int lineNum = 1;
                
                for (TextChunk chunk : chunks) {
                    float y = chunk.getLocation().getStartLocation().getY();
                    if (currentLineY == -1 || Math.abs(y - currentLineY) > 2f) {
                        if (currentLineY != -1) {
                            lines.add("页" + pageNum + "行" + lineNum + ": " + currentLine);
                            lineNum++;
                            currentLine.setLength(0);
                        }
                        currentLineY = y;
                        currentLine.append(chunk.getText());
                    } else {
                        currentLine.append(chunk.getText());
                    }
                }
                // 添加最后一行
                if (currentLine.length() > 0) {
                    lines.add("页" + pageNum + "行" + lineNum + ": " + currentLine);
                }
            }
        }
        return lines;
    }

    public static void main(String[] args) throws Exception {
        List<String> lines = extractLinesWithNumbers("input.pdf");
        lines.forEach(System.out::println);
    }
}

3. Aspose.PDF

通过TextAbsorber提取文本片段,按Y坐标分组行:

import com.aspose.pdf.Document;
import com.aspose.pdf.TextAbsorber;
import com.aspose.pdf.TextFragment;

import java.io.File;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class AsposeLineNumberExtractor {
    public static List<String> extractLinesWithNumbers(String pdfPath) {
        List<String> lines = new ArrayList<>();
        Document document = new Document(pdfPath);
        
        for (int pageNum = 1; pageNum <= document.getPages().size(); pageNum++) {
            TextAbsorber absorber = new TextAbsorber();
            document.getPages().get_Item(pageNum).accept(absorber);
            
            // 按Y坐标降序排序文本片段
            List<TextFragment> fragmentList = new ArrayList<>(absorber.getTextFragments());
            fragmentList.sort(Comparator.comparing(f -> f.getPosition().getYIndent()).reversed());
            
            float currentLineY = -1;
            StringBuilder currentLine = new StringBuilder();
            int lineNum = 1;
            
            for (TextFragment fragment : fragmentList) {
                float y = fragment.getPosition().getYIndent();
                if (currentLineY == -1 || Math.abs(y - currentLineY) > 2f) {
                    if (currentLineY != -1) {
                        lines.add("页" + pageNum + "行" + lineNum + ": " + currentLine);
                        lineNum++;
                        currentLine.setLength(0);
                    }
                    currentLineY = y;
                    currentLine.append(fragment.getText());
                } else {
                    currentLine.append(fragment.getText());
                }
            }
            // 添加最后一行
            if (currentLine.length() > 0) {
                lines.add("页" + pageNum + "行" + lineNum + ": " + currentLine);
            }
        }
        document.close();
        return lines;
    }

    public static void main(String[] args) {
        List<String> lines = extractLinesWithNumbers("input.pdf");
        lines.forEach(System.out::println);
    }
}

注意事项

  • Y坐标阈值(Y_TOLERANCE)需要根据PDF的字体大小和排版调整,避免误判行。
  • 部分PDF可能存在文本块乱序的情况,排序后再分组能提升准确性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 06:43:14