PDF解析


昨天事沒辦完抽空去大濕公司小坐了一會,聊了很多也有一些感觸,可喜的是公司越搞越好了,還有那么一大幫小伙跟着干,好生羡慕呢。金錢、事業、二奶、名利多收,各種光環,TVP、MVP羡煞旁人哪,我心里在想能不能不要這么囂張,最后預祝新產品路演成功。接下來吹我自己,前段時間因為工作的原因 ,接觸到了PDF文件解析以及打印,當時是被虐待了,這不被虐待了的想辦法報仇不是,最近因工作比較清閑,抽空研究了幾天PDF文件格式以及WIN打印(打印下一篇單獨寫),看似一個簡單格式的PDF文件,個人感覺格式設計的還是相當復雜,涉及多方面的基礎內容,比如分辨率、壓縮算法、字體、多媒體內嵌等內容。簡單說明下今天只簡單介紹PDF格式以及通過CODE讀取和寫入PDF內容,不涉及WIN文件系統,這部分太復雜,涉及到軟硬件、文件過濾驅動等。
 
PDF文件由ADOBE公司設計研發的便攜式文檔,支持跨平台、字體、多媒體嵌入、加密、執行腳 本等特點。概念性的東西就簡單介紹就到這吧,接下來簡單介紹一下學習PDF
文件相關的一些資源。1.PDF格式權威指南 https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/pdf_reference_1-7.pdf,ADOBE官方的,呵呵全英文的,有翻譯版本但是缺少前面4章,個人覺得這也是目前學習PDF最好的學習資料,非常詳細,也是你寫代碼解析PDF格式的權威指導者。2.開源解析庫PDFSHARP、ITEXTSHARP。PDFSHARP幾年前就沒更新了(不推介),GITHUB地址: https://github.com/empira/PDFsharp,需要的朋友自行下載瞅瞅。比起PDFSHARP來說ITEXTSHARP這個庫目前更受歡迎,在GITHUB上有1.1kstart,並且還在維護,支持.NETCORE及.NETSTANDARD標准庫規范,GITHUB下載地址 https://github.com/itext/itextsharp,說實話這兩庫我都沒怎么看過,因為看起來比較費力,資料就是這些吧,個人建議最好先把PDF格式規范看個大概,起碼主要的格式要了解,然后自己寫代碼嘗試讀取和寫入,說白了就是一堆字節流操作,閑聊就到這,下面看實操。
 
對象集合初始化PDF詳細格式我就不說了啊,說實話我了解的也不多,這種細活需要時間和精力去研究。整個實操分兩部分吧,讀取PDF和寫入PDF,我們先從讀取開始吧,我這邊整個用的是控制台程序,代碼就不貼了,處理字節流的代碼沒啥可讀性,主要是處理思路,我先用acrobatprodc工具創建一份簡單的不能再簡單的PDF文件,看圖
 
就一個漢字,現在我們通過FileStram結合二進制流讀取器讀取里面的內容,看內容
"%PDF-1.7\n1 0 obj\n<</Type/Catalog/Pages 2 0 R>>\nendobj\n2 0 obj\n<</Type/Pages/Kids[4 0 R]/Count 1>>\nendobj\n7 0 obj\n<<
/Filter/FlateDecode/Length 219>>\nstream\nx�]��j�0\f��y\n\u001d�Cq���`(\u001d�\u001c���}\0�R�a���\u001c��s�$�\t,\u0010��
\u0013��n�K�.��\u0014ok��:F���b\t\u001a�\u001c\u0017�g@g�2�n{\u0013\n��z\u001a\"�\u0015��,�W��(����\u0010$q���q���
\u001eC���8�Qk�6mx3���2q�0�.N���p�\u0002�S�O�!�G\u001a��$�;*�c*]���\u00051��\u0017�i��g����bfVu��ϭ���\")U�@�3
\aqLۑ�\u000f3��\u000f}�p�\nendstream\nendobj\n10 0 obj\n<</Length1 4960/Filter/FlateDecode/Length ................................ndstream
\nendobj\n9 0 obj\n<</Type/FontDescriptor/FontName/PFWAAB+�o��,Bold/Flags 32/ItalicAngle 0/FontWeight 700/Leading 0/Ascent 1058.105\n
/Descent -261.7188/XHeight 540.0391/AvgWidth 584.9609/CapHeight 756.3477/StemV 160/MaxWidth 1000/FontBBox[12.6953125 -136.23046875
985.3515625 821.77734375]\n/FontFile2 10 0 R>>\nendobj\n11 0 obj\n[3[1000]]\nendobj\n6 0 obj\n<</Type/Font/Subtype/Type0/BaseFont
/PFWAAB+�o��,Bold/Encoding/Identity-H/ToUnicode 7 0 R/DescendantFonts[8 0 R]>>\nendobj\n4 0 obj\n<</Type/Page/Parent 2 0 R
/MediaBox[0 0 595.2756 841.8898]/Contents[5 0 R]/Resources<</ProcSet [/PDF/Text/ImageB/ImageC/ImageI]\n/Font <</F2 6 0 R>>\n>>>>\
nendobj\n5 0 obj\n<</Length 55>>\nstream\nq\nq\n0 0 0 rg\nBT\n50.25 770.577 Td\n/F2 9 Tf(\0\u0003)Tj\nET\nQ\nQ\n\nendstream\nendobj\n8 0 obj
\n<</Type/Font/Subtype/CIDFontType2/BaseFont/PFWAAB+�o��,Bold/CIDSystemInfo<</Ordering(Identity)/Registry(Adobe)\n/Supplement 0>>
\n/FontDescriptor 9 0 R/W 11 0 R>>\nendobj\nxref\n0 12\n0000000000 65535 f \n0000000009 00000 n \n0000000054 00000 n \n0000000000 00000 f
\n0000003905 00000 n \n0000004072 00000 n \n0000003776 00000 n \n0000000105 00000 n \n0000004175 00000 n \n0000003445 00000 n
\n0000000392 00000 n \n0000003750 00000 n \ntrailer\n<</Root 1 0 R/ID[<474b3c8539d96eb244f8fb3f8ed789ac><474b3c8539d96eb244f8fb3f8ed789ac>]
/Size 12>>\nstartxref\n4350\n%%EOF\n"

 
里面大部分內容被我精簡過了,中間部分的內容基本是被壓縮過的內容,全是亂碼,需要特定的算法解壓。以上就是通過二進制流讀取PDF文件的大概內容,幾乎沒有可讀性可言啊,一般情況下如果不了解PDF格式,基本處於懵逼狀態,有強迫症的可能還會崩潰,呵呵開個玩笑哈。上面只是給大家看個全貌啊,PDF文件解析不會這么干啊。以上那坨內容具有實際解析意義的也就是前后1024個字節,在這2048個字節里面,解析工作主要是處理最后面這1024個字節。接下來我們看看具體怎么解析(思路)。
1.首先我們需要索引到交叉引用表(startxref)的地址,后面的數值對應的也就是上一個xref的起始地址,緊跟着后面就是對象總數,這里是12個間接對象。2.接着處理文件尾trailer,獲取size對象。3.把流的偏移址移動到交叉引用段xref,獲取所有間接對象,過濾掉被釋放的對象,f表示該對象被刪除。4.獲取所有間接對象的屬性,從間接對象里面獲取對象基地址如0000000009,表示該對象的流地址是9,族個解析。到此所有間接對象初始化已完成,后續就是通過key索引內容,在這里我們的目標很簡單就是讀取PDF里面的文本內容,包括x、y坐標,字體、大小、顏色等等,還原到這一步,是不是整個PDF文件都是你的菜了,看效果。
 
上面就是按照之前邏輯思路解析出來PDF文件的所有間接對象,上面這張圖片我大概解釋一下啊,Catalog根目錄對象,position文件流地址索引,pages文檔頁面樹的根結點,引用的是間接對象2 0 R,表示page這個間接對象在2號對象,簡單解釋就到這吧。到此我們要讀取的文本內容、坐標啥的是不是啥都沒看到?是的,確實如此,到這里只是把整個PDF文件的所有對象加載出來,而我們需要讀取的內容、坐標等信息就包含在它們里面,並且一般情況下被壓縮過。
 
還原文本信息,看到上面這一大坨間接對象,我們怎么找呢?1.我們索引到page這個對象,里面有個資源字典,該字典里面包含了一個間接對象6 0 R,2.接着我們索引到對象號6這個間接對象,在這個對象里面有個屬性ToUnicode引用了間接對象7 0 R,3.繼續索引到對象號7,是不是很繁瑣啊?呵呵,接着來,不要怕繁瑣,奇跡會出現的,ADOBE公司出品的必屬精品,在這個對象號里面終於發現奇跡快出現了,我們通過偏移址和長度讀取這部分二進制流,接着重頭戲來了,FlateDecode過濾器表示該部分內容通過zlib壓縮(可能會有更復雜的操作,帶參數),過濾器我的理解就是壓縮算法,文檔上面描述還支持很多如LZWDecode等等,LZWDecode同赫夫曼屬於無損壓縮,4.通過deflate解壓這部分二進制流。還有一種解析方案,直接索引Contents對象號,解析它里面的二進制流,這部分二進制流可以索引所有內容信息,或者間接引用對象,當然這部分二進制流同樣需要解壓,這里我貼出解析出來的contents和文本內容信息里面的內容吧
contents
BT                                                                                                              
50.25 770.577 Td
/F2 9 Tf // 通過字體資源索引文本內容
文本內容
 <0003> Tj         
 
/CIDInit /ProcSet findresource begin
14 dict begin
begincmap
/CIDSystemInfo<</Registry(Adobe)/Ordering (UCS)/Supplement 0>>def
/CMapName/Adobe-Identity-UCS def
/CMapType 2 def
1 begincodespacerange
<0000><FFFF>
endcodespacerange
1 beginbfrange
<0003> <53cd> // unicode字符集,這就是我們要找的內容
endbfrange
endcmap
CMapName currentdict /CMap defineresource pop
end
end 
                                                                            
拿到以上內容,我們的文本內容屬性就全部拿到了,有繪制坐標、字體、size、unicode字符集、編碼等內容,到現在是不是簡單的一份PDF文件,基本被我們蹂躪了?呵呵,不要急還有比較復雜的一步操作,嵌入的字體文件處理。我們知道字體ttf、ttc其實際就是一堆字符輪廓(有矢量、點、位圖),通過設備繪制輸出,接下來我們通過對象集合索引出字體文件,看,不要看我,看上面對象集合圖,這些不是亂看的啊,通過對象引用追朔下來的啊,在8號對象里面找到屬性FontDescriptor 9 0 R表示字體文件描述信息在9號對象里面,繼續索引9號對象,最終守得雲開見月明,找到屬性FontFile2 10 0 R表示trueType字體信息在10號對象,看內容。
 
............cvt XMTñ.......´fpgm,.·,...P..
.glyfà.3ë..
X...¨head
b¬¥.......6hhea.u.....8...$hmtx.[.Í...\....loca.ý.R...l...
maxp.O.....x... prep.Kî:.......Å.c.....
 
刪了很多信息啊,在里面是不是發現了head hmtx字樣,這就是字體文件ttf、ttc字體文件結構,透露一下啊,如果我們自己實現PDF編輯工具,字體嵌入的實現,其實際就是創建這么一個ttf、ttc字體文件。看到上面這些東西,朋友們不要心急啊,這玩意還用不了啊,因為這部分內容信息同樣壓縮了,需要解壓。我們現在還缺少一個步驟,通過字體繪制到顯示器,到這里就比較簡單了啊,拿到字體文件信息,各平台有加載ttf、ttc的類庫,直接加載,然后設置相關字體信息就完成了。題外話,整個嵌入字體文件,如果基於代碼實現還是比較復雜的啊,需要非常熟悉trueType、openType等字體結構,差不多就到這吧,寫入同樣按上面的邏輯來,當然肯里面還有些尋字節偏移址細節,需要自己多注意,
最后看看Linux上面的現實效果

 

 

就到這吧。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM