基于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实现基于URL和IP的访问频率限制

《SpringBoot实现基于URL和IP的访问频率限制》在现代Web应用中,接口被恶意刷新或暴力请求是一种常见的攻击手段,为了保护系统资源,需要对接口的访问频率进行限制,下面我们就来看看如何使用... 目录1. 引言2. 项目依赖3. 配置 Redis4. 创建拦截器5. 注册拦截器6. 创建控制器8.

使用JavaScript将PDF页面中的标注扁平化的操作指南

《使用JavaScript将PDF页面中的标注扁平化的操作指南》扁平化(flatten)操作可以将标注作为矢量图形包含在PDF页面的内容中,使其不可编辑,DynamsoftDocumentViewer... 目录使用Dynamsoft Document Viewer打开一个PDF文件并启用标注添加功能扁平化

React实现原生APP切换效果

《React实现原生APP切换效果》最近需要使用Hybrid的方式开发一个APP,交互和原生APP相似并且需要IM通信,本文给大家介绍了使用React实现原生APP切换效果,文中通过代码示例讲解的非常... 目录背景需求概览技术栈实现步骤根据 react-router-dom 文档配置好路由添加过渡动画使用

Springboot使用RabbitMQ实现关闭超时订单(示例详解)

《Springboot使用RabbitMQ实现关闭超时订单(示例详解)》介绍了如何在SpringBoot项目中使用RabbitMQ实现订单的延时处理和超时关闭,通过配置RabbitMQ的交换机、队列和... 目录1.maven中引入rabbitmq的依赖:2.application.yml中进行rabbit

Spring使用@Retryable实现自动重试机制

《Spring使用@Retryable实现自动重试机制》在微服务架构中,服务之间的调用可能会因为一些暂时性的错误而失败,例如网络波动、数据库连接超时或第三方服务不可用等,在本文中,我们将介绍如何在Sp... 目录引言1. 什么是 @Retryable?2. 如何在 Spring 中使用 @Retryable

C语言线程池的常见实现方式详解

《C语言线程池的常见实现方式详解》本文介绍了如何使用C语言实现一个基本的线程池,线程池的实现包括工作线程、任务队列、任务调度、线程池的初始化、任务添加、销毁等步骤,感兴趣的朋友跟随小编一起看看吧... 目录1. 线程池的基本结构2. 线程池的实现步骤3. 线程池的核心数据结构4. 线程池的详细实现4.1 初

Python如何实现 HTTP echo 服务器

《Python如何实现HTTPecho服务器》本文介绍了如何使用Python实现一个简单的HTTPecho服务器,该服务器支持GET和POST请求,并返回JSON格式的响应,GET请求返回请求路... 一个用来做测试的简单的 HTTP echo 服务器。from http.server import HT

轻松掌握python的dataclass让你的代码更简洁优雅

《轻松掌握python的dataclass让你的代码更简洁优雅》本文总结了几个我在使用Python的dataclass时常用的技巧,dataclass装饰器可以帮助我们简化数据类的定义过程,包括设置默... 目录1. 传统的类定义方式2. dataclass装饰器定义类2.1. 默认值2.2. 隐藏敏感信息

Python绘制土地利用和土地覆盖类型图示例详解

《Python绘制土地利用和土地覆盖类型图示例详解》本文介绍了如何使用Python绘制土地利用和土地覆盖类型图,并提供了详细的代码示例,通过安装所需的库,准备地理数据,使用geopandas和matp... 目录一、所需库的安装二、数据准备三、绘制土地利用和土地覆盖类型图四、代码解释五、其他可视化形式1.

opencv实现像素统计的示例代码

《opencv实现像素统计的示例代码》本文介绍了OpenCV中统计图像像素信息的常用方法和函数,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录1. 统计像素值的基本信息2. 统计像素值的直方图3. 统计像素值的总和4. 统计非零像素的数量