PDFBox简介

官网 : https://pdfbox.apache.org/

PDFBox是一个开源的Java库,用于在Java平台上处理PDF文件。它提供了一系列功能,使开发人员能够读取、创建、修改和提取PDF文件的内容。

PDFBox是Apache软件基金会的一个顶级项目,拥有广泛的用户群体和活跃的开发社区。

PDFBox的主要功能包括:

  1. 读取PDF文件:PDFBox可以将PDF文件解析为文本或其他格式,以便进一步处理和分析PDF内容。
  2. 创建和修改PDF文件:PDFBox可以生成全新的PDF文件,也可以对现有的PDF文件进行修改,添加文本、图片、链接、表单等内容。
  3. 提取PDF内容:PDFBox可以从PDF文件中提取文本、图像、链接、元数据等信息,使得开发人员能够轻松地获取和利用这些信息。
  4. 操作PDF页面:PDFBox允许你对PDF页面进行裁剪、旋转、缩放和合并等操作。
  5. 处理表单:PDFBox可以填充和提取PDF表单中的数据,使得表单处理变得更加简单。
  6. 支持加密和签名:PDFBox支持对PDF文件进行加密和数字签名,确保PDF文件的安全性和完整性。

PDFBox是一个功能强大且稳定的库,适用于各种Java应用程序,特别是需要处理PDF文件的场景,如文档处理、报表生成、表单处理、PDF文档解析等。由于它是一个开源项目,因此开发人员可以免费使用、修改和分发PDFBox,并在需要时从活跃的开发社区中获取支持和帮助。

基础使用

依赖

首先下载pdfbox并导入到Java工程中,下载地址 : https://pdfbox.apache.org/download.html

如果使用了MAVEN,可以直接在pom.xml文件中导入依赖 :

1
2
3
4
5
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.24</version> <!-- 使用最新版本或根据需要指定版本 -->
</dependency>

核心服务

新建一个PDFService,并填充以下代码。

