国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > C# > 正文

用C#來解析PDF文件

2020-01-24 01:38:04
字體:
來源:轉載
供稿:網友

2015712151420057.png (680×475)

1. 介紹

這個項目讓你可以去讀取并解析一個PDF文件,并將其內部結構展示出來. PDF文件的格式標準文檔可以從Adobe那兒獲取到. 這個項目基于“PDF指南,第六版,Adobe便攜文檔格式1.7 2006年11月”. 它是一個恐怕有1310頁的大部頭. 本文提供了對這份文檔的簡潔概述. 與此相關的項目定義了用來讀取和解析PDF文件的C#類. 為了測試這些類,附帶的測試程序PdfFileAnalyzer讓你可以去讀取一個PDF文件,分析它并展示和保存結果. 程序將PDF文件分割成單獨每頁的描述,字體,圖片和其它對象. 有兩種類型的PDF文件不受此程序的支持: 加密文件和多代文件.

這個程序的1.1版本允許世界各地使用點符號作為小數分隔符的程序員來編譯和運行程序.

1.2版本則修復了一個有關使用跨多個引用流來讀取PDF文檔的問題. 1.2之前的版本對此場景只會以一個對象數字重復的錯誤而終止運行.

2. 概要

PDF格式的文件,借助Adobe Acrobat軟件,可以在各種屏幕上顯示查看,使用各種打印機打印。但是,如果使用二進制文件編輯器打開PDF文件,你會發現文件的大部分是不可讀的,有小部分是可讀的,如下:

 

1 0 obj<</Lang(en-CA)/MarkInfo<</Marked true>>/Pages 2 0 R/StructTreeRoot 10 0 R/Type/Catalog>>endobj2 0 obj<</Count 1/Kids[4 0 R]/Type/Pages>>endobj 4 0 obj<</Contents 5 0 R/Group <</CS/DeviceRGB /S/Transparency /Type/Group>>/MediaBox[0 0 612 792] /Parent 2 0 R/Resources <</Font <</F1 6 0 R /F2 8 0 R>>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI]>>/StructParents 0/Tabs/S/Type/Page>>endobj5 0 obj<</Filter/FlateDecode/Length 2319>>stream. . .endstreamendobj

看上去,該文件是由嵌套在“n 0 OBJ ”和“ endobj ”關鍵詞之間的對象組成的,術語PDF也就是間接對象的意思。 “obj”前面的數字是對象編號和第幾代對象標識, 雙尖括號中的內容表示數據字典對象,中括號中的內容表示數組對象, 以斜杠/ 開始的內容表示參數名稱 (例如: /Pages)。上例中的第一項 “1 0 obj” 表示文檔的目錄或者文檔的根對象。文檔目錄的字典對象 “/Pages 2 0 R”,指向定義頁碼樹對象的引用。按照這樣推算,編號為2的對象包含指向 “/Kids[4 0 R]”的頁面的引用,是一個頁面文檔。 編號為4的對象是唯一的一個頁面定義, 頁面大小為612*792點, 換句話說,也就是8.5” * 11” (1” 代表72 點)點。該頁面使用了兩種字體F1和F2,這兩種字體分別在編號為6和8的對象中定義。該頁面的內容在編號為5的對象中描述,該對象中包含頁面繪圖的流信息,示例中的 “. . .”代表這部分流信息。如果使用二進制文件編輯器打開PDF文件,會發現這部分流信息看起來是一長串不可讀的隨機數,原因是那是壓縮數據。流數據采用Zlib方法壓縮,壓縮方式由字典對象“/Filter /FlateDecode”描述,被壓縮流的大小為2319字節。解壓這部分流信息,前面幾行內容如下所示:
 

q37.08 56.424 537.84 679.18 reW* n/P <</MCID 0>> BDC 0.753 g36.6 465.43 537.96 24.84 ref*EMC /P <</MCID 1/Lang (x-none)>> BDC BT/F1 18 Tf1 0 0 1 39.6 718.8 Tm0 g0 G[(GRA)29(NOTECH LI)-3(MIT)-4(ED)] TJET

