In the age of transition to digital workflow, there are funny cases when digitalization seems to be there, but it seems not. One such case was a situation when employees printed out a contract sent by e-mail, put a facsimile or stamp on the printout, then scanned it and sent it back.
To correct this misunderstanding, it seems to me possible in two ways: by switching to digital signatures, which will require changes in the workflow for both parties, or by programmatically inserting a print image. In view of the impossibility of influencing the workflow of clients, I had to use the second way, programmatically inserting an image into a document.
There are many programs for working with pdf, but inserting images into them is either paid or limited. The current task requires an unlimited possibility of editing documents and the most simple interface so that any person can immediately use the program without any training.
Thus, I decided to write my application for inserting images into pdf that meets all the above requirements. And since the size of the application and the speed of work (within reason!) Are not key, it seemed to me optimal to write an application in python and then wrap it in an executable file.
So, the application. To create the graphical interface, the tkinter module was used, since it is mastered "on the fly", and the appearance of the application was sacrificed for the sake of development speed. So something like this happened:
: . - pdf, . poppler - pdf2image, convert_from_path pdf . , , ( 768*768 ) . = / max( , ). , . :
. pdf reportlab, , pdf , , , . pdf , pdf, PIL, , . : ( ), tkinter- 'coord' , . ( paste PIL Image). .
, python . pyinstaller, python . : - pdf poppler, exe , exe . -noconsole , . , :
from tkinter import *
from tkinter import filedialog
from PIL import ImageTk, Image
from pathlib import Path
from pdf2image import convert_from_path
import os
canvas_size = 768
document_type = (("document file", "*.jpg *.jpeg *.pdf"),
("pdf files", "*.pdf"), ("image files", "*.jpg *.jpeg"))
sign_type = (("stamp file","*.png"),)
class DocCanv(Canvas):
#Document
DocumentList=None
DocumentImage = None
DocResize = 1
DocImgLink = None
CurentPage=0
#Signature
SignImage = None
SignResize = DocResize
SignImgLink = None
SignObj = None
def DocFile(self, use_in_func=False):
if use_in_func is False:
doc_path = filedialog.askopenfilename(filetypes=document_type)
if (Path(doc_path).suffix).lower() == '.pdf':
try:
#try to use poppler from pyinstaller bundle temp directory
self.DocumentList=convert_from_path(doc_path, poppler_path = os.path.join(sys._MEIPASS, "poppler") )
except:
#reserve for poppler
self.DocumentList=convert_from_path(doc_path, poppler_path = "poppler" )
self.DocumentImage=self.DocumentList[0]
else:
self.DocumentImage = Image.open(doc_path)
self.DocumentList = [self.DocumentImage]
(width, height) = self.DocumentImage.size
self.DocResize = canvas_size / max(height, width)
self.DocImgLink=ImageTk.PhotoImage(
self.DocumentImage.resize((int(width * self.DocResize), int(height * self.DocResize)), Image.ANTIALIAS))
self.create_image(0, 0, image=self.DocImgLink, anchor=NW)
def SignFile(self, sign_path=None):
if self.SignImage is not None:
self.MergeFile()
self.DocFile(True)
if sign_path is None:
sign_path = filedialog.askopenfilename(filetypes = sign_type)
self.SignImage = Image.open(sign_path)
(width, height) = self.SignImage.size
self.SignResize=self.DocResize
self.SignImgLink=ImageTk.PhotoImage(
self.SignImage.resize((int(width * self.SignResize), int(height * self.SignResize)), Image.ANTIALIAS))
self.SignObj = self.create_image(0, 0, image=self.SignImgLink, anchor=NW)
def MoveSign(self, event):
self.coords(self.SignObj, event.x, event.y)
def ResizeSign(self, event):
if event.delta > 0:
self.SignResize = self.SignResize + 0.1
else:
self.SignResize = self.SignResize - 0.1
(width, height) = self.SignImage.size
self.SignImage.resize((int(width * self.SignResize), int(height * self.SignResize)), Image.ANTIALIAS)
self.SignImgLink=ImageTk.PhotoImage(
self.SignImage.resize((int(width * self.SignResize), int(height * self.SignResize)), Image.ANTIALIAS) )
x, y = self.coords(self.SignObj)
self.SignObj = self.create_image(x, y, image=self.SignImgLink, anchor=NW)
def MergeFile(self):
sign_coords =self.coords(self.SignObj)
sign_coords = [(int)(x / self.DocResize) for x in sign_coords]
(width, height) = self.SignImage.size
width=int((width * self.SignResize)/self.DocResize)
height=int((height * self.SignResize) / self.DocResize)
ResizedSign=self.SignImage.resize((width,height), Image.ANTIALIAS)
self.DocumentImage.paste(ResizedSign, box=sign_coords , mask=ResizedSign.convert('RGBA'))
def SaveFile(self,f_type="jpg"):
try:
self.MergeFile()
except:
pass
SavePath=filedialog.asksaveasfilename()
if (SavePath.split('.'))[-1]!=f_type:
SavePath=(SavePath.split('.'))[0]+'.'+f_type
if f_type == 'pdf':
self.DocumentList[0].save(SavePath,save_all=True,append_images=self.DocumentList[1:])
else:
self.DocumentImage.save(SavePath)
def NextPage(self):
try:
self.MergeFile()
self.DocumentList[self.CurentPage]=self.DocumentImage
except:
pass
if (len(self.DocumentList)-1) > self.CurentPage:
self.CurentPage+=1
self.DocumentImage=self.DocumentList[self.CurentPage]
self.SignImage = None
self.SignImgLink = None
self.SignObj = None
self.DocFile(True)
def PrevPage(self):
try:
self.MergeFile()
self.DocumentList[self.CurentPage]=self.DocumentImage
except:
pass
if self.CurentPage>0:
self.CurentPage-=1
self.DocumentImage=self.DocumentList[self.CurentPage]
self.SignImage = None
self.SignImgLink = None
self.SignObj = None
self.DocFile(True)
root = Tk()
root.title("Documents signer")
DocCan = DocCanv(root, width=canvas_size, height=canvas_size)
DocCan.pack(side='right', fill=BOTH, expand=1)
MenuFrame = Frame(root, width=120, bg='gray22')
MenuFrame.pack(side='right', fill=Y)
OpenDocBtn = Button(MenuFrame, text='Open Document',command=DocCan.DocFile)
OpenDocBtn.pack(fill=X, padx=5,pady=3)
SignDocBtn = Button(MenuFrame, text='Open sign',command=DocCan.SignFile)
SignDocBtn.pack(fill=X, padx=5,pady=3)
SavePDFBtn = Button(MenuFrame, text='Save as pdf',command = lambda arg1=DocCan, arg2='pdf': DocCanv.SaveFile(arg1,arg2))
SavePDFBtn.pack(fill=X, padx=5,pady=3)
SaveJPGBtn = Button(MenuFrame, text='Save as jpg',command = lambda arg1=DocCan, arg2='jpg': DocCanv.SaveFile(arg1,arg2))
SaveJPGBtn.pack(fill=X, padx=5,pady=3)
NextPageBtn = Button(MenuFrame, text='Next page',command = DocCan.NextPage)
NextPageBtn.pack(fill=X, padx=5,pady=3)
PrevPageBtn = Button(MenuFrame, text='Prev page',command = DocCan.PrevPage)
PrevPageBtn.pack(fill=X, padx=5,pady=3)
DocCan.bind("<B1-Motion>", DocCan.MoveSign)
DocCan.bind("<MouseWheel>", DocCan.ResizeSign)
root.mainloop()
git : https://github.com/mostdefaultusername/SignPDF/releases/tag/1.0