pdfbox

1.PDFBox的IKVM版本:据我所知,目前只有PDFBox的IKVM版本能比较好地从PDF中提取文本,PDFBOX更多信息请访问http://www.pdbox.org,
关于其应用实例,可以参考CodeProject上的:http://www.codeproject.com/csharp/pdf2text.asp ;
2.使用Acrobat的SDK(这个价格可不便宜);
3.XPDF:如果条件允许可以考虑使用XPDF的PDFToText,
XPDF是用C语言编写的PDF解析库,并提供多个工具,开放源代码(如果你熟悉C和dotnet,也许你可以在dotnet环境下编译为你所用),但是基于GUN协议,如果商业应用,需要money;
更多信息访问:http://www.foolabs.com/xpdf
3.Ghostscript:另外一个可以考虑的是Ghostscript,官方网址是:www.cs.wisc.edu/~ghost/ ,抽取Text的方法,google下ps2txt;
4.其它一些相关资源:
http://www.mj10777.de/NETFramework/Desktop/SharpZipLib/PdfToTxt/index.htm
Extract Text from PDF File: http://www.codeproject.com/Purgatory/DotNetPDF.asp?df=100&forumid=104443
Code to extract plain text from a PDF file: http://www.codeproject.com/cpp/ExtractPDFText.asp?df=100&forumid=47947

顺便说下,很多朋友询问iTextSharp中抽取文本的方法,这里说下,就目前而言,iTextSharp还不支持这个功能,也无法抽取图片,当然我通过摸索也只能抽取最简单格式的图片(jpeg),其它的还在研究怎么处理。

==========================================================================================

C#编程读取pdf文件

这看起来是一个不太难的任务,或许您已经在web中找到了如何实现的参考资料。如果您有一个PDF文件,而您不知道如何从中读取数据,可以参考下面的内容。

首先,您需要一些能够帮助您处理PDF文件的动态库。我用的是PDFBox。那么,什么是PDFBox呢?官方网 站的介绍如下PDFBox 是一个开源的用于处理PDF文档的Java PDF 库 。它能够创建新的PDF文档,处理现存的PDF文档,还能从文档中抽取内容。PDFBox还包含几个命令行工具。

您也许会说,这很不错,可是我需要一个基于.NET的方案。不用担心,尽管PDFBox是用 Java 写的,但也有一个 .NET 版本。它使用 IKVM (also, a very interesting project: an implementation of the Java language for .NET Framework and Mono) 来为.net 创建一个全功能的PDF库。发布包中的bin目录包含所有需要的DLL文件

所以,需要 下载 PDFBox 包。在这个包中,有一个bin目录。为了读取PDF文件,需要下面的文件:

  • IKVM.GNU.Classpath.dll
  • PDFBox-0.7.3.dll
  • FontBox-0.1.0-dev.dll
  • IKVM.Runtime.dll

