C#读取pdf文件

dotnet环境下从PDF文档中抽取Text文本的一些方法汇总
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 {
code <<= 8;
code = (data[offset+i]+256)%6; //这行应该是 |=
}
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
参数StandardEncoding创建一个
不能正常工作
GBKEucHEncoding没有提供正确的getCharacter方法
编制一个自行分析byte值是否在GBK范围
不能正常工作
发现PDFBox的逻辑有问题 font.encode()是不可能返回null的,所以实际上不会执行双字节部份
发现PDFBox的问题在于,cmap/GBK-EUC-H parse之后居然没有doubleByteMap
确认认CMapParser没有处理begincidrange,只认识beginbfrange/char/space
加上begincidrange处理
不对,cidrange只影响从字体查找glyphy
回到encoding上
修正PDFont.getCodeFromArray()
GbkEucHEncoding.getCharacter()分析lead byte并转换bytes为单字符string
发现并patch 以解决encode()总是在lead byte时就返回一个单字节字符
测试通过
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更好些,别忘了分享你的好经验。

你可能感兴趣的:(C#)