Introduction
In today's digital era PDF is a very familiar name. It's a file format used for various purposes. A PDF file can consist of one or multiple pages. In my college life, I faced a major problem when we needed to send our hand-written papers as a PDF file.
We used various mobile applications and online tools to merge and split PDF files when needed. But, after I started working with Python, I found a way to simplify the task using this programming language.
I developed a PDF Editor using Python Tkinter that will handle the following tasks:
☛Split a PDF file: Splitting a PDF file(consisting of many pages) into single pages in a specific range. You can select the range as per your needs.
☛Merge PDF files: You can merge multiple PDF files (single or multiple pages) into a single file.
☛Rotate Single or Multiple pages of a PDF file
I have used the Tkinter library to give a beautiful User Interface to this application. Also, I used another library, PyPDF2 for working with PDF files.
Watch the entire video to understand how the application works.
Requirements
Install the following Python libraries before you proceed.
☛Install Tkinter: pip install tk
☛Install PyPDF2: pip install PyPDF2
Import the modules
Create a separate folder for this project and declare a python file there with this name, "PDF_Editor.py". Now start writing your code by importing these modules.
import os
import PyPDF2
import os.path
from tkinter import *
from functools import partial
from tkinter import filedialog
from PyPDF2 import PdfFileReader
from tkinter import ttk, messagebox
from PyPDF2.pdf import PdfFileWriter
Declare PDF Editor Class
Declare a class called "PDF_Editor". It will create a GUI window for us.
class PDF_Editor:
def __init__(self, root):
self.window = root
self.window.geometry("740x480")
self.window.title('PDF Editor')
# Color Options
self.color_1 = "white"
self.color_2 = "gray30"
self.color_3 = "black"
self.color_4 = 'orange red'
# Font Options
self.font_1 = "Helvetica"
self.font_2 = "Times New Roman"
self.font_3 = "Kokila"
self.saving_location = ''
# ==============================================
# ================Menubar Section===============
# ==============================================
# Creating Menubar
self.menubar = Menu(self.window)
# Adding Edit Menu and its sub menus
edit = Menu(self.menubar, tearoff=0)
self.menubar.add_cascade(label='Edit', menu=edit)
edit.add_command(label='Split PDF',command=partial(self.SelectPDF, 1))
edit.add_command(label='Merge PDFs',command=self.Merge_PDFs_Data)
edit.add_separator()
edit.add_command(label='Rotate PDFs',command=partial(self.SelectPDF, 2))
# Adding About Menu
about = Menu(self.menubar, tearoff=0)
self.menubar.add_cascade(label='About', menu=about)
about.add_command(label='About', command=self.AboutWindow)
# Exit the Application
exit = Menu(self.menubar, tearoff=0)
self.menubar.add_cascade(label='Exit', menu=exit)
exit.add_command(label='Exit', command=self.Exit)
# Configuring the menubar
self.window.config(menu=self.menubar)
# ===================End=======================
# Creating a Frame
self.frame_1 = Frame(self.window,bg=self.color_2,width=740,height=480)
self.frame_1.place(x=0, y=0)
# Calling Home Page Window
self.Home_Page()
Declare the Miscellaneous Functions
Let's do a smart task here. We will announce some functions which will be used by the rest of the functions later.
I declared one single comment line before each function. This will help you understand the purpose of each function.
def AboutWindow(self):
messagebox.showinfo("PDF Editor", \
"PDF Editor 22.04\nDeveloped by Subhankar Rakshit")
# Remove all widgets from the Home Page
def ClearScreen(self):
for widget in self.frame_1.winfo_children():
widget.destroy()
# It updates the current saving path label
def Update_Path_Label(self):
self.path_label.config(text=self.saving_location)
# After performing the rotation operation this
# function gets call
def Update_Rotate_Page(self):
self.saving_location = ''
self.ClearScreen()
self.Home_Page()
# It destroys the main GUI window of the
# application
def Exit(self):
self.window.destroy()
Implement the Home Page
Now implement the home page which consists of three buttons for the three main tasks that our PDF Editor application performs(Split, Merge, and Rotate).
# Home Page: It consists Three Buttons
def Home_Page(self):
self.ClearScreen()
# ==============================================
# ================Buttons Section===============
# ==============================================
# Split Button
self.split_button = Button(self.frame_1, text='Split',\
font=(self.font_1, 25, 'bold'), bg="yellow", fg="black", width=8,\
command=partial(self.SelectPDF, 1))
self.split_button.place(x=260, y=80)
# Merge Button
self.merge_button = Button(self.frame_1, text='Merge', \
font=(self.font_1, 25, 'bold'), bg="yellow", fg="black", \
width=8, command=self.Merge_PDFs_Data)
self.merge_button.place(x=260, y=160)
# Merge Button
self.rotation_button = Button(self.frame_1, text='Rotate', \
font=(self.font_1, 25, 'bold'), bg="yellow", fg="black", \
width=8, command=partial(self.SelectPDF, 2))
self.rotation_button.place(x=260, y=240)
# ===================End=======================
A function for selecting only one PDF file
Tkinter offers to select a file from the system storage through the file dialog(a graphical interface that shows the directory and files present in the system storage). It reduces the headache of programming by choosing the file directly instead of mentioning every file path in the program.
This SelectPDF() function will let users select only one PDF file at once from the system storage.
# Select the PDF for Splitting and Rotating
def SelectPDF(self, to_call):
self.PDF_path = filedialog.askopenfilename(initialdir = "/", \
title = "Select a PDF File", filetypes = (("PDF files", "*.pdf*"),))
if len(self.PDF_path) != 0:
if to_call == 1:
self.Split_PDF_Data()
else:
self.Rotate_PDFs_Data()
A function for selecting multiple PDF files
This function does the same task as above; just a small difference. It gives the option to select multiple PDF files instead of just one.
# Select PDF files only for merging
def SelectPDF_Merge(self):
self.PDF_path = filedialog.askopenfilenames(initialdir = "/", \
title = "Select PDF Files", filetypes = (("PDF files", "*.pdf*"),))
for path in self.PDF_path:
self.PDF_List.insert((self.PDF_path.index(path)+1), path)
Choose the Saving Location
This editor application will allow users to select the path where they want to save the resulting file/files.
# Select the directory where the result PDF
# file/files will be stored
def Select_Directory(self):
# Storing the 'saving location' for the result file
self.saving_location = filedialog.askdirectory(title = \
"Select a location")
self.Update_Path_Label()
Get the data to Split a PDF file
Till now, users can select only one or multiple PDF files that they want to work with. Here we'll create an interface through which they can give the information regarding the Split Operation.
For example, the page range(From and To page number), the saving location, the result file name, etc. After completing all the formalities you just need to press the Split button.
Image - Split PDF File |
# Get the data from the user for splitting a PDF file
def Split_PDF_Data(self):
pdfReader = PyPDF2.PdfFileReader(self.PDF_path)
total_pages = pdfReader.numPages
self.ClearScreen()
# Button for getting back to the Home Page
home_btn = Button(self.frame_1, text="Home", \
font=(self.font_1, 10, 'bold'), command=self.Home_Page)
home_btn.place(x=10, y=10)
# Header Label
header = Label(self.frame_1, text="Split PDF", \
font=(self.font_3, 25, "bold"), bg=self.color_2, fg=self.color_1)
header.place(x=265, y=15)
# Label for showing the total number of pages
self.pages_label = Label(self.frame_1, \
text=f"Total Number of Pages: {total_pages}", \
font=(self.font_2, 20, 'bold'), bg=self.color_2, fg=self.color_3)
self.pages_label.place(x=40, y=70)
# From Label: the page number from where the
# user want to split the PDF pages
From = Label(self.frame_1, text="From", \
font=(self.font_2, 16, 'bold'), bg=self.color_2, fg=self.color_1)
From.place(x=40, y= 120)
self.From_Entry = Entry(self.frame_1, font=(self.font_2, 12, 'bold'), \
width=8)
self.From_Entry.place(x=40, y= 160)
# To Label
To = Label(self.frame_1, text="To", font=(self.font_2, 16, 'bold'), \
bg=self.color_2, fg=self.color_1)
To.place(x=160, y= 120)
self.To_Entry = Entry(self.frame_1, font=(self.font_2, 12, 'bold'), \
width=8)
self.To_Entry.place(x=160, y= 160)
Cur_Directory = Label(self.frame_1, text="Storing Location", \
font=(self.font_2, 16, 'bold'), bg=self.color_2, fg=self.color_1)
Cur_Directory.place(x=300, y= 120)
# Constant
self.path_label = Label(self.frame_1, text='/', \
font=(self.font_2, 16, 'bold'), bg=self.color_2, fg=self.color_3)
self.path_label.place(x=300, y= 160)
# Button for selecting the directory
# where the splitted PDFs will be stored
select_loc_btn = Button(self.frame_1, text="Select Location", \
font=(self.font_1, 8, 'bold'), command=self.Select_Directory)
select_loc_btn.place(x=320, y=200)
split_button = Button(self.frame_1, text="Split", \
font=(self.font_3, 16, 'bold'), bg=self.color_4, fg=self.color_1, \
width=12, command=self.Split_PDF)
split_button.place(x=250, y=250)
Get the data to Merge PDF files
Here, the users can give information related to merging multiple PDF files. I've added a Listbox here, for showing the PDF files they have chosen for this task.
Here, an interesting feature is added; users can add more PDF files to this Listbox and delete a specific file from there.
Image - Merge PDF Files |
# Get the data from the user for Merge PDF files
def Merge_PDFs_Data(self):
self.ClearScreen()
# Button for get back to the Home Page
home_btn = Button(self.frame_1, text="Home", \
font=(self.font_1, 10, 'bold'), command=self.Home_Page)
home_btn.place(x=10, y=10)
# Header Label
header = Label(self.frame_1, text="Merge PDFs", \
font=(self.font_3, 25, "bold"), bg=self.color_2, fg=self.color_1)
header.place(x=265, y=15)
select_pdf_label = Label(self.frame_1, text="Select PDFs", \
font=(self.font_2, 20, 'bold'), bg=self.color_2, fg=self.color_3)
select_pdf_label.place(x=40, y=70)
open_button = Button(self.frame_1, text="Open Folder", \
font=(self.font_1, 9, 'bold'), command=self.SelectPDF_Merge)
open_button.place(x=55, y=110)
Cur_Directory = Label(self.frame_1, text="Storing Location", \
font=(self.font_2, 18, 'bold'), bg=self.color_2, fg=self.color_1)
Cur_Directory.place(x=40, y= 150)
# Constant
self.path_label = Label(self.frame_1, text='/', \
font=(self.font_2, 16, 'bold'), bg=self.color_2, fg=self.color_3)
self.path_label.place(x=40, y= 190)
# Button for selecting the directory
# where the merged PDFs will be stored
select_loc_btn = Button(self.frame_1, text="Select Location", \
font=(self.font_1, 9, 'bold'), command=self.Select_Directory)
select_loc_btn.place(x=55, y=225)
saving_name = Label(self.frame_1, text="Choose a Name", \
font=(self.font_2, 18, 'bold'), bg=self.color_2, fg=self.color_1)
saving_name.place(x=40, y=270)
# Get the 'result file' name from the user
self.sv_name_entry = Entry(self.frame_1, \
font=(self.font_2, 12, 'bold'), width=20)
self.sv_name_entry.insert(0, 'Result')
self.sv_name_entry.place(x=40, y=310)
merge_btn = Button(self.frame_1, text="Merge", \
font=(self.font_1, 10, 'bold'), command=self.Merge_PDFs)
merge_btn.place(x=80, y=350)
listbox_label = Label(self.frame_1, text="Selected PDFs", \
font=(self.font_2, 18, 'bold'), bg=self.color_2, fg=self.color_1)
listbox_label.place(x=482, y=72)
# Listbox for showing the selected PDF files
self.PDF_List = Listbox(self.frame_1,width=40, height=15)
self.PDF_List.place(x=400, y=110)
delete_button = Button(self.frame_1, text="Delete", \
font=(self.font_1, 9, 'bold'), command=self.Delete_from_ListBox)
delete_button.place(x=400, y=395)
more_button = Button(self.frame_1, text="Select More", \
font=(self.font_1, 9, 'bold'), command=self.SelectPDF_Merge)
more_button.place(x=480, y=395)
Get the data to Rotate page/pages of a PDF file
In this case, the same scenario as the last two functions. You need to provide information related to rotating single or multiple pages of a PDF file.
Here, you must specify the page numbers you want to rotate.
Image - Rotate PDF Pages |
# Get the data from the user for Rotating one/multiple
# pages of a PDF file
def Rotate_PDFs_Data(self):
self.ClearScreen()
pdfReader = PyPDF2.PdfFileReader(self.PDF_path)
total_pages = pdfReader.numPages
# Button for get back to the Home Page
home_btn = Button(self.frame_1, text="Home", \
font=(self.font_1, 10, 'bold'), command=self.Home_Page)
home_btn.place(x=10, y=10)
# Header Label
header = Label(self.frame_1, text="Rotate PDFs", \
font=(self.font_3, 25, "bold"), bg=self.color_2, fg=self.color_1)
header.place(x=265, y=15)
# Label for showing the total number of pages
self.pages_label = Label(self.frame_1, \
text=f"Total Number of Pages: {total_pages}", \
font=(self.font_2, 20, 'bold'), bg=self.color_2, fg=self.color_3)
self.pages_label.place(x=40, y=90)
Cur_Directory = Label(self.frame_1, text="Storing Location", \
font=(self.font_2, 18, 'bold'), bg=self.color_2, fg=self.color_1)
Cur_Directory.place(x=40, y= 150)
self.fix_label = Label(self.frame_1, \
text="Rotate this Pages(Comma-Separated-Number)", \
font=(self.font_2, 16, 'bold'), bg=self.color_2, fg=self.color_1)
self.fix_label.place(x=260, y= 150)
self.fix_entry = Entry(self.frame_1, \
font=(self.font_2, 12, 'bold'), width=40)
self.fix_entry.place(x=260, y=190)
# Constant
self.path_label = Label(self.frame_1, text='/', \
font=(self.font_2, 16, 'bold'), bg=self.color_2, fg=self.color_3)
self.path_label.place(x=40, y= 190)
# Button for selecting the directory
# where the rotated PDFs will be stored
select_loc_btn = Button(self.frame_1, text="Select Location", \
font=(self.font_1, 9, 'bold'), command=self.Select_Directory)
select_loc_btn.place(x=55, y=225)
saving_name = Label(self.frame_1, text="Choose a Name", \
font=(self.font_2, 18, 'bold'), bg=self.color_2, fg=self.color_1)
saving_name.place(x=40, y=270)
# Get the 'result file' name from the user
self.sv_name_entry = Entry(self.frame_1, \
font=(self.font_2, 12, 'bold'), width=20)
self.sv_name_entry.insert(0, 'Result')
self.sv_name_entry.place(x=40, y=310)
which_side = Label(self.frame_1, text="Rotation Alignment", \
font=(self.font_2, 16, 'bold'), bg=self.color_2, fg=self.color_1)
which_side.place(x=260, y=230)
# Rotation Alignment(Clockwise and Anti-Clockwise)
text = StringVar()
self.alignment = ttk.Combobox(self.frame_1, textvariable=text)
self.alignment['values'] = ('ClockWise',
'Anti-ClockWise'
)
self.alignment.place(x=260, y=270)
rotate_button = Button(self.frame_1, text="Rotate", \
font=(self.font_3, 16, 'bold'), bg=self.color_4, \
fg=self.color_1, width=12, command=self.Rotate_PDFs)
rotate_button.place(x=255, y=360)
Perform the Split Operation
This function is called when the Split button is pressed. It handles the main task of splitting a PDF file using the PyPDF2 module.
# It manages the task for Splitting the
# selected PDF file
def Split_PDF(self):
if self.From_Entry.get() == "" and self.To_Entry.get() == "":
messagebox.showwarning("Warning!", \
"Please mention the page range\n you want to split")
else:
from_page = int(self.From_Entry.get()) - 1
to_page = int(self.To_Entry.get())
pdfReader = PyPDF2.PdfFileReader(self.PDF_path)
for page in range(from_page, to_page):
pdfWriter = PdfFileWriter()
pdfWriter.addPage(pdfReader.getPage(page))
splitPage = os.path.join(self.saving_location,f'{page+1}.pdf')
resultPdf = open(splitPage, 'wb')
pdfWriter.write(resultPdf)
resultPdf.close()
messagebox.showinfo("Success!","The PDF file has been splitted")
self.saving_location = ''
self.total_pages = 0
self.ClearScreen()
self.Home_Page()
Perform the Merge Operation
This function merges multiple PDF files into one result file.
# It manages the task for Merging the
# selected PDF files
def Merge_PDFs(self):
if len(self.PDF_path) == 0:
messagebox.showerror("Error!", "Please Select PDFs first")
else:
if self.saving_location == '':
curDirectory = os.getcwd()
else:
curDirectory = str(self.saving_location)
presentFiles = list()
for file in os.listdir(curDirectory):
presentFiles.append(file)
checkFile = f'{self.sv_name_entry.get()}.pdf'
if checkFile in presentFiles:
messagebox.showwarning('Warning!', \
"Please select an another file name to saved")
else:
pdfWriter = PyPDF2.PdfFileWriter()
for file in self.PDF_path:
pdfReader = PyPDF2.PdfFileReader(file)
numPages = pdfReader.numPages
for page in range(numPages):
pdfWriter.addPage(pdfReader.getPage(page))
mergePage = os.path.join(self.saving_location, \
f'{self.sv_name_entry.get()}.pdf')
mergePdf = open(mergePage, 'wb')
pdfWriter.write(mergePdf)
mergePdf.close()
messagebox.showinfo("Success!", \
"The PDFs have been merged successfully")
self.saving_location = ''
self.ClearScreen()
self.Home_Page()
Delete items from the Listbox
Remember, a little earlier(Merge Operation), we were talking about, users can delete a selected PDF file from the Listbox. Let's declare a separate function for this task.
# Delete an item(One PDF Path)
def Delete_from_ListBox(self):
try:
if len(self.PDF_path) < 1:
messagebox.showwarning('Warning!', \
'There is no more files to delete')
else:
for item in self.PDF_List.curselection():
self.PDF_List.delete(item)
self.PDF_path = list(self.PDF_path)
del self.PDF_path[item]
except Exception:
messagebox.showwarning('Warning!',"Please select PDFs first")
Perform the Rotation Operation
This function performs the rotation operation(clockwise, or anti-clockwise, as per the users' choice) on some selected pages(selected by the users).
# It manages the task for Rotating the pages/page of
# the selected PDF file
def Rotate_PDFs(self):
need_to_fix = list()
if self.fix_entry.get() == "":
messagebox.showwarning("Warning!", \
"Please enter the page number separated by comma")
else:
for page in self.fix_entry.get().split(','):
need_to_fix.append(int(page))
if self.saving_location == '':
curDirectory = os.getcwd()
else:
curDirectory = str(self.saving_location)
presentFiles = list()
for file in os.listdir(curDirectory):
presentFiles.append(file)
checkFile = f'{self.sv_name_entry.get()}.pdf'
if checkFile in presentFiles:
messagebox.showwarning('Warning!', \
"Please select an another file name to saved")
else:
if self.alignment.get() == 'ClockWise':
pdfReader = PdfFileReader(self.PDF_path)
pdfWriter = PdfFileWriter()
rotatefile = os.path.join(self.saving_location, \
f'{self.sv_name_entry.get()}.pdf')
fixed_file = open(rotatefile, 'wb')
for page in range(pdfReader.getNumPages()):
thePage = pdfReader.getPage(page)
if (page+1) in need_to_fix:
thePage.rotateClockwise(90)
pdfWriter.addPage(thePage)
pdfWriter.write(fixed_file)
fixed_file.close()
messagebox.showinfo('Success', 'Rotation Complete')
self.Update_Rotate_Page()
elif self.alignment.get() == 'Anti-ClockWise':
pdfReader = PdfFileReader(self.PDF_path)
pdfWriter = PdfFileWriter()
rotatefile = os.path.join(self.saving_location, \
f'{self.sv_name_entry.get()}.pdf')
fixed_file = open(rotatefile, 'wb')
for page in range(pdfReader.getNumPages()):
thePage = pdfReader.getPage(page)
if (page+1) in need_to_fix:
thePage.rotateCounterClockwise(90)
pdfWriter.addPage(thePage)
pdfWriter.write(fixed_file)
fixed_file.close()
messagebox.showinfo('Success','Rotation Complete')
self.Update_Rotate_Page()
else:
messagebox.showwarning('Warning!', \
"Please Select a Right Alignment")
The main function
Now it's time for the main function. Declare an object of Tk() and PDF_Editor() class here.
# The main function
if __name__ == "__main__":
root = Tk()
# Creating a CountDown class object
obj = PDF_Editor(root)
root.mainloop()
Full Code
Here, is the full code for your convenience.
'''PDF Editor(Split, Merge, and Rotation)
using Python Tkinter.
Copyright (c) 2022 Subhankar Rakshit
~PySeek~'''
import os
import PyPDF2
import os.path
from tkinter import *
from functools import partial
from tkinter import filedialog
from PyPDF2 import PdfFileReader
from tkinter import ttk, messagebox
from PyPDF2.pdf import PdfFileWriter
class PDF_Editor:
def __init__(self, root):
self.window = root
self.window.geometry("740x480")
self.window.title('PDF Editor')
# Color Options
self.color_1 = "white"
self.color_2 = "gray30"
self.color_3 = "black"
self.color_4 = 'orange red'
# Font Options
self.font_1 = "Helvetica"
self.font_2 = "Times New Roman"
self.font_3 = "Kokila"
self.saving_location = ''
# ==============================================
# ================Menubar Section===============
# ==============================================
# Creating Menubar
self.menubar = Menu(self.window)
# Adding Edit Menu and its sub menus
edit = Menu(self.menubar, tearoff=0)
self.menubar.add_cascade(label='Edit', menu=edit)
edit.add_command(label='Split PDF',command=partial(self.SelectPDF, 1))
edit.add_command(label='Merge PDFs',command=self.Merge_PDFs_Data)
edit.add_separator()
edit.add_command(label='Rotate PDFs',command=partial(self.SelectPDF, 2))
# Adding About Menu
about = Menu(self.menubar, tearoff=0)
self.menubar.add_cascade(label='About', menu=about)
about.add_command(label='About', command=self.AboutWindow)
# Exit the Application
exit = Menu(self.menubar, tearoff=0)
self.menubar.add_cascade(label='Exit', menu=exit)
exit.add_command(label='Exit', command=self.Exit)
# Configuring the menubar
self.window.config(menu=self.menubar)
# ===================End=======================
# Creating a Frame
self.frame_1 = Frame(self.window,bg=self.color_2,width=740,height=480)
self.frame_1.place(x=0, y=0)
# Calling Home Page Window
self.Home_Page()
# ==============================================
# =============Miscellaneous Functions==========
# ==============================================
def AboutWindow(self):
messagebox.showinfo("PDF Editor", \
"PDF Editor 22.04\nDeveloped by Subhankar Rakshit")
# Remove all widgets from the Home Page
def ClearScreen(self):
for widget in self.frame_1.winfo_children():
widget.destroy()
# It updates the current saving path label
# with '/' (current working directory)
def Update_Path_Label(self):
self.path_label.config(text=self.saving_location)
# After performing the rotation operation this
# function gets call
def Update_Rotate_Page(self):
self.saving_location = ''
self.ClearScreen()
self.Home_Page()
# It destroy the main GUI window of the
# application
def Exit(self):
self.window.destroy()
# ===================End========================
# Home Page: It consists Three Buttons
def Home_Page(self):
self.ClearScreen()
# ==============================================
# ================Buttons Section===============
# ==============================================
# Split Button
self.split_button = Button(self.frame_1, text='Split',\
font=(self.font_1, 25, 'bold'), bg="yellow", fg="black", width=8,\
command=partial(self.SelectPDF, 1))
self.split_button.place(x=260, y=80)
# Merge Button
self.merge_button = Button(self.frame_1, text='Merge', \
font=(self.font_1, 25, 'bold'), bg="yellow", fg="black", \
width=8, command=self.Merge_PDFs_Data)
self.merge_button.place(x=260, y=160)
# Merge Button
self.rotation_button = Button(self.frame_1, text='Rotate', \
font=(self.font_1, 25, 'bold'), bg="yellow", fg="black", \
width=8, command=partial(self.SelectPDF, 2))
self.rotation_button.place(x=260, y=240)
# ===================End=======================
# Select the PDF for Splitting and Rotating
def SelectPDF(self, to_call):
self.PDF_path = filedialog.askopenfilename(initialdir = "/", \
title = "Select a PDF File", filetypes = (("PDF files", "*.pdf*"),))
if len(self.PDF_path) != 0:
if to_call == 1:
self.Split_PDF_Data()
else:
self.Rotate_PDFs_Data()
# Select PDF files only for merging
def SelectPDF_Merge(self):
self.PDF_path = filedialog.askopenfilenames(initialdir = "/", \
title = "Select PDF Files", filetypes = (("PDF files", "*.pdf*"),))
for path in self.PDF_path:
self.PDF_List.insert((self.PDF_path.index(path)+1), path)
# Select the directory where the result PDF
# file/files will be stored
def Select_Directory(self):
# Storing the 'saving location' for the result file
self.saving_location = filedialog.askdirectory(title = \
"Select a location")
self.Update_Path_Label()
# Get the data from the user for splitting a PDF file
def Split_PDF_Data(self):
pdfReader = PyPDF2.PdfFileReader(self.PDF_path)
total_pages = pdfReader.numPages
self.ClearScreen()
# Button for getting back to the Home Page
home_btn = Button(self.frame_1, text="Home", \
font=(self.font_1, 10, 'bold'), command=self.Home_Page)
home_btn.place(x=10, y=10)
# Header Label
header = Label(self.frame_1, text="Split PDF", \
font=(self.font_3, 25, "bold"), bg=self.color_2, fg=self.color_1)
header.place(x=265, y=15)
# Label for showing the total number of pages
self.pages_label = Label(self.frame_1, \
text=f"Total Number of Pages: {total_pages}", \
font=(self.font_2, 20, 'bold'), bg=self.color_2, fg=self.color_3)
self.pages_label.place(x=40, y=70)
# From Label: the page number from where the
# user want to split the PDF pages
From = Label(self.frame_1, text="From", \
font=(self.font_2, 16, 'bold'), bg=self.color_2, fg=self.color_1)
From.place(x=40, y= 120)
self.From_Entry = Entry(self.frame_1, font=(self.font_2, 12, 'bold'), \
width=8)
self.From_Entry.place(x=40, y= 160)
# To Label
To = Label(self.frame_1, text="To", font=(self.font_2, 16, 'bold'), \
bg=self.color_2, fg=self.color_1)
To.place(x=160, y= 120)
self.To_Entry = Entry(self.frame_1, font=(self.font_2, 12, 'bold'), \
width=8)
self.To_Entry.place(x=160, y= 160)
Cur_Directory = Label(self.frame_1, text="Storing Location", \
font=(self.font_2, 16, 'bold'), bg=self.color_2, fg=self.color_1)
Cur_Directory.place(x=300, y= 120)
# Constant
self.path_label = Label(self.frame_1, text='/', \
font=(self.font_2, 16, 'bold'), bg=self.color_2, fg=self.color_3)
self.path_label.place(x=300, y= 160)
# Button for selecting the directory
# where the splitted PDFs will be stored
select_loc_btn = Button(self.frame_1, text="Select Location", \
font=(self.font_1, 8, 'bold'), command=self.Select_Directory)
select_loc_btn.place(x=320, y=200)
split_button = Button(self.frame_1, text="Split", \
font=(self.font_3, 16, 'bold'), bg=self.color_4, fg=self.color_1, \
width=12, command=self.Split_PDF)
split_button.place(x=250, y=250)
# Get the data from the user for Merge PDF files
def Merge_PDFs_Data(self):
self.ClearScreen()
# Button for get back to the Home Page
home_btn = Button(self.frame_1, text="Home", \
font=(self.font_1, 10, 'bold'), command=self.Home_Page)
home_btn.place(x=10, y=10)
# Header Label
header = Label(self.frame_1, text="Merge PDFs", \
font=(self.font_3, 25, "bold"), bg=self.color_2, fg=self.color_1)
header.place(x=265, y=15)
select_pdf_label = Label(self.frame_1, text="Select PDFs", \
font=(self.font_2, 20, 'bold'), bg=self.color_2, fg=self.color_3)
select_pdf_label.place(x=40, y=70)
open_button = Button(self.frame_1, text="Open Folder", \
font=(self.font_1, 9, 'bold'), command=self.SelectPDF_Merge)
open_button.place(x=55, y=110)
Cur_Directory = Label(self.frame_1, text="Storing Location", \
font=(self.font_2, 18, 'bold'), bg=self.color_2, fg=self.color_1)
Cur_Directory.place(x=40, y= 150)
# Constant
self.path_label = Label(self.frame_1, text='/', \
font=(self.font_2, 16, 'bold'), bg=self.color_2, fg=self.color_3)
self.path_label.place(x=40, y= 190)
# Button for selecting the directory
# where the merged PDFs will be stored
select_loc_btn = Button(self.frame_1, text="Select Location", \
font=(self.font_1, 9, 'bold'), command=self.Select_Directory)
select_loc_btn.place(x=55, y=225)
saving_name = Label(self.frame_1, text="Choose a Name", \
font=(self.font_2, 18, 'bold'), bg=self.color_2, fg=self.color_1)
saving_name.place(x=40, y=270)
# Get the 'result file' name from the user
self.sv_name_entry = Entry(self.frame_1, \
font=(self.font_2, 12, 'bold'), width=20)
self.sv_name_entry.insert(0, 'Result')
self.sv_name_entry.place(x=40, y=310)
merge_btn = Button(self.frame_1, text="Merge", \
font=(self.font_1, 10, 'bold'), command=self.Merge_PDFs)
merge_btn.place(x=80, y=350)
listbox_label = Label(self.frame_1, text="Selected PDFs", \
font=(self.font_2, 18, 'bold'), bg=self.color_2, fg=self.color_1)
listbox_label.place(x=482, y=72)
# Listbox for showing the selected PDF files
self.PDF_List = Listbox(self.frame_1,width=40, height=15)
self.PDF_List.place(x=400, y=110)
delete_button = Button(self.frame_1, text="Delete", \
font=(self.font_1, 9, 'bold'), command=self.Delete_from_ListBox)
delete_button.place(x=400, y=395)
more_button = Button(self.frame_1, text="Select More", \
font=(self.font_1, 9, 'bold'), command=self.SelectPDF_Merge)
more_button.place(x=480, y=395)
# Get the data from the user for Rotating one/multiple
# pages of a PDF file
def Rotate_PDFs_Data(self):
self.ClearScreen()
pdfReader = PyPDF2.PdfFileReader(self.PDF_path)
total_pages = pdfReader.numPages
# Button for get back to the Home Page
home_btn = Button(self.frame_1, text="Home", \
font=(self.font_1, 10, 'bold'), command=self.Home_Page)
home_btn.place(x=10, y=10)
# Header Label
header = Label(self.frame_1, text="Rotate PDFs", \
font=(self.font_3, 25, "bold"), bg=self.color_2, fg=self.color_1)
header.place(x=265, y=15)
# Label for showing the total number of pages
self.pages_label = Label(self.frame_1, \
text=f"Total Number of Pages: {total_pages}", \
font=(self.font_2, 20, 'bold'), bg=self.color_2, fg=self.color_3)
self.pages_label.place(x=40, y=90)
Cur_Directory = Label(self.frame_1, text="Storing Location", \
font=(self.font_2, 18, 'bold'), bg=self.color_2, fg=self.color_1)
Cur_Directory.place(x=40, y= 150)
self.fix_label = Label(self.frame_1, \
text="Rotate this Pages(Comma-Separated-Number)", \
font=(self.font_2, 16, 'bold'), bg=self.color_2, fg=self.color_1)
self.fix_label.place(x=260, y= 150)
self.fix_entry = Entry(self.frame_1, \
font=(self.font_2, 12, 'bold'), width=40)
self.fix_entry.place(x=260, y=190)
# Constant
self.path_label = Label(self.frame_1, text='/', \
font=(self.font_2, 16, 'bold'), bg=self.color_2, fg=self.color_3)
self.path_label.place(x=40, y= 190)
# Button for selecting the directory
# where the rotated PDFs will be stored
select_loc_btn = Button(self.frame_1, text="Select Location", \
font=(self.font_1, 9, 'bold'), command=self.Select_Directory)
select_loc_btn.place(x=55, y=225)
saving_name = Label(self.frame_1, text="Choose a Name", \
font=(self.font_2, 18, 'bold'), bg=self.color_2, fg=self.color_1)
saving_name.place(x=40, y=270)
# Get the 'result file' name from the user
self.sv_name_entry = Entry(self.frame_1, \
font=(self.font_2, 12, 'bold'), width=20)
self.sv_name_entry.insert(0, 'Result')
self.sv_name_entry.place(x=40, y=310)
which_side = Label(self.frame_1, text="Rotation Alignment", \
font=(self.font_2, 16, 'bold'), bg=self.color_2, fg=self.color_1)
which_side.place(x=260, y=230)
# Rotation Alignment(Clockwise and Anti-Clockwise)
text = StringVar()
self.alignment = ttk.Combobox(self.frame_1, textvariable=text)
self.alignment['values'] = ('ClockWise',
'Anti-ClockWise'
)
self.alignment.place(x=260, y=270)
rotate_button = Button(self.frame_1, text="Rotate", \
font=(self.font_3, 16, 'bold'), bg=self.color_4, \
fg=self.color_1, width=12, command=self.Rotate_PDFs)
rotate_button.place(x=255, y=360)
# It manages the task for Splitting the
# selected PDF file
def Split_PDF(self):
if self.From_Entry.get() == "" and self.To_Entry.get() == "":
messagebox.showwarning("Warning!", \
"Please mention the page range\n you want to split")
else:
from_page = int(self.From_Entry.get()) - 1
to_page = int(self.To_Entry.get())
pdfReader = PyPDF2.PdfFileReader(self.PDF_path)
for page in range(from_page, to_page):
pdfWriter = PdfFileWriter()
pdfWriter.addPage(pdfReader.getPage(page))
splitPage = os.path.join(self.saving_location,f'{page+1}.pdf')
resultPdf = open(splitPage, 'wb')
pdfWriter.write(resultPdf)
resultPdf.close()
messagebox.showinfo("Success!","The PDF file has been splitted")
self.saving_location = ''
self.total_pages = 0
self.ClearScreen()
self.Home_Page()
# It manages the task for Merging the
# selected PDF files
def Merge_PDFs(self):
if len(self.PDF_path) == 0:
messagebox.showerror("Error!", "Please Select PDFs first")
else:
if self.saving_location == '':
curDirectory = os.getcwd()
else:
curDirectory = str(self.saving_location)
presentFiles = list()
for file in os.listdir(curDirectory):
presentFiles.append(file)
checkFile = f'{self.sv_name_entry.get()}.pdf'
if checkFile in presentFiles:
messagebox.showwarning('Warning!', \
"Please select an another file name to saved")
else:
pdfWriter = PyPDF2.PdfFileWriter()
for file in self.PDF_path:
pdfReader = PyPDF2.PdfFileReader(file)
numPages = pdfReader.numPages
for page in range(numPages):
pdfWriter.addPage(pdfReader.getPage(page))
mergePage = os.path.join(self.saving_location, \
f'{self.sv_name_entry.get()}.pdf')
mergePdf = open(mergePage, 'wb')
pdfWriter.write(mergePdf)
mergePdf.close()
messagebox.showinfo("Success!", \
"The PDFs have been merged successfully")
self.saving_location = ''
self.ClearScreen()
self.Home_Page()
# Delete an item(One PDF Path)
def Delete_from_ListBox(self):
try:
if len(self.PDF_path) < 1:
messagebox.showwarning('Warning!', \
'There is no more files to delete')
else:
for item in self.PDF_List.curselection():
self.PDF_List.delete(item)
self.PDF_path = list(self.PDF_path)
del self.PDF_path[item]
except Exception:
messagebox.showwarning('Warning!',"Please select PDFs first")
# It manages the task for Rotating the pages/page of
# the selected PDF file
def Rotate_PDFs(self):
need_to_fix = list()
if self.fix_entry.get() == "":
messagebox.showwarning("Warning!", \
"Please enter the page number separated by comma")
else:
for page in self.fix_entry.get().split(','):
need_to_fix.append(int(page))
if self.saving_location == '':
curDirectory = os.getcwd()
else:
curDirectory = str(self.saving_location)
presentFiles = list()
for file in os.listdir(curDirectory):
presentFiles.append(file)
checkFile = f'{self.sv_name_entry.get()}.pdf'
if checkFile in presentFiles:
messagebox.showwarning('Warning!', \
"Please select an another file name to saved")
else:
if self.alignment.get() == 'ClockWise':
pdfReader = PdfFileReader(self.PDF_path)
pdfWriter = PdfFileWriter()
rotatefile = os.path.join(self.saving_location, \
f'{self.sv_name_entry.get()}.pdf')
fixed_file = open(rotatefile, 'wb')
for page in range(pdfReader.getNumPages()):
thePage = pdfReader.getPage(page)
if (page+1) in need_to_fix:
thePage.rotateClockwise(90)
pdfWriter.addPage(thePage)
pdfWriter.write(fixed_file)
fixed_file.close()
messagebox.showinfo('Success', 'Rotation Complete')
self.Update_Rotate_Page()
elif self.alignment.get() == 'Anti-ClockWise':
pdfReader = PdfFileReader(self.PDF_path)
pdfWriter = PdfFileWriter()
rotatefile = os.path.join(self.saving_location, \
f'{self.sv_name_entry.get()}.pdf')
fixed_file = open(rotatefile, 'wb')
for page in range(pdfReader.getNumPages()):
thePage = pdfReader.getPage(page)
if (page+1) in need_to_fix:
thePage.rotateCounterClockwise(90)
pdfWriter.addPage(thePage)
pdfWriter.write(fixed_file)
fixed_file.close()
messagebox.showinfo('Success','Rotation Complete')
self.Update_Rotate_Page()
else:
messagebox.showwarning('Warning!', \
"Please Select a Right Alignment")
# The main function
if __name__ == "__main__":
root = Tk()
# Creating a CountDown class object
obj = PDF_Editor(root)
root.mainloop()
Convert the python program to an executable file
Downloading different libraries and modules to run a program is an awkward process. So I thought it was necessary to create an executable file for this Python project.
Here, I will show how to convert a python program into an executable file for Windows and Linux both.
Do install PyInstaller at first. Use 'pip' instead of 'pip3' for Windows.
☞pip3 install pyinstaller
Windows
Go to the command prompt and change the current working directory to the folder where the python file is in. Then run this command.
☞pyinstaller --onefile -w PDF_Editor.py
Linux
Almost the same logic for Linux except for a minor change.
☞pyinstaller --onefile PDF_Editor.py
Summary
In this tutorial, we have created an excellent python project that can be useful in our daily life. It's a PDF Editor using Python Tkinter.
We used the PyPDF2 library to work with the PDF files and the Tkinter library to manage the graphics of this application. Users can Split, Merge, and Rotate PDF files using this application. Most importantly, We kept everything user-friendly.
In the end, we talked about how to convert a program file into an executable file so that this Editor Application is easy to use for those who are not familiar with programming.
I hope you enjoyed this tutorial. You're also welcome to give me any further suggestions to improve the PDF Editor application. You can share your opinion in the comment section.
To get more lovely Tkinter Examples, visit the separate page created only for Python Projects. Some examples are given below.
👉Library Management System Project in Python with MySQL
👉An Advanced Alarm Clock using Python Tkinter
👉Image to Pencil Sketch Converter in PythonIf you encounter any problems while running this project, let me know. You will receive an immediate response.
Thanks for reading!💙
PySeek