Insert signature into pdf or how to save trees

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








All Articles