PDFBox简介

Posted by lili on March 12, 2019

本文介绍PDFBox的简单用法。PDFBox是apache旗下的用于parse pdf文件的开源库,我们可以用它来提取pdf中的文字和图片,也可以用它来生成pdf文件(比如我们想自动做报表)。

目录

最近想把北师大版本的《汪曾祺全集》用OCR转成文字,这样方便搜索。上网能搜到PDF文件,这些PDF其实是扫描的图片,我们需要使用OCR把图片变成文字。第一步当然是需要解析PDF然后把每一页都变成图片。十多年前使用过PDFBox,这次搜索了一下”java pdf parser”,这个apache的项目依然还在。

PDF格式非常复杂,不过我的需求比较简单,只需要把每一页变成图片就行了。但是这里也会介绍更多的用法,比如我们需要parse一篇论文(当年我的毕设啊),提取标题、作者、摘要和引文等等信息,那么我们就需要知道每一行文字(甚至每个字符)的位置、大小等信息。比如标题的字体很大,而且通常出现在第一页的上方。

依赖

我们只需要在maven的依赖里加入:

<dependency>
	<groupId>org.apache.pdfbox</groupId>
	<artifactId>pdfbox</artifactId>
	<version>2.0.13</version>
</dependency>

另外可能还会用到一些工具,比如ImageIOUtil,那么还需要加入下面的依赖:

<dependency>
	<groupId>org.apache.pdfbox</groupId>
	<artifactId>pdfbox-tools</artifactId>
	<version>2.0.13</version>
</dependency>

把PDF转换成图片

完整代码在这里

public class ConvertToImages {

	public static void main(String[] args) throws InvalidPasswordException, IOException {
		System.setProperty("sun.java2d.cmm", "sun.java2d.cmm.kcms.KcmsServiceProvider"); 

		String path="/home/lili/下载/books/汪曾祺全集1.pdf";
		String dir="/home/lili/data/wang/book1";
		new File(dir).mkdirs();
		PDDocument document = PDDocument.load(new File(path));
		PDFRenderer pdfRenderer = new PDFRenderer(document);
		for (int page = 0; page < document.getNumberOfPages(); ++page) {
			BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 300, ImageType.RGB);
			ImageIOUtil.writeImage(bim, dir + "/" + (page + 1) + ".png", 300);
		}
		document.close();
	}

}

代码非常简单,注意第一行是因为JDK的问题,参考这里

首先用PDDocument.load来加载PDDocument,然后用这个PDDocument对象构造一个PDFRenderer对象。然后用pdfRenderer.renderImageWithDPI来把每一页变成图片,注意第二个参数是dpi,这里是300。对于很多ocr软件来说,至少需要300以上的dpi才能达到比较好的效果。

非常简单!目的已经达到。不过下面的某些功能读者可能也会感兴趣,我们继续学习。

抽取PDF的文本

完整代码在这里

public class ExtractTextExample {
	public static void main(String[] args) throws InvalidPasswordException, IOException {
	    try (PDDocument document = PDDocument.load(new File("/home/lili/data/test.pdf"))) {
	        if (!document.isEncrypted()) {
	            PDFTextStripper tStripper = new PDFTextStripper();
	            // 如果想抽取某一页或者某几页,可以使用下面的方法限定范围。
	            // 目前是抽取所有页
	            tStripper.setStartPage(0);
	            tStripper.setEndPage(document.getNumberOfPages());
	            String pdfFileInText = tStripper.getText(document);
	            String lines[] = pdfFileInText.split("\\r?\\n"); 
	            for (String line : lines) {
	                System.out.println(line); 
	            } 
	        }
	    }
	}
}

这里注意用到PDFTextStripper,调用getText(document)方法就可以得到所有的文字,然后用回车和/或换行split就得到每一行的文字。如果想只抽取一个pdf的某些页,那么可以使用setStartPage()和setEndPage()限定范围。

获取每行(个)文字的位置和大小信息

完整代码在这里

public class PrintTextLocations extends PDFTextStripper {

  public PrintTextLocations() throws IOException {
  }

  public static void main(String[] args) throws IOException {
    PDDocument document = null;
    try {
      document = PDDocument.load(new File("/home/lili/data/test.pdf"));

      PDFTextStripper stripper = new PrintTextLocations();
      stripper.setSortByPosition(true);
      stripper.setStartPage(0);
      stripper.setEndPage(document.getNumberOfPages());

      Writer dummy = new OutputStreamWriter(new ByteArrayOutputStream());
      stripper.writeText(document, dummy);
    } finally {
      if (document != null) {
        document.close();
      }
    }
  }

  @Override
  protected void writeString(String string, List<TextPosition> textPositions) throws IOException {
    for (TextPosition text : textPositions) {
      System.out.println("String[" + text.getXDirAdj() + "," + text.getYDirAdj() 
          + " fs=" + text.getFontSize()
          + " xscale=" + text.getXScale()
          + " height=" + text.getHeightDir()
          + " space="  + text.getWidthOfSpace()
          + " width=" + text.getWidthDirAdj() + "]" 
          + text.getUnicode());
    }
  }

}

我们需要继承PDFTextStripper然后重载PDFTextStripper方法,当调用PDFTextStripper.writeText()的时候每parse一行文字就会回调writeString方法,第一个参数string就是这一行文字,而第二个参数List<TextPosition> textPositions是这一行每一个字符的位置信息。

更多示例

更多示例可以参考1.8的cookbook,虽然是1.8的,但是大部分可以在2.x上运行。

另外在pdfbox的源代码目录树下有一个示例项目