前言:
之前想学习使用Java操作pdf的时候看过了IText的文档。确实IText的文档很全,也有一个官网可以很方便的查找信息。但IText的开源协议为AGPL,使用者必须传染性的开源代码,商业使用必须付费获取商业许可。所以有一些风险。所以转而来学习使用PDFBOX。现在pdfbox的文档并不是很多,列出如下链接以做参考。
https://iowiki.com/pdfbox/pdfbox_quick_guide.html
如果需要源码进行研究,可以在https://www.apache.org/dyn/closer.lua/pdfbox/2.0.25/pdfbox-2.0.25-src.zip下载使用
有一些坑在网上的资料也比较少,也可以到https://issues.apache.org/jira/browse/PDFBOX-5103里查找相关的issue(英文),如果你找到了bug也可以往里提。
一.加载已有的pdf文件
1.加载全文/页的文本
注意:PDFBOX依赖commons-logging,fontbox包,使用请确保fontbox与pdfbox的版本相同,不然可能会有兼容BUG(亲测)
具体信息可以查看:https://mvnrepository.com/artifact/org.apache.pdfbox/pdfbox/2.0.25
先准备一个pdf文件(有内容的),如下:
通过加载PDF文件获取文本内容:
public class LoadPDF {
public static void main(String[] args) throws FileNotFoundException, IOException {
//通过文件和输入流都可以加载pdf文件
PDDocument doc = PDDocument.load(new FileInputStream("src/Target.pdf"));
PDFTextStripper text = new PDFTextStripper();
//获取全文件的所有文本
String FinalText = text.getText(doc);
System.out.println(FinalText);
//关闭
doc.close();
}
}
运行:
邮政银行卡类型查询指引
方法一:拨打中国邮政储蓄银行电话“95580”查询。
方法二:携带本人身份证及银行卡至邮政银行网点查询。
方法三:手机网银查询,步骤如下图:
打开手机APP<邮储银行>,点击”我的“,点击"银行卡",查看薪资卡类型。
这样便很容易通过正则表达式进行内容的提取了。比如常见的一些招聘模块,通常有上传pdf简历的功能,这样大致也可以实现,只是方法有些不同。
2.加载保存pdf中的图片信息
当然,我们也可以从PDF文件中读取图片信息。
//打开PDF文件
PDDocument doc = PDDocument.load(new FileInputStream("src/Target.pdf"));
//获取第一页的数据
PDPage pageOne = doc.getPage(0);
//获取页面resources
PDResources resources = pageOne.getResources();
//获取COS对象的名字集合(如果不了解什么是COS对象,请参考这个链接
//https://www.pdftron.com/documentation/cli/guides/pdf-cosedit/faq/#what-is-cos
Iterable<COSName> xObjectNames = resources.getXObjectNames();
//遍历COS对象
xObjectNames.forEach(item->{
try {
PDXObject xObject = resources.getXObject(item);
System.out.println(item.getName());
//如果为图片类型就调Java的图片处理接口,将其保存下来
if(xObject instanceof PDImageXObject) {
PDImageXObject imgobject = (PDImageXObject)xObject;
BufferedImage image = imgobject.getImage();
ImageIO.write(image, "png", new File("第"+Count+"张图片.png"));
System.out.println("ImageSaved");
Count++;
}
} catch (IOException e) {
System.out.println("图片保存出错");
e.printStackTrace();
}
});
运行:
二.创建普通的pdf文件
创建一个空的pdf非常的简单,只需要创建然后保存即可
PDDocument doc = new PDDocument();
doc.save(null);
如果要写入内容,需要使用PDFBOX提供的流对象进行写入。
写入英文
写入英文文本是不需要设置任何额外的东西,直接向流里面丢就可以了。
public static void main(String[] args) throws IOException {
//创建文件,设置页码
PDDocument doc = new PDDocument();
PDPage pageOne = new PDPage(PDRectangle.A4);
doc.addPage(pageOne);
//创建页面内容流
PDPageContentStream pageStream = new PDPageContentStream(doc, pageOne);
//设置要使用的字体
PDFont font = PDType1Font.COURIER_BOLD_OBLIQUE;
pageStream.setFont(font,18);
pageStream.beginText();
//直接写入内容即可
pageStream.showText("hello pdfbox");
pageStream.endText();
//记得关闭流对象要不然是无法成功保存pdf文档的
pageStream.close();
doc.save(new File("src\\hello.pdf"));
doc.close();
}
看一下效果
内容是写上去了,但感觉位置如果不指定的话是随机的,下一节应该学习一下如下指定文字及图片绘制的位置。
写入中文
中文的写入则更加复杂。
如果直接把上述代码的写入字符串变成中文,则会出现如下异常
pageStream.showText("想要写入一些中文,但是会报错");
异常的详细信息:
Exception in thread "main" java.lang.IllegalArgumentException: U+6BD4 ('.notdef') is not available in this font Courier-BoldOblique encoding: WinAnsiEncoding
at org.apache.pdfbox.pdmodel.font.PDType1Font.encode(PDType1Font.java:428)
at org.apache.pdfbox.pdmodel.font.PDFont.encode(PDFont.java:333)
at org.apache.pdfbox.pdmodel.PDAbstractContentStream.showTextInternal(PDAbstractContentStream.java:300)
at org.apache.pdfbox.pdmodel.PDAbstractContentStream.showText(PDAbstractContentStream.java:254)
at org.apache.pdfbox.pdmodel.PDPageContentStream.showText(PDPageContentStream.java:37)
at PDFBOX3.MainTest.main(MainTest.java:35)
这是因为在pdf的14种原生的字体中并不支持中文(在PDFType1Font中可以通过静态属性列出),
如果需要写入中文,可以通过嵌入中文字体文件(支持中文的.ttf文件)或者直接从本地导入。
所以请先自备中文字体。(或者可以直接去C盘里找,路径为C:/Windows/Fonts)
现在,先加载中文字体就好了。
private static final float MARGIN_LEFT = 0.8f * 72; // 0.8 inch
private static final float MARGIN_TOP = 0.4f * 72; // 0.4 inch
private static final float LOGO_WIDTH = 72;
private static final float LOGO_MARGIN = 18;
private static final float FONT_SIZE_TITLE = 24;
private static final float FONT_SIZE_SMALL = 10;
private static final float LINE_OFFSET_FACTOR = -1.8f;
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
//创建文档和页面
PDDocument document = new PDDocument();
PDPage page = new PDPage(PDRectangle.A4);
document.addPage(page);
@SuppressWarnings("resource")
PDPageContentStream contentStream = new PDPageContentStream(document, page);
PDRectangle boundingBox = page.getBBox();
contentStream.beginText();
//加载字体文件
contentStream.setFont(PDType0Font.load(document, MainTest.class.getResourceAsStream("SimHei.ttf")),
24);
contentStream.newLineAtOffset(MARGIN_LEFT + LOGO_WIDTH + LOGO_MARGIN,
boundingBox.getHeight() - MARGIN_TOP - FONT_SIZE_TITLE);
//写入中文
contentStream.showText("中国你好!");
contentStream.newLineAtOffset(0, LINE_OFFSET_FACTOR * FONT_SIZE_SMALL);
contentStream.showText("RiderKick");
contentStream.endText();
contentStream.close();
document.save(new File("Chinese.pdf"));
document.close();
}
写入图片
写入图片和写入文字一样,只需要将图片加载到PDImageXObject里,然后再从对象流中写入文档即可
以下图为例
public static void main(String[] args) throws IOException {
PDDocument doc = new PDDocument();
PDPage pageOne = new PDPage(PDRectangle.A4);
doc.addPage(pageOne);
PDPageContentStream pageStream = new PDPageContentStream(doc, pageOne);
PDFont font = PDType1Font.COURIER_BOLD_OBLIQUE;
PDImageXObject img = PDImageXObject.createFromFile("src/PDFBOX3/AutoFac.png", doc);
pageStream.setFont(font,18);
pageStream.beginText();
pageStream.showText("hi this is AutoFac");
pageStream.endText();
pageStream.drawImage(img, 50, 50);
pageStream.close();
doc.save(new File("src\\hello.pdf"));
doc.close();
}