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上面的现实效果

 

 

就到这吧。

posted @ 2020-12-09 12:29  小菜 。  阅读(3372)  评论(0编辑  收藏  举报