提取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
相关产品推荐
相关产品推荐

