当前位置: 首页 > article >正文

基于 Python 的 PDF 动画翻页效果的阅读器实现

简介

在这篇博客中,我们将深入分析一个基于 wxPython 实现的 PDF 阅读器程序。该程序支持加载 PDF 文件并显示页面内容,同时支持页面切换动画效果。

主要功能包括:

  • 加载 PDF 文件
  • 显示当前页面
  • 上一页/下一页切换
  • 页面切换动画
    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)
        
        # 创建工具栏
        toolbar = wx.BoxSizer(wx.HORIZONTAL)
        
        open_btn = wx.Button(self.panel, label='打开PDF')
        prev_btn = wx.Button(self.panel, label='上一页')
        next_btn = wx.Button(self.panel, label='下一页')
        
        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):
        # 创建定时器用于动画
        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 = []
        
        # 预加载所有页面
        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()
        
        # 创建背景
        background = Image.new('RGB', (panel_size.width, panel_size.height), 'WHITE')
        
        # 获取当前页面并调整大小
        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:  # 向右翻页
                background.paste(current_img, (-self.animation_offset, 0))
                background.paste(next_img, (panel_size.width - self.animation_offset, 0))
            else:  # 向左翻页
                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))
        
        # 转换为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):
        """开始页面切换动画"""
        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()
    
    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()
代码结构

整个程序由以下几个核心部分组成:

  1. 初始化 UI 界面
  2. 加载 PDF 文件
  3. 显示 PDF 页面
  4. 页面切换动画

以下是代码的详细解析。


初始化 UI 界面

代码段:

self.panel = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)

# 创建工具栏
toolbar = wx.BoxSizer(wx.HORIZONTAL)

open_btn = wx.Button(self.panel, label='打开PDF')
prev_btn = wx.Button(self.panel, label='上一页')
next_btn = wx.Button(self.panel, label='下一页')

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()

解析:

  1. 创建主面板 wx.Panel 并使用 BoxSizer 布局管理组件。
  2. 创建工具栏,包括三个按钮:打开 PDF、上一页和下一页。
  3. 创建 PDF 显示区域,绑定 EVT_PAINT 事件用于页面绘制。
  4. 使用 Add 方法将工具栏和显示区域添加到垂直布局中。
加载 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 = []
    
    # 预加载所有页面
    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()

解析:

  1. 使用 wx.FileDialog 打开文件对话框,选择 PDF 文件。
  2. 调用 fitz.open 加载 PDF 文件,存储为 self.doc
  3. 遍历 PDF 页面的每一页,使用 get_pixmap 提取页面图像,并转换为 PIL 图像对象,存储到 self.page_images 列表中。
  4. 调用 render_current_page 渲染第一页。
显示 PDF 页面

代码段:

def render_current_page(self):
    if not self.doc or self.current_page >= len(self.page_images):
        return
        
    panel_size = self.pdf_panel.GetSize()
    
    # 创建背景
    background = Image.new('RGB', (panel_size.width, panel_size.height), 'WHITE')
    
    # 获取当前页面并调整大小
    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:  # 向右翻页
            background.paste(current_img, (-self.animation_offset, 0))
            background.paste(next_img, (panel_size.width - self.animation_offset, 0))
        else:  # 向左翻页
            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()

解析:

  1. 检查当前文档和页面索引的有效性。
  2. 创建一个与显示区域大小一致的白色背景。
  3. 将当前页面图像调整为显示区域的大小。
  4. 如果处于动画状态,还需要绘制下一页面,并根据动画方向和偏移量计算粘贴位置。
  5. 将结果图像转换为 wx.Bitmap,刷新显示区域。
页面切换动画

代码段:

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()

解析:

  1. start_animation 初始化动画参数并启动定时器,控制动画帧率。
  2. on_timer 事件处理器更新动画偏移量,并检查动画是否完成。
  3. 动画完成后,更新当前页面索引并停止定时器。

运行效果

在这里插入图片描述

总结

这段代码展示了如何结合 wxPython 和 PyMuPDF 构建一个功能齐全的 PDF 阅读器。它不仅实现了基本的 PDF 加载和显示功能,还加入了平滑的页面切换动画,提升了用户体验。通过合理的模块化设计和事件绑定,代码逻辑清晰,便于扩展。


http://www.kler.cn/a/472584.html

相关文章:

  • 备考蓝桥杯:顺序表相关算法题
  • Personal APP
  • 后台管理系统引导功能的实现
  • 从Linux本地软件存储库安装MySQL
  • 数据库模型全解析:从文档存储到搜索引擎
  • asp.net core中的 Cookie 和 Session
  • Android NDK开发入门2之适应idm环境
  • .NET AI 开发人员库 --AI Dev Gallery
  • Linux pget 下载命令详解
  • filebeat、kafka
  • 基于Android的疫苗预约系统
  • 腾讯云AI代码助手编程挑战赛-古诗词学习
  • WordPress静态缓存插件WP Super Cache与 WP Fastest Cache
  • Spring Boot集成RocketMQ
  • C++实现银行排队系统
  • 单片机-定时器中断
  • C++ 复习总结记录五
  • (k8s)kubectl不断重启问题解决!
  • 代码随想录算法训练营第二十七天-贪心算法-455. 分发饼干
  • 技术速递|通过 .NET Aspire 使用本地 AI 模型
  • 【大模型+本地自建知识图谱/GraphRAG/neo4j/ollama+Qwen千问(或llama3)】 python实战(中)
  • 支持各大平台账单处理,支持复杂业财数据的精细化对账|商派OMS
  • 将java前后端项目和使用了conda虚拟环境的python项目添加到ubuntu服务
  • python中的列表推导式详解
  • 华灯已上:夜色跌宕绘情谱
  • 【AI日记】25.01.08