x-easypdf 是一个基于 pdfbox/fop 二次封装的框架,目前拥有两大模块:【pdfbox 模块】与【fop 模块】。【pdfbox 模块】主打 pdf 编辑功能,以组件化的形式进行 pdf 的构建;【fop 模块】主打 pdf 导出功能,采用数据源的方式对 xsl-fo 模板进行转换,同时提供 java 对象的方式构建 dom 模板文档,即使完全不懂 xsl-fo 的语法,也能轻松上手写出对应的模板。两个模块均可单独使用,也可以结合使用,帮助开发者快速生成 pdf 文档。
java 对象方式生成 pdf 文档的部分功能使用演示
@Test
public void document() {
// 定义输出路径
String outputPath = "E:\\\\pdf\\\\test\\\\fo\\\\template-demo.pdf";
// 定义书签
XEasyPdfTemplateBookmark bookmark = XEasyPdfTemplateHandler.Bookmark.build()
// 设置标题
.setTitle("目录")
// 设置内部地址(对应组件id)
.setInternalDestination("title");
// 创建标题
XEasyPdfTemplateText title = XEasyPdfTemplateHandler.Text.build()
// 设置id
.setId("title")
// 设置文本
.setText("贵阳市简介")
// 设置字体大小
.setFontSize("20pt")
// 设置水平居中
.setHorizontalStyle("center");
// 创建扩展文本
XEasyPdfTemplateTextExtend createText = XEasyPdfTemplateHandler.TextExtend.build()
// 设置字体大小
.setFontSize("12pt")
// 设置段前空白
.setSpaceBefore("12pt")
// 设置段前缩进
.setStartIndent("60pt")
// 设置文本间隔
.setTextSpacing("80pt")
// 设置文本
.addText(
XEasyPdfTemplateHandler.Text.build().setText("创建时间:2022-11-11 00:00:00"),
XEasyPdfTemplateHandler.Text.build().setText("创建人:x-easypdf")
);
// 创建二维码
XEasyPdfTemplateBarcode barcode = XEasyPdfTemplateHandler.Barcode.build()
// 设置条形码类型
.setType("qr_code")
// 设置条形码内容
.setContent("https://baike.baidu.com/item/贵阳/438289")
// 设置条形码说明文字
.setWords("扫一扫")
// 设置条形码图像宽度
.setWidth("60pt")
// 设置条形码图像高度
.setHeight("60pt")
// 设置水平居右
.setHorizontalStyle("right")
// 设置上移62pt
.setMarginTop("-62pt");
// 创建文本
XEasyPdfTemplateText text = XEasyPdfTemplateHandler.Text.build()
// 设置id
.setId("text")
// 设置文本
.setText(
"贵阳,简称“筑”,别称林城、筑城,贵州省辖地级市、省会、Ⅰ型大城市,国务院批复确定的中国西南地区重要的中心城市之一、" +
"重要的区域创新中心和全国重要的生态休闲度假旅游城市。" +
"贵阳地处黔中山原丘陵中部,东南与黔南布依族苗族自治州的瓮安、龙里、惠水、长顺4县接壤," +
"西靠安顺市的平坝区和毕节市的织金县,北邻毕节市的黔西市、金沙县和遵义市的播州区," +
"截至2020年,全市下辖6个区、3个县,代管1个县级市。截至2021年末,贵阳市常住人口610.23万人。"
)
// 设置段前空白
.setSpaceBefore("12pt")
// 设置文本缩进
.setTextIndent("24pt");
// 创建文本
XEasyPdfTemplateText remark = XEasyPdfTemplateHandler.Text.build()
// 设置文本
.setText("-- 摘自百度百科")
// 设置字体大小
.setFontSize("12pt")
// 设置水平居右
.setHorizontalStyle("right");
// 创建子书签
XEasyPdfTemplateBookmark child = XEasyPdfTemplateHandler.Bookmark.build()
// 设置标题
.setTitle("贵阳市行政区划")
// 设置内部地址(对应组件id)
.setInternalDestination("tableText");
// 添加子书签
bookmark.addChild(child);
// 创建文本
XEasyPdfTemplateText tableText = XEasyPdfTemplateHandler.Text.build()
// 设置id
.setId("tableText")
// 设置文本
.setText("贵阳市行政区划")
// 设置字体大小
.setFontSize("20pt")
// 设置当前位置分页
.setBreakBefore("page")
// 设置段前空白
.setSpaceBefore("20pt")
// 设置水平居右
.setHorizontalStyle("center");
// 创建表格
XEasyPdfTemplateTable table = XEasyPdfTemplateHandler.Table.build().setHeader(
// 设置表头
XEasyPdfTemplateHandler.Table.Header.build().addRow(
this.createRow("#7C7D7D", "center", "名称", "区划代码", "人口(万人)", "面积(平方千米)")
)
).setBody(
// 设置表格主体
XEasyPdfTemplateHandler.Table.Body.build().addRow(
this.createRow(null, "left", "贵阳市", "520100", "599", "8035"),
this.createRow(null, "left", "南明区", "520102", "105", "271"),
this.createRow(null, "left", "云岩区", "520103", "106", "94"),
this.createRow(null, "left", "花溪区", "520111", "97", "964"),
this.createRow(null, "left", "乌当区", "520112", "34", "686"),
this.createRow(null, "left", "白云区", "520113", "46", "270"),
this.createRow(null, "left", "观山湖区", "520115", "64", "309"),
this.createRow(null, "right", "清镇市", "520181", "63", "1302"),
this.createRow(null, "right", "开阳县", "520121", "34", "2026"),
this.createRow(null, "right", "息烽县", "520122", "22", "1037"),
this.createRow(null, "right", "修文县", "520123", "29", "1076")
)
).setMinRowHeight("30pt").setVerticalStyle("center");
// 创建页面
XEasyPdfTemplatePage page = XEasyPdfTemplateHandler.Page.build()
// 设置页面id
.setId("pageId")
// 设置字体
.setFontFamily("微软雅黑")
// 设置字体大小
.setFontSize("15pt")
// 设置边距(上下左右)
.setMargin("20pt")
// 设置主体内容
.addBodyComponent(title, createText, barcode, text, remark, tableText, table)
// 设置页脚高度
.setFooterHeight("20pt")
// 设置页脚内容
.addFooterComponent(
// 创建块容器并添加内容
XEasyPdfTemplateHandler.BlockContainer.build().addComponent(
// 创建文本
XEasyPdfTemplateHandler.Text.build().setText("第 "),
// 创建当前页码
XEasyPdfTemplateHandler.CurrentPageNumber.build(),
XEasyPdfTemplateHandler.Text.build().setText(" 页,共 "),
// 创建总页码并设置页面id
XEasyPdfTemplateHandler.TotalPageNumber.build().setPageId("pageId"),
XEasyPdfTemplateHandler.Text.build().setText(" 页")
).setHorizontalStyle("center")
)
// 设置主体水印
.setBodyWatermark(
// 创建文字水印
XEasyPdfTemplateHandler.Watermark.build()
// 设置水印图片目录
.setTempDir("E:\\\\pdf\\\\test\\\\fo")
// 设置水印id
.setId("watermark")
// 设置水印内容
.setText("贵阳", "x-easypdf")
// 设置水印图像宽度
.setWidth("600pt")
// 设置水印图像高度
.setHeight("300pt")
// 设置水印显示宽度
.setShowWidth("200pt")
// 设置文字大小
.setFontSize("100pt")
// 设置文字颜色
.setFontColor("gray")
// 设置文字透明度
.setFontAlpha("100")
// 设置逆时针旋转
.setRadians("-45")
// 开启文件覆盖
.enableOverwrite()
);
// 添加页面、书签并转换
XEasyPdfTemplateHandler.Document.build().addPage(page).addBookmark(bookmark).transform(outputPath);
}
private XEasyPdfTemplateTableRow createRow(String backgroundColor, String style, String... texts) {
XEasyPdfTemplateTableRow row = XEasyPdfTemplateHandler.Table.Row.build();
for (int i = 0; i < texts.length; i++) {
row.addCell(
XEasyPdfTemplateHandler.Table.Cell.build().addComponent(
XEasyPdfTemplateHandler.Text.build().setText(texts[i])
).setBorderStyle("solid").setBackgroundColor(backgroundColor).setHorizontalStyle(style)
);
}
return row;
}
生成的对应模板(格式化后):
<?xml version="1.0" encoding="UTF-8"?>
<!--根标签-->
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:fox="http://xmlgraphics.apache.org/fop/extensions">
<!--页面模板-->
<fo:layout-master-set>
<fo:simple-page-master margin-bottom="20pt" margin-left="20pt" margin-right="20pt" margin-top="20pt" master-name="page1" page-height="29.7cm" page-width="21cm">
<fo:region-body background-image="url('/E:/pdf/test/fo/watermark.png')" fox:background-image-width="200pt"/>
<fo:region-before/>
<fo:region-after extent="20pt"/>
<fo:region-start/>
<fo:region-end/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:bookmark-tree>
<fo:bookmark internal-destination="title">
<fo:bookmark-title>目录</fo:bookmark-title>
<fo:bookmark internal-destination="tableText">
<fo:bookmark-title>贵阳市行政区划</fo:bookmark-title>
</fo:bookmark>
</fo:bookmark>
</fo:bookmark-tree>
<fo:page-sequence font-family="微软雅黑" font-size="15pt" id="pageId" master-reference="page1">
<fo:static-content flow-name="xsl-region-after">
<fo:block-container>
<fo:block text-align="center">
<fo:inline>第 </fo:inline>
<fo:page-number/>
<fo:inline>页,共 </fo:inline>
<fo:page-number-citation-last ref-id="pageId"/>
<fo:inline>页</fo:inline>
</fo:block>
</fo:block-container>
</fo:static-content>
<fo:flow flow-name="xsl-region-body">
<fo:block id="title" text-align="center">
<fo:inline font-size="20pt">贵阳市简介</fo:inline>
</fo:block>
<fo:block space-before="12pt" start-indent="60pt">
<fo:inline font-size="12pt">创建时间:2022-11-11 00:00:00</fo:inline>
<fo:leader leader-length="80pt"/>
<fo:inline font-size="12pt">创建人:x-easypdf</fo:inline>
</fo:block>
<fo:block margin-top="-62pt" text-align="right">
<fo:instream-foreign-object>
<fo:barcode content="https://baike.baidu.com/item/贵阳/438289" height="60pt" type="qr_code" width="60pt" words="扫一扫" xmlns:fo="http://www.x-easypdf.cn/ns"/>
</fo:instream-foreign-object>
</fo:block>
<fo:block id="text" space-before="12pt" text-indent="24pt">
<fo:inline text-indent="24pt">贵阳,简称“筑”,别称林城、筑城,贵州省辖地级市、省会、Ⅰ型大城市,国务院批复确定的中国西南地区重要的中心城市之一、重要的区域创新中心和全国重要的生态休闲度假旅游城市。贵阳地处黔中山原丘陵中部,东南与黔南布依族苗族自治州的瓮安、龙里、惠水、长顺4县接壤,西靠安顺市的平坝区和毕节市的织金县,北邻毕节市的黔西市、金沙县和遵义市的播州区,截至2020年,全市下辖6个区、3个县,代管1个县级市。截至2021年末,贵阳市常住人口610.23万人。</fo:inline>
</fo:block>
<fo:block text-align="right">
<fo:inline font-size="12pt">-- 摘自百度百科</fo:inline>
</fo:block>
<fo:block break-before="page" id="tableText" space-before="20pt" text-align="center">
<fo:inline font-size="20pt">贵阳市行政区划</fo:inline>
</fo:block>
<fo:block>
<fo:table display-align="center">
<fo:table-header>
<fo:table-row>
<fo:table-cell background-color="#7c7d7d" border-style="solid" height="30pt" text-align="center">
<fo:block>
<fo:inline>名称</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell background-color="#7c7d7d" border-style="solid" height="30pt" text-align="center">
<fo:block>
<fo:inline>区划代码</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell background-color="#7c7d7d" border-style="solid" height="30pt" text-align="center">
<fo:block>
<fo:inline>人口(万人)</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell background-color="#7c7d7d" border-style="solid" height="30pt" text-align="center">
<fo:block>
<fo:inline>面积(平方千米)</fo:inline>
</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-header>
<fo:table-body>
<fo:table-row>
<fo:table-cell border-style="solid" height="30pt" text-align="left">
<fo:block>
<fo:inline>贵阳市</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="left">
<fo:block>
<fo:inline>520100</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="left">
<fo:block>
<fo:inline>599</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="left">
<fo:block>
<fo:inline>8035</fo:inline>
</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell border-style="solid" height="30pt" text-align="left">
<fo:block>
<fo:inline>南明区</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="left">
<fo:block>
<fo:inline>520102</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="left">
<fo:block>
<fo:inline>105</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="left">
<fo:block>
<fo:inline>271</fo:inline>
</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell border-style="solid" height="30pt" text-align="left">
<fo:block>
<fo:inline>云岩区</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="left">
<fo:block>
<fo:inline>520103</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="left">
<fo:block>
<fo:inline>106</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="left">
<fo:block>
<fo:inline>94</fo:inline>
</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell border-style="solid" height="30pt" text-align="left">
<fo:block>
<fo:inline>花溪区</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="left">
<fo:block>
<fo:inline>520111</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="left">
<fo:block>
<fo:inline>97</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="left">
<fo:block>
<fo:inline>964</fo:inline>
</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell border-style="solid" height="30pt" text-align="left">
<fo:block>
<fo:inline>乌当区</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="left">
<fo:block>
<fo:inline>520112</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="left">
<fo:block>
<fo:inline>34</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="left">
<fo:block>
<fo:inline>686</fo:inline>
</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell border-style="solid" height="30pt" text-align="left">
<fo:block>
<fo:inline>白云区</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="left">
<fo:block>
<fo:inline>520113</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="left">
<fo:block>
<fo:inline>46</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="left">
<fo:block>
<fo:inline>270</fo:inline>
</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell border-style="solid" height="30pt" text-align="left">
<fo:block>
<fo:inline>观山湖区</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="left">
<fo:block>
<fo:inline>520115</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="left">
<fo:block>
<fo:inline>64</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="left">
<fo:block>
<fo:inline>309</fo:inline>
</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell border-style="solid" height="30pt" text-align="right">
<fo:block>
<fo:inline>清镇市</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="right">
<fo:block>
<fo:inline>520181</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="right">
<fo:block>
<fo:inline>63</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="right">
<fo:block>
<fo:inline>1302</fo:inline>
</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell border-style="solid" height="30pt" text-align="right">
<fo:block>
<fo:inline>开阳县</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="right">
<fo:block>
<fo:inline>520121</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="right">
<fo:block>
<fo:inline>34</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="right">
<fo:block>
<fo:inline>2026</fo:inline>
</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell border-style="solid" height="30pt" text-align="right">
<fo:block>
<fo:inline>息烽县</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="right">
<fo:block>
<fo:inline>520122</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="right">
<fo:block>
<fo:inline>22</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="right">
<fo:block>
<fo:inline>1037</fo:inline>
</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell border-style="solid" height="30pt" text-align="right">
<fo:block>
<fo:inline>修文县</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="right">
<fo:block>
<fo:inline>520123</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="right">
<fo:block>
<fo:inline>29</fo:inline>
</fo:block>
</fo:table-cell>
<fo:table-cell border-style="solid" height="30pt" text-align="right">
<fo:block>
<fo:inline>1076</fo:inline>
</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
</fo:block>
</fo:flow>
</fo:page-sequence>
</fo:root>
PDF效果
PDFBOX详解
摘要
自从Adobe公司1993年第一次发布公共PDF参考以来,支持各种语言和平台的PDF工具和类库就如雨后春笋般涌现。然而,Java应用开发中Adobe技术的支持相对滞后了。
自从Adobe公司1993年第一次发布公共PDF参考以来,支持各种语言和平台的PDF工具和类库就如雨后春笋般涌现。然而,Java应用开发中Adobe技术的支持相对滞后了。这是个奇怪的现象,因为PDF文档是企业信息系统存储和交换信息的大势所趋,而Java技术特别适合这种应用。然而,Java开发人员似乎直到最近才获得成熟可用的PDF支持。
PDFBox(一个BSD许可下的源码开放项目)是一个为开发人员读取和创建PDF文档而准备的纯Java类库。它提供如下特性:
- 提取文本,包括Unicode字符。
- 和Jakarta Lucene等文本搜索引擎的整合过程十分简单。
- 加密/解密PDF文档。
- 从PDF和XFDF格式中导入或导出表单数据。
- 向已有PDF文档中追加内容。
- 将一个PDF文档切分为多个文档。
- 覆盖PDF文档。
PDFBox API
PDFBox设计时采用面向对象的方式来描述PDF文档。PDF文档的数据是一系列基本对象的集合:数组,布尔型,字典,数字,字符串和二进制流。PDFBox在org.pdfbox.cos包(COS模型)中定义这些基本对象类型。你可以使用这些对象与PDF文档进行任何交互,但你应该先对PDF文档内部结构以及高层概念作一些深入的了解。例如,页面和字体都是带有特殊属性的字典对象;PDF参考手册提供这些特殊属性的含义和类型的说明,但这是一个枯燥的文档查阅过程。
于是,org.pdfbox.pdfmodel包(PD模型)应运而生,它的基础是COS模型,但提供了以一种熟悉的方式访问PDF文档对象的高层API(如图1)。对底层COS模型进行了封装的PDPage和PDFont等类就在这个包中。
注意,虽然PD模型提供了一些优秀的功能,但它依然是一个开发中的模型。在有些实例中,你可能需要借助于COS模型才能访问PDF的特定功能性。所有的PD模型对象都提供返回相应的COS模型对象的方法。所以,在一般情况下,你都会使用PD模型,但PD模型鞭长莫及时你可以直接操作底层的COS模型。
上文对PDFBox作了大体上的介绍,现在是举一些例子的时候了。我们从如何读已存在的PDF文档开始:
PDDocument document = PDDocument.load( "./test.pdf" );
上面的语句解析指定的PDF文件并在内存中创建其文档对象。考虑到处理大文档时的效率问题,PDFBox只在内存中存储文档结构,图像、内嵌字体和页面内容等对象将被缓存在一个临时文件中。
注意:PDDocument对象使用完毕时需要调用其close()方法来释放创建时使用的资源。
文本提取和Lucene整合
这是一个信息展现时代(an information retrieval age),不管信息存放在哪种媒体中,应用程序都应该支持检索和索引。对信息进行组织和分类从而形成可检索的格式是很关键的。这对于文本文档和HTML文档来说是很简单的,但PDF文档包含大量的结构和元信息,提取文档内容决不是一件简单的事情。PDF语言和Postscript相似,二者中的对象都是作为矢量绘制在页面的某些位置。例如:
- /Helv 12 Tf
- 0 13.0847 Td
- (Hello World) Tj
上面的指令将字体设为12号的Helvetica,移到下一行然后打印“Hello World”。这些命令流通常是经过压缩的,文字在屏幕上的显示顺序并不一定是文件中的字符出现顺序。因此,你有时无法直接从原始PDF文档中提取字符串。然而,PDFBox成熟的文本提取算法使得开发人员可以提取文档内容,就像在阅读器中呈现的那样。
Lucene是Apache Jakarta项目的子项目,它是一个流行的源代码开放的搜索引擎库。开发人员可以使用Lucene来创建索引,并基于该索引对大量的文本内容进行复杂的检索。Lucene只支持文本内容的检索,所以开发人员需要将其他形式的数据转换为文本形式才能使用Lucene。例如,Microsoft Word和StarOffice文档都必须先转换为文本形式才能添加到Lucene索引中。
PDF文件也不例外,但PDFBox提供一个特殊的整合对象,这让在Lucene索引中包含PDF文档变得非常容易。将一个基本PDF文档转换为Lucene文档只需要一条语句:
Document doc = LucenePDFDocument.getDocument( file );
这条语句解析指定的PDF文档,提取其内容并创建一个Lucene文档对象。然后你就可以将该对象添加到Lucene索引中了。如上文所述,PDF文档中也包含作者信息和关键词等元数据,在索引PDF文档时对这些元数据进行跟踪时很重要的。表1列出了创建Lucene文档时PDFBox将填写(populate)的字段。
这种整合使得开发人员可以轻松地使用Lucene来支持PDF文档的检索和索引。当然,有些应用程序要求更成熟的文本提取方法。此时可以直接使用PDFTextStripper类,或继承该类来满足这种复杂的需求。
通过继承PDFTextStripper并覆盖showCharacter()方法,你可以从许多方面对文本提取进行控制。例如,使用x、y位置信息进行限制以提取特定文本块。你可以有效地忽略所有的y坐标大于某个值的文本,这样文档头部内容就会被排除。
另一个例子。常常有这种情况:从表单创建了一组PDF文档,但这些原始数据被丢失了。也就是说,这些文档都包含一些你感兴趣的文本,而且这些文本都在相似的位置上,但填充文档的表单数据丢失了。例如,你有一些信封,在相同的位置上都有名字和地址信息。这时,你就可以使用PDFTextStripper的派生类来提取期望的字段,这个类就像一种截取屏幕区域的设备。
加密/解密
PDF的一个流行特性是允许对文档内容进行加密、对访问进行控制,限制只能阅读未加密文档。PDF文档加密时采用一个主密码和一个可选的用户密码。如果设定了用户密码,那么PDF阅读器(如Acrobat)将在显示文档之前提示输入密码。而主密码则用于授权修改文档内容。
PDF规范允许PDF文档的创建者对用户使用Acrobat阅读器查看文档时的某些操作进行限制。这些限制包括:
- 打印
- 修改内容
- 提取内容
PDF文档安全的讨论不在本文范畴之内,有兴趣的读者可以参考PDF规范的相关部分。PDF文档的安全模型是可插拔式的(pluggable),你可以在加密文档时使用不同的安全处理器(security handler)。对本文而言,PDFBox支持标准的安全处理器,它是大多数PDF文档所使用的。
加密文档时必须先指定一个安全处理器,然后使用一个主密码和用户密码进行加密。在下面的代码中,文档被加密,用户不需要敲入就可以在Acrobat中打开它(没有设置用户密码),但是该文档不可被打印。
//load the document
PDDocument pdf =
PDDocument.load( "test.pdf");
//create the encryption options
PDStandardEncryption encryptionOptions =
new PDStandardEncryption();
encryptionOptions.setCanPrint( false);
pdf.setEncryptionDictionary(
encryptionOptions );
//encrypt the document
pdf.encrypt( "master", null);
//save the encrypted document
//to the file system
pdf.save( "test-output.pdf");
更详细的示例参见PDFBox发布版中包含的加密工具类源代码:org.pdfbox.Encrypt。
许多应用程序可以生成PDF文档,但不支持控制文档的安全选项。这时PDFBox就可以用来在发送给用户之前截获并加密PDF文档
注意
PDF规范共有1172页之多,其实现的确是一浩大工程。同样,PDFBox发布版中说它“正在进行中”,新的功能会慢慢地添加上去。它的主要弱点是从零开始创建PDF文档。然而,有一些源码开放的Java项目可用于填补这个缺口。例如,Apache FOP项目支持从特殊的XML文档生成PDF,这个XML文档描述了要生成的PDF文档。此外,iText提供一个高层API用于创建表格和列表。
PDFBox的下一个版本将支持新的PDF 1.5 对象流和交叉引用流。然后将提供内嵌字体和图像的支持。在PDFBox的努力下,Java应用程序中的PDF技术有望得到充分的支持。
参考资源
PDFBox: www.pdfbox.org
iText: www.lowagie.com/iText/
PDF Reference: http://partners.adobe.com/asn/tech/pdf/specifications.jsp
Jakarta Lucene: http://jakarta.spache.org/lucene/