You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python Tkinter中PDF无法在Canvas显示及选择报错求助

问题解决:Tkinter PDF查看器的显示与选框文本提取问题

问题分析

你遇到两个核心问题:

  1. PDF无法在屏幕显示:运行时输出Canvas width : 1 Canvas height :1,说明获取Canvas尺寸时组件还未完成渲染,导致计算出的缩放因子极小,PDF图片被缩到几乎看不见。
  2. 选框操作报错:AttributeError: 'TextPage' object has no attribute 'get_text',因为PyMuPDF的TextPage对象没有get_text方法,调用方式错误。

解决方案

1. 修复PDF显示问题

通过tkinter的after方法延迟执行页面加载,确保Canvas已完成渲染并获取到实际尺寸。

2. 修复选框文本提取错误

直接使用Page对象的get_text方法并传入clip参数提取选框内文本,无需创建TextPage;同时利用保存的缩放因子,将Canvas坐标反向转换为PDF原始页面坐标。

修改后的完整代码

import tkinter as tk
import fitz
from PIL import Image, ImageTk
import os


class PDFViewer:
    def __init__(self, pdf_path):
        self.pdf_doc = fitz.open(pdf_path)
        self.current_page = 0
        self.scale_factor = 1.0  # 保存缩放因子,用于坐标转换

        # 创建窗口和Canvas
        self.root = tk.Tk()
        self.root.title("PDF Viewer")
        self.canvas = tk.Canvas(self.root, width=1000, height=1000)
        self.canvas.pack(expand=True, fill='both')

        # 延迟加载页面,确保Canvas渲染完成
        self.root.after(100, self.load_page)

        # 绑定鼠标事件
        self.canvas.bind("<Button-1>", self.start_selection)
        self.canvas.bind("<B1-Motion>", self.update_selection)
        self.canvas.bind("<ButtonRelease-1>", self.end_selection)

        self.root.mainloop()

    def load_page(self):
        page = self.pdf_doc[self.current_page]
        page_width, page_height = page.mediabox.width, page.mediabox.height
        
        # 获取Canvas实际渲染后的尺寸
        canvas_width = self.canvas.winfo_width()
        canvas_height = self.canvas.winfo_height()
        
        # 避免Canvas尺寸为0的极端情况
        if canvas_width == 0 or canvas_height == 0:
            canvas_width = 1000
            canvas_height = 1000

        # 计算缩放因子
        self.scale_factor = min(canvas_width / page_width, canvas_height / page_height)
        print(f"PDF width :{page_width} PDF height :{page_height}")
        print(f"Canvas width : {canvas_width} Canvas height :{canvas_height}")
        print(f"Scale factor: {self.scale_factor}")

        # 生成缩放后的PDF图片
        pix = page.get_pixmap(matrix=fitz.Matrix(self.scale_factor, self.scale_factor))
        img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
        self.image = ImageTk.PhotoImage(img)
        
        # 清除Canvas旧内容,居中显示图片
        self.canvas.delete("all")
        self.canvas.create_image(canvas_width / 2, canvas_height / 2, image=self.image, anchor=tk.CENTER)

    def start_selection(self, event):
        self.start_x = self.canvas.canvasx(event.x)
        self.start_y = self.canvas.canvasy(event.y)
        self.selection_rect = self.canvas.create_rectangle(self.start_x, self.start_y, self.start_x, self.start_y, outline="red")

    def update_selection(self, event):
        x = self.canvas.canvasx(event.x)
        y = self.canvas.canvasy(event.y)
        self.canvas.coords(self.selection_rect, self.start_x, self.start_y, x, y)

    def end_selection(self, event):
        if not hasattr(self, 'selection_rect') or self.selection_rect is None:
            return
            
        coords = self.canvas.coords(self.selection_rect)
        x1, y1, x2, y2 = coords

        # 将Canvas坐标转换为PDF页面原始坐标(反向缩放)
        pdf_x1 = min(x1, x2) / self.scale_factor
        pdf_y1 = min(y1, y2) / self.scale_factor
        pdf_x2 = max(x1, x2) / self.scale_factor
        pdf_y2 = max(y1, y2) / self.scale_factor

        rect = fitz.Rect(pdf_x1, pdf_y1, pdf_x2, pdf_y2)
        page = self.pdf_doc[self.current_page]
        
        # 直接提取选框内的文本
        selected_text = page.get_text("text", clip=rect).strip()

        # 显示选中文本(复用同一个文本框,避免重复创建)
        if hasattr(self, 'text_box'):
            self.text_box.destroy()
        self.text_box = tk.Text(self.root, height=5, width=80)
        self.text_box.insert("1.0", selected_text if selected_text else "未选中文本")
        self.text_box.pack(pady=10)

        self.canvas.delete(self.selection_rect)
        self.selection_rect = None

if __name__ == '__main__':
    pdf_path = "Invoice.pdf"
    pdf_abs_path = os.path.abspath(pdf_path)
    viewer = PDFViewer(pdf_abs_path)

关键修改说明

  • 用self.root.after(100, self.load_page)延迟加载页面,确保Canvas获取到实际渲染后的尺寸。
  • 保存缩放因子self.scale_factor,用于将Canvas选框坐标反向转换为PDF原始坐标。
  • 移除错误的TextPage调用,直接使用page.get_text("text", clip=rect)提取选框文本。
  • 复用文本框,避免每次选框都创建新的Text组件。
  • 处理坐标的极值(确保x1<x2,y1<y2),避免创建无效的Rect。

内容的提问来源于stack exchange,提问作者bay bay

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 12:05:33