尝试了一下使用Python来修改创建PDF文件。试用了PyPDF2pdfrw这两个库之后还是前者提供的功能更丰富些,在这里记录下PyPDF2的简单使用。

方法

因为只是API接口的直接调用,也没什么可分析的,直接附上代码,

class Stripper(object):
    def __init__(self, in_path, out_path, mark):
        self.in_path = in_path
        self.out_path = out_path
        self.mark = mark
        self.reader = None
        self.writer = None

    def execute(self):
        with open(self.in_path, 'rb') as inputs:
            self.reader = PdfFileReader(inputs)
            self.writer = PdfFileWriter()
            self.writer.cloneReaderDocumentRoot(self.reader)
            total_nums = self.reader.getNumPages()
            print 'total_nums', total_nums, self.in_path
            for idx in xrange(total_nums):
                page = self.reader.getPage(idx)
                self.strip_annots(page)
                self.strip_content(page)
            with open(self.out_path, 'wb') as outputs:
                self.writer.write(outputs)
                outputs.flush()

    def strip_annots(self, page):
        annots = page['/Annots']
        for link_info in list(annots):
            obj_info = self.reader.getObject(link_info)
            if '/A' not in obj_info:
                continue
            if self.mark in obj_info['/A'].get('/URI', ''):
                annots.remove(link_info)

    def strip_content(self, page):
        contents = page['/Contents']
        for content_info in list(contents):
            content = self.reader.getObject(content_info)
            if not hasattr(content, 'getData'):
                continue
            if self.mark not in content.getData():
                continue
            contents.remove(content_info)

上述 Stripper 类的功能是移除PDF中不想看到的某些内容,具体其实是移除来自it-ebooks.info加在页脚上的水印。大体步骤如下,

  1. PdfFileReader拷贝内容至PdfFileWriter
  2. 分析处理PDF每一页,对其中的内容进行相应的修改移除
  3. 将修改后的结果进行保存

PDF本身的格式没有花时间去了解,上述两个库的文档也都很简略。通过它们的示例能简单了解其功能,更具体的接口需要直接看代码。自己尝试的是用PyCharm断点跟踪来分析相应接口的用法与返回的数据结构。

问题

最先想使用PdfFileReaderaddPage接口一页一页从PdfFileWriter中进行添加,然后进行处理与输出。按照这个思路实现后先后遇到两个问题。

首先是导航书签的缺失,在分析相关接口之后实现了手动添加导航书签的方法,同时好像也发现了库的一出bug。虽然追后没采用这个方法,这里也权且记录一下,

def add_bookmarks(self):
    self._add_bookmark_imp(self.reader.outlines, None)

def _add_bookmark_imp(self, destinations, parent=None):
    bookmark = None
    for destination in destinations:
        if isinstance(destination, list):
            self._add_bookmark_imp(destination, bookmark)
        else:
            page_num = self.reader.getDestinationPageNumber(destination)
            title = unicode(destination.title)
            bookmark = self.writer.addBookmark(title, page_num, parent)

PdfFileWriteraddBookmark存在bug,在标签名称包含unicode字符的情况输出的文件中会显示奇怪字符,对PyPDF2/pdf.py需要修改713行,将,

dest = Destination(NameObject("/"+title + " bookmark"), pageRef, NameObject(fit), *zoomArgs)

修改为,

dest = Destination(createStringObject("/"+title + " bookmark"), pageRef, NameObject(fit), *zoomArgs)

另一个问题是内部跳转关系丢失。在分析代码之后,应当是可以通过PdfFileWriteraddNamedDestinationObject接口来手动创建的。但自己没找到正确的调用姿势,怎么添加在输出的文件中这个关系都还是建立的不正确。

最后还有一个问题是对某些PDF文件直接调用PdfFileReaderPdfFileWriter进行读写会报错,估计是库有问题。