自 1993 年 Adobe 首次发布公共 PDF 参考以来,支持各种语言和平台的 PDF 工具和库如雨后春笋般涌现。但Adobe技术在Java应用开发方面的支持相对滞后。
自 1993 年 Adobe 首次发布公共 PDF 参考以来,支持各种语言和平台的 PDF 工具和库如雨后春笋般涌现。但Adobe技术在Java应用开发方面的支持相对滞后。这是一个奇怪的现象,因为PDF文档是企业信息系统存储和交换信息的大势所趋,而Java技术特别适合这种应用。然而,Java 开发人员似乎直到最近才拥有成熟且可用的 PDF 支持。
PDFBox(BSD许可下的开源项目)是一个纯Java类库,供开发人员阅读和创建PDF文档。它具有以下功能:
PDFBox 的设计采用面向对象的方法来描述 PDF 文档。 PDF文档的数据是基本对象的集合:数组、布尔类型、字典、数字、字符串和二进制流。 PDFBox 在 org.pdfbox.cos 包(COS 模型)中定义了这些基本对象类型。您可以使用这些对象以任何方式与 PDF 文档进行交互,但您应该首先对 PDF 文档的内部结构和高级概念有一些深入的了解。例如,页面和字体是具有特殊属性的字典对象; PDF参考手册提供了这些特殊属性的含义和类型的解释,但这是一个繁琐的文档审查过程。
于是,org.pdfbox.pdfmodel包(PD模型)应运而生。它基于 COS 模型,但提供了高级 API 来以熟悉的方式访问 PDF 文档对象(图 1)。封装底层 COS 模型的 PDPage、PDFont 等类都在这个包中。
请注意,尽管 PD 模型提供了一些出色的功能,但它仍然是一个正在开发的模型。在某些情况下,您可能需要 COS 模型的帮助才能访问 PDF 的特定功能。所有 PD 模型对象都提供返回相应 COS 模型对象的方法。所以,一般情况下,你都会使用PD模型,但是PD模型并不是只要你可以直接操作底层的COS模型即可。
上面已经对PDFBox进行了大概的介绍。现在是时候举一些例子了。让我们从如何阅读现有的 PDF 文档开始:
PDDocument 文档= PDDocument.load( "./test.pdf" );
上面的语句解析指定的PDF文件并在内存中创建其文档对象。考虑到处理大文档时的效率问题,PDFBox仅将文档结构存储在内存中,图像、嵌入字体和页面内容等对象将缓存在临时文件中。
注意:当PDDocument对象使用完毕后,需要调用其close()方法来释放创建时使用的资源。
这是一个信息检索时代,无论信息存储在什么介质中,应用程序都应该支持检索和索引。将信息组织和分类为可搜索的格式至关重要。这对于文本文档和HTML文档来说非常简单,但是PDF文档包含大量的结构和元信息,提取文档内容绝非一件简单的事情。 PDF 语言与 Postscript 类似,两者的对象都在页面上的某些位置绘制为矢量。例如:
上面的命令将字体设置为 12 Helvetica,将其移动到下一行并打印“Hello World”。这些命令流通常是压缩的,文本在屏幕上出现的顺序不一定是字符在文件中出现的顺序。因此,有时您无法直接从原始 PDF 文档中提取字符串。然而,PDFBox 复杂的文本提取算法允许开发人员提取文档内容,就像它出现在阅读器中一样。
Lucene 是 Apache Jakarta 项目的子项目,Apache Jakarta 项目是一个流行的开源搜索引擎库。开发者可以使用Lucene创建索引,并根据索引对大量文本内容进行复杂的检索。 Lucene仅支持文本内容的检索,因此开发者需要将其他形式的数据转换为文本形式才能使用Lucene。例如,Microsoft Word 和 StarOffice 文档都必须先转换为文本形式,然后才能添加到 Lucene 索引中。
PDF 文件也不例外,但 PDFBox 提供了一个特殊的集成对象,可以非常轻松地将 PDF 文档包含在 Lucene 索引中。将基本 PDF 文档转换为 Lucene 文档只需要一条语句:
文档 doc = LucenePDFDocument.getDocument( file );
该语句解析指定的PDF文档,提取其内容并创建Lucene文档对象。然后您可以将该对象添加到 Lucene 索引中。如上所述,PDF文档还包含作者信息和关键字等元数据,在索引PDF文档时跟踪这些元数据非常重要。表 1 列出了 PDFBox 在创建 Lucene 文档时将填充的字段。
这种集成使开发人员可以轻松使用 Lucene 来支持 PDF 文档的检索和索引。当然,某些应用程序需要更复杂的文本提取方法。这时可以直接使用PDFTextStripper类或者继承该类来满足这种复杂的需求。
通过继承PDFTextStripper并重写showCharacter()方法,您可以通过多种方式控制文本提取。例如,限制使用 x,y 位置信息来提取特定的文本块。您可以有效地忽略 y 坐标大于某个值的所有文本,从而将文档标题内容排除在外。
另一个例子。经常会发生这样的情况:从表单创建一组 PDF 文档,但原始数据丢失。也就是说,这些文档都包含一些您感兴趣的文本,并且这些文本位于相似的位置,但填充文档的表单数据丢失了。例如,您有一些在同一位置包含姓名和地址信息的信封。这时,你可以使用PDFTextStripper的派生类来提取所需的字段。这个类就像一个捕获屏幕区域的设备。
PDF 的一项流行功能可让您加密文档内容、控制访问并限制读取未加密的文档。 PDF 文档使用主密码和可选的用户密码进行加密。如果设置了用户密码,PDF 阅读器(例如 Acrobat)将在显示文档之前提示输入密码。主密码用于授权对文档内容的修改。
PDF 规范允许 PDF 文档的创建者限制用户使用 Acrobat reader 查看文档时的某些操作。这些限制包括:
PDF 文档安全性的讨论超出了本文的范围。有兴趣的读者可以参考PDF规范的相关部分。 PDF 文档的安全模型是可插拔的,您在加密文档时可以使用不同的安全处理程序。就本文而言,PDFBox 支持标准安全处理程序,这是大多数 PDF 文档所使用的。
加密文档时,必须先指定安全处理器,然后使用主密码和用户密码进行加密。在下面的代码中,文档已加密,用户无需键入即可在 Acrobat 中打开它(未设置用户密码),但无法打印文档。
//加载文档 PD 文档 pdf = PDDocument.load(“测试.pdf”); //创建加密选项 PDStandardEncryption 加密选项 = 新的 PDStandardEncryption(); 加密选项.setCanPrint(false); pdf.setEncryptionDictionary( 加密选项); //加密文档 pdf.加密(“主”,空); //保存加密后的文档 //到文件系统 www.sychzs.cn("test-output.pdf");
有关更详细的示例,请参阅 PDFBox 版本中包含的加密工具类源代码:org.pdfbox.Encrypt。
许多应用程序可以生成 PDF 文档,但不支持控制文档的安全选项。这时可以使用PDFBox来拦截并加密PDF文档,然后再将其发送给用户。
当应用程序的输出是一系列表单字段值时,需要提供将表单保存到文件的功能。这时PDF技术将是一个不错的选择。开发人员可以手动编写 PDF 指令来绘制图形、表格和文本。或者将数据保存为 XML 并使用 XSL-FO 模板创建 PDF 文档。然而,这些方法耗时、容易出错且灵活性较差。对于简单的表单,更好的方法是创建模板,然后使用给定的输入数据填充模板以生成文档。
就业资格验证是大多数人都熟悉的形式。它也称为“I-9 表格”,请参阅:http://www.sychzs.cn/graphics/formsfee/forms/files/i-9.pdf
您可以使用包含在PDFBox 分发:
还有一个以文本形式将数据插入指定字段的示例程序:
在 Acrobat 中打开此 PDF 文档,您将看到“姓氏”字段已被填写。您还可以使用以下代码来完成相同的操作:
PD文档 pdf = PDDocument.load(“i-9.pdf”); PDDocumentCatalog docCatalog = pdf.getDocumentCatalog(); PDAcroForm acroForm = docCatalog.getAcroForm(); PDField 字段 = acroForm.getField(“NAME1”); field.setValue(“史密斯”); www.sychzs.cn(“i-9-copy.pdf”);
以下代码可用于提取您刚刚填写的表单字段的值:
PDField 字段 = acroForm.getField(“NAME1”); 系统.out.println( “名字=”field.getValue());
Acrobat 支持将表单数据导入或导出为特定文件格式“表单数据格式”。此类文件有两种类型:FDF 和 XFDF。 FDF 文件以与 PDF 相同的格式存储表单数据,而 XFDF 以 XML 格式存储表单数据。 PDFBox 在一个类中处理 FDF 和 XFDF:FDFDocument。下面的代码片段演示了如何从上面的 I-9 表格导出 FDF 数据:
PD文档 pdf = PDDocument.load(“i-9.pdf”); PDDocumentCatalog docCatalog = pdf.getDocumentCatalog(); PDAcroForm acroForm = docCatalog.getAcroForm(); FDFDocument fdf = acroForm.exportFDF(); www.sychzs.cn(“exportedData.fdf”);
PDFBox表单集成步骤:
演示
1。创建 PDF 文件
1 public void createHelloPDF() { 2 PDDocument 文档 = null; 3 PDPage页面=null; 45 尝试{ 6 doc = new PDDocument(); 7 页 = new PDPage(); 8 doc.addPage(页面); 9 PDFont 字体 = PDType1Font.HELVETICA_BOLD; 10 PDPageContentStream 内容 = new PDPageContentStream(doc, page); 11 内容.beginText(); 12 content.setFont(字体, 12); 13 内容.moveTextPositionByAmount(100, 700); 14 内容.drawString("你好"); 15 16. 内容.endText(); 17 内容.close(); 18 www.sychzs.cn("F:\\java56班\\eclipse-SDK-4.2-win32\\pdfwithText.pdf"); 19 doc.close(); 20 } catch (异常 e) { 21 系统.out.println(e); 22} 23}
2、读取PDF文件:
1 public void readPDF() { 2 PDDocument helloDocument = null; 3 尝试{ 4 helloDocument = PDDocument.load(新文件(5 "F:\\java56班\\eclipse-SDK-4.2-win32\\pdfwithText.pdf")); 6 PDFTextStripper textStripper = new PDFTextStripper("GBK"); 7 System.out.println(textStripper.getText(helloDocument)); 8 9 helloDocument.close(); 10 } catch (IOException e) { 11 // TODO 自动生成的 catch 块 12 e.printStackTrace(); 13} 14}
3、修改PDF文件(处理中文乱码,我可以搞定的):
1 /** 2 * 找到 PDF 中的字符串并将其替换为新字符串。 3 * 4 * @param inputFile 要打开的 PDF。 5 * @param outputFile 要写入的 PDF。 6 * @param strToFind 要在 PDF 文档中查找的字符串。 7 * @param message 要写入文件的消息。 8 * 9 * @throws IOException 如果写入数据时发生错误。 10 * @throws COSVisitorException 如果写入 PDF 时出现错误。 11*/12 public void doIt(字符串输入文件,字符串输出文件,字符串strToFind,字符串消息) 13 抛出 IOException、COSVisitorException 14{ 15 // 文档 16 PDDocument 文档 = null; 17 尝试 18{ 19 doc = PDDocument.load( 输入文件 ); 20 // PDFTextStripper stripper=new PDFTextStripper("ISO-8859-1"); 21 列表页面 = doc.getDocumentCatalog().getAllPages(); 22 for( int i=0; i
4、在PDF中加入图片:
1 /**2 * 将图像添加到现有 PDF 文档。 3 * 4 * @param inputFile 要添加图像的输入 PDF。 5 * @param image 要放入 PDF 中的图像的文件名。 6 * @param outputFile 要写入 pdf 的文件。 7 * 8 * @throws IOException 如果写入数据时发生错误。 9 * @throws COSVisitorException 如果写入 PDF 时出现错误。 10*/ 11 公共无效createPDFFromImage(字符串输入文件,字符串图像,字符串输出文件) 12 抛出 IOException、COSVisitorException 13{ 14 // 文档 15 PDDocument 文档 = null; 16 尝试 17{ 18 doc = PDDocument.load( 输入文件 ); 19 20 //我们将把图像添加到第一页。 21 PDPage 页 = (PDPage)doc.getDocumentCatalog().getAllPages().get( 0 ); 22 23 PDXObjectImage ximage = null;24 if( image.toLowerCase().endsWith( ".jpg" ) ) 25{ 26 ximage = new PDJpeg(doc, new FileInputStream( image ) ); 27} 28 else if (image.toLowerCase().endsWith(".tif") || image.toLowerCase().endsWith(".tiff")) 29{ 30 ximage = new PDCcitt(doc, new RandomAccessFile(new File(image),"r")); 31} 32 其他 33{ 34 BufferedImage awtImage = www.sychzs.cn( 新文件( 图像 ) ); 35 ximage = new PDPixelMap(doc, awtImage); 36} 37 PDPageContentStream contentStream = new PDPageContentStream(doc, page, true, true); 38 39 //contentStream.drawImage(ximage, 20, 20 ); 40 // 受http://www.sychzs.cn/a/22318681/535646启发的更好方法 41浮子刻度=0.5f; // 如果图像太大则减小该值42 System.out.println(ximage.getHeight()); 43 System.out.println(ximage.getWidth()); 44 // ximage.setHeight(ximage.getHeight()/5); 45 // ximage.setWidth(ximage.getWidth()/5); 46 contentStream.drawXObject(ximage, 20, 200, ximage.getWidth()*scale, ximage.getHeight()*scale); 47 48. 内容流.close(); 49 www.sychzs.cn( 输出文件 ); 50} 终于51了 52{ 53 if( 文档!= null ) 54{ 55. 关闭(); 56} 57} 58}
5、PDF文件转换为图片:
1 公共无效 toImage() { 2 尝试{ 3 PDDocument 文档 = PDDocument 4 .load("F:\\java56班\\eclipse-SDK-4.2-win32\\pdfwithText.pdf"); 5 int pageCount = doc.getPageCount(); 6 System.out.println(pageCount); 7 列表页面 = doc.getDocumentCatalog().getAllPages();8 for (int i = 0; i
6.将图片转换为PDF文件(支持将多张图片转换为PDF文件):
1 /** 2 * 根据 PDF 文件格式规范创建第二个示例文档。 3* 4 * @param 文件 5 * 将 PDF 写入的文件。 6 * @参数图像 7 * 要放入 PDF 中的图像的文件名。 8 * 9 * @抛出IOException 10 * 如果写入数据出错。 11 * @抛出COSVisitorException 12 * 如果写入 PDF 时出现错误。 13*/ 14 public void createPDFFromImage(String file, String image) 抛出 IOException, COSVisitorException { 15 //将多张图片转换为PDF文件 16 PDDocument 文档 = null; 17 doc = new PDDocument(); 18 PDPage页=null; 19 PDXObjectImage ximage = null;20 PDPageContentStream 内容流 = null; 21 22 File 文件 = new File(image); 23 String[] a = 文件.list(); 24 for (字符串字符串: a) { 25 if (string.toLowerCase().endsWith(".jpg")) { 26 字符串 temp = 图像 + "\\" + 字符串; 27 ximage = new PDJpeg(doc, new FileInputStream(temp)); 28 页 = new PDPage(); 29 doc.addPage(页面); 30 contentStream = new PDPageContentStream(文档,页面); 31浮子刻度=0.5f; 32 contentStream.drawXObject(ximage, 20, 400, ximage.getWidth() 33 * 缩放比例,ximage.getHeight() * 缩放比例); 34 35 PDFont字体= PDType1Font.HELVETICA_BOLD; 36. 内容流.beginText(); 37 contentStream.setFont(字体, 12); 38 内容流.moveTextPositionByAmount(100, 700);39 内容流.drawString("你好"); 40 内容流.endText(); 41 42. 内容流.close(); 43} 44} 45 www.sychzs.cn(文件); 46. 关闭(); 47}
7、替换PDF文件中的某个字符串:
1 /** 2 * 找到 PDF 中的字符串并将其替换为新字符串。 3 * 4 * @param inputFile 要打开的 PDF。 5 * @param outputFile 要写入的 PDF。 6 * @param strToFind 要在 PDF 文档中查找的字符串。 7 * @param message 要写入文件的消息。 8 * 9 * @throws IOException 如果写入数据时发生错误。 10 * @throws COSVisitorException 如果写入 PDF 时出现错误。 11*/ 12 public void doIt(字符串输入文件,字符串输出文件,字符串strToFind,字符串消息) 13 抛出 IOException、COSVisitorException 14{15 // 文档 16 PDDocument 文档 = null; 17 尝试 18{ 19 doc = PDDocument.load( 输入文件 ); 20 // PDFTextStripper stripper=new PDFTextStripper("ISO-8859-1"); 21 列表页面 = doc.getDocumentCatalog().getAllPages(); 22 for( int i=0; i
除了上面介绍的API之外,PDFBox还提供了一系列的命令行工具。表 2 列出了这些工具类并给出了简要介绍。
PDF规范共有1172页,其实现确实是一个巨大的工程。同样,PDFBox 版本指出它“正在进行中”,新功能将慢慢添加。它的主要弱点是从头开始创建 PDF 文档。然而,有几个开源 Java 项目可以填补这一空白。例如,Apache FOP 项目支持从描述要生成的 PDF 文档的特殊 XML 文档生成 PDF。此外,iText 还提供了用于创建表格和列表的高级 API。
PDFBox 的下一版本将支持新的 PDF 1.5 对象流和交叉引用流。然后将提供对嵌入字体和图像的支持。在PDFBox的努力下,Java应用中的PDF技术有望得到全面支持。
PDFBox:www.sychzs.cn Apache FOP:http://www.sychzs.cn/fop/ iText:www.sychzs.cn/iText/ PDF 参考:http:/ /www.sychzs.cn/asn/tech/pdf/specifications.jsp Jakarta Lucene:http://www.sychzs.cn/lucene/