您必须在项目中引用前两个动态库,还要把后两个复制到项目的bin目录中。 示例代码如下(假定使用控制台程序

 

using System;

using org.pdfbox.pdmodel;

using org.pdfbox.util;

 

namespace PDFReader

{

    class Program

    {

        static void Main(string [] args)

        {

            PDDocument doc = PDDocument .load("lopreacamasa.pdf" );

            PDFTextStripper pdfStripper = new PDFTextStripper ();

            Console .Write(pdfStripper.getText(doc));

        }

    }

}

 

解决PDFBox不能读取中文PDF问题

PDFBox是一个相对简洁实用的处理PDF文件的Java类库。PDFBox具 有丰富的功能,但我只关心利用它来抽取PDF文件中的文本,这也非常简单,PDFBox作者已经考虑到这种用法并提供了很好的支持,这是PDFBox相对 iText对我更具吸引力的重要原因,另外它的体积也比iText小多了。

不过PDFBox处理中文文件有问题,google了一下,抱怨的一堆,解决方案没找着,还是自己动手解决吧。还好,不是太复杂,在小黑屋里关了一天就找到了原因了。以下就是寻找解决方案过程的记录。

PDF Box 的总体结构

1 Mysoo 使用PDF Box 涉及三个层次

PDFTextStripper ==> PD Model ==> COS Model

COS Model 是对PDF 文件格式的直接映射,PD Model 则是对COS Model OO 封装,其关系如下图所示:


上半部份是PDModel ,每个PDModel 对象都是对于特定COSModel 对象的封装,下半部份是COSModel ,每个COSModel 对象都直接对应PDF 文档的一段内容。

PDFTextStripper 是一辅助工具,它封装了通过PDModel 取得文档的所有页面,并从每一页可能包含有文本的对象中取出字符的操作。

2 PDF Box 取出文本的处理过程

    • PDFTextStripper.writeText()

    •   For each PDPage of PDDocument
    •     ProcessPage()
    •       PDFStreamEngine.processStream() in the page
    •         Parse cosStream to get operator name and its parameters
    •         Delegate to operator retrieved from stream
    •           => SetGraphicsStateParameters operator parse GraphicStates
    •           => ShowText operator handle COSString
    •             Retreive byte[] from COSString
    •             PDFStreamEngine.showString(bytes[])

文本处理的实际内容就在 PDFStreamEngine.showString() 里,在这之前 SetGraphicsStateParameters 已经分析并保存了PDF 的当前绘制格式参数,其中与文本处理相关的就是字体PDFont ShowString() 会利用这个PDFont 对源自PDF 文件的字节串进行解码,形成对应的字符串。

3 PDFStreamEngine 的字符解码过程

PDFStreamEngine.showString(byte[] string)

     for each byte in input string

        firtly try PDFont.encode( string[offset], 1 byte )

        if return null, then try PDFont.encode( string[offset], 2 bytes)

            PDFont.encode( bytes, count ), convert bytes into a single char string

                PDFont try to get CMap

                    if the font has embbed TO_UNICODE Cmap then parse it

                    else retrieve Cmap based on ENCODING attribute of the font

// 测试中 cn.pdf Type0 字体, encodingName GBK-EUC-H

// GBK-EUC-H 代表 MS CP936 ( lfCharSet 0x86) , GBK charset, GBK encoding

// 第一块文本处理时, cmapObjects 为空,

PDFont perform cmap name subtitution

PDFont parseCmap from Resource/cmap/cmapName (GBK-EUC-H )

CmapParser.parse(resStream) 分析并创建cmap 实例

cmap 对象注册到cmapObjects {encodingName, cmap}

                    如果1 字节字符识别返回null ,则尝试2 字符

cmap.lookup( bytes, count )

如果字符表里没找到

PDFont.getEncoding()

    从font ENCODING 属性中取得encoding (GBK-EUC-H )

         EncodingManager.getEncoding( encoding )

                Manager 从内部ENCODINGS 表中名字代表的Encoding 对象

       这张表是在Manger static 块里初始化的

            加ENCODINGS.put( COSName.GBK_EUC_H_ENCODING, new GbkEucHEncoding() );

PDFont.getCodeFromArray(bytes, count)

        ' ' -1 字节时应该返回 0xC7 ,两字节时应该返回 0xC7B0

Encoding.getCharacter(code)

如果Encoding 也处理不了就getStringFromArray()


faint! PDFont:

protected int getCodeFromArray( byte[] data, int offset, int length )

{

int code = 0;

for( int i=0; i<length; i++ )

{

code <<= 8;

code = (data[offset+i]+256)%256; // 这行应该是 |=

}

return code;

}


// 注: PDFont.java 395~401 if 分支的代码貌似多余

// cmap = (CMap)cmapObjects.get( encodingName );

// if( cmap != null )

// {

// cmap = (CMap)cmapObjects.get( encodingName );

// }

// else

// { ... }


PDF 文件中字体描述部份:

0020de0: 626a 0d3c 3c20 0d2f 5479 7065 202f 466f bj.<< ./Type /Fo

0020df0: 6e74 200d 2f53 7562 7479 7065 202f 5479 nt ./Subtype /Ty

0020e00: 7065 3020 0d2f 4e61 6d65 202f 4631 200d pe0 ./Name /F1 .

0020e10: 2f42 6173 6546 6f6e 7420 2f23 4241 2344 /BaseFont /#BA#D

0020e20: 4123 4343 2345 3520 0d2f 4465 7363 656e A#CC#E5 ./Descen

0020e30: 6461 6e74 466f 6e74 7320 5b20 3230 3720 dantFonts [ 207

0020e40: 3020 5220 5d20 0d2f 456e 636f 6469 6e67 0 R ] ./Encoding

0020e50: 202f 4742 4b2d 4555 432d 4820 0d3e 3e20 /GBK-EUC-H .>>

Type0 复合字体:基本字体名 BADA CCE5 是黑体,编码是 GBK-EUC-H CID 子字体 210 0 R (PDF 对象 ID)

0020e60: 0d65 6e64 6f62 6a0d 3230 3720 3020 6f62 .endobj.207 0 ob

0020e70: 6a0d 3c3c 200d 2f54 7970 6520 2f46 6f6e j.<< ./Type /Fon

0020e80: 7420 0d2f 5375 6274 7970 6520 2f43 4944 t ./Subtype /CID

CID Type2 字体:基本字体黑体, WinCharSet 0x86 ,描述符 208 0 R(PDF 对象 ID)

0020e90: 466f 6e74 5479 7065 3220 0d2f 4261 7365 FontType2 ./Base

0020ea0: 466f 6e74 202f 2342 4123 4441 2343 4323 Font /#BA#DA#CC#

0020eb0: 4535 200d 2f57 696e 4368 6172 5365 7420 E5 ./WinCharSet

0020ec0: 3133 3420 0d2f 466f 6e74 4465 7363 7269 134 ./FontDescri

0020ed0: 7074 6f72 2032 3038 2030 2052 200d 2f43 ptor 208 0 R ./C

0020ee0: 4944 5379 7374 656d 496e 666f 203c 3c20 IDSystemInfo <<

0020ef0: 2f52 6567 6973 7472 7920 284b 77b0 6789 /Registry (Kw.g.

0020f00: 292f 4f72 6465 7269 6e67 2028 4d51 ee29 )/Ordering (MQ.)

0020f10: 2f53 7570 706c 656d 656e 7420 3220 3e3e /Supplement 2 >>

0020f20: 200d 2f44 5720 3130 3030 200d 2f57 205b ./DW 1000 ./W [

0020f30: 2038 3134 2039 3037 2035 3030 2037 3731 814 907 500 771

0020f40: 3620 3737 3136 2035 3030 205d 200d 3e3e 6 7716 500 ] .>>

0020f50: 200d 656e 646f 626a 0d32 3038 2030 206f .endobj.208 0 o


问题求解

    • 没有GBK-EUC-H 对应的encoding

      1. 参数StandardEncoding 创建一个
      2. 不能正常工作
    • GBKEucHEncoding 没有提供正确的getCharacter 方法
      1. 编制一个自行分析byte 值是否在GBK 范围
      2. 不能正常工作
    • 发现PDFBox 的逻辑有问题 font.encode() 是不可能返回null 的,所以实际上不会执行双字节部份
    • 发现PDFBox 的问题在于,cmap/GBK-EUC-H parse 之后居然没有doubleByteMap
    • 确认认CMapParser 没有处理begincidrange ,只认识beginbfrange/char/space
      1. 加上begincidrange 处理
      2. 不对,cidrange 只影响从字体查找glyphy
    • 回到encoding
      1. 修正PDFont.getCodeFromArray()
      2. GbkEucHEncoding.getCharacter() 分析lead byte 并转换bytes 为单字符string
      3. 发现并patch 以解决encode() 总是在lead byte 时就返回一个单字节字符
      4. 测试通过

Holly(http://www.jsfsoft.com:8080/beyond-pebble/lee ) 认为可以有更为优雅的解决方案并做了一个patch, patch 已经提交给 pdfbox 项目,在官方版本更新之前,你可以直接从这里获得这个 patch(http://sourceforge.net/tracker/index.php?func=detail&aid=1640071&group_id=78314&atid=552834 )

 

=========================================================================

PDFBox是不错的选择呀,另外你也可以看看PDFFilter,adobe公司的免费产品
还有如果考虑付费的话jpedal更好些,别忘了分享你的好经验。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值