Robin's Blog

记录 积累 学习 成长

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

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的总体结构

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

PDFTextStripper ==> PD Model ==> COS Model

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


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

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

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

  1. PDFTextStripper.writeText()

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

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

3PDFStreamEngine的字符解码过程

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.pdfType0字体,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对象

       这张表是在Mangerstatic块里初始化的

            加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~401if分支的代码貌似多余

// 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-HCID子字体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


问题求解

  1. 没有GBK-EUC-H对应的encoding

    1. 参数StandardEncoding创建一个
    2. 不能正常工作
  2. GBKEucHEncoding没有提供正确的getCharacter方法
    1. 编制一个自行分析byte值是否在GBK范围
    2. 不能正常工作
  3. 发现PDFBox的逻辑有问题 font.encode()是不可能返回null的,所以实际上不会执行双字节部份
  4. 发现PDFBox的问题在于,cmap/GBK-EUC-H parse之后居然没有doubleByteMap
  5. 确认认CMapParser没有处理begincidrange,只认识beginbfrange/char/space
    1. 加上begincidrange处理
    2. 不对,cidrange只影响从字体查找glyphy
  6. 回到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更好些,别忘了分享你的好经验。

posted on 2009-11-29 18:12  Robin99  阅读(9665)  评论(0编辑  收藏  举报