1. 程式人生 > >C#讀取pdf檔案

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<length; 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更好些,別忘了分享你的好經驗。