基于Python實(shí)現(xiàn)PDF動(dòng)畫(huà)翻頁(yè)效果的閱讀器
主要功能包括:
- 加載 PDF 文件
- 顯示當(dāng)前頁(yè)面
- 上一頁(yè)/下一頁(yè)切換
- 頁(yè)面切換動(dòng)畫(huà)
C:\pythoncode\new\pdfreader.py
全部代碼
import wx
import fitz # PyMuPDF
from PIL import Image
import time
class PDFReader(wx.Frame):
def __init__(self, parent, title):
super(PDFReader, self).__init__(parent, title=title, size=(800, 600))
self.current_page = 0
self.doc = None
self.page_images = []
self.animation_offset = 0
self.is_animating = False
self.animation_direction = 0
self.next_page_idx = 0
self.init_ui()
self.init_timer()
def init_ui(self):
self.panel = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)
# 創(chuàng)建工具欄
toolbar = wx.BoxSizer(wx.HORIZONTAL)
open_btn = wx.Button(self.panel, label='打開(kāi)PDF')
prev_btn = wx.Button(self.panel, label='上一頁(yè)')
next_btn = wx.Button(self.panel, label='下一頁(yè)')
open_btn.Bind(wx.EVT_BUTTON, self.on_open)
prev_btn.Bind(wx.EVT_BUTTON, self.on_prev_page)
next_btn.Bind(wx.EVT_BUTTON, self.on_next_page)
toolbar.Add(open_btn, 0, wx.ALL, 5)
toolbar.Add(prev_btn, 0, wx.ALL, 5)
toolbar.Add(next_btn, 0, wx.ALL, 5)
self.pdf_panel = wx.Panel(self.panel)
self.pdf_panel.SetBackgroundColour(wx.WHITE)
self.pdf_panel.Bind(wx.EVT_PAINT, self.on_paint)
vbox.Add(toolbar, 0, wx.EXPAND)
vbox.Add(self.pdf_panel, 1, wx.EXPAND | wx.ALL, 5)
self.panel.SetSizer(vbox)
self.Centre()
def init_timer(self):
# 創(chuàng)建定時(shí)器用于動(dòng)畫(huà)
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.on_timer)
def on_open(self, event):
with wx.FileDialog(self, "選擇PDF文件", wildcard="PDF files (*.pdf)|*.pdf",
style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as fileDialog:
if fileDialog.ShowModal() == wx.ID_CANCEL:
return
pdf_path = fileDialog.GetPath()
self.load_pdf(pdf_path)
def load_pdf(self, path):
self.doc = fitz.open(path)
self.current_page = 0
self.page_images = []
# 預(yù)加載所有頁(yè)面
for page in self.doc:
pix = page.get_pixmap()
img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
self.page_images.append(img)
self.render_current_page()
def render_current_page(self):
if not self.doc or self.current_page >= len(self.page_images):
return
panel_size = self.pdf_panel.GetSize()
# 創(chuàng)建背景
background = Image.new('RGB', (panel_size.width, panel_size.height), 'WHITE')
# 獲取當(dāng)前頁(yè)面并調(diào)整大小
current_img = self.page_images[self.current_page].resize(
(panel_size.width, panel_size.height), Image.LANCZOS)
# 如果在動(dòng)畫(huà)中,需要繪制兩個(gè)頁(yè)面
if self.is_animating:
next_img = self.page_images[self.next_page_idx].resize(
(panel_size.width, panel_size.height), Image.LANCZOS)
# 計(jì)算位置并粘貼圖像
if self.animation_direction > 0: # 向右翻頁(yè)
background.paste(current_img, (-self.animation_offset, 0))
background.paste(next_img, (panel_size.width - self.animation_offset, 0))
else: # 向左翻頁(yè)
background.paste(current_img, (self.animation_offset, 0))
background.paste(next_img, (-panel_size.width + self.animation_offset, 0))
else:
# 非動(dòng)畫(huà)狀態(tài),直接顯示當(dāng)前頁(yè)
background.paste(current_img, (0, 0))
# 轉(zhuǎn)換為wx.Bitmap
self.current_bitmap = wx.Bitmap.FromBuffer(
panel_size.width, panel_size.height, background.tobytes())
# 刷新顯示
self.pdf_panel.Refresh()
def start_animation(self, direction):
"""開(kāi)始頁(yè)面切換動(dòng)畫(huà)"""
if self.is_animating:
return
next_page = self.current_page + direction
if next_page < 0 or next_page >= len(self.page_images):
return
self.is_animating = True
self.animation_direction = direction
self.next_page_idx = next_page
self.animation_offset = 0
# 啟動(dòng)定時(shí)器,控制動(dòng)畫(huà)
self.timer.Start(16) # 約60fps
def on_timer(self, event):
"""定時(shí)器事件處理,更新動(dòng)畫(huà)"""
if not self.is_animating:
return
# 更新動(dòng)畫(huà)偏移
panel_width = self.pdf_panel.GetSize().width
step = panel_width // 15 # 調(diào)整這個(gè)值可以改變動(dòng)畫(huà)速度
self.animation_offset += step
# 檢查動(dòng)畫(huà)是否完成
if self.animation_offset >= panel_width:
self.animation_offset = 0
self.is_animating = False
self.current_page = self.next_page_idx
self.timer.Stop()
self.render_current_page()
def on_prev_page(self, event):
if self.is_animating or not self.doc:
return
if self.current_page > 0:
self.start_animation(-1)
def on_next_page(self, event):
if self.is_animating or not self.doc:
return
if self.current_page < len(self.page_images) - 1:
self.start_animation(1)
def on_paint(self, event):
if not hasattr(self, 'current_bitmap'):
return
dc = wx.PaintDC(self.pdf_panel)
dc.DrawBitmap(self.current_bitmap, 0, 0, True)
def main():
app = wx.App()
frame = PDFReader(None, title='PDF閱讀器')
frame.Show()
app.MainLoop()
if __name__ == '__main__':
main()
代碼結(jié)構(gòu)
整個(gè)程序由以下幾個(gè)核心部分組成:
- 初始化 UI 界面
- 加載 PDF 文件
- 顯示 PDF 頁(yè)面
- 頁(yè)面切換動(dòng)畫(huà)
以下是代碼的詳細(xì)解析。
初始化 UI 界面
代碼段:
self.panel = wx.Panel(self) vbox = wx.BoxSizer(wx.VERTICAL) # 創(chuàng)建工具欄 toolbar = wx.BoxSizer(wx.HORIZONTAL) open_btn = wx.Button(self.panel, label='打開(kāi)PDF') prev_btn = wx.Button(self.panel, label='上一頁(yè)') next_btn = wx.Button(self.panel, label='下一頁(yè)') open_btn.Bind(wx.EVT_BUTTON, self.on_open) prev_btn.Bind(wx.EVT_BUTTON, self.on_prev_page) next_btn.Bind(wx.EVT_BUTTON, self.on_next_page) toolbar.Add(open_btn, 0, wx.ALL, 5) toolbar.Add(prev_btn, 0, wx.ALL, 5) toolbar.Add(next_btn, 0, wx.ALL, 5) self.pdf_panel = wx.Panel(self.panel) self.pdf_panel.SetBackgroundColour(wx.WHITE) self.pdf_panel.Bind(wx.EVT_PAINT, self.on_paint) vbox.Add(toolbar, 0, wx.EXPAND) vbox.Add(self.pdf_panel, 1, wx.EXPAND | wx.ALL, 5) self.panel.SetSizer(vbox) self.Centre()
解析:
- 創(chuàng)建主面板
wx.Panel并使用BoxSizer布局管理組件。 - 創(chuàng)建工具欄,包括三個(gè)按鈕:打開(kāi) PDF、上一頁(yè)和下一頁(yè)。
- 創(chuàng)建 PDF 顯示區(qū)域,綁定
EVT_PAINT事件用于頁(yè)面繪制。 - 使用
Add方法將工具欄和顯示區(qū)域添加到垂直布局中。
加載 PDF 文件
代碼段:
def on_open(self, event):
with wx.FileDialog(self, "選擇PDF文件", wildcard="PDF files (*.pdf)|*.pdf",
style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) as fileDialog:
if fileDialog.ShowModal() == wx.ID_CANCEL:
return
pdf_path = fileDialog.GetPath()
self.load_pdf(pdf_path)
def load_pdf(self, path):
self.doc = fitz.open(path)
self.current_page = 0
self.page_images = []
# 預(yù)加載所有頁(yè)面
for page in self.doc:
pix = page.get_pixmap()
img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
self.page_images.append(img)
self.render_current_page()
解析:
- 使用
wx.FileDialog打開(kāi)文件對(duì)話框,選擇 PDF 文件。 - 調(diào)用
fitz.open加載 PDF 文件,存儲(chǔ)為self.doc。 - 遍歷 PDF 頁(yè)面的每一頁(yè),使用
get_pixmap提取頁(yè)面圖像,并轉(zhuǎn)換為 PIL 圖像對(duì)象,存儲(chǔ)到self.page_images列表中。 - 調(diào)用
render_current_page渲染第一頁(yè)。
顯示 PDF 頁(yè)面
代碼段:
def render_current_page(self):
if not self.doc or self.current_page >= len(self.page_images):
return
panel_size = self.pdf_panel.GetSize()
# 創(chuàng)建背景
background = Image.new('RGB', (panel_size.width, panel_size.height), 'WHITE')
# 獲取當(dāng)前頁(yè)面并調(diào)整大小
current_img = self.page_images[self.current_page].resize(
(panel_size.width, panel_size.height), Image.LANCZOS)
if self.is_animating:
next_img = self.page_images[self.next_page_idx].resize(
(panel_size.width, panel_size.height), Image.LANCZOS)
if self.animation_direction > 0: # 向右翻頁(yè)
background.paste(current_img, (-self.animation_offset, 0))
background.paste(next_img, (panel_size.width - self.animation_offset, 0))
else: # 向左翻頁(yè)
background.paste(current_img, (self.animation_offset, 0))
background.paste(next_img, (-panel_size.width + self.animation_offset, 0))
else:
background.paste(current_img, (0, 0))
self.current_bitmap = wx.Bitmap.FromBuffer(
panel_size.width, panel_size.height, background.tobytes())
self.pdf_panel.Refresh()
解析:
- 檢查當(dāng)前文檔和頁(yè)面索引的有效性。
- 創(chuàng)建一個(gè)與顯示區(qū)域大小一致的白色背景。
- 將當(dāng)前頁(yè)面圖像調(diào)整為顯示區(qū)域的大小。
- 如果處于動(dòng)畫(huà)狀態(tài),還需要繪制下一頁(yè)面,并根據(jù)動(dòng)畫(huà)方向和偏移量計(jì)算粘貼位置。
- 將結(jié)果圖像轉(zhuǎn)換為
wx.Bitmap,刷新顯示區(qū)域。
頁(yè)面切換動(dòng)畫(huà)
代碼段:
def start_animation(self, direction):
if self.is_animating:
return
next_page = self.current_page + direction
if next_page < 0 or next_page >= len(self.page_images):
return
self.is_animating = True
self.animation_direction = direction
self.next_page_idx = next_page
self.animation_offset = 0
self.timer.Start(16) # 約60fps
def on_timer(self, event):
if not self.is_animating:
return
panel_width = self.pdf_panel.GetSize().width
step = panel_width // 15
self.animation_offset += step
if self.animation_offset >= panel_width:
self.animation_offset = 0
self.is_animating = False
self.current_page = self.next_page_idx
self.timer.Stop()
self.render_current_page()
解析:
start_animation初始化動(dòng)畫(huà)參數(shù)并啟動(dòng)定時(shí)器,控制動(dòng)畫(huà)幀率。on_timer事件處理器更新動(dòng)畫(huà)偏移量,并檢查動(dòng)畫(huà)是否完成。- 動(dòng)畫(huà)完成后,更新當(dāng)前頁(yè)面索引并停止定時(shí)器。
運(yùn)行效果

總結(jié)
這段代碼展示了如何結(jié)合 wxPython 和 PyMuPDF 構(gòu)建一個(gè)功能齊全的 PDF 閱讀器。它不僅實(shí)現(xiàn)了基本的 PDF 加載和顯示功能,還加入了平滑的頁(yè)面切換動(dòng)畫(huà),提升了用戶體驗(yàn)。通過(guò)合理的模塊化設(shè)計(jì)和事件綁定,代碼邏輯清晰,便于擴(kuò)展。
以上就是基于Python實(shí)現(xiàn)PDF動(dòng)畫(huà)翻頁(yè)效果的閱讀器的詳細(xì)內(nèi)容,更多關(guān)于Python PDF閱讀器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python實(shí)現(xiàn)視頻目標(biāo)檢測(cè)與軌跡跟蹤流程詳解
通過(guò)閱讀相關(guān)文獻(xiàn)及測(cè)試,找到了一種基于多模板匹配的改進(jìn)方法,可以對(duì)遙感視頻衛(wèi)星中的移動(dòng)目標(biāo)進(jìn)行探測(cè),并繪制其軌跡。根據(jù)實(shí)驗(yàn)結(jié)果發(fā)現(xiàn),可以比較有效的對(duì)運(yùn)動(dòng)目標(biāo)進(jìn)行跟蹤2023-01-01
使用Python進(jìn)行中文繁簡(jiǎn)轉(zhuǎn)換的實(shí)現(xiàn)代碼
這篇文章主要介紹了使用Python進(jìn)行中文繁簡(jiǎn)轉(zhuǎn)換的實(shí)現(xiàn)代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10
python pip安裝包出現(xiàn):Failed building wheel for xxx錯(cuò)誤的解決
今天小編就為大家分享一篇python pip安裝包出現(xiàn):Failed building wheel for xxx錯(cuò)誤的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-12-12
Python?Collections庫(kù)的高級(jí)功能使用示例詳解
Python的collections庫(kù)提供了一系列有用的數(shù)據(jù)類型,擴(kuò)展了內(nèi)建的數(shù)據(jù)類型,為開(kāi)發(fā)者提供了更多高級(jí)功能,本文將深入探討collections庫(kù)的一些高級(jí)功能,通過(guò)詳細(xì)的示例代碼演示,幫助大家更好地理解和應(yīng)用這些功能2023-12-12
Python爬蟲(chóng)之解析HTML頁(yè)面詳解
本文介紹了Python中用于解析HTML頁(yè)面的重要工具之一——BeautifulSoup庫(kù),詳細(xì)講解了BeautifulSoup庫(kù)的基本使用方法、標(biāo)簽選擇器、CSS選擇器、正則表達(dá)式、遍歷文檔樹(shù)等內(nèi)容,并結(jié)合實(shí)例代碼展示了BeautifulSoup庫(kù)的應(yīng)用場(chǎng)景2023-04-04
class類在python中獲取金融數(shù)據(jù)的實(shí)例方法
在本篇文章里小編給大家整理了關(guān)于class類怎樣在python中獲取金融數(shù)據(jù)的相關(guān)內(nèi)容,有需要的朋友們可以學(xué)習(xí)下。2020-12-12
利用Python實(shí)現(xiàn)Excel的文件間的數(shù)據(jù)匹配功能
這篇文章主要介紹了利用Python實(shí)現(xiàn)Excel的文件間的數(shù)據(jù)匹配,本文通過(guò)一個(gè)函數(shù)實(shí)現(xiàn)此功能,通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06
使用Pyhton集合set()實(shí)現(xiàn)成果查漏的例子
今天小編就為大家分享一篇使用Pyhton集合set()實(shí)現(xiàn)成果查漏的例子,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-11-11
Python linecache.getline()讀取文件中特定一行的腳本
Python中使用標(biāo)準(zhǔn)庫(kù)中的linecache中的getline方法可以從某個(gè)文件中讀取出特定的一行。2008-09-09