這是頁面描述語言的一個小例子。 示例中, “re” 代表矩形,“re” 前面的4個數字代表矩形的位置和大小,依次為:起點橫坐標、起點縱坐標、寬度、高度。


這個簡單的例子演示了PDF文件內部實現的總體思路。從頁面層次結構的根對象開始, 每一頁都定義了諸如字體、圖片、內容流的資源,內容流由操作符和繪制頁面所需要的參數構成。PDF文件分析器會產生一個對象匯總文件,該文件包含非流對象的其他所有對象。每個數據流會被解碼并保存為一個單獨的文件, 頁面描述流保存為文本格式的文件, 圖片流保存為.jpg或.bmp格式的文件,字體流保存為.ttf格式的文件,其他二進制流保存為.bin 格式的文件,文本流保存為.txt格式的文件。通過另一個解析過程,晦澀難懂的頁面描述會被轉換為偽C#代碼,如上例中的頁面描述被轉為:
 

SaveGraphicsState(); // qRectangle(37.08, 56.424, 537.84, 679.18); // reClippingPathEvenOddRule(); // W*NoPaint(); // nBeginMarkedContentPropList("/P", "<</MCID 0>>"); // BDCGrayLevelForNonStroking(0.753); // gRectangle(36.6, 465.43, 537.96, 24.84); // reFillEvenOddRule(); // f*EndMarkedContent(); // EMCBeginMarkedContentPropList("/P", "<</Lang(x-none)/MCID 1>>"); // BDCBeginText(); // BTSelectFontAndSize("/F1", 18); // TfTextMatrix(1, 0, 0, 1, 39.6, 718.8); // TmGrayLevelForNonStroking(0); // gGrayLevelForStroking(0); // GShowTextWithGlyphPos("[(GRA)29(NOTECH LI)-3(MIT)-4(ED)]"); // TJEndTextObject(); // ET

文章接下來的部分將對PDF文件的結構和解析過程進行更為詳細的描述,接下來的章節包括:對象定義,文件結構,文件解析,文件讀取,以及使用PDF文件分析器編程。

3. 免責聲明

pdf 文件分析器能處理大量的文件,這是我在自己的系統上掃描眾多PDF文件的經驗。不過,該程序不支持加密文件或者多個代文件(在對象不為零之前的第二個數字)。在PDF規格文件之中可用功能的數量是非常顯著的。這并不可能為一個單的個開發者系統地測試所有的功能。如果在整個文件分析期間該程序拋出一個異常,將顯示一條錯誤信息,該信息顯示源代碼模塊名和行號。
4.對象定義

PDF文件生成多個對象。在PDF文件分析器項目中每個PDF對象都有一個對應的類。所有這些對象類都派生于PDFbase類。對象類定義源代碼是BasicObjects.cs.確卻地PDF對象定義在Adobe pdf文件 規格第三章之中是有用的


4.1. 基礎的對象

    Boolean對象是靠PdfBoolean類來實現的. Boolean在PDF上的定義同C#上的是相同的.

    Integer 對象是靠PdfInt類來實現的. PDF上的定義同C#上Int32的定義是相同的.

    實數對象是靠PdfReal類來實現的. PDF上的定義同C#上的Single定義相同.

    String 對象是靠PdfStr類來實現的. PDF上的定義同C#相比有所不同. String 是用字節構造出來的,而不是字符. 它被包在圓括號()里面. PdfFileAnalyzer會把包含在圓括號中的C#字符串保存成PDF的字符串. PDF的字符串對于ASCII編碼非常有用.

    十六進制字符串獨享是靠PdfHex類來實現的. 它是由每字節兩個十六進制數定義,并包在尖括號里面的字符串. PdfFileAnalyzer 將包含在尖括號中的C#字符串保存成PDF十六進制字符串. 對于 PDF 讀取器,字符串和十六進制字符串對象可用于同種目的. 字符串 (AB) 等同于<4142>. PDF 十六進制字符串對于任意編碼的場景非常有用.

    Name 對象是靠PdfName類來實現的. Name 對象是由打頭的正斜杠后面跟著一些字符組成的. 例如 /Width. Named 對象用作參數名稱. PdfFileAnalyzer 將正斜杠開頭的C#字符串保存成Name對象.

    Null 對象是靠PdfNull類來實現的. PDF 對于null的定義基本上同C#中的是一樣的.