各行功能已在代码中进行注释。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Service
public class PDFService {
/**
* 直接提取pdf中的文本,形成一整段文字
* @param pdfFile
* @return
*/
public String extractTextFromPDF(MultipartFile pdfFile) {
try (InputStream inputStream = pdfFile.getInputStream()) {
//加载pdf
PDDocument document = PDDocument.load(inputStream);
//PDFTextStripper对象 提取文档的文本内容
PDFTextStripper textStripper = new PDFTextStripper();
//得到pdf文本
String pdfText = textStripper.getText(document);
//确保PDDocument对象在处理完成后自动关闭,避免资源泄露。
document.close();
return pdfText;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

控制器

本项目使用了SwaggerUI,书写一个importPDF的Controller来接收前端PDF文件的导入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Controller
@Api(tags = "标准录入控制器")
@RequestMapping("/api/inputStandard")
@CrossOrigin("http://localhost:9528")
public class InputStandardControl {

@Autowired
private PDFService pdfService;

@PostMapping("/importPDF")
@ResponseBody
@ApiOperation(value = "方法说明:导入PDF",notes = "导入PDF并解析")
//CommonResult类请自己封装。
public CommonResult importPDF(@RequestPart("file") MultipartFile file){
return CommonResult.success(pdfService.extractTextFromPDF(file));
}
}

测试

本项目使用了SwaggerUI,启动项目后在SwaggerUI中可以进行测试 :

对于如下pdf

输出结果 :

拓展PDFBox

在上述用法中,可以提取PDF中的文字信息,但未免太过简单,不能获取其他额外的有效信息。

本节主要实现对PDFTextStripper类的拓展,以完成按行提取文字,以及获得该行的字号、行号、语言等信息。

核心类

自定义CustomPDFTextStripper类,实现对 PDFTextStripper的拓展。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package com.dpf.source.visualTool.util.pdf;

import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.text.PDFTextStripper;
import org.apache.pdfbox.text.TextPosition;

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

/**
* 根据PDFTextStripper类拓展而来
* PDFTextStripper类只能返回每一行的文本信息
* CustomPDFTextStripper类的特点是可以返回行号、字号信息
*/
public class CustomPDFTextStripper extends PDFTextStripper {
public CustomPDFTextStripper() throws IOException {
}
//暂时存储当前行的文本内容。
private StringBuilder currentLine = new StringBuilder();
//字体大小
private float currentFontSize = -1;
//字体,似乎无法识别
private PDFont currentFont ;
//语言信息
private String currentLanguage = "unknown";
//用于保存行号的字段
private int lineNumber = 1;
//用于保存页码信息的字段
private int pageNumber = 0;
//保存每一行的文本内容和字体大小等信息。
private List<HashMap<String, Object>> linesInfo = new ArrayList<>();

public List<HashMap<String, Object>> getLinesInfo() {
return linesInfo;
}

/**
* 重写writeString方法,
* 此方法是在解析PDF文本时的一个回调方法,每次在解析到文本内容时都会被调用。
* 在这个方法中
* 1.我们将解析到的文本内容追加到currentLine中
* 2.通过textPositions参数获取当前文本行的第一个字符的字体大小,并将其赋值给currentFontSize。
* @param text
* @param textPositions
* @throws IOException
*/
@Override
protected void writeString(String text, List<TextPosition> textPositions) throws IOException {
if (!textPositions.isEmpty()) {
// 获取当前文本行的字号与字体
currentFontSize = textPositions.get(0).getFontSize();
// currentFont = textPositions.get(0).getFont();
// 启发式判断文本语言,这里简单示意根据文本中的中文字符数判断,只要有一个中文就判断这行为中文
int chineseCharacterCount = countChineseCharacters(text);
if (chineseCharacterCount > 0) {
currentLanguage = "Chinese";
} else {
currentLanguage = "English";
}
}

currentLine.append(text);
super.writeString(text, textPositions);
}

/**
* 重写writeLineSeparator方法
* 此方法在解析到一行文本结束时被调用。
* 在这个方法中
* 1.将当前行的文本内容和字体大小信息保存在一个HashMap对象lineInfo中
* 2.将lineInfo添加到linesInfo列表中。
* 3.我们将currentLine对象重置为空,以准备解析下一行文本。
* @throws IOException
*/
@Override
protected void writeLineSeparator() throws IOException {
// 保存当前行的文本和字号信息到HashMap
HashMap<String, Object> lineInfo = new HashMap<>();
lineInfo.put("text", currentLine.toString());
lineInfo.put("fontSize", currentFontSize);
// lineInfo.put("font", currentFont);
lineInfo.put("currentLanguage", currentLanguage);
lineInfo.put("lineNumber", lineNumber);
lineInfo.put("pageNumber", pageNumber);
linesInfo.add(lineInfo);
currentLine.setLength(0); // 重置StringBuilder
lineNumber++; // 增加行号
super.writeLineSeparator();
}

/**
* 重写writePageStart
* 此方法是是PDFTextStripper类的一个回调方法,它在开始解析新的一页时被调用。
* 我们可以通过覆写这个方法实现:
* 1.行号信息:在每一页开始时将行号计数器重置为0。
* 2.页码信息:在每一页开始时将页码计数器++。
* @throws IOException
*/
@Override
protected void writePageStart() throws IOException {
// 开始解析新一页时,将行号计数器重置为1,并增加页数
lineNumber = 1;
pageNumber ++;
super.writePageStart();
}

/**
* 启发式计算中文字符数
* @param text
* @return
*/
private int countChineseCharacters(String text) {
int count = 0;
for (char c : text.toCharArray()) {
if (isChineseCharacter(c)) {
count++;
}
}
return count;
}

/** 判断字符是否为中文字符(可能不准确)
* 使用 Unicode 编码范围来判断。
* 中文字符的 Unicode 编码范围通常是 \u4e00 到 \u9fff
* 但要注意这个范围并不完全覆盖所有的中文字符。
* @param c
* @return
*/
private boolean isChineseCharacter(char c) {
return (c >= '\u4e00' && c <= '\u9fff');
}
}

核心服务

PDFService服务中,根据上述的类定义,引入相应功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* 本类只提供pdf文字读取功能
*/
@Service
public class PDFService {

/**
* 这里可以提取pdf每一行的文字、字号、行号信息
* 使用自定义类CustomPDFTextStripper实现(extends PDFTextStripper)
* @param pdfFile
* @return
*/
public List<HashMap<String,Object>> extractInfoFromPDFWithLine(MultipartFile pdfFile){
List<String> lines = new ArrayList<>();
List<HashMap<String,Object>> PDFLines = new ArrayList<>();

try (InputStream inputStream = pdfFile.getInputStream()) {

//加载pdf文档与pdf文档提取器
PDDocument document = PDDocument.load(inputStream);
//使用CustomPDFTextStripper类
CustomPDFTextStripper textStripper = new CustomPDFTextStripper();

// 设置只提取页码的范围
int startPage = 1;
int endPage = Math.min(document.getNumberOfPages(), 5);
textStripper.setStartPage(startPage);
textStripper.setEndPage(endPage);

textStripper.getText(document);
document.close();

return textStripper.getLinesInfo();
} catch (IOException e) {
throw new RuntimeException(e);
}

}

}

控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Controller
@Api(tags = "标准录入控制器")
@RequestMapping("/api/inputStandard")
@CrossOrigin("http://localhost:9528")
public class InputStandardControl {

@Autowired
private PDFService pdfService;

@PostMapping("/importPDF")
@ResponseBody
@ApiOperation(value = "方法说明:导入PDF",notes = "导入PDF并解析")
//CommonResult类请自己封装。
public CommonResult importPDF(@RequestPart("file") MultipartFile file){
return CommonResult.success(pdfService.extractInfoFromPDFWithLine(file));
}
}

测试

还是前面那个PDF文件从,测试结果如图 :