I want to append the contents of a Tkinter widget to the end of an existing pdf.
First, I capture the widget to an PIL image. Then, it seems it is required when using PyPDF2 to create an intermediate temporary file from the image, which feels unnecessary. Instead I would like to append the image directly, or at least convert the image to something that can be appended without the need to be written to a file.
In the code snippet below I save the image to a temporary pdf, then open the pdf and append. This works, but is not a very elegant solution.
import tkinter as tk
from PIL import ImageGrab
import os
import PyPDF2
def process_pdf(widget, pdf_filepath):
""""Append Tkinter widget to pdf"""
# capture the widget
img = capture_widget(widget)
# create pdf merger object
merger = PyPDF2.PdfMerger()
# creating a pdf file object of original pdf and add to output
pdf_org = open(pdf_filepath, 'rb')
merger.append(pdf_org)
# NOTE I want to get rid of this step
# create temporary file, read it, append it to output, delete it.
temp_filename = pdf_filepath[:-4] "_temp.pdf"
img.save(temp_filename)
pdf_temp = open(temp_filename, 'rb')
merger.append(pdf_temp)
pdf_temp.close()
# write
outputfile = open(pdf_filepath, "wb")
merger.write(outputfile)
# clean up
pdf_org.close()
outputfile.close()
os.remove(temp_filename)
def capture_widget(widget):
"""Take screenshot of the passed widget"""
x0 = widget.winfo_rootx()
y0 = widget.winfo_rooty()
x1 = x0 widget.winfo_width()
y1 = y0 widget.winfo_height()
img = ImageGrab.grab((x0, y0, x1, y1))
return img
Does someone have a more elegant solution not requiring an intermediate file while retaining the flexibility PyPDF2 provides?
Thanks.
CodePudding user response:
So I was able to find a solution myself. The trick is writing the PIL image to a byte array (see this question), then converting that to a pdf using img2pdf. For img2pdf, it is required to use the format = "jpeg" argument during saving to byte array.
Subsequently, the result of img2pdf can be written to another io.BytesIO() stream. Since it implements a .read() method, PyPDF2.PdfMerger() can read it.
Below is the code, hope this helps someone.
import io
import PyPDF2
import img2pdf
def process_pdf(widget, pdf_filepath):
""""Append Tkinter widget to pdf"""
# create pdf merger object
merger = PyPDF2.PdfMerger()
# creating a pdf file object of original pdf and add to output
pdf_org = open(pdf_filepath, 'rb')
merger.append(pdf_org)
pdf_org.close()
# capture the widget and rotate
img = capture_widget(widget)
# create img bytearray
img_byte_arr = io.BytesIO()
img.save(img_byte_arr, format = "jpeg", quality=100)
img_byte_arr = img_byte_arr.getvalue()
# create a pdf bytearray and do formatting using img2pdf
pdf_byte_arr = io.BytesIO()
layout_fun = img2pdf.get_layout_fun((img2pdf.mm_to_pt(210),img2pdf.mm_to_pt(297)))
pdf_byte_arr.write(img2pdf.convert(img_byte_arr, layout_fun=layout_fun))
merger.append(pdf_byte_arr)
# write
outputfile = open(pdf_filepath[:-4] "_appended.pdf", "wb")
merger.write(outputfile)
outputfile.close()