Python Tkinter中PDF无法在Canvas显示及选择报错求助
问题解决:Tkinter PDF查看器的显示与选框文本提取问题
问题分析
你遇到两个核心问题:
- PDF无法在屏幕显示:运行时输出
Canvas width : 1 Canvas height :1,说明获取Canvas尺寸时组件还未完成渲染,导致计算出的缩放因子极小,PDF图片被缩到几乎看不见。 - 选框操作报错:
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
相关产品推荐
相关产品推荐

