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

如何在tkinter中直接显示内存中BytesIO生成的PDF文件

问题描述

我正在开发一款可在内部显示PDF文件的应用,计划步骤如下:

  • 使用ReportLab生成PDF文件
  • 不将PDF保存到硬盘,采用BytesIO进行内存存储
  • 尝试使用tkpdfviewer包在应用内展示PDF

编写的示例代码如下:

from tkinter import *
from tkinter import ttk
import tkinter as tk
from io import BytesIO
from tkPDFViewer import tkPDFViewer as pdf
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4, landscape
from reportlab.lib.units import mm

main = tk.Tk()

main.title("PDF Viewer Main")

# Main window sizes
app_height = 350
app_width = 350

# Define of screen sizes
screen_width = main.winfo_screenwidth()
screen_height = main.winfo_screenheight()

# Define of left corner coordinates 
x = (screen_width - app_width) / 2
y = ((screen_height - app_height)-50) / 2

# Applying defineded parameters to root.geometry
main.geometry(f'{app_width}x{app_height}+{int(x)}+{int(y)}')

main.resizable(False, False)
main.configure(background="white")

def create_pdf():
    pdf_reader = Toplevel(main)
        
    pdf_reader.title("PDF Viewer")

    # Main window sizes
    app_height = 700
    app_width = 900

    # Define of screen sizes
    screen_width = pdf_reader.winfo_screenwidth()
    screen_height = pdf_reader.winfo_screenheight()

    # Define of left corner coordinates 
    x = (screen_width - app_width) / 2
    y = ((screen_height - app_height)-50) / 2

    # Applying defineded parameters to root.geometry
    pdf_reader.geometry(f'{app_width}x{app_height}+{int(x)}+{int(y)}')

    pdf_reader.resizable(False, False)
    pdf_reader.configure(background="white")
    
    buffer = BytesIO()
    
    c = canvas.Canvas(buffer, pagesize=landscape(A4), bottomup=0)
    c.drawString(400,300,"Sample PDF file")
    c.save()
    
    buffer.seek(0)
    pdf_path = buffer.getvalue()
    #print(pdf_path)
    
    # creating object of ShowPdf from tkPDFViewer.
    v1 = pdf.ShowPdf()
    
    # Adding pdf location and width and height.
    v2 = v1.pdf_view(pdf_reader, pdf_location = pdf_path, width = 50, height = 100, bar=True, load="before")
    
    # Placing Pdf in my gui.
    v2.pack()
    
    pdf_reader.mainloop()

create_btn = Button(main, text="Create PDF file", command=create_pdf)
create_btn.pack(pady=135)


main.mainloop()

运行后出现错误,tkpdfviewer无法从字节数据中显示PDF,报错信息:

Exception in thread Thread-1 (add_img):
Traceback (most recent call last):
  File "C:\Users\User\AppData\Local\Programs\Python\Python311\Lib\threading.py", line 1038, in _bootstrap_inner
    self.run()
  File "C:\Users\User\AppData\Local\Programs\Python\Python311\Lib\threading.py", line 975, in run
    self._target(*self._args, **self._kwargs)
  File "C:\Users\User\AppData\Local\Programs\Python\Python311\Lib\site-packages\tkPDFViewer\tkPDFViewer.py", line 43, in add_img
    open_pdf = fitz.open(pdf_location)
               ^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\User\AppData\Local\Programs\Python\Python311\Lib\site-packages\fitz\fitz.py", line 3925, in __init__
    raise TypeError(msg)
TypeError: bad filename

需要解决的问题:如何生成PDF后无需保存到电脑即可在应用内展示?


原因分析

tkpdfviewer的pdf_view方法仅支持传入文件路径作为pdf_location参数,底层调用fitz.open()时,会把你传入的字节数据当作文件名处理,因此抛出"bad filename"错误。虽然PyMuPDF(fitz)本身支持直接读取内存中的PDF字节流,但tkpdfviewer的封装逻辑没有适配这种场景。


