用Python為PDF檔案批量新增書籤

weixin_34320159發表於2017-12-30

本文講述的核心庫:PyPDF2
官方文件:http://pythonhosted.org/PyPDF2/

平時看一些大部頭的技術書籍,大多數都是PDF版的,而且有一些書籍是影印掃描版的,幾百上千頁的書,沒有任何書籤,想要找到一個章節的位置非常費勁。那麼就想,能不能搞一個工具,來自動地為這些大部頭的PDF書籍新增書籤便於自己閱讀呢?下面就是這樣一個工具的開發過程。

為PDF檔案新增一個最簡單的書籤

學習使用一個技術,我們都從最簡單的開始入手。比如我現在想為一個名為book.pdf的PDF檔案新增一個Hello World書籤,該怎麼做呢?show code:

# coding:utf-8
# 往pdf檔案中新增書籤
from PyPDF2 import PdfFileReader as reader,PdfFileWriter as writer

import sys
reload(sys)
sys.setdefaultencoding('utf-8')

def main():
    # 讀取PDF檔案,建立PdfFileReader物件
    book = reader('./book.pdf')

    # 建立PdfFileWriter物件,並用拷貝reader物件進行初始化
    pdf = writer()
    pdf.cloneDocumentFromReader(book)

    # 新增書籤
    # 注意:頁數是從0開始的,中文要用unicode字串,否則會出現亂碼
    # 如果這裡的頁碼超過文件的最大頁數,會報IndexError異常
    pdf.addBookmark(u'Hello World! 你好,世界!',2)

    # 儲存修改後的PDF檔案內容到檔案中
    # 注意:這裡必須用二進位制的'wb'模式來寫檔案,否則寫到檔案中的內容都為亂碼
    with open('./book-with-bookmark.pdf','wb') as fout:
        pdf.write(fout)

if __name__ == '__main__':
    main()

執行上述程式碼,發現當前目錄下生成了一個名為book-with-bookmark.pdf的檔案,開啟這個檔案,看到成功新增了一個書籤:

8819542-2b7e97382c072042.png

點選這個書籤,會自動跳轉到第3頁。

PDF處理工具類

下面先編寫一個功能更為豐富的PDF處理工具類,程式碼如下:

# coding:utf-8
# 封裝的PDF文件處理工具
from PyPDF2 import PdfFileReader as reader,PdfFileWriter as writer
import os

import sys
reload(sys)
sys.setdefaultencoding('utf-8')

class PDFHandleMode(object):
    '''
    處理PDF檔案的模式
    '''
    # 保留源PDF檔案的所有內容和資訊,在此基礎上修改
    COPY = 'copy'
    # 僅保留源PDF檔案的頁面內容,在此基礎上修改
    NEWLY = 'newly'

class MyPDFHandler(object):
    '''
    封裝的PDF檔案處理類
    '''
    def __init__(self,pdf_file_path,mode = PDFHandleMode.COPY):
        '''
        用一個PDF檔案初始化
        :param pdf_file_path: PDF檔案路徑
        :param mode: 處理PDF檔案的模式,預設為PDFHandleMode.COPY模式
        '''
        # 只讀的PDF物件
        self.__pdf = reader(pdf_file_path)

        # 獲取PDF檔名(不帶路徑)
        self.file_name = os.path.basename(pdf_file_path)
        #
        self.metadata = self.__pdf.getXmpMetadata()
        #
        self.doc_info = self.__pdf.getDocumentInfo()
        #
        self.pages_num = self.__pdf.getNumPages()

        # 可寫的PDF物件,根據不同的模式進行初始化
        self.__writeable_pdf = writer()
        if mode == PDFHandleMode.COPY:
            self.__writeable_pdf.cloneDocumentFromReader(self.__pdf)
        elif mode == PDFHandleMode.NEWLY:
            for idx in range(self.pages_num):
                page = self.__pdf.getPage(idx)
                self.__writeable_pdf.insertPage(page, idx)

    def save2file(self,new_file_name):
        '''
        將修改後的PDF儲存成檔案
        :param new_file_name: 新檔名,不要和原檔名相同
        :return: None
        '''
        # 儲存修改後的PDF檔案內容到檔案中
        with open(new_file_name, 'wb') as fout:
            self.__writeable_pdf.write(fout)
        print 'save2file success! new file is: {0}'.format(new_file_name)

    def add_one_bookmark(self,title,page,parent = None, color = None,fit = '/Fit'):
        '''
        往PDF檔案中新增單條書籤,並且儲存為一個新的PDF檔案
        :param str title: 書籤標題
        :param int page: 書籤跳轉到的頁碼,表示的是PDF中的絕對頁碼,值為1表示第一頁
        :paran parent: A reference to a parent bookmark to create nested bookmarks.
        :param tuple color: Color of the bookmark as a red, green, blue tuple from 0.0 to 1.0
        :param list bookmarks: 是一個'(書籤標題,頁碼)'二元組列表,舉例:[(u'tag1',1),(u'tag2',5)],頁碼為1代表第一頁
        :param str fit: 跳轉到書籤頁後的縮放方式
        :return: None
        '''
        # 為了防止亂碼,這裡對title進行utf-8編碼
        self.__writeable_pdf.addBookmark(title.decode('utf-8'),page - 1,parent = parent,color = color,fit = fit)
        print 'add_one_bookmark success! bookmark title is: {0}'.format(title)

    def add_bookmarks(self,bookmarks):
        '''
        批量新增書籤
        :param bookmarks: 書籤元組列表,其中的頁碼錶示的是PDF中的絕對頁碼,值為1表示第一頁
        :return: None
        '''
        for title,page in bookmarks:
            self.add_one_bookmark(title,page)
        print 'add_bookmarks success! add {0} pieces of bookmarks to PDF file'.format(len(bookmarks))

    def read_bookmarks_from_txt(self,txt_file_path,page_offset = 0):
        '''
        從文字檔案中讀取書籤列表
        文字檔案有若干行,每行一個書籤,內容格式為:
        書籤標題 頁碼
        注:中間用空格隔開,頁碼為1表示第1頁
        :param txt_file_path: 書籤資訊文字檔案路徑
        :param page_offset: 頁碼便宜量,為0或正數,即由於封面、目錄等頁面的存在,在PDF中實際的絕對頁碼比在目錄中寫的頁碼多出的差值
        :return: 書籤列表
        '''
        bookmarks = []
        with open(txt_file_path,'r') as fin:
            for line in fin:
                line = line.rstrip()
                if not line:
                    continue
                # 以'@'作為標題、頁碼分隔符
                print 'read line is: {0}'.format(line)
                try:
                    title = line.split('@')[0].rstrip()
                    page = line.split('@')[1].strip()
                except IndexError as msg:
                    print msg
                    continue
                # title和page都不為空才新增書籤,否則不新增
                if title and page:
                    try:
                        page = int(page) + page_offset
                        bookmarks.append((title, page))
                    except ValueError as msg:
                        print msg

        return bookmarks

    def add_bookmarks_by_read_txt(self,txt_file_path,page_offset = 0):
        '''
        通過讀取書籤列表資訊文字檔案,將書籤批量新增到PDF檔案中
        :param txt_file_path: 書籤列表資訊文字檔案
        :param page_offset: 頁碼便宜量,為0或正數,即由於封面、目錄等頁面的存在,在PDF中實際的絕對頁碼比在目錄中寫的頁碼多出的差值
        :return: None
        '''
        bookmarks = self.read_bookmarks_from_txt(txt_file_path,page_offset)
        self.add_bookmarks(bookmarks)
        print 'add_bookmarks_by_read_txt success!'

MyPDFHandler類可以用一個PDF物件進行初始化,支援從一個txt檔案中讀取要新增的書籤列表,然後根據這個書籤列表自動為PDF新增書籤,書籤列表txt檔案是類似這樣格式的檔案,書籤標題和頁碼(一個整數,代表書籤的相對頁碼數,可以為負數)用@作為分隔符隔開:

目錄@-5
【第一篇 開發基礎】@1
第1章 Eclipse平臺簡介@1
    1.1 Eclipse整合開發環境(IDE)介紹@2
    1.2 什麼是Eclipse@9
    1.3 SWT/JFace技術@11
    1.4 外掛技術和OSGi@12
    1.5 RCP技術@15
    1.6 EMF技術@16
    1.7 GEF技術@17

編寫書籤列表txt檔案

從網上找到一本大部頭的書籍Eclipse外掛開發學習筆記.pdf,這是一本非常好的Eclipse外掛開發入門書籍,但是是掃描版的,沒有任何書籤,看起來比較費勁。

這是這本書的目錄:


8819542-c4491ce0c7b13e17.png

下面手工將目錄內容和頁碼資訊錄入新增書籤所需的書籤列表文字檔案中(bookmarks-eclipse_plutin.txt):

[meta]
page_offset = 11

目錄@-5
【第一篇 開發基礎】@1
第1章 Eclipse平臺簡介@1
    1.1 Eclipse整合開發環境(IDE)介紹@2
    1.2 什麼是Eclipse@9
    1.3 SWT/JFace技術@11
    1.4 外掛技術和OSGi@12
    1.5 RCP技術@15
    1.6 EMF技術@16
    1.7 GEF技術@17

第2章 SWT/JFace概述@19

第3章 SWT程式設計基礎@39

第4章 使用基本控制元件與對話方塊@64

第5章 容器與佈局管理器@92

第6章 介面開發工具@121

第7章 高階控制元件使用@135

第8章 SWT/JFace的事件處理@166

【第二篇 核心技術】@183
第9章 Eclipse外掛體系結構@183
    9.1 Eclipse體系結構@184
    9.2 外掛的載入過程@187
    9.3 外掛的擴充套件模式@191

第10章 開發第一個外掛專案@196
    10.1 建立外掛工程@197
    10.2 "外掛開發"透檢視@200
    10.3 外掛工程結構@203
    10.4 外掛檔案@204
    10.5 外掛類@207
    10.6 執行外掛程式@208
    10.7 除錯外掛@210
    10.8 釋出外掛@211

第11章 操作(Actions)@213
    11.1 Eclipse中的操作概覽@214
    11.2 新增工作臺視窗操作@214
    11.3 IAction與IActionDelegate介面@222
    11.4 物件操作@224
    11.5 檢視操作@230
    11.6 編輯器操作@234
    11.7 快捷鍵對映@237

第12章 檢視(Views)@241
    12.1 Eclipse檢視體系結構概覽@242
    12.2 Eclipse工作環境中的檢視@243
    12.3 建立一個檢視@248
    12.4 檢視類@250
    12.5 為檢視新增操作@260
    12.6 檢視間通訊@265
    12.7 新增狀態列支援@272
    12.8 檢視狀態@273
    12.9 載入和解除安裝圖示@279

第13章 編輯器(Editors)@282
    13.1 Eclipse編輯器體系結構概覽@283
    13.2 Eclipse工作環境中的編輯器@284
    13.3 為例子增加一個編輯器@289
    13.4 編輯器使用的資料模型@294
    13.5 編輯器頁面@301
    13.6 響應編輯器更改@313
    13.7 儲存編輯器模型@318
    13.8 編輯器生命週期@322
    13.9 為編輯器新增操作@326

第14章 透檢視(Perspectives)@334
    14.1 什麼是透檢視@335
    14.2 建立一個透檢視@336
    14.3 IPageLayout@339
    14.4 填充透檢視@341
    14.5 擴充套件現有透檢視@344

第15章 對話方塊和嚮導@349
    15.1 對話方塊和嚮導概述@350
    15.2 對話方塊類別@350
    15.3 為例子增加SWT對話方塊@354
    15.4 建立JFace對話方塊@355
    15.5 嚮導介紹@362
    15.6 新增嚮導@364

第16章 首選項(Preferences)@379
    16.1 首選項頁面結構@381
    16.2 新增首選項頁面@382
    16.3 示例首選項@383
    16.4 為例子建立首選項頁面@387

第17章 幫助內容(Help Contents)@397

第18章 備忘單(CheatSheet)@410

【第三篇 高階進階】@426
第19章 外掛開發高階內容@426

第20章 富客戶端平臺(RCP)技術@473

第21章 Draw2d@509

第22章 GEF介紹與實現@526

【第四篇 綜合例項】@586
第23章 外掛開發例項@586

第24章 GEF例項@630

注:一開始的page_offset的值表示書籤頁碼的偏移量,即某一頁所在的實際頁碼與PDF目錄中所寫的頁碼值的差值,這是考慮到在PDF的目錄頁之前還會有其他的一些封面、前言等頁面,實際頁碼會和目錄中所寫的頁碼不一致。

為PDF批量新增書籤

上面的準備工作就做好了,下面來開始為Eclipse外掛開發學習筆記.pdf這本書新增目錄:

# coding:utf-8
# 新增PDF書籤
from pdf_utils import MyPDFHandler,PDFHandleMode as mode
import sys
reload(sys)
sys.setdefaultencoding('utf-8')

def main():
    pdf_handler = MyPDFHandler(u'Eclipse外掛開發學習筆記.pdf',mode = mode.NEWLY)
    pdf_handler.add_bookmarks_by_read_txt('./bookmarks-eclipse_plutin.txt',page_offset = 11)
    pdf_handler.save2file(u'Eclipse外掛開發學習筆記-目錄書籤版.pdf')

if __name__ == '__main__':
    main()

執行上面程式碼,發現在當前目錄生成了一個名為'Eclipse外掛開發學習筆記-目錄書籤版.pdf的檔案,開啟它,看到書籤已經全部完美地新增了進去,並且點選各個書籤頁面跳轉的位置也是正確的,大大地方便了平時的閱讀:

8819542-33d2ffaac6d5e3ab.png

本文程式碼GitHub地址

本文涉及到的程式碼都放在了本人的GitHub

相關文章