4.2. 復合的對象

    Array 對象是靠 PdfArray 類來實現的. PDF 數組是一個封裝在一堆中括號中的對象的集合. 一個數組的對象可以是除了流之外的任何對象.PdfFileAnalyzer 將一個C#數組中的對象保存成PdfBase類

    . 因為所有的對象都繼承自PdfBase,所有在這個數組中保存多種類型的對象沒有啥問題. 當數組對象被轉換成一個字符串時(使用ToString()方法), 程序會在首位添加中括號. 數組可以是空的. 下面是一個有六個對象的數組示例: [120 9.56 true null (string) <414243>].

    Dictionary 對象是靠PdfDict類實現的. PDF 字典是一組被包入一對雙尖括號中的鍵值對集合. Dictionary 的鍵是一個對象的名稱,而值則可以是除了流之外的任何對象.  PdfFileAnalyzer 將一個鍵值對保存到PdfPair類中. 鍵是一個C#字符串,而值則是一個PdfBase.PdfDict 類有一個PdfPair類的數組. Dictionary 可以用鍵來訪問. 因而鍵值對的順序沒有啥意義. PdfFileAnalyzer 用鍵來對鍵值對進行排序. 下面是一個有三個鍵值對的字典: <</CropBox [0 0 612 792] /Rotate 0 /Type /Page>>.

    Stream 對象是靠PdfStream來實現的. Streams 被用來處理面熟語言,圖形和字體. PDF Stream 由一個字典和一個字節流組成. 字典中定義了流的參數. 比如流對象中字典的一個鍵值對 /Filter. PDF 文檔定義了10種類型的過濾器. PdfFileAnalyzer 支持了4種. 這是我發現在實際場景中只會被用到那4種. 壓縮過濾器 FlateDecode 是現在的PDF寫入器最長被用到的過濾器. FlateDecode支持ZLib解壓縮. LZWDecode 壓縮過濾器在過去些年用的比較多. 為了能讀取比較老的PDF文件, 我們的程序支持這個過濾器. ASCII85Decode 過濾器將可被打印的ASCII轉換成二進制位. DCTDecode 用于JPEG圖像的壓縮.PdfFileAnalyzer 為前三種實現了解壓縮. DCTDecode 流則以文件擴展名.jpg保存. 它是一個可以被展示的圖片文件.

    Object 流在PDF 1.5中被引入. 它是一個包含多個間接對象(在下面會描述道)的流. 上面描述的Stream 對象一次只壓縮一個流. Object 流會將所有包含進來的流壓縮到一個壓縮域中.

    多引用流在PDF 1.5中被引入. 它是一個包含多引用表格的流,下文會描述到.

    內聯圖片對象是靠 PdfInlineImage來實現的. 它是一個帶有一個流的流. 內聯圖片是頁面描述語言的一部分. 它由BI-開頭圖形, ID-圖形數據和EI-結尾圖形這三個操作符組成. BI 和 ID 之間的區域是一個圖形字典,而ID 和 EI 之間的區域則包含圖形數據.

4.3. 間接對象

    間接對象是靠 PdfIndirectObject實現的. 它是一個PDF文檔的主要構造塊. 間接對象是任何被包在 “n 0 obj” 和 “endobj”之間的對象. 其它對象可以通過設定“n 0 R”來引用間接對象. “n”代表對象編號. “0”代表生成編號. 這個程序不支持0之外的生成編號. PDF 規范允許其它的編號. 多代生成的理念允許PDF的修改操作是在保留原有文件的基礎上追加變更.

    對象引用時一種引用間接對象的方法. 例如 /Pages 2 0 R 是目錄對象中的字典里的一項. 它是一個指向 /Pages 對象的指針. pages對象是編號為2的間接對象.

4.4. 操作符和關鍵詞

    操作符和關鍵詞不被認為是PDF對象. 而PdfFileAnalyzer 程序有一個PdfOp 和一個PdfKeyword 類可以從中得到 PdfBase 的類. 在轉換過程中,轉換器為每一個可用的字符序列創建了一個 PdfOp 或者PdfKeyword . Pdf文件規范的附錄A-操作符總結中列出了所有的操作符. 列表中有73個操作符. 下面是一些操作符的示例: BT-打頭的文本對象, G-用于做記號的設置灰度操作, m-移動到, re-矩形和Tc-設置字符間距. 下面是關鍵詞的示例: stream, obj, endobj, xref.

5. 文件結構

PDF文件由四個部分構成: 頭部Header , 主體body, 多引用cross-reference 和附帶簽名 trailer signature.

  •     Header: 頭部是文件的簽名. 它必須是 %PDF-1.x , x 從 0 到 7.
  •     Body: 主體區域包含所有的間接對象.
  •     Cross-reference: 多引用是一個指向所有間接對象的文件位置指針列表. 有兩種類型的多引用表格. 原始的類型有ASCII字符組成. 新式的是一個包含一個間接對象的流. 信息以二進制數字編碼. 在多引用表格的結束部分有一個附件字典. 一個文件可以有超過一個的多引用區域.
  •     Trailer signature: 附帶簽名由關鍵詞“startxref”, 最后一個多引用表格的偏移位, 和結束簽名 %%EOF 組成. 請注意: 附帶簽名是多引用區域的一部分.

6. 文件轉換

PDF 文件是一個字節的序列. 一些字節有特殊的意義.

空格被定義成: null, tab, 換行, 換頁, 回車和間隔.

分隔符被定義成: (, ), <, >, [, ], {, }, /, %, 以及空格字符.

文件轉換是由PdfParser 類來完成的. 開始進行轉換過程是,程序會設置文件需要被轉換區域的位置. ParseNextItem() 是提取下一個對象的方法.


解析器跳過空格符和注釋。如果下一個字節是“(”,判斷對象為一個字符串。如果下一個字節是“[”,判斷對象是一個數組。如果接下來的兩個字節是“<<”,判斷對象是一個字典。如果下一個字節是“<”,判斷對象是一個十六進制字符串。如果下一個字節是“/”,判斷對象是一個名稱。如果下一個字節不是上述任何一種,解析器會采集隨后的字節直到發現定界符。定界符不是當前標記符的一部分。標記符可以是整數,實數,操作符或關鍵詞。在整數的情況下,程序將進一步搜索對象引用“n 0 R”或間接對象“n 0 obj”中 n 為該整數的對象。從 ParseNextItem() 返回的值是第4節“對象的定義”中所述的適當對象。對象的類作為 PdfBase 類返回。

在數組或字典的情況下,程序將執行遞歸調用 ParseNextItem() 來解析數組或字典的內部對象。

7. 文件讀取

PdfDocument 類是 PDF 文件分析的主要類。入口方法是 ReadPdfFile(String FileName)。程序以二進制讀取的方式打開 PDF 文件(一次一個字節)。

文件分析開始于檢查頭部簽名 %PDF-1.x(x為0到7)和結尾簽名%%EOF。有人會認為,所有的 PDF 生成器會把頭部簽名放在文件的零位置,結尾簽名放在文件的最后。不幸的是,實際并非如此。程序必須在文件的兩端搜索這兩個簽名。如果頭部簽名不在零位置,所有間接對象的文件位置的指針也必須調整。

就在結尾簽名的前面有一個指向最后一個交叉引用表開始位置的指針。

解析器為多引用表設置文件位置. 如果下一個對象是“xref” 關鍵詞,我們就有了原來類型的多引用. 否則,它就是新的基于流的多引用. 文件可以有多個多引用表. 文件也可以同時擁有新的和舊的風格的表. 每一個表都有一個對象數目和指向間接引用開頭的指針的列表. 對于每一個活動對象程序都會創建一個PdfIndirectObject 對象并將其保存在 ObjectArray中. 除了對象的數字和位置,這個對象的其它東西都是空的. 對于原來的多引用表,其位置是相對于文件而言的. 對于流類型的多引用,位置是相對于一個父間接對象流而言的.

在處理過程中,如果間接對象生成了0之外的數字, 程序的執行就會被終止. PdfFileAnalyzer 不支持多代的形式.


附件字典在交叉引用表的末尾處。分析PDF文件的時候,我們創建了一個帶負對象號的虛擬間接對象用于保存附件字典。

程序在附件字典中尋找四個特定的入口。如果找到/Encrypt入口,表示PDF文件是被加密的,程序的將結束分析,因為程序不支持分析加密格式的PDF文件。接著程序尋找/Root目錄對象的對象號。如果找到/XRefStm入口,我們就有了兩種交叉引用的類型。最后如果存在/Prev入口,我們有了另一個用于處理的交叉引用表。

交叉引用的處理完成后,我們擁有所有的間接對象的數組。  在處理階段,可用信息是對象號和對象位置。下一步,程序遍歷數組,讀取并解析每一個間接對象,并設置對象的值。如果對象是流,僅字典部分被解析,因為在這個時候還不知道流的長度。除了上述對象,如果字典和流對象的對象類型和子類型成員是可用的,系統將為字典和流對象設置這兩個值。


接下來程序遍歷所有的對象,并處理流對象。流對象的對象類型是"/ObjStm"。程序讀取和對象相關聯的流,并分解流到多個間接對象上。

接下來程序搜索所有的字典對象和流對象引用的對象字典對象。程序查找鍵值對,例如“/name n 0 R”。加入鍵值對被找到,程序檢查對象類型。如果再對象解析階段沒有設置對象類型,對象類型將設置為/name值。

下一步,讀取所有前面沒有讀取的流。系統讀取從文件讀取流。流被解碼并保存到對應的文件中。PdfFileAnalyzer支持如下的過濾:/FlateDecode,/LZWDecode, /ASCII85Decode和/DCTDecode。文本文件的擴展名是.txt,二進制文件的擴展名是.bin,圖片文件的擴展名是.jpg和.bmp,字體文件的擴展名是.ttf,交叉引用文件的擴展名是.xref。/FlateDecode是ZLib Deflate壓縮算法。
下一步是構建頁的內容。程序跟隨從根開始的頁面樹。頁對象不是流對象。換句話說,頁描述命令是不能直接在也對象中的。頁對象字典有/Contents的鍵值對。如果不存在這個鍵值對,那么頁面就是空的。內容入口值可以是一個單獨的引用或者是一個應用數組。程序將為來自于一個或多個內容流的頁面創建虛擬的內容流。頁內容虛擬流保存在PageObj_xx.txt和PageSource_xx.txt中。PageObj_xx.txt是頁面的實際描述內容。PageSource_xx.txt是將頁面的描述內容轉換為偽C#源代碼。在第二節概要中,有這兩個文件的例子。

頁內容流是由參數和操作符組成的。例如矩形由四個實數描述的,內嵌的圖片不遵循這個規則。它的描述是在第三節對象定義中。

最后,程序產生對象匯總文件ObjectSummary.txt。文件顯示所有簡介對象的信息不包含流。

8. PdfFileAnalyzer 程序

開發應用程序 PdfFileAnalyzer 的目的是用來測試這個 PDF 文件解析類。如果你想在開發環境之外測試它的可執行程序,需創建一個名為 PdfFileAnalyzer 的目錄并復制 PdfFileAnalyzer.exe 到這個目錄中,然后運行這個程序。如果你想從 Visual C# 開發環境中運行這個項目,請確保你在“項目屬性”的“Debug”標簽欄中定義了一個工作目錄。此程序是使用 Microsoft Visual C# 2012 開發的。

運行程序,可用的操作項有: Open, Setup 和 Exit.

程序首次執行時你必須使用 Setup 定義工程目錄。這個目錄盛放所有被分析的 PDF 文件所產生的對應子目錄。

Open 按鈕會顯示一個標準的文件選擇對話框,你可以在其中找到你要進行分析的 PDF 文件。


PDF文件分析器界面將切換到類的匯總界面:

2015712151511150.png (600×581)

每行代表一個間接的PDF對象。每列是:

  •     Object No. 間接對象號。對于附件字典來說dummy號,對象號是一個,對象號是負數時,在界面上顯示為TRn
  •     Ojbect 在第4節中定義的對象類型
  •     Type 如果對象是字典或者流,類型是/Type字典的值。如果類型不是字典或者字典不包含/Type,顯示值來自于對這個對象的間接引用
  •     Subtype 如果對象是字典或者流,或者字典包含/Subtype,將顯示在這一列
  •     Parent Object No. 如果間接對象是對象流的一部分(見4.2節復合對象),這一列顯示流對象的對象號
  •     Parent Index 如果間接對象是對象流的一部分,索引號是父對象流的號
  •     File Name 流對象和頁面對象存在文件名。File Name是文件存儲在流對象內的名字。文件有如下的擴展名:.txt是文本文件,.bin是二進制文件,.bmp是圖片,.jpg是圖片,.ttf是字體,.xref是多引用流。如果分析MyFile.PDF的流文件,工程目錄的子目錄MyFile將被指定在啟動界面上。頁面對象不是流。文件表示這一頁所有對象的關聯關系
  •     Ojbect Position 如果間接對象文件不是對象流類型,這是對象在PDF文件內的位置。如果間接對象是對象流的一部分,這對象在父對象內的位置。位置按照十進制和十六進制數字顯示,便于程序員再二進制編輯器中查看PDF文件
  •     Stream Position 和 Stream Length 流的位置和長度。流的位置是相對于文件或者父對象的,同對象的位置使用相同的計算方法


點擊Summary按鈕,查看ObjectSummary.txt 文件。

選擇一行并點擊View按鈕或者雙擊一行后將顯示對象分析界面,用于查看間接對象的詳情。

2015712151532679.png (600×657)

對于所有的非流對象,前面的三個按鈕是不能點擊的。僅僅顯示對象自身的信息。你能用文本方式或者十六進制格式查看這些信息。

對于流對象,第一個按鈕的名字是object type。前兩個按鈕object type和Stream允許你在查看對象和流之間切換。Hex和Text按鈕允許你采用二進制格式或者文本格式查看。如果是圖片流,文本格式顯示為四列:(1) 對象號,(2) 類型 (0-未使用,1-普通對象,2-流對象),(3)普通對象的位置和流對象的父對象,(4) 父對象的索引號。如果是二進制流(例如:字體),則僅能用十六進制格式查看。

頁面對象按照流對象來處理。所有內容對象的文本顯示是關聯的。另外,Source按鈕允許你查看頁面在C#代碼中的描述語言。

JPG圖片和BMP圖片可以旋轉方向和調整大小。

 

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 九台市| 逊克县| 东海县| 利辛县| 沧源| 德江县| 台东县| 仁寿县| 鄢陵县| 武平县| 蚌埠市| 镇平县| 洪泽县| 大方县| 浑源县| 新宁县| 聂荣县| 建瓯市| 色达县| 休宁县| 万山特区| 仁寿县| 盐亭县| 阜新市| 万宁市| 水富县| 诸暨市| 包头市| 黎平县| 余干县| 南澳县| 甘洛县| 潍坊市| 龙泉市| 榆树市| 沐川县| 湖南省| 石景山区| 贡觉县| 县级市| 秦安县|