解决方案

直接使用PyMuPDF(fitz)处理内存中的PDF字节流,将PDF页面转换为图片后在Tkinter窗口中展示,无需依赖tkpdfviewer。修改后的完整代码如下:

from tkinter import *
import tkinter as tk
from io import BytesIO
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4, landscape
import fitz  # 导入PyMuPDF

main = tk.Tk()
main.title("PDF Viewer Main")

# 主窗口配置
app_height = 350
app_width = 350
screen_width = main.winfo_screenwidth()
screen_height = main.winfo_screenheight()
x = (screen_width - app_width) / 2
y = ((screen_height - app_height)-50) / 2
main.geometry(f'{app_width}x{app_height}+{int(x)}+{int(y)}')
main.resizable(False, False)
main.configure(background="white")

def create_pdf():
    pdf_reader = Toplevel(main)
    pdf_reader.title("PDF Viewer")
    
    # 预览窗口配置
    app_height = 700
    app_width = 900
    screen_width = pdf_reader.winfo_screenwidth()
    screen_height = pdf_reader.winfo_screenheight()
    x = (screen_width - app_width) / 2
    y = ((screen_height - app_height)-50) / 2
    pdf_reader.geometry(f'{app_width}x{app_height}+{int(x)}+{int(y)}')
    pdf_reader.resizable(False, False)
    pdf_reader.configure(background="white")
    
    # 生成PDF到内存缓冲区
    buffer = BytesIO()
    c = canvas.Canvas(buffer, pagesize=landscape(A4), bottomup=0)
    c.drawString(400,300,"Sample PDF file")
    c.save()
    buffer.seek(0)
    
    # 直接读取内存中的PDF字节流
    doc = fitz.open("pdf", buffer.read())
    
    # 创建带滚动条的画布区域
    canvas_frame = Frame(pdf_reader)
    canvas_frame.pack(fill=BOTH, expand=1)
    
    scroll_y = Scrollbar(canvas_frame, orient=VERTICAL)
    scroll_y.pack(side=RIGHT, fill=Y)
    
    pdf_canvas = Canvas(canvas_frame, yscrollcommand=scroll_y.set, bg="white")
    pdf_canvas.pack(fill=BOTH, expand=1)
    
    scroll_y.config(command=pdf_canvas.yview)
    
    # 将PDF每页转为图片并绘制到画布
    y_pos = 0
    pdf_canvas.image_refs = []  # 保存图片引用,防止被垃圾回收
    for page_num in range(doc.page_count):
        page = doc.load_page(page_num)
        pix = page.get_pixmap()
        # 转换为Tkinter可用的图片格式
        img_data = pix.tobytes("ppm")
        img = tk.PhotoImage(data=img_data)
        pdf_canvas.image_refs.append(img)
        pdf_canvas.create_image(0, y_pos, anchor=NW, image=img)
        y_pos += pix.height
    
    # 设置画布滚动范围
    pdf_canvas.config(scrollregion=(0, 0, pix.width, y_pos))
    
    pdf_reader.mainloop()

create_btn = Button(main, text="Create PDF file", command=create_pdf)
create_btn.pack(pady=135)

main.mainloop()

代码说明
  1. 内存PDF生成:保留原有的ReportLab+BytesIO逻辑,将PDF内容写入内存缓冲区,无需落地到硬盘。
  2. 读取内存PDF:使用fitz.open("pdf", buffer.read())直接读取字节数据,通过指定文件类型为pdf,绕开文件路径的限制。
  3. PDF转图片:通过PyMuPDF将每页PDF转换为像素图,再转为Tkinter支持的PhotoImage格式。
  4. 滚动显示:利用Canvas和Scrollbar实现多页PDF的滚动查看,同时通过保存图片引用避免图片被垃圾回收导致消失。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 07:55:42