<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          PyQt5 從零開始制作一個 PDF 閱讀器

          共 8139字,需瀏覽 17分鐘

           ·

          2021-01-25 13:56


          大家好,歡迎來到 Crossin的編程教室 !

          今天,我們分享一個開發(fā)實例:用 Python 的 PyQt5 庫制作帶 UI 界面的 PDF 閱讀器。

          這篇文章介紹了如何創(chuàng)建主界面,以及添加、刪除圖書封面,并實現(xiàn)閱讀功能,可以對 PDF 文檔進(jìn)行翻頁、縮放等基本操作。

          效果圖

          UI 設(shè)計

          首先使用 Qt Designer 設(shè)計出圖形界面:

          新建一個 MainWindow 主界面,然后設(shè)置一個 toolbar,并在 toolbar 中添加三個 action,并為每個 action 設(shè)置好相應(yīng)圖標(biāo)。

          也可以直接 compile 我制作好的 PyReader.ui 文件,或者導(dǎo)入 Ui_PyReader.py 文件。

          依賴要求

          1. Python3

          2. PyQt5

          3. PyMuPDF

          主要任務(wù)

          我們使用 PyMuPDF 來解析 PDF ,來獲取 PDF 文本信息。

          • 安裝

          我們只要在 cmd 中輸入:

          pip?install?PyMuPDF

          即可安裝 PyMuPDF。

          • 導(dǎo)入

          #?導(dǎo)入?PyMuPDF?
          import?fitz

          我們需要了解以下幾個基本操作:

          fitz.open() 函數(shù)用來讀取 PDF 文件內(nèi)容,doc.loadPage() 函數(shù)用來獲取具體某一頁的信息。特別的 ,我們使用loadPage(0) 來獲取封面信息。

          #?讀取?PDF
          doc?=?fitz.open(fname)
          #?獲取第?n?頁內(nèi)容
          page?=?doc.loadPage(n)

          這一部分的主要內(nèi)容就是把封面渲染到主界面中,并完成添加與刪除封面的任務(wù)。

          顯示表格

          我們采用 QtWidgets.QTableWidget 表格控件來顯示封面。

          首先讓我們設(shè)置表格樣式與功能:

          其中,我們設(shè)置了單元格的縱橫比為 4 : 3,以及其他的一些靜態(tài)屬性,并將 self.table 與右鍵菜單綁定,支持點擊單元格調(diào)用 self.generateMenu 函數(shù)。

          def?_setTableStyle(self):
          ????#?開啟水平與垂直滾軸
          ????self.table.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
          ????self.table.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
          ????#?設(shè)置?5?行?8?列?的表格
          ????self.table.setColumnCount(8)
          ????self.table.setRowCount(5)
          ????#?設(shè)置標(biāo)準(zhǔn)寬度
          ????self.width?=?self.screen.width()?//?8
          ????#?設(shè)置單元格的寬度
          ????for?i?in?range(8):
          ????????self.table.setColumnWidth(i,?self.width)
          ????#?設(shè)置單元格的高度
          ????#?設(shè)置縱橫比為?4?:?3
          ????for?i?in?range(5):
          ????????self.table.setRowHeight(i,?self.width?*?4?//?3)
          ????#?隱藏標(biāo)題欄
          ????self.table.verticalHeader().setVisible(False)
          ????self.table.horizontalHeader().setVisible(False)
          ????#?禁止編輯
          ????self.table.setEditTriggers(QAbstractItemView.NoEditTriggers)
          ????#?不顯示網(wǎng)格線
          ????self.table.setShowGrid(False)
          ????#?將單元格綁定右鍵菜單
          ????#?點擊單元格,調(diào)用?self.generateMenu?函數(shù)
          ????self.table.setContextMenuPolicy(Qt.CustomContextMenu)
          ????self.table.customContextMenuRequested.connect(self.generateMenu)

          添加封面

          首先讓我們來看如何生成 TableWidget 可顯示的 圖像類文件。

          我們通過 doc.loadPage(0) 獲取頁面對象,并傳遞給 render_pdf_page() 函數(shù),設(shè)置縮放比為 1 : 1。首先構(gòu)建 QImage 對象,在通過 convertFromImage 函數(shù)將 QImage 對象轉(zhuǎn)化為可顯示對象。

          #?顯示?PDF?封面
          #?page_data?為?page?對象
          def?render_pdf_page(page_data,?for_cover=False):
          ????#?圖像縮放比例
          ????zoom_matrix?=?fitz.Matrix(4,?4)
          ????if?for_cover:
          ????????zoom_matrix?=?fitz.Matrix(1,?1)

          ????#?獲取封面對應(yīng)的?Pixmap?對象
          ????#?alpha?設(shè)置背景為白色
          ????pagePixmap?=?page_data.getPixmap(
          ????????matrix?=?zoom_matrix,?
          ????????alpha=False)?
          ????#?獲取?image?格式
          ????imageFormat?=?QtGui.QImage.Format_RGB888?
          ????#?生成?QImage?對象
          ????pageQImage?=?QtGui.QImage(
          ????????pagePixmap.samples,
          ????????pagePixmap.width,?
          ????????pagePixmap.height,?
          ????????pagePixmap.stride,
          ????????imageFormat)

          ????#?生成?pixmap?對象
          ????pixmap?=?QtGui.QPixmap()
          ????pixmap.convertFromImage(pageQImage)
          ????return?pixmap

          接著,我們就要向單元格中添加封面圖片:

          我們使用工具欄中的 + 號來添加 PDF 封面。

          self.addbar.triggered.connect(self.open),當(dāng)點擊 + 時,就會調(diào)用 self.open 函數(shù)。

          我們通過 getOpenFileName() 函數(shù)來獲取文件地址,self 后面的三個參數(shù)分別是窗口名稱,文件默認(rèn)路徑以及支持的文件類型。這個函數(shù)返回文件的地址。

          filter_book() 函數(shù)用來確保不會重復(fù)顯示同一本書的封面。

          def?getfile(self):
          ????#?打開單個文件
          ????fname,?_?=?QFileDialog.getOpenFileName(self,?'Open?files',?'./',?'(*.pdf)')
          ????return?fname

          def?open(self):
          ????#?打開文件
          ????fname?=?self.getfile()
          ????if?self.filter_book(fname):
          ????????self.setIcon(fname)

          #?獲取無重復(fù)圖書的地址
          def?filter_book(self,?fname):
          ????if?not?fname:
          ????????return?False
          ????if?fname?not?in?self.booklist:
          ????????self.booklist.append(fname)
          ????????return?True
          ????return?False?????????????????????

          然后,我們就要將 PDF 封面渲染到主界面上:

          label.setScaledContents(True) 使得圖片可以充滿 label。self.table.setCellWidget(self.x, self.y, label) 用來設(shè)置標(biāo)簽的行與列。最后確保每八個元素?fù)Q行,換行后將列數(shù)清零。

          def?setIcon(self,?fname):
          ????#?打開?PDF
          ????doc?=?fitz.open(fname)
          ????#?加載封面
          ????page?=?doc.loadPage(0)
          ????#?生成封面圖像
          ????cover?=?render_pdf_page(page,?True)
          ????label?=?QLabel(self)
          ????#?設(shè)置圖片自動填充?label
          ????label.setScaledContents(True)
          ????#?設(shè)置封面圖片
          ????label.setPixmap(QPixmap(cover))
          ????#?設(shè)置單元格元素為?label
          ????self.table.setCellWidget(self.x,?self.y,?label)
          ????#?刪除?label?對象,防止后期無法即時刷新界面
          ????#?因為?label?的生存周期未結(jié)束
          ????del?label
          ????#?設(shè)置當(dāng)前行數(shù)與列數(shù)
          ????self.crow,?self.ccol?=?self.x,?self.y
          ????#?每?8?個元素?fù)Q行
          ????if?(not?self.y?%?7)?and?(self.y):
          ????????self.x?+=?1
          ????????self.y?=?0
          ????else:
          ????????self.y?+=?1

          右鍵菜單

          上面我們已經(jīng)提到,如何將單元格與右鍵菜單綁定。

          本次教程中,右鍵菜單只有兩項,分別為開始閱讀,以及刪除圖書。

          def?generateMenu(self,?pos):
          ????row_num?=?col_num?=?-1
          ????#?獲取選中的單元格的行數(shù)以及列數(shù)
          ????for?i?in?self.table.selectionModel().selection().indexes():
          ????????row_num?=?i.row()
          ????????col_num?=?i.column()
          ????#?若選取的單元格中有元素,則支持右鍵菜單
          ????if?(row_num?or?(row_num?==?self.crow?and?col_num?<=?self.ccol):
          ????????menu?=?QMenu()
          ????????#?添加選項
          ????????item1?=?menu.addAction('開始閱讀')
          ????????item2?=?menu.addAction('刪除圖書')
          ????????#?獲取選項
          ????????action?=?menu.exec_(self.table.mapToGlobal(pos))
          ????????if?action?==?item1:
          ????????????pass
          ????????#?點擊選項二,調(diào)用?self.delete_book?刪除圖書
          ????????elif?action?==?item2:
          ????????????self.delete_book(row_num,?col_num)

          接下來,讓我們看如何刪除圖書:

          首先維護(hù)一個 self.booklist ,里面儲存無重復(fù) PDF 文件地址。首先獲取圖書在 booklist 中的索引,在 booklist 中刪除該元素。接著清空選中單元格之后(包含選中單元格)的所有單元格的內(nèi)容。最后將 booklist 中 index 之后的圖書地址重新顯示到 table 上。簡單地說,就是刪除選中單元格,并將之后單元格向前挪一位。

          #?刪除圖書
          def?delete_book(self,?row,?col):
          ????#?獲取圖書在列表中的位置
          ????index?=?row?*?8?+?col
          ????self.x?=?row
          ????self.y?=?col
          ????if?index?>=?0:
          ????????self.booklist.pop(index)

          ????i,?j?=?row,?col
          ????while?1:
          ????????#?移除?i?行?j?列單元格的元素
          ????????self.table.removeCellWidget(i,?j)
          ????????#?一直刪到最后一個有元素的單元格
          ????????if?i?==?self.crow?and?j?==?self.ccol:
          ????????????break
          ????????if?(not?j?%?7)?and?j:
          ????????????i?+=?1
          ????????????j?=?0
          ????????else:
          ????????????j?+=?1

          ????#?如果?booklist?為空,設(shè)置當(dāng)前單元格為?-1
          ????if?not?self.booklist:
          ????????self.crow?=?-1
          ????????self.ccol?=?-1
          ????#?刪除圖書后,重新按順序顯示封面圖片
          ????for?fname?in?self.booklist[index:]:
          ????????self.setIcon(fname)

          閱讀功能

          現(xiàn)在我們已經(jīng)完成了 PDF 閱讀器的初始界面。接下來要新增閱讀功能,實現(xiàn)基本的翻頁以及縮放等操作。

          下圖為效果圖:

          下面我們來看具體實現(xiàn):

          選項卡

          QTabWidget 可以允許我們在一個窗口顯示多個頁面。對于書庫的這個選項卡,頁面顯示為 self.table ,即初始界面。

          self.table(QTableWidget) -> self.tabwidget(QTabWidge)

          #?初始化選項卡
          self.tabwidget?=?QTabWidget()
          #?添加書庫選項卡
          self.tabwidget.addTab(self.table,?'書庫')
          self.setCentralWidget(self.tabwidget)
          #?設(shè)置選項卡可以關(guān)閉
          self.tabwidget.setTabsClosable(True)
          #?點擊選項卡叉號時,執(zhí)行?removeTabab?操作
          self.tabwidget.tabCloseRequested[int].connect(self.remove_tab)

          新建選項卡:每次開始閱讀時,新建一個選項卡,名稱為文件名。

          def?read_book(self,?fname):
          ????#?self.close()
          ????#?內(nèi)存有可能泄露
          ????self.doc?=?fitz.open(fname)
          ????#?metadata?=?doc.metadata
          ????title?=?fname.split('/'?or?'\\')[-1].replace('.pdf',?'')

          ????vbox?=?self.book_area(self.doc.loadPage(0))
          ????self.book_add_tab(title,?vbox)

          其中,我們要求主選項卡,即書庫選項卡是不可以關(guān)閉的。

          def?remove_tab(self,?index):
          ????if?index:
          ????????#?當(dāng)前頁數(shù)
          ????????self.current_page?=?0
          ????????self.tabwidget.removeTab(index)
          ????????#?正在閱讀的書
          ????????self.read_list.pop(index)

          閱讀界面的選項卡對應(yīng)的頁面區(qū)域為 QScrollArea ,QScrollArea 支持滾輪操作。也就是說,如果我們縮放 PDF 頁面大小超過 QScrollArea 的大小,那么就會自動出現(xiàn)滾輪,以便我們?yōu)g覽頁面。其中,MyArea 類是對 QScrollArea 的重載,綁定了快捷鍵以支持翻頁以及縮放等操作。

          Pixmap -> label -> area(MyArea) -> vbox(QVBoxLayout) -> tab(QWidget) -> self.tabwidget(QTabWidge)

          def?book_add_tab(self,?title,?vbox):
          ????tab?=?QWidget()
          ????tab.setLayout(vbox)
          ????#?tab?為頁面,title?為標(biāo)簽名稱
          ????self.tabwidget.addTab(tab,?title)

          def?book_area(self,?page):
          ????label?=?self.page_pixmap(page)
          ????#?area?=?QScrollArea()
          ????area?=?MyArea(self)
          ????area.init(self)
          ????area.setWidget(label)

          ????vbox?=?QVBoxLayout()
          ????vbox.addWidget(area)
          ????return?vbox

          下面我們來看看, MyArea 這個類該如何定義:

          MyArea(QScrollArea)

          MyArea 繼承了 QScrollArea 類,所以支持自適應(yīng)滾輪操作。這里,我們定義了 init 方法,用來接受 Reader 主類 的 self 參數(shù), 即通過 self.widget 調(diào)用 Reader 類的實例方法。

          在 init_action 函數(shù)中,我們新建了四個 QShortCut 實例,分別支持快捷鍵實現(xiàn)縮小、放大、下一頁、上一頁的操作。

          class?MyArea(QScrollArea):
          ????def?init(self,?widget):
          ????????self.widget?=?widget
          ????????self.init_action()

          ????def?init_action(self):
          ????????zoom_minus?=?QShortcut(QKeySequence("Ctrl+-"),?self)
          ????????zoom_minus.activated.connect(self.minus)
          ????????zoom_plus?=?QShortcut(QKeySequence("Ctrl+="),?self)
          ????????zoom_plus.activated.connect(self.plus)

          ????????switch_left?=?QShortcut(QKeySequence(Qt.Key_Left),?self)
          ????????switch_left.activated.connect(self.left)
          ????????switch_right?=?QShortcut(QKeySequence(Qt.Key_Right),?self)
          ????????switch_right.activated.connect(self.right)


          ????def?plus(self):
          ????????self.widget.zoom_book(plus=True)

          ????def?minus(self):
          ????????self.widget.zoom_book(plus=False)

          ????def?right(self):
          ????????self.widget.switch_page(right=True)

          ????def?left(self):
          ????????self.widget.switch_page(right=False)

          下面,我們來介紹縮放與翻頁功能的具體實現(xiàn):

          縮放功能

          self.size 用來存儲頁面大小,self.page 正是根據(jù) self.size 來實現(xiàn)縮放功能。

          def?zoom_book(self,?plus=True):
          ????a,?b?=?self.size
          ????if?plus:
          ????????a?+=?0.4
          ????????b?+=?0.4
          ????????self.size?=?(a,?b)
          ????????self.set_page()
          ????elif?not?plus?and?a?>?0:
          ????????if?a?>=?1:
          ????????????a?-=?0.4
          ????????????b?-=?0.4
          ????????self.size?=?(a,?b)
          ????????self.set_page()

          Pixmap -> label -> area(MyArea) -> vbox(QVBoxLayout) -> tab(QWidget) -> self.tabwidget(QTabWidge)

          tab 獲取 tab 對象,layout 獲取 vbox 對象,widget 獲取 area 對象,直接更改 area 上 label 控件。

          def?set_page(self):
          ????#?加載頁面
          ????page?=?self.doc.loadPage(self.current_page)
          ????#?獲取當(dāng)前?Widget
          ????tab?=?self.tabwidget.currentWidget()
          ????#?獲取當(dāng)前的?Layout
          ????layout?=?tab.layout()
          ????#?獲取?Layout?上的控件
          ????widget?=?layout.itemAt(0).widget()
          ????#?獲取已經(jīng)繪制好的?label?對象
          ????label?=?self.page_pixmap(page)
          ????#?將?widget?的內(nèi)容更改為現(xiàn)在的?label?對象
          ????widget.setWidget(label)

          最后我們來介紹如何實現(xiàn)翻頁功能

          翻頁功能

          這次,我們實現(xiàn)的 PDF 閱讀器只能同時閱讀一本書,所以翻頁功能只需由 self.current_page 控制就行。

          self.doc.pageCount 為總頁數(shù),當(dāng)前頁數(shù)不能為負(fù)數(shù)或者大于總頁數(shù)。更改完 self.current_page 之后,就可以執(zhí)行 self.set_page 操作,直接更改 area 上的 label 控件。

          def?set_current_page(self,?right):
          ????if?right?and?self.current_page?1:
          ????????self.current_page?+=?1

          ????elif?not?right?and?self.current_page?>?0:
          ????????self.current_page?-=?1



          def?switch_page(self,?right=True):
          ????self.set_current_page(right)
          ????self.set_page()


          到這里,我們就已經(jīng)實現(xiàn)了一個具有完整功能的 PDF 閱讀器。感興趣的同學(xué)也可以下載代碼后參考實現(xiàn)一個,并在此基礎(chǔ)做一些修改和擴(kuò)展。


          公眾號對話頁回復(fù)關(guān)鍵字 PDF 可獲取源碼。


          覺得這個案例對你有幫助的話,歡迎點贊/收藏/轉(zhuǎn)發(fā)。



          作者:借我一生執(zhí)拗
          來源:Python高效編程


          _往期文章推薦_

          pdfkit,生成PDF就靠它了




          瀏覽 122
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产肏屄青青肏屄视频 | 日本欧美亚洲 | 五月天婷婷色亚洲丁香 | 影音先锋av成人电影在线网址 | 综合 夜夜|