PDF檔案結構(一)
PDF檔案結構(一)
————物理結構
作者:bobob
PDF(Portable Document Format,行動式文件結構)是一種很有用的檔案格式,其最大的特點是平臺無關而且功能強大(支援文字/圖象/表單/連結/音樂/視訊等).做PDF的解析,首先要熟悉PDF檔案的物理結構和邏輯結構。PDF檔案物理結構可分為以下幾塊:
1.檔案頭檔案頭是PDF檔案的第一行,格式如下:
%PDF-1.4
這是個固定格式,表示這個PDF檔案遵循的PDF規範版本,目前PDF的生成工具,除了官方的acrobat,其他生成的以1.4
從1.4版本以後,PDF檔案的版本並不唯一的只是在這裡表示了,可能後面會改寫(catalog的Version詞條),所以解析PDF的時候,如果這裡的版本大於等於1.4,應該再比較一下catalog裡面的version,取其中高一點的版本。
2.物件集合這是一個PDF檔案最重要的部分,檔案中用到的所有物件,包括文字/圖象/音樂/視訊/字型/超連線/加密資訊/文件結構資訊等等,都在這裡定義。格式如下:
2 0 obj
...
end obj
一個物件的定義包含4個部分:
前面的2是物件序號,其用來唯一標記一個物件;0是生成號,按照PDF規範,如果一個PDF檔案被修改,那這個數字是累加的,它和物件序號一起標記是原始物件還是修改後的物件,但是實際開發中,很少有用這種方式修改PDF的,都是重新編排物件號;obj和endobj是物件的定義範圍,可以抽象的理解為這就是一個左括號和右括號;省略號部分是PDF規定的任意合法物件(一共8種,見後面附A)。
可以通過R關鍵字來引用任何一個物件,比如要引用上面的物件,可以使用2 0 R,需要主意的是,R關鍵字不僅可以引用一個已經定義的物件,還可以引用一個並不存在的物件,而且效果就和引用了一個空物件一樣。
3.交叉引用表交叉引用表是PDf檔案內部一種特殊的檔案組織方式,可以很方便的根據物件號隨機訪問一個物件。其格式如下:
xref
0 1
0000000000 65535 f
4 1
0000000009 00000 n
8 3
0000000074 00000 n
0000000120 00000 n
0000000179 00000 n
其中,xref是開始標誌,表示以下為一個交叉引用表的內容;每個交叉引用表又可以分為若干個子段,每個子段的第一行是兩個數字,第一個是物件起始號,後面是連續的物件個數,接著每行是這個子段的每個物件的具體資訊——每行的前10個數字代表這個這個物件相對檔案頭的偏移地址,後面的5位數字是生成號(用於標記PDF的更新資訊,和物件的生成號作用類似),最後一位f或n表示物件是否被使用(n表示使用,f表示被刪除或沒有用)。上面這個交叉引用表一共有3個子段,分別有1個,1個,3個物件,第一個子段的物件不可用,其餘子段物件可用。
4.trailer:
通過trailer可以快速的找到交叉引用表的位置,進而可以精確定位每一個物件;還可以通過它本身的字典還可以獲取檔案的一些全域性資訊(作者,關鍵字,標題等),加密資訊,等等。具體形式如下:
trailer
<<
key1 value1
key2 value2
key3 value3
…
>>
startxref
553
%%EOF
trailer後面緊跟一個字典,包含若干鍵-值對。具體含義如下:
鍵 |
值型別 |
值說明 |
Size |
整形數字 |
所有間接物件的個數。一個PDF檔案,如果被更新過,則會有多個物件集合、交叉引用表、trailer,最後一個trailer的這個欄位記錄了之前所有物件的個數。這個值必須是直接物件。 |
Prev |
整形數字 |
當檔案有多個物件集合、交叉引用表和trailer時,才會有這個鍵,它表示前一個相對於檔案頭的偏移位置。這個值必須是直接物件。 |
Root |
字典 |
Catalog字典(檔案的邏輯入口點)的物件號。必須是間接物件。 |
Encrypt |
字典 |
文件被保護時,會有這個欄位,加密字典的物件號。 |
Info |
字典 |
存放文件資訊的字典,必須是間接物件。 |
ID |
陣列 |
檔案的ID |
startxref: 後面的數字表示最後一個交叉引用表相對於檔案起始位置的偏移量。
%%EOF :檔案結束符.
一個PDF檔案,都會有上面這樣的結構(線性化優化的PDF例外,這個後面單獨說)。實際一個pdf檔案是很複雜的,但是上面幾個部分是確定的,只能多不能少.瞭解了PDF檔案的物理結構,就可以提取出一個一個的物件了.PDF中的物件有8種:
1.booleam
用關鍵字true或false表示,可以是array物件的一個元素,或dictionary物件的一個條目.也可以用在PostScript計算函式裡面,做為if或ifesle的一個條件。
2.numeric
包括整形和實型,不支援非十進位制數字,不支援指數形式的數字.
例:
1)整數 1234567+111-2
範圍:正2的31次方-1到負的2的31次方
2)實數 12.30.8+6.3-4.01-3.+.03
範圍:±3.403×10的38次方±1.175×10的-38次方
注意:如果整數超過表示範圍將轉化成實數,如果實數超過範圍就出錯了
3.string
由一系列0-255之間的位元組組成,一個string總長度不能超過65535.string有以下兩種方式:
1)直接字串
由()包含起來的一個字串,中間可以使用轉義符"/".
例:
(abc) 表示abc
(a//) 表示a/
轉義符的定義如下:
轉義字元 |
含義 |
/n |
換行 |
/r |
回車 |
/t |
水平製表符 |
/b |
退格 |
/f |
換頁(Form feed (FF)) |
/( |
左括號 |
/) |
右括號 |
// |
反斜槓 |
/ddd |
八進位制形式的字元 |
2)十六進位制字串
由<>包含起來的一個16進位制串,兩位表示一個字元,不足兩位用0補齊
例:
<Aabb> 表示AA和BB兩個字元
<AAB> 表示AA和B0兩個字元
4.name
由一個前導/和後面一系列字元組成,最大長度為127.和string不同的是,name是不可分割的和唯一的,不可分割就是說一個name物件就是一個原子,比如/name,不能說n就是這個name的一個元素;唯一就是指兩個相同的name一定代表同一個物件.從pdf1.2開始,除了ascii的0,別的都可以用一個#加兩個十六進位制的數字表示.
例:
/name 表示name
/name#20is 表示name is
/name#200 表示name 0
5.array
用[]包含的一組物件,可以是任何pdf物件(包括array).雖然pdf只支援一維array,但可以通過array的巢狀實現任意維數的array(但是一個array的元素不能超過8191)
例:
[5493.14false(Ralph)/SomeName]
6.Dictionary
用"<<"和">>"包含的若干組條目,每組條目都由key和value組成,其中key必須是name物件,並且一個dictionary內的key是唯一的;value可以是任何pdf的合法物件(包括dictionary物件).
例:
<</IntegerItem12
/StringItem(astring)
/Subdictionary
<</Item10.4
/Item2true
/LastItem(not!)
/VeryLastItem(OK)
>>
>>
7.stream
由一個字典,和緊跟其後面的一組關鍵字stream和endstream以及這組關鍵字中間包含一系列位元組組成.內容和string很相似,但有區別:stream可以分幾次讀取,分開使用不同的部分,string必須作為一個整體一次全部讀取使用;string有長度限制,但stream卻沒有這個限制.一般較大的資料都用stream表示. 需要注意的是,Stream必須是間接物件,並且stream的字典必須是直接物件。從1.2規範以後,stream可以以外部檔案形式存在,這種情況下,解析PDF的時候stream和endstream之間的內容就被忽略掉。
例:
dictionary
stream
… data …
endstream
stream字典中常用的欄位如下:
欄位名 |
型別 |
值 |
Length |
整形 |
(必須)關鍵字stream和endstream之間的資料長度,endstream之前可能會有一個多餘的EOL標記,這個不計算在資料的長度中。 |
Filter |
名字或陣列 |
(可選)Stream的編碼演算法名稱(列表)。如果有多個,則陣列中的編碼演算法列表順序就是資料被編碼的順序。 |
DecodeParms |
字典或陣列 |
(可選)一個引數字典或由引數字典組成的一個數組,供Filter使用。如果僅有一個Filter並且這個Filter需要引數,除非這個Filter的所有引數都已經給了預設值,否則的話DecodeParms必須設定給Filter。如果有多個Filter,並且任意一個Filter使用了非預設的引數, DecodeParms 必須是個陣列,每個元素對應一個Filter的引數列表(如果某個Filter無需引數或所有引數都有了預設值,就用空物件代替)。如果沒有Filter需要引數,或者所有Filter的引數都有預設值,DecodeParms 就被忽略了。 |
F |
檔案標識 |
(可選)儲存stream資料的檔案。如果有這個欄位, stream和endstream就被忽略,FFilter將會代替Filter, FDecodeParms將代替DecodeParms。Length欄位還是表示stream和endstream之間資料的長度,但是通常此刻已經沒有資料了,長度是0. |
FFilter |
名字或字典 |
(可選)和filter類似,針對外部檔案。 |
FDecodeParms |
字典或陣列 |
(可選)和DecodeParams類似,針對外部檔案。 |
8.NULL
用null表示,代表空.如果一個key的值為null,則這個key可以被忽略;如果引用一個不存在的object則等價於引用一個空物件.
例:(略)
以上八種物件是按照物件內涵來分的,如果按照物件的使用規則來說,物件又分為間接物件和直接物件。間接物件是PDF中最常用的物件,如前面物件集合裡面的,所有物件都是間接物件,在其他位置通過R關鍵字來引用,在交叉引用表裡面都是通過間接物件來引用的。直接物件就更好理解了,上面的8種物件單獨出現的時候就叫直接物件。