PdfToy应用案例(九)
在readfree论坛复活以后,我上去看了一下以前发的《PDF应用案例》系列文章都还在,那就接着以前的编号继续发。另外考虑到现在不少PT用户并非readfree论坛成员,所以新教程将在我的博客上同步发布。不过以前的教程就只能在readfree看,转过来实在太累,不值当。
===================================================================================
案例十四:去除PDF文件的时间限制
本案例的示例PDF文件及最终的过滤表达式见这里:
链接:https://pan.baidu.com/s/1Tn6gTbHfMxx31OjXLgWA3w
提取码:9gwt
事先声明:
1、该示例文件仅为教学讲解之用,因此仅从原文中抽取了两页,无意侵犯原文版权。
2、PDF文件中与原下载时间、下载者相关的信息,均做了力所能及的混淆处理,避免被回溯。
3、在页面提取过程中Acrobat自动对PDF文件结构做了修改,因此下面给出的正则表达式仅针对这两页示例文件,不一定能够直接对原始完整PDF进行处理,但原理是一样的。
言归正传。从上面的链接下载示例文件包,解压,双击示例PDF用Acobat打开,则Acrobat会弹出“警告:JavaScript 窗口”,提示“View date of this document has ended. Please download again.”(文件已过期,请重新下载),点击“确定”按钮后,则看不到正常的PDF页面内容,每页只能看到文档的下载时间和下载者ID:Downloaded at 2022/08/02 12:04:297 by 64026E08 SLRge#WyzcK
如果想先看PT的处理效果,可以:
- 如果已经打开了示例PDF文件,请务必先关闭它,否则下面的流过滤功能将失效。
- 运行PT,在“流过滤”界面中,点击工具条上最右边的“调入”按钮,调入解压出来的正则表达式文件(pte文件),然后把示例PDF拖拽到“需要处理的PDF文件或文件夹”窗口,点击“开始过滤”按钮,等待结束。
- 双击过滤后的示例PDF,这时可以看到页面的真实内容完整显示出来,也不会提示文件已过期,证明正则表达式的过滤结果很圆满。
下面就讲解pte文件中的这两个正则表达式是怎么来的。
首先重新解压下载到的示例文件包,使示例PDF回到原始状态,然后按照用PT处理PDF的习惯,先在“文件结构”界面中,导出示例PDF的文件树,注意勾选“包括图像对象”,结果如下:
page 1: 13 0 R used images: 21 0 R: 56 x 596, Flate, DeviceRGB, comps=3, bits=8 content: 19 0 R page 2: 14 0 R content: 22 0 R
看到第1页显示used images,就知道其实第1页内容应该是已经显示出来了,那个“Downloaded at ……”的提示不过是覆盖在原PDF页面内容之上而已。推理过程简单明了,毫无技术含量:
- PT导出页面树的时候,如果勾选了“包括图像对象”,就会在内存中对PDF文件进行模拟显示,只有真正显示出来的图像对象,才会报告used images。所以对于大型PDF,勾选此选项后导出速度要比不勾选慢得多,但有时候是不得不勾选。
- 那个“Downloaded at ……”的提示是纯文字,根本没有用到图像,所以这里报告的used images,只能是在真正的页面内容中用到了。
既然如此,那就用同一个界面中的“导出指定的PDF对象”,把第1页的页面字典(对象13)、内容流对象(对象19)导出。直接用记事本看导出的结果,内容流部分可能会粘连在一起,比较难看,用UltraEdit32看就比较整齐了。
大概看了一下,内容流部分比较正常,再次验证了前面“页面内容应该是已经显示出来了”的猜测,但是页面字典中的注释部分(/Annots [ 10 0 R ])引起了我的重视,因为在PDF显示的时候,注释内容是显示在页面内容之上的,所以如果注释的范围足够大,就可以把正常页面内容完全遮盖住。为此导出注释对象(对象10),结果如下:
10 0 obj
<< /AP << /N 15 0 R >> /BS << /S /S /W 1 >> /DA (/Helv 14 Tf 0 0 0 rg ) /DR << /Font << /Helv 16 0 R >> >> /F 4 /FT /Tx /Ff 0 /MK << /BG [ 1 1 1 ] >> /P 13 0 R /Q 1 /Rect [ 595 0 0 842 ] /Subtype /Widget /T (BZ3Sb6bF0) /Type /Annot /V (Downloaded at 2022/08/02 12:04:297 by 64026E08 SLRge#WyzcK ) >>
果然在其中看到了遮盖正常页面内容的“Downloaded at ……”提示。继续追踪注释内容,即/AP << /N 15 0 R >> ,导出对象15的内容,可以看到是一个表单(Form),先对整个页面范围填白(1 1 1 rg 0 0 842 595 re f),然后显示“Downloaded at ……”提示。
至此可以总结一下前面的分析结论:示例PDF中的正常内容其实已经显示出来,但被注释内容所覆盖,去掉注释即可正常显示。
既然如此,那么第一个正则表达式就比较好写了,即在页面对象中把/Annots [ 10 0 R ]之类的替换成空即可。调入下载到的pte文件,双击第一个表达式就可以看到,即把表达式/Annots \[ [\d]+? 0 R \]替换成空,替换范围是匹配/Type /Page的字典对象。
仅用这个表达式对示例PDF进行过滤,然后双击用Acrobat打开,可以看到“警告:JavaScript 窗口”还在,但点击“确定”按钮以后,已经可以看到页面的正常内容。
所以下面还要继续努力,把打开PDF时的提示窗口去掉。
重新解压下载到的示例文件包,使示例PDF回到原始状态,然后用UltraEdit32打开,搜索ASCII字符串JavaScript,可以找到两个结果:
5 0 obj
<< /JavaScript 12 0 R >>
endobj
18 0 obj
<< /JS 24 0 R /S /JavaScript >>
endobj
把对象12导出,其内容为:
12 0 obj
<< /Names [ (0000000000000000) 18 0 R ] >>
所以对象5、12、18其实是一个对象,只不过被生生拆开了而已,原因不明,也没必要知道。有兴趣的还可以把这个俄罗斯套娃一路追下去,直到看到真正对时间进行判断的JavaScript代码(对象15)。
在UltraEdit32里搜索对象5的引用,即ASCII字符串5 0 R,可以看到是在Catalog里引用:
1 0 obj
<< /AcroForm 3 0 R /Metadata 4 0 R /Names 5 0 R /Pages 6 0 R /Type /Catalog >>
endobj
按照《PDF Reference》规定,每个PDF文件只有一个有效的Catalog对象,该对象在打开PDF时被解析,解析到/Names 5 0 R时就会执行其中的JavaScript代码,对时间进行判断,发现超期就弹出提示对话框,并控制各页中注释对象的显示或隐藏。
所以再加一个正则表达式对Catalog对象进行过滤,去掉对JavaScript代码的调用,即可避免超期弹框。调入下载到的pte文件,双击第二个表达式就可以看到,即把表达式/Names [\d]+? 0 R替换成空,替换范围是匹配/Type /Catalog的字典对象。
如果是强迫症患者,可能还会对页面左下角、右上角的灰色文字水印有所不满,不过这种固定内容的ASCII字符串替换起来太容易,就不多说了。
另外以上都是用“流过滤”功能进行处理,如果对自己的技术有足够的信心,也可以换一个思路:
- 重新解压下载到的示例文件包,使示例PDF回到原始状态。
- 用PT的“解密++”功能,先点一下“恢复缺省参数”,然后把“压缩”改成“解压”,把示例PDF拖过来,点“开始处理”,把PDF中的压缩内容都展开。
- 然后用UltraEdit32打开解压后的PDF,搜索ASCII字符串“function”,定位到对时间进行判断的JavaScript函数处,然后按快捷键Ctrl+E进入十六进制编辑模式。
- 除了最后那个for循环外,函数secure中的其余内容均用等量的空格覆盖,千万不能直接删除。
- 存盘退出UltraEdit32。
- 双击用Acrobat打开编辑过的PDF,看上去也能达到需要的效果。然后在Acrobat中另存为,即可重新对PDF进行压缩。
函数secure中仅保留以下JS代码:
for(var i = 0; i < 2; i++){ var over = 'BZ3Sb6bF'+i; this.getField(over).display = 1; }
(完)