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

2025-01-08 15:50

本文主要是介绍基于Python实现PDF动画翻页效果的阅读器,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《基于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 = www.chinasem.cnImage.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(
            panelhttp://www.chinasem.cn_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 = selfwww.chinasem.cn.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="基于Python实现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()
    
    # 创建背景
    backgr编程ound = 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 = selphpf.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. 动画完成后,更新当前页面索引并停止定时器。

运行效果

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

总结

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

以上就是基于Python实现PDF动画翻页效果的阅读器的详细内容,更多关于Python PDF阅读器的资料请关注China编程(www.chinasem.cn)其它相关文章!

这篇关于基于Python实现PDF动画翻页效果的阅读器的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1152985

相关文章

SpringBoot集成redisson实现延时队列教程

《SpringBoot集成redisson实现延时队列教程》文章介绍了使用Redisson实现延迟队列的完整步骤,包括依赖导入、Redis配置、工具类封装、业务枚举定义、执行器实现、Bean创建、消费... 目录1、先给项目导入Redisson依赖2、配置redis3、创建 RedissonConfig 配

Python的Darts库实现时间序列预测

《Python的Darts库实现时间序列预测》Darts一个集统计、机器学习与深度学习模型于一体的Python时间序列预测库,本文主要介绍了Python的Darts库实现时间序列预测,感兴趣的可以了解... 目录目录一、什么是 Darts?二、安装与基本配置安装 Darts导入基础模块三、时间序列数据结构与

Python正则表达式匹配和替换的操作指南

《Python正则表达式匹配和替换的操作指南》正则表达式是处理文本的强大工具,Python通过re模块提供了完整的正则表达式功能,本文将通过代码示例详细介绍Python中的正则匹配和替换操作,需要的朋... 目录基础语法导入re模块基本元字符常用匹配方法1. re.match() - 从字符串开头匹配2.

Python使用FastAPI实现大文件分片上传与断点续传功能

《Python使用FastAPI实现大文件分片上传与断点续传功能》大文件直传常遇到超时、网络抖动失败、失败后只能重传的问题,分片上传+断点续传可以把大文件拆成若干小块逐个上传,并在中断后从已完成分片继... 目录一、接口设计二、服务端实现(FastAPI)2.1 运行环境2.2 目录结构建议2.3 serv

C#实现千万数据秒级导入的代码

《C#实现千万数据秒级导入的代码》在实际开发中excel导入很常见,现代社会中很容易遇到大数据处理业务,所以本文我就给大家分享一下千万数据秒级导入怎么实现,文中有详细的代码示例供大家参考,需要的朋友可... 目录前言一、数据存储二、处理逻辑优化前代码处理逻辑优化后的代码总结前言在实际开发中excel导入很

通过Docker容器部署Python环境的全流程

《通过Docker容器部署Python环境的全流程》在现代化开发流程中,Docker因其轻量化、环境隔离和跨平台一致性的特性,已成为部署Python应用的标准工具,本文将详细演示如何通过Docker容... 目录引言一、docker与python的协同优势二、核心步骤详解三、进阶配置技巧四、生产环境最佳实践

Python一次性将指定版本所有包上传PyPI镜像解决方案

《Python一次性将指定版本所有包上传PyPI镜像解决方案》本文主要介绍了一个安全、完整、可离线部署的解决方案,用于一次性准备指定Python版本的所有包,然后导出到内网环境,感兴趣的小伙伴可以跟随... 目录为什么需要这个方案完整解决方案1. 项目目录结构2. 创建智能下载脚本3. 创建包清单生成脚本4

SpringBoot+RustFS 实现文件切片极速上传的实例代码

《SpringBoot+RustFS实现文件切片极速上传的实例代码》本文介绍利用SpringBoot和RustFS构建高性能文件切片上传系统,实现大文件秒传、断点续传和分片上传等功能,具有一定的参考... 目录一、为什么选择 RustFS + SpringBoot?二、环境准备与部署2.1 安装 RustF

Nginx部署HTTP/3的实现步骤

《Nginx部署HTTP/3的实现步骤》本文介绍了在Nginx中部署HTTP/3的详细步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录前提条件第一步:安装必要的依赖库第二步:获取并构建 BoringSSL第三步:获取 Nginx

MyBatis Plus实现时间字段自动填充的完整方案

《MyBatisPlus实现时间字段自动填充的完整方案》在日常开发中,我们经常需要记录数据的创建时间和更新时间,传统的做法是在每次插入或更新操作时手动设置这些时间字段,这种方式不仅繁琐,还容易遗漏,... 目录前言解决目标技术栈实现步骤1. 实体类注解配置2. 创建元数据处理器3. 服务层代码优化填充机制详