First commit

This commit is contained in:
Léo 2018-05-18 08:49:19 +02:00
commit 171db182aa
17 changed files with 2646 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*.pyc
*.pdf
.*.swp
*__pycache__*

20
README.md Normal file
View File

@ -0,0 +1,20 @@
# PDF Embanner
## Presentation
This is a GUI software designed to manipulate existing pdf. It allows to :
* Concatenate several PDF files, changing page order and selecting only relevant pages and adding blank pages
* Rotate and/or crop PDF pages
* Editting PDF metadata
* Watermarking of PDF with other existing pdf or with custom text
* Extracting text from PDF
* Splitting a single PDF file into a file per page
* Encrypting PDF and decrypting if the encryption method is supported by python PyPDF2 package
## Installation
Simply clone or download this repository, and copy it on your computer.
On linux systems simply type `./pdfembanner` should work, else you will have to launch pdfembanner with python3 manually.
You have to have a python 3.5+ installed, and for watermarking with custom text a Latex installation is also necessary. To tell PDF embanner which latex compiler to use, please edit the `pdfembannersrc/compiler.conf`.
## Licence and credits
This software is distributed under GNU/GPL v3 licence, see www.gnu.org/licenses/

62
pdfembanner Executable file
View File

@ -0,0 +1,62 @@
#!/usr/bin/env python3
# -*-coding:utf-8 -*
import tkinter as tk
from pdfembannersrc import choice
import logging
import argparse
#TODO :
# * Remove Forms
parser = argparse.ArgumentParser(description="""
PDF Embanner
Program to manipulate PDFs based on PyPDF2 python library
use pdfembanner to launch GUI
""", formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument('--verbose', '-v', action='count')
args = parser.parse_args()
# Logger
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
console_handler = logging.StreamHandler()
if(args.verbose is not None):
if(args.verbose==1):
console_handler.setLevel(logging.WARN)
elif(args.verbose==2):
console_handler.setLevel(logging.INFO)
elif(args.verbose>=3):
console_handler.setLevel(logging.DEBUG)
else:
console_handler.setLevel(logging.ERROR)
formatter = logging.Formatter('%(levelname)s :: %(message)s')
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
if(args.verbose>=2):
logger.info("Logger level set to {}".format("INFO" if args.verbose==2 else "DEBUG"))
logger.debug("Launching app")
# Main window
fenetre = tk.Tk()
fenetre.geometry("+400+200")
fenetre.title("PDF Embanner")
interface = choice.Choice(fenetre)
fenetre.resizable(0,0)
fenetre.bind("<Escape>", interface.close)
fenetre.bind("1", interface.do1)
fenetre.bind("2", interface.do2)
fenetre.bind("3", interface.do3)
fenetre.bind("4", interface.do4)
fenetre.bind("5", interface.do5)
fenetre.bind("6", interface.do6)
fenetre.bind("7", interface.do7)
logger.debug("App launched")
fenetre.mainloop()
logger.debug("App closed")

View File

257
pdfembannersrc/cat.py Normal file
View File

@ -0,0 +1,257 @@
# -*-coding:utf-8 -*
import tkinter as tk
from tkinter import filedialog
from tkinter import messagebox
from pdfembannersrc.fich import Fich
from pdfembannersrc import subwindows
import logging
import PyPDF2
logger = logging.getLogger()
class Interface(tk.Toplevel):
"""
Main window
"""
def __init__(self, parent, **kwargs):
tk.Toplevel.__init__(self, parent)
self.transient(parent)
self.grab_set()
self.title("PDF Embanner : concatenation")
self.geometry("800x400")
self.protocol("WM_DELETE_WINDOW", self.close)
self.bind("<Escape>", self.close)
self.bind("<Control-o>", self.open)
self.bind("<Control-s>", self.saveas)
self.bind("<Control-m>", self.editmetadata)
self.bind("<Control-Return>", self.do)
self.lfiles = []
self.save_file = None
self.output_produced=False
self.metadata = {'/Title': '',
'/Author': '',
'/Subject': '',
'/Creator': 'PDF Embanner'}
self.f = tk.Frame(self, width=768, height=576, **kwargs)
self.f.pack(fill=tk.BOTH)
# Création de nos widgets
self.f.columnconfigure(2, weight=1)
self.f.rowconfigure(1, weight=1)
tk.Button(self.f, text="Open", command=self.open).grid(row=0, column=0)
tk.Button(self.f, text="Delete all", command=self.delall).grid(row=0, column=1)
vsb = tk.Scrollbar(self.f, orient=tk.VERTICAL)
vsb.grid(row=1, column=5, sticky=tk.N+tk.S)
self.c = tk.Canvas(self.f,yscrollcommand=vsb.set)
self.c.grid(row=1, column=0, columnspan=5, sticky="news")
vsb.config(command=self.c.yview)
self.frame_files = tk.Frame(self.c)
self.c.create_window(0, 0, window=self.frame_files, anchor=tk.NW)
self.frame_files.update_idletasks()
self.c.config(scrollregion=self.c.bbox("all"))
self.frame_files.columnconfigure(4, weight=1)
tk.Button(self.f, text="Save as", command=self.saveas).grid(row=2, column=0)
self.save_label = tk.Label(self.f, text="-" if self.save_file is None else self.save_file)
self.save_label.grid(row=2, column=1, columnspan=2, sticky=tk.W)
tk.Button(self.f, text="Metadata", command=self.editmetadata).grid(row=2, column=4)
tk.Button(self.f, text="Close", command=self.close).grid(row=3, column=3)
tk.Button(self.f, text="Generate PDF", fg="blue", command=self.do).grid(row=3, column=4)
self.message = tk.Label(self.f, text="Welcome !")
self.message.grid(row=4, column=0, columnspan=5, sticky=tk.W)
def open(self, *args):
"""
Ouvrir un fichier
"""
self.message["text"] = "Open..."
ftypes = [('PDF files (Portable Document Format)', '*.pdf'), ('All files', '*')]
lfl = filedialog.askopenfilenames(filetypes = ftypes)
if len(lfl) >0:
for fl in lfl :
self.message["text"] = "Opening {}".format(fl)
try:
flo = Fich(fl)
i = len(self.lfiles)
self.lfiles.append(flo)
self.message["text"] = "Opened {}".format(flo.name)
logger.info("Concatenate : Opened {}".format(fl))
flo.add_widgets(self.frame_files, self, i)
self.frame_files.update_idletasks()
self.c.config(scrollregion=self.c.bbox("all"))
except IOError:
self.message["text"] = "Failed to open {}".format(fl)
logger.warning("Concatenate : Error while opening {}".format(fl))
else:
self.message["text"] = "Nothing to open !"
logger.info("Concatenate : Asked for opening but nothing to open")
def delall(self):
"""
Delete all opened files
"""
if messagebox.askyesno("Delete all", "Are you sure you want to delete all ?"):
for e in self.lfiles:
e.destroy_widgets()
self.lfiles = []
self.message["text"] = "Everything deleted..."
def do(self, *args):
"""
do concatenation of PDFs with correct options !
"""
if(self.save_file is None):
messagebox.showwarning(title="PDF Output", message="Please define the 'Save As' path before !")
else:
self.message["text"] = "Generating PDF..."
nmax=0
for elem in self.lfiles:
nmax+=elem.npages
progress = subwindows.Progress(self, nmax, "Producing PDF {}".format(self.save_file))
output = PyPDF2.PdfFileWriter()
output.addMetadata(self.metadata)
in_f_list = []
currentf=''
try:
for elem in self.lfiles:
currentf=elem.path
in_f = open(elem.path, "rb")
in_f_list.append(in_f)
logger.debug("Concatenate : Processing {}".format(elem.path))
inpdf = PyPDF2.PdfFileReader(in_f)
progress.message["text"] = elem.path
if(elem.use is None):
# All is used
for i in range(elem.npages):
output.addPage(inpdf.getPage(i))
progress.next()
logger.debug("Concatenate : All pages concatenated")
else:
# variables used, order are read. Possible crop or rotation
for i in elem.order:
if(elem.use[i]):
if(i>=elem.npages): # It is a blank page
refpage = inpdf.getPage(0)
refbox = refpage.mediaBox
larg = abs(refbox.upperRight[0]-refbox.upperLeft[0])
haut = abs(refbox.lowerLeft[1]-refbox.upperLeft[1])
if(elem.crop is not None):
larg = larg * (elem.crop[1][0]-elem.crop[0][0])
haut = haut * (elem.crop[1][1]-elem.crop[0][1])
if(elem.rotate==1 or elem.rotate==3):
output.addBlankPage(width=haut, height=larg)
else:
output.addBlankPage(width=larg, height=haut)
else: # Get correspondig page of pdf
page = inpdf.getPage(i)
if(elem.crop is not None):
xmax = page.mediaBox.getUpperRight_x()
ymax = page.mediaBox.getUpperRight_y()
uR = (elem.crop[1][0]*float(xmax), elem.crop[1][1]*float(ymax))
lL = (elem.crop[0][0]*float(xmax), elem.crop[0][1]*float(ymax))
page.cropBox.lowerLeft = lL
page.cropBox.upperRight = uR
if(elem.rotate is not None):
if(elem.rotate==1):
page.rotateClockwise(270)
elif(elem.rotate==2):
page.rotateClockwise(180)
elif(elem.rotate==3):
page.rotateClockwise(90)
output.addPage(page)
progress.next()
logger.debug("Concatenate : Relevant pages concatenated")
currentf=self.save_file
progress.message["text"] = "Saving in {}".format(currentf)
with open(self.save_file, "wb") as out_f:
logger.debug("Concatenate : Writing into file : file open")
output.write(out_f)
logger.debug("Concatenate : Writing into file : done")
self.message["text"] = "Done"
self.output_produced=True
except IOError as e:
logger.warn("Concatenate : Could not open one of the files :: {} {} {}".format(currentf, e.errno, e.strerror))
messagebox.showerror(title="Error",
message="IO Error occured :\nImpossible to open one of the files ({})\nNo output produced!".format(currentf))
self.message["text"] = "An error occured!"
except Exception as e:
logger.warn("Concatenate : Unknown error occured during PDF production. {}".format(str(e)))
messagebox.showerror(title="Error",
message="An Error occured :\n{}\nNo output produced!".format(e))
self.message["text"] = "An error occured!"
finally:
for f in in_f_list:
f.close()
progress.close()
def close(self, *args):
if(self.output_produced or messagebox.askyesno("Quit", "Are you sure you want to quit ?")):
logger.debug("Concatenate : Quit")
self.destroy()
def saveas(self, *args):
fsas = filedialog.asksaveasfilename()
if fsas != '':
self.save_file = fsas
self.save_label["text"] = fsas
def delete(self, ident):
i=0
for i in range(len(self.lfiles)):
if(ident==self.lfiles[i].id):
break
elem = self.lfiles[i]
elem.destroy_widgets()
del self.lfiles[i]
for j in range(i,len(self.lfiles)):
self.lfiles[j].unset_i_widgets()
self.lfiles[j].set_i_widgets(j)
logger.debug("Concatenate : Delete {}".format(i))
def up(self, ident):
if(not self.lfiles[0].id==ident):
for i in range(1, len(self.lfiles)):
if(ident==self.lfiles[i].id):
break
self.lfiles[i-1].unset_i_widgets()
self.lfiles[i].unset_i_widgets()
obj1 = self.lfiles[i]
self.lfiles[i] = self.lfiles[i-1]
self.lfiles[i-1] = obj1
self.lfiles[i].set_i_widgets(i)
self.lfiles[i-1].set_i_widgets(i-1)
logger.debug("Concatenate : Up {}".format(i))
def down(self, ident):
if(not self.lfiles[-1].id==ident):
for i in range(0, len(self.lfiles)-1):
if(ident==self.lfiles[i].id):
break
self.lfiles[i].unset_i_widgets()
self.lfiles[i+1].unset_i_widgets()
obj1 = self.lfiles[i+1]
self.lfiles[i+1] = self.lfiles[i]
self.lfiles[i] = obj1
self.lfiles[i].set_i_widgets(i)
self.lfiles[i+1].set_i_widgets(i+1)
logger.debug("Concatenate : Down {}".format(i))
def editmetadata(self, *args):
interface3 = subwindows.Metadata(self)
logger.debug("Concatenate : Output metadata set")
interface3.mainloop()
interface3.destroy()
logger.debug("Concatenate : End output metadata set with value {}".format(self.metadata))

91
pdfembannersrc/choice.py Normal file
View File

@ -0,0 +1,91 @@
# -*-coding:utf-8 -*
import tkinter as tk
from tkinter import messagebox
import logging
from pdfembannersrc import cat, help, encrypt, full_split, get_text, watermark
logger = logging.getLogger()
class Choice(tk.Frame):
"""
Window to chose what to do...
"""
def __init__(self, fenetre, **kwargs):
self.parent = fenetre
tk.Frame.__init__(self, fenetre, **kwargs)
self.pack(fill=tk.BOTH)
# Création de nos widgets
crocus = tk.PhotoImage(file='pdfembannersrc/crocus.png')
img1 = tk.Label(self, image=crocus)
img1.image=crocus
img1.grid(row=0, column=0, rowspan=10, sticky=tk.W)
pdfem = tk.PhotoImage(file='pdfembannersrc/pdfembanner.png')
img2 = tk.Label(self, image=pdfem)
img2.image=pdfem
img2.grid(row=0, column=1, sticky=tk.W+tk.N)
self.bouton_open = tk.Button(self, text="1. Concatenate", command=self.do1)
self.bouton_open.grid(row=1, column=1, sticky=tk.W+tk.E)
self.bouton_open = tk.Button(self, text="2. Watermark", command=self.do2)
self.bouton_open.grid(row=2, column=1, sticky=tk.W+tk.E)
self.bouton_open = tk.Button(self, text="3. Encryption", command=self.do3)
self.bouton_open.grid(row=3, column=1, sticky=tk.W+tk.E)
self.bouton_open = tk.Button(self, text="4. Extract text", command=self.do4)
self.bouton_open.grid(row=4, column=1, sticky=tk.W+tk.E)
self.bouton_open = tk.Button(self, text="5. Full split", command=self.do5)
self.bouton_open.grid(row=5, column=1, sticky=tk.W+tk.E)
self.bouton_open = tk.Button(self, text="6. Forms", command=self.do6)
self.bouton_open.grid(row=6, column=1, sticky=tk.W+tk.E)
self.bouton_open = tk.Button(self, text="7. Help", command=self.do7)
self.bouton_open.grid(row=7, column=1, sticky=tk.W+tk.E)
self.bouton_open = tk.Button(self, text="Close", command=self.quit)
self.bouton_open.grid(row=8, column=1, sticky=tk.W+tk.E)
def do1(self, *args):
"""
Open concatenation interface
"""
cat.Interface(self.parent)
def do2(self, *args):
"""
Open Watermerk interface
"""
watermark.Interface(self.parent)
def do3(self, *args):
"""
Open encryption interface
"""
encrypt.Interface(self.parent)
def do4(self, *args):
"""
Open Text extraction interface
"""
get_text.Interface(self.parent)
def do5(self, *args):
"""
Open Full split interface
"""
full_split.Interface(self.parent)
def do6(self, *args):
"""
Open Forms interface
"""
messagebox.showwarning(title="Warning", message="Not yet implemented!")
def do7(self, *args):
"""
Open help interface
"""
help.Interface(self.parent)
def close(self, *args):
self.quit()

304
pdfembannersrc/editfich.py Normal file
View File

@ -0,0 +1,304 @@
# -*-coding:utf-8 -*
import tkinter as tk
from tkinter import messagebox
from tkinter import simpledialog
from pdfembannersrc import subwindows
import logging
logger = logging.getLogger()
class InterfaceEdit(tk.Toplevel):
"""
Edition window
"""
def __init__(self, parent, fichobj, **kwargs):
tk.Toplevel.__init__(self, parent)
self.transient(parent)
self.grab_set()
self.geometry("800x400")
self.bind("<Return>", self.ok)
self.fich = fichobj
self.title("Edit {}".format(self.fich.name))
if(self.fich.use is None):
self.fich.use = []
if(self.fich.order is None):
self.fich.order = []
self.pages = []
self.f = tk.Frame(self, width=768, height=576, **kwargs)
self.f.pack(fill=tk.BOTH)
# Création de nos widgets
self.f.columnconfigure(7, weight=1)
self.f.rowconfigure(3, weight=1)
info1 = tk.Label(self.f, text="Choose used pages :", fg="white", padx=20, bg="blue")
info1.grid(row=0, column=0, columnspan=10, sticky=tk.W)
tk.Button(self.f, text="Select all", command=self.selectall).grid(row=1, column=0)
tk.Button(self.f, text="Unselect all", command=self.deselectall).grid(row=2, column=0)
tk.Button(self.f, text="Select range", command=self.selectrange).grid(row=1, column=1)
tk.Button(self.f, text="Unselect range", command=self.deselectrange).grid(row=2, column=1)
tk.Button(self.f, text="Select pair", command=self.selectpair).grid(row=1, column=2)
tk.Button(self.f, text="Unselect pair", command=self.unselectpair).grid(row=2, column=2)
tk.Button(self.f, text="Select impair", command=self.selectimpair).grid(row=1, column=3)
tk.Button(self.f, text="Unselect impair", command=self.unselectimpair).grid(row=2, column=3)
tk.Button(self.f, text="Toggle", command=self.toggle).grid(row=1, column=4)
tk.Button(self.f, text="Add blank page", command=self.addblankpage).grid(row=1, column=10)
vsb = tk.Scrollbar(self.f, orient=tk.VERTICAL)
vsb.grid(row=3, column=11, sticky=tk.N+tk.S)
self.c = tk.Canvas(self.f, yscrollcommand=vsb.set)
self.c.grid(row=3, column=0, columnspan=10, sticky="news")
vsb.config(command=self.c.yview)
self.frame_pages = tk.Frame(self.c)
self.frame_pages.columnconfigure(4, weight=1)
for e in range(self.fich.npages+self.fich.nblankpages):
if(len(self.fich.use)<=e):
self.fich.use.append(True)
if(len(self.fich.order)<=e):
self.fich.order.append(e)
page = Page(self, e)
self.pages.append(page)
page.add_widgets(self.frame_pages)
self.c.create_window(0, 0, window=self.frame_pages, anchor=tk.NW)
self.frame_pages.update_idletasks()
self.c.config(scrollregion=self.c.bbox("all"))
info1 = tk.Label(self.f, text="Options on the whole file :", fg="white", padx=20, bg="blue")
info1.grid(row=4, column=0, columnspan=10, sticky=tk.W)
tk.Button(self.f, text="Rotate", command=self.rotate).grid(row=5, column=0)
tk.Button(self.f, text="Crop", command=self.crop).grid(row=5, column=1)
tk.Button(self.f, text="Ok", command=self.ok).grid(row=6, column=10)
self.message = tk.Label(self.f, text="Editing {}".format(self.fich.name))
self.message.grid(row=7, column=0, columnspan=11, sticky=tk.W)
def ok(self, *args):
"""
Save and quit
"""
self.message["text"] = "Done"
self.quit()
self.destroy()
def up(self, i):
pos = self.fich.order[i]
if(pos>0 and pos<self.fich.npages):
for toech in range(self.fich.npages):
if(self.fich.order[toech]==pos-1):
break
self.fich.order[i] -= 1
self.fich.order[toech] += 1
self.pages[i].unset_i_widgets()
self.pages[toech].unset_i_widgets()
self.pages[i].set_i_widgets(pos-1)
self.pages[toech].set_i_widgets(pos)
def down(self, i):
pos = self.fich.order[i]
if(pos<self.fich.npages-1):
for toech in range(self.fich.npages):
if(self.fich.order[toech]==pos+1):
break
self.fich.order[i] += 1
self.fich.order[toech] -= 1
self.pages[i].unset_i_widgets()
self.pages[toech].unset_i_widgets()
self.pages[i].set_i_widgets(pos+1)
self.pages[toech].set_i_widgets(pos)
def selectall(self):
for i in range(self.fich.npages):
self.fich.use[i] = True
self.pages[i].select()
def deselectall(self):
for i in range(self.fich.npages):
self.fich.use[i] = False
self.pages[i].deselect()
def selectrange(self):
selstr = simpledialog.askstring("Select range", "Range to select (ex 0-2;4):")
for e1 in selstr.split(';'):
e1 = e1.split('-')
if(len(e1)==1 and e1[0].isdigit() and int(e1[0])>0 and int(e1[0])<=len(self.fich.use)):
i = int(e1[0])
self.fich.use[i-1] = True
self.pages[i-1].select()
elif(len(e1)==2 and e1[0].isdigit() and e1[1].isdigit()):
i = int(e1[0])
j = int(e1[1])
if(i>0 and i<j and j<=len(self.fich.use)):
for k in range(i,j+1):
self.fich.use[k-1] = True
self.pages[k-1].select()
else:
messagebox.showwarning(title="Warning", message="Some ranges could not be interpeted, skipped.")
logger.info("Concatenate : Select range : Could not interpret : {}".format(e1))
else:
messagebox.showwarning(title="Warning", message="Some ranges could not be interpeted, skipped.")
logger.info("Concatenate : Select range : Could not interpret_ : {}".format(e1))
def deselectrange(self):
selstr = simpledialog.askstring("Select range", "Range to select (ex 0-2;4):")
for e1 in selstr.split(';'):
e1 = e1.split('-')
if(len(e1)==1 and e1[0].isdigit() and int(e1[0])>0 and int(e1[0])<=len(self.fich.use)):
i = int(e1[0])
self.fich.use[i-1] = False
self.pages[i-1].deselect()
elif(len(e1)==2 and e1[0].isdigit() and e1[1].isdigit()):
i = int(e1[0])
j = int(e1[1])
if(i>0 and i<j and j<=len(self.fich.use)):
for k in range(i,j+1):
self.fich.use[k-1] = False
self.pages[k-1].deselect()
else:
messagebox.showwarning(title="Warning", message="Some ranges could not be interpeted, skipped.")
logger.info("Concatenate : Select range : Could not interpret : {}".format(e1))
else:
messagebox.showwarning(title="Warning", message="Some ranges could not be interpeted, skipped.")
logger.info("Concatenate : Select range : Could not interpret_ : {}".format(e1))
def selectpair(self):
for i in range(self.fich.npages):
if((i+1)%2==0):
self.fich.use[i] = True
self.pages[i].select()
def selectimpair(self):
for i in range(self.fich.npages):
if((i+1)%2==1):
self.fich.use[i] = True
self.pages[i].select()
def unselectpair(self):
for i in range(self.fich.npages):
if((i+1)%2==0):
self.fich.use[i] = False
self.pages[i].deselect()
def unselectimpair(self):
for i in range(self.fich.npages):
if((i+1)%2==1):
self.fich.use[i] = False
self.pages[i].deselect()
def toggle(self):
for i in range(self.fich.npages):
self.pages[i].Wcheck.toggle()
self.pages[i].toggle()
def crop(self):
interface = subwindows.SetCrop(self, self.fich)
logger.debug("Concatenate : Crop set")
interface.mainloop()
interface.destroy()
logger.debug("Concatenate : End crop set with value {}".format(self.fich.crop))
def rotate(self):
interfaceRotate = subwindows.Rotate(self, self.fich)
logger.debug("Rotate {}".format(self.fich.path))
interfaceRotate.mainloop()
interfaceRotate.destroy()
logger.debug("End Rotate {} with value {}".format(self.fich.path, self.fich.rotate))
def addblankpage(self):
values={"nr":0, "after":[]}
fen = subwindows.AskBlankPage(self, self.fich, values)
fen.mainloop()
fen.destroy()
if(values["nr"]>0):
added=0
for e in values["after"]:
e = e+added
# Decalage vers le bas des pages suivantes
for i in range(e,len(self.fich.order)):
self.pages[self.fich.order[i]].unset_i_widgets()
self.pages[self.fich.order[i]].set_i_widgets(i+1+values["nr"])
# Ajout des pages blanches
part1 = self.fich.order[:e]
part2 = self.fich.order[e:]
for i in range(values["nr"]):
self.fich.use.append(True)
self.fich.nblankpages+=1
added+=1
self.fich.order.append(self.fich.npages+self.fich.nblankpages-1)
page = Page(self, self.fich.npages+self.fich.nblankpages-1)
self.pages.append(page)
page.add_widgets(self.frame_pages)
page.unset_i_widgets()
page.set_i_widgets(e+i)
part1.append(self.fich.npages+self.fich.nblankpages-1)
self.fich.order = part1 + part2
self.frame_pages.update_idletasks()
self.c.config(scrollregion=self.c.bbox("all"))
class Page:
def __init__(self, parent, i):
self.num = i
self.parent = parent
self.Wlabel = None
self.Wcheck = None
self.Wup = None
self.Wdown = None
def add_widgets(self, frame):
if self.num>=self.parent.fich.npages:
self.Wlabel = tk.Label(frame, text="Blank page")
else:
self.Wlabel = tk.Label(frame, text="Page {}".format(self.num+1))
self.Wnum = tk.Label(frame, text="{}.".format(self.num+1))
self.Wup = tk.Button(frame, text="Up", command=lambda: self.parent.up(self.num))
self.Wdown = tk.Button(frame, text="Down", command=lambda: self.parent.down(self.num))
self.Wcheck = tk.Checkbutton(frame, command=self.toggle)
if(self.parent.fich.use[self.num]):
self.select()
else:
self.deselect()
self.set_i_widgets(self.parent.fich.order[self.num])
def set_i_widgets(self, i):
self.Wnum["text"] = "{}.".format(i+1)
self.Wnum.grid(row=i, column=0, sticky=tk.W)
self.Wlabel.grid(row=i, column=4)
self.Wcheck.grid(row=i, column=1)
self.Wup.grid(row=i, column=2)
self.Wdown.grid(row=i, column=3)
def unset_i_widgets(self):
self.Wnum.grid_forget()
self.Wlabel.grid_forget()
self.Wcheck.grid_forget()
self.Wup.grid_forget()
self.Wdown.grid_forget()
def toggle(self):
if(self.parent.fich.use[self.num]):
self.deselect()
self.parent.fich.use[self.num] = False
else:
self.select()
self.parent.fich.use[self.num] = True
def select(self):
self.Wlabel.config(fg="black")
self.Wcheck.select()
def deselect(self):
self.Wlabel.config(fg="grey")
self.Wcheck.deselect()

260
pdfembannersrc/editwmark.py Normal file
View File

@ -0,0 +1,260 @@
# -*-coding:utf-8 -*
import tkinter as tk
from tkinter import messagebox
from tkinter import ttk
from pdfembannersrc import strings
import logging
logger = logging.getLogger()
class InterfaceEdit(tk.Toplevel):
"""
Edition for wmark
"""
def __init__(self, parent, wmark, **kwargs):
tk.Toplevel.__init__(self, parent)
self.transient(parent)
self.grab_set()
self.geometry("500x300")
self.bind("<Return>", self.ok)
self.bind("<Escape>", self.close)
self.bind('<<ComboboxSelected>>', self.comboselectmaj)
self.wmark = wmark
self.name = tk.StringVar()
if(self.wmark.name is not None):self.name.set(self.wmark.name)
self.position = tk.StringVar()
if(self.wmark.position is not None):self.position.set(self.wmark.position)
self.userposition = tk.StringVar()
if(self.wmark.userposition is not None and self.position.get()==strings.custom):
self.userposition.set("{},{}".format(self.wmark.userposition[0],self.wmark.userposition[1]))
self.text = tk.StringVar()
if(self.wmark.text is not None):self.text.set(self.wmark.text)
self.size = tk.StringVar()
if(self.wmark.size is not None):self.size.set(self.wmark.size)
self.notfirstpage = self.wmark.notfirstpage
self.notlastpage = self.wmark.notlastpage
self.onlyfirstpage = self.wmark.onlyfirstpage
self.onlylastpage = self.wmark.onlylastpage
self.notpages = tk.StringVar()
if(self.wmark.notpages is not None):self.notpages.set(self.wmark.notpages)
self.onlypages = tk.StringVar()
if(self.wmark.onlypages is not None):self.onlypages.set(self.wmark.onlypages)
self.bold = self.wmark.bold
self.italic = self.wmark.italic
self.boxed = self.wmark.boxed
self.lined = self.wmark.lined
self.title("Edit {}".format(self.wmark.name))
self.f = tk.Frame(self, width=768, height=576, **kwargs)
self.f.pack(fill=tk.BOTH)
# Création de nos widgets
self.f.columnconfigure(1, weight=1)
self.f.rowconfigure(10, weight=1)
tk.Label(self.f, text="Name:").grid(row=0, column=0)
tk.Entry(self.f, textvariable=self.name).grid(row=0, column=1, columnspan=2, sticky=tk.W+tk.E)
tk.Label(self.f, text="Text:", fg="red").grid(row=1, column=0)
tk.Entry(self.f, textvariable=self.text).grid(row=1, column=1, columnspan=2, sticky=tk.W+tk.E)
tk.Label(self.f, text="Position:").grid(row=2, column=0)
ttk.Combobox(self.f, state='readonly', textvariable=self.position, values=strings.positionlist).grid(row=2, column=1, columnspan=2, sticky=tk.W+tk.E)
self.Wuserposition = tk.Entry(self.f, textvariable=self.userposition, state='readonly')
self.Wuserposition.grid(row=3, column=1, columnspan=2, sticky=tk.W+tk.E)
if(self.position.get()==strings.custom):self.Wuserposition['state']='normal'
self.sf = ttk.Labelframe(self.f, text='Text formatting')
self.sf.grid(row=4, column=0, columnspan=3, sticky=tk.W+tk.E)
self.pf = ttk.Labelframe(self.f, text='Pages to apply')
self.pf.grid(row=5, column=0, columnspan=3, sticky=tk.W+tk.E)
self.pf.columnconfigure(0, weight=1)
self.pf.columnconfigure(1, weight=1)
self.Wnotfirstpage = tk.Checkbutton(self.pf, text="Not on first page", command=self.toggle_notfirstpage)
self.Wnotfirstpage.grid(row=0, column=0)
if(self.notfirstpage):self.Wnotfirstpage.select()
self.Wonlyfirstpage = tk.Checkbutton(self.pf, text="Only on first page", command=self.toggle_onlyfirstpage)
self.Wonlyfirstpage.grid(row=0, column=1)
if(self.onlyfirstpage):self.Wonlyfirstpage.select()
self.Wnotlastpage = tk.Checkbutton(self.pf, text="Not on last page", command=self.toggle_notlastpage)
self.Wnotlastpage.grid(row=1, column=0)
if(self.notlastpage):self.Wnotlastpage.select()
self.Wonlylastpage = tk.Checkbutton(self.pf, text="Only on last page", command=self.toggle_onlylastpage)
self.Wonlylastpage.grid(row=1, column=1)
if(self.onlylastpage):self.Wonlylastpage.select()
tk.Label(self.pf, text="Not on pages:").grid(row=2, column=0)
tk.Entry(self.pf, textvariable=self.notpages).grid(row=2, column=1)
tk.Label(self.pf, text="Only on pages:").grid(row=3, column=0)
tk.Entry(self.pf, textvariable=self.onlypages).grid(row=3, column=1)
self.sf.columnconfigure(0, weight=1)
self.sf.columnconfigure(1, weight=1)
self.sf.columnconfigure(2, weight=1)
tk.Label(self.sf, text="Size:").grid(row=0, column=0)
ttk.Combobox(self.sf, state='readonly', textvariable=self.size, values=strings.sizelist).grid(row=0, column=1, columnspan=2)
self.Wbold = tk.Checkbutton(self.sf, text="Bold", command=self.toggle_bold)
self.Wbold.grid(row=1, column=0)
if(self.bold):self.Wbold.select()
self.Witalic = tk.Checkbutton(self.sf, text="Italic", command=self.toggle_italic)
self.Witalic.grid(row=1, column=1)
if(self.italic):self.Witalic.select()
self.Wboxed = tk.Checkbutton(self.sf, text="Boxed", command=self.toggle_boxed)
self.Wboxed.grid(row=2, column=0)
if(self.boxed):self.Wboxed.select()
self.Wlined = tk.Checkbutton(self.sf, text="Lined", command=self.toggle_lined)
self.Wlined.grid(row=2, column=1)
if(self.lined):self.Wlined.select()
tk.Button(self.f, text="Ok", command=self.ok).grid(row=10, column=1, columnspan=2, sticky=tk.W+tk.E)
tk.Button(self.f, text="Close", command=self.close).grid(row=10, column=0)
ttk.Separator(self.f, orient="vertical").grid(row=0, column=3, rowspan=7, sticky=tk.N+tk.S, padx=5, pady=5)
self.tf = ttk.Labelframe(self.f, text='Text directives')
self.tf.grid(row=0, column=4, columnspan=2, rowspan=4)
tk.Label(self.tf, text="%p").grid(row=1, column=0)
tk.Label(self.tf, text="%P").grid(row=2, column=0)
tk.Label(self.tf, text="Page number").grid(row=1, column=1, sticky=tk.W)
tk.Label(self.tf, text="Total page number").grid(row=2, column=1, sticky=tk.W)
self.tf = ttk.Labelframe(self.f, text='Text directives')
self.tf.grid(row=4, column=4, columnspan=2, rowspan=2)
tk.Label(self.tf, text="Custom position:", bg="grey").grid(row=1, column=0, sticky=tk.W+tk.E)
tk.Label(self.tf, text="coma-separated coord-").grid(row=2, column=0, sticky=tk.W)
tk.Label(self.tf, text="inates between 0 and 1").grid(row=3, column=0, sticky=tk.W)
tk.Label(self.tf, text="Not/only on pages:", bg="grey").grid(row=4, column=0, sticky=tk.W+tk.E)
tk.Label(self.tf, text="use e for even pages").grid(row=5, column=0, sticky=tk.W)
tk.Label(self.tf, text="and o for even pages").grid(row=6, column=0, sticky=tk.W)
tk.Label(self.tf, text="end is the last page").grid(row=7, column=0, sticky=tk.W)
tk.Label(self.tf, text="Separator: ; ranges: -").grid(row=8, column=0, sticky=tk.W)
def ok(self, *args):
"""
Save and quit
"""
self.wmark.name = self.name.get()
if(self.position.get()==''):
self.wmark.position = None
else:
self.wmark.position = self.position.get()
if(self.position.get()==strings.custom):
up = self.userposition.get().split(',')
if(len(up)==2):
x = up[0]
y = up[1]
try:
x = float(x)
y = float(y)
except:
messagebox.showwarning(title="Position", message="User defined position have to be coma-separated coordinates between 0 and 1")
return
if(x>=0. and y>=0. and y<=1. and x<=1.):
self.wmark.userposition = (x, y)
else:
messagebox.showwarning(title="Position", message="User defined position have to be coma-separated coordinates between 0 and 1")
return
else:
messagebox.showwarning(title="Position", message="User defined position have to be coma-separated coordinates between 0 and 1")
return
else:
self.wmark.userposition = None
self.wmark.text = self.text.get()
if(self.size.get()==''):
self.wmark.size=None
else:
self.wmark.size = self.size.get()
self.wmark.notfirstpage = self.notfirstpage
self.wmark.notlastpage = self.notlastpage
self.wmark.notpages = self.notpages.get()
self.wmark.onlyfirstpage = self.onlyfirstpage
self.wmark.onlylastpage = self.onlylastpage
self.wmark.onlypages = self.onlypages.get()
self.wmark.Wlabel["text"] = self.wmark.name
self.wmark.bold = self.bold
self.wmark.italic = self.italic
self.wmark.boxed = self.boxed
self.wmark.lined = self.lined
self.close()
def close(self, *args):
self.destroy()
def toggle_notfirstpage(self):
if(self.notfirstpage):
self.notfirstpage = False
self.Wnotfirstpage.deselect()
else:
self.notfirstpage = True
self.onlyfirstpage=False
self.Wnotfirstpage.select()
self.Wonlyfirstpage.deselect()
def toggle_notlastpage(self):
if(self.notlastpage):
self.notlastpage = False
self.Wnotlastpage.deselect()
else:
self.notlastpage = True
self.onlylastpage = False
self.Wnotlastpage.select()
self.Wonlylastpage.deselect()
def toggle_onlyfirstpage(self):
if(self.onlyfirstpage):
self.onlyfirstpage = False
self.Wonlyfirstpage.deselect()
else:
self.onlyfirstpage = True
self.notfirstpage = False
self.Wonlyfirstpage.select()
self.Wnotfirstpage.deselect()
def toggle_onlylastpage(self):
if(self.onlylastpage):
self.onlylastpage = False
self.Wonlylastpage.deselect()
else:
self.onlylastpage = True
self.notlastpage = False
self.Wonlylastpage.select()
self.Wnotlastpage.deselect()
def toggle_bold(self):
if(self.bold):
self.bold = False
self.Wbold.deselect()
else:
self.bold = True
self.Wbold.select()
def toggle_italic(self):
if(self.italic):
self.italic = False
self.Witalic.deselect()
else:
self.italic = True
self.Witalic.select()
def toggle_boxed(self):
if(self.boxed):
self.boxed = False
self.Wboxed.deselect()
else:
self.boxed = True
self.Wboxed.select()
def toggle_lined(self):
if(self.lined):
self.lined = False
self.Wlined.deselect()
else:
self.lined = True
self.Wlined.select()
def comboselectmaj(self, *args):
if(self.position.get()==strings.custom):
self.Wuserposition['state']='normal'
else:
self.Wuserposition['state']='readonly'

199
pdfembannersrc/encrypt.py Normal file
View File

@ -0,0 +1,199 @@
# -*-coding:utf-8 -*
import tkinter as tk
from tkinter import filedialog
from tkinter import messagebox
from tkinter import simpledialog
from tkinter import ttk
from pdfembannersrc import subwindows
import logging
import PyPDF2
logger = logging.getLogger()
class Interface(tk.Toplevel):
"""
Main encryption window
Allows to :
* Encrypt a pdf
* Decrypt a pdf
"""
def __init__(self, parent, **kwargs):
tk.Toplevel.__init__(self, parent)
self.transient(parent)
self.grab_set()
self.title("PDF Embanner : encryption")
self.geometry("800x300")
self.protocol("WM_DELETE_WINDOW", self.close)
self.bind("<Escape>", self.close)
self.enc_file = None
self.dec_file = None
self.enc_saveas = None
self.dec_saveas = None
self.f = tk.Frame(self, width=768, height=576, **kwargs)
self.f.pack(fill=tk.BOTH)
# Création de nos widgets
self.f.columnconfigure(1, weight=1)
self.f.rowconfigure(12, weight=1)
tk.Label(self.f, text="Encryption...", bg="blue", fg="white", padx=20).grid(row=0, column=0, columnspan=3, sticky=tk.W)
tk.Button(self.f, text="Open", command=self.enc_open).grid(row=1, column=0)
self.enc_open_label = tk.Label(self.f, text="-" if self.enc_file is None else self.enc_file)
self.enc_open_label.grid(row=1, column=1, columnspan=3, sticky=tk.W)
tk.Button(self.f, text="Save as", command=self.set_enc_saveas).grid(row=2, column=0)
self.enc_save_label = tk.Label(self.f, text="-" if self.enc_saveas is None else self.enc_saveas)
self.enc_save_label.grid(row=2, column=1, columnspan=3, sticky=tk.W)
tk.Button(self.f, text="Encrypt", command=self.enc_do, fg="blue").grid(row=4, column=3)
ttk.Separator(self.f, orient="horizontal").grid(row=5, column=0, columnspan=4, sticky=tk.W+tk.E, padx=5, pady=10)
tk.Label(self.f, text="Decrypt...", bg="blue", fg="white", padx=20).grid(row=6, column=0, columnspan=3, sticky=tk.W)
tk.Button(self.f, text="Open", command=self.dec_open).grid(row=7, column=0)
self.dec_open_label = tk.Label(self.f, text="-" if self.dec_file is None else self.dec_file)
self.dec_open_label.grid(row=7, column=1, columnspan=3, sticky=tk.W)
tk.Button(self.f, text="Save as", command=self.set_dec_saveas).grid(row=8, column=0)
self.dec_save_label = tk.Label(self.f, text="-" if self.dec_saveas is None else self.dec_saveas)
self.dec_save_label.grid(row=8, column=1, columnspan=3, sticky=tk.W)
tk.Button(self.f, text="Decrypt", command=self.dec_do, fg="blue").grid(row=9, column=3)
ttk.Separator(self.f, orient="horizontal").grid(row=10, column=0, columnspan=4, sticky=tk.W+tk.E, padx=5, pady=10)
tk.Button(self.f, text="Close", command=self.close).grid(row=11, column=3)
self.message = tk.Label(self.f, text="Welcome!")
self.message.grid(row=13, column=0, columnspan=4, sticky=tk.W)
def enc_open(self, *args):
ftypes = [('PDF files (Portable Document Format)', '*.pdf'), ('All files', '*')]
fl = filedialog.askopenfilename(filetypes = ftypes)
if fl!='':
self.enc_file = fl
self.enc_open_label["text"] = fl
def dec_open(self, *args):
ftypes = [('PDF files (Portable Document Format)', '*.pdf'), ('All files', '*')]
fl = filedialog.askopenfilename(filetypes = ftypes)
if fl!='':
self.dec_file = fl
self.dec_open_label["text"] = fl
def enc_do(self, *args):
if(self.enc_file is None):
messagebox.showwarning(title="PDF Output", message="Please open the PDF to encrypt before !")
elif(self.enc_saveas is None):
messagebox.showwarning(title="PDF Output", message="Please define the 'Save As' path before !")
else:
pwd1 = simpledialog.askstring("Encryption", "Password:", show='*')
if(pwd1 is not None):
pwd2 = simpledialog.askstring("Encryption", "Re-type password:", show='*')
if(pwd2 is None):
return
elif(pwd1==pwd2):
pwd=pwd1
else:
messagebox.showwarning(title="Encryption", message="Passwords do not match!")
return
else:
return
currentf = self.enc_file
progress = subwindows.Progress(self, 4, "Producing PDF...")
progress.message["text"] = 'Reading files'
self.message["text"] = "Encrypting"
try:
with open(self.enc_file, 'rb') as in_f:
inpdf = PyPDF2.PdfFileReader(in_f)
progress.next()
output = PyPDF2.PdfFileWriter()
output.addMetadata(inpdf.getDocumentInfo())
output.appendPagesFromReader(inpdf)
progress.next()
output.encrypt(pwd)
progress.next()
currentf = self.enc_saveas
with open(self.enc_saveas, 'wb') as out_f:
logger.debug("Encrypt : Writing into file : start")
progress.message["text"] = 'Encryption and writing file'
output.write(out_f)
logger.debug("Encrypt : Writing into file : done")
self.message["text"] = "Done"
progress.next()
except IOError:
logger.warn("Encrypt : Could not open one of the files :: {} {} {}".format(currentf, e.errno, e.strerror))
messagebox.showerror(title="Error",
message="IO Error occured :\nImpossible to open one of the files ({})\nNo output produced!".format(currentf))
except Exception as e:
logger.warn("Encrypt : Unknown error occured during PDF production. {}".format(str(e)))
messagebox.showerror(title="Error",
message="An Error occured :\n{}\nNo output produced!".format(e))
finally:
progress.close()
def dec_do(self, *args):
if(self.dec_file is None):
messagebox.showwarning(title="PDF Output", message="Please open the PDF to encrypt before !")
elif(self.dec_saveas is None):
messagebox.showwarning(title="PDF Output", message="Please define the 'Save As' path before !")
else:
pwd = simpledialog.askstring("Encrypted", "Password:", show='*')
if(pwd is None):
return
currentf = self.dec_file
progress = subwindows.Progress(self, 4, "Producing PDF...")
progress.message["text"] = 'Reading file'
self.message["text"] = "Decrypting"
try:
with open(self.dec_file, 'rb') as in_f:
inpdf = PyPDF2.PdfFileReader(in_f)
progress.next()
okpwd = inpdf.decrypt(pwd)
if(okpwd==0):
messagebox.showwarning(title="Encrypted", message="Incorrect password!")
return
progress.next()
output = PyPDF2.PdfFileWriter()
output.addMetadata(inpdf.getDocumentInfo())
output.appendPagesFromReader(inpdf)
progress.next()
currentf = self.dec_saveas
with open(self.dec_saveas, 'wb') as out_f:
logger.debug("Decrypt : Writing into file : start")
output.write(out_f)
logger.debug("Decrypt : Writing into file : done")
self.message["text"] = "Done"
progress.next()
except IOError as e:
logger.warn("Encrypt : Could not open one of the files :: {} {} {}".format(currentf, e.errno, e.strerror))
messagebox.showerror(title="Error",
message="IO Error occured :\nImpossible to open one of the files ({})\nNo output produced!".format(currentf))
except NotImplementedError as e:
logger.warn("Decrypt : Not supported encryption method :: {}".format(e))
messagebox.showerror(title="Error",
message="The encryption method is not supported! Sorry :(")
except Exception as e:
logger.warn("Encrypt : Unknown error occured during PDF production. {}".format(str(e)))
messagebox.showerror(title="Error",
message="An Error occured :\n{}\nNo output produced!".format(e))
finally:
progress.close()
def set_enc_saveas(self, *args):
fsas = filedialog.asksaveasfilename()
if fsas != '':
self.enc_saveas = fsas
self.enc_save_label["text"] = fsas
def set_dec_saveas(self, *args):
fsas = filedialog.asksaveasfilename()
if fsas != '':
self.dec_saveas = fsas
self.dec_save_label["text"] = fsas
def close(self, *args):
self.destroy()

93
pdfembannersrc/fich.py Normal file
View File

@ -0,0 +1,93 @@
# -*-coding:utf-8 -*
from PyPDF2 import PdfFileReader, utils as PyPDF2utils
import tkinter as tk
from pdfembannersrc import editfich
import logging
logger = logging.getLogger()
class Fich:
"""
Class to store data on a pdf file
"""
count = 0
def __init__(self, filename):
self.id = Fich.count
Fich.count += 1
self.name = None
self.path = None
self.npages = 0
self.nblankpages = 0
self.use = None
self.order = None
self.rotate = None
self.crop = None
self.Wnum =None
self.Wlabel = None
self.Wedit = None
self.Wdel = None
self.Wup = None
self.Wdown = None
if(filename is not None):
self._initFromFile(filename)
def _initFromFile(self, filename):
self.path = filename
self.name = filename.split('/')[-1]
try:
with open(filename, "rb") as in_f:
input1 = PdfFileReader(in_f)
self.npages = input1.getNumPages()
except (PyPDF2utils.PdfReadError, EOFError, IOError, NotImplementedError):
raise IOError("Impossible to read file {}".format(filename))
def print1l(self):
if(self.name is not None):
return self.name
else:
return '-'
def edit(self, parent):
interface2 = editfich.InterfaceEdit(parent, self)
logger.debug("Concatenate : Editting {}".format(self.path))
interface2.mainloop()
interface2.destroy()
logger.debug("Concatenate : End Editting {}".format(self.path))
def add_widgets(self, frame, parent, i):
self.Wnum = tk.Label(frame, text="{}.".format(i+1))
self.Wlabel = tk.Label(frame, text=self.print1l())
self.Wedit = tk.Button(frame, text="Edit", command=lambda: self.edit(parent))
self.Wdel = tk.Button(frame, text="Del", command=lambda: parent.delete(self.id))
self.Wup = tk.Button(frame, text="Up", command=lambda: parent.up(self.id))
self.Wdown = tk.Button(frame, text="Down", command=lambda: parent.down(self.id))
self.set_i_widgets(i)
def unset_i_widgets(self):
self.Wnum.grid_forget()
self.Wlabel.grid_forget()
self.Wedit.grid_forget()
self.Wdel.grid_forget()
self.Wup.grid_forget()
self.Wdown.grid_forget()
def set_i_widgets(self, i):
self.Wnum.grid(row=i, column=0, sticky=tk.W)
self.Wnum["text"] = "{}.".format(i+1)
self.Wlabel.grid(row=i, column=5, sticky=tk.W)
self.Wedit.grid(row=i, column=1)
self.Wdel.grid(row=i, column=2)
self.Wup.grid(row=i, column=3)
self.Wdown.grid(row=i, column=4)
def destroy_widgets(self):
self.Wnum.destroy()
self.Wlabel.destroy()
self.Wedit.destroy()
self.Wdel.destroy()
self.Wup.destroy()
self.Wdown.destroy()

View File

@ -0,0 +1,99 @@
# -*-coding:utf-8 -*
import tkinter as tk
from tkinter import filedialog
from tkinter import messagebox
from tkinter import ttk
from pdfembannersrc import subwindows
import logging
import PyPDF2
import os
logger = logging.getLogger()
class Interface(tk.Toplevel):
"""
Full split interace
"""
def __init__(self, parent, **kwargs):
tk.Toplevel.__init__(self, parent)
self.transient(parent)
self.grab_set()
self.title("PDF Embanner : full split")
self.geometry("800x160")
self.protocol("WM_DELETE_WINDOW", self.close)
self.bind("<Escape>", self.close)
self.file = None
self.f = tk.Frame(self, width=768, height=576, **kwargs)
self.f.pack(fill=tk.BOTH)
# Création de nos widgets
self.f.columnconfigure(1, weight=1)
self.f.rowconfigure(12, weight=1)
tk.Label(self.f, text="Full split", bg="blue", fg="white", padx=20).grid(row=0, column=0, columnspan=3, sticky=tk.W)
tk.Label(self.f, text="Split a PDF file in single-page PDF files stored in a new folder").grid(row=1, column=0, columnspan=3, sticky=tk.W)
tk.Button(self.f, text="Open", command=self.open).grid(row=2, column=0)
self.open_label = tk.Label(self.f, text="-" if self.file is None else self.file)
self.open_label.grid(row=2, column=1, columnspan=3, sticky=tk.W)
tk.Button(self.f, text="Split", command=self.do, fg="blue").grid(row=3, column=3)
ttk.Separator(self.f, orient="horizontal").grid(row=5, column=0, columnspan=4, sticky=tk.W+tk.E, padx=5, pady=10)
tk.Button(self.f, text="Close", command=self.close).grid(row=11, column=3)
self.message = tk.Label(self.f, text="Welcome!")
self.message.grid(row=13, column=0, columnspan=4, sticky=tk.W)
def open(self, *args):
ftypes = [('PDF files (Portable Document Format)', '*.pdf'), ('All files', '*')]
fl = filedialog.askopenfilename(filetypes = ftypes)
if fl!='':
self.file = fl
self.open_label["text"] = fl
def do(self, *args):
if(self.file is None):
messagebox.showwarning(title="PDF Output", message="Please open the PDF to split before !")
else:
self.message["text"] = "Splitting"
outfoldername=self.file[:-4]
outbasename=self.file[:-4]+"/"+self.file.split('/')[-1][:-4]
try:
os.mkdir(outfoldername)
except OSError as e:
logger.warn("Full Split : Could not create folder {} :: {}".format(outfoldername, e))
messagebox.showerror(title="Error",
message="Folder {} already exists or could not be created folder".format(outfoldername))
progress = None
try:
with open(self.file, 'rb') as in_f:
inpdf = PyPDF2.PdfFileReader(in_f)
progress = subwindows.Progress(self, inpdf.getNumPages(), "Producing PDFs...")
progress.message["text"] = 'Reading files'
for i in range(inpdf.getNumPages()):
output = PyPDF2.PdfFileWriter()
output.addPage(inpdf.getPage(i))
progress.next()
with open("{}_{:03d}.pdf".format(outbasename, i), 'wb') as out_f:
output.write(out_f)
self.message["text"] = "Done"
except IOError:
logger.warn("Encrypt : Could not open one of the files.")
messagebox.showerror(title="Error",
message="IO Error occured :\nImpossible to open one of the files\nNo output produced!")
except Exception as e:
logger.warn("Encrypt : Unknown error occured during PDF production. {}".format(str(e)))
messagebox.showerror(title="Error",
message="An Error occured :\n{}\nNo output produced!".format(e))
finally:
if(progress is not None):
progress.close()
def close(self, *args):
self.destroy()

139
pdfembannersrc/get_text.py Normal file
View File

@ -0,0 +1,139 @@
# -*-coding:utf-8 -*
import tkinter as tk
from tkinter import filedialog
from tkinter import messagebox
from tkinter import ttk
from pdfembannersrc import subwindows
import logging
import PyPDF2
import os
logger = logging.getLogger()
class Interface(tk.Toplevel):
"""
Full split interace
"""
def __init__(self, parent, **kwargs):
tk.Toplevel.__init__(self, parent)
self.transient(parent)
self.grab_set()
self.title("PDF Embanner : full split")
self.geometry("800x160")
self.protocol("WM_DELETE_WINDOW", self.close)
self.bind("<Escape>", self.close)
self.file = None
self.f = tk.Frame(self, width=768, height=576, **kwargs)
self.f.pack(fill=tk.BOTH)
# Création de nos widgets
self.f.columnconfigure(1, weight=1)
self.f.rowconfigure(12, weight=1)
tk.Label(self.f, text="Extract text", bg="blue", fg="white", padx=20).grid(row=0, column=0, columnspan=3, sticky=tk.W)
tk.Label(self.f, text="Return the text in PDF in one or several text files").grid(row=1, column=0, columnspan=3, sticky=tk.W)
tk.Button(self.f, text="Open", command=self.open).grid(row=2, column=0)
self.open_label = tk.Label(self.f, text="-" if self.file is None else self.file)
self.open_label.grid(row=2, column=1, columnspan=3, sticky=tk.W)
tk.Button(self.f, text="Extract text in one file", command=self.do_one, fg="blue").grid(row=3, column=3)
tk.Button(self.f, text="Extract text per page", command=self.do_ppage, fg="blue").grid(row=3, column=2)
ttk.Separator(self.f, orient="horizontal").grid(row=5, column=0, columnspan=4, sticky=tk.W+tk.E, padx=5, pady=10)
tk.Button(self.f, text="Close", command=self.close).grid(row=11, column=3)
self.message = tk.Label(self.f, text="Welcome!")
self.message.grid(row=13, column=0, columnspan=4, sticky=tk.W)
def open(self, *args):
ftypes = [('PDF files (Portable Document Format)', '*.pdf'), ('All files', '*')]
fl = filedialog.askopenfilename(filetypes = ftypes)
if fl!='':
self.file = fl
self.open_label["text"] = fl
def do_one(self, *args):
if(self.file is None):
messagebox.showwarning(title="PDF Output", message="Please open the PDF to split before !")
else:
self.message["text"] = "Extracting"
outbasename=self.file[:-4]+".txt"
progress = None
try:
with open(self.file, 'rb') as in_f:
inpdf = PyPDF2.PdfFileReader(in_f)
progress = subwindows.Progress(self, inpdf.getNumPages(), "Producing PDFs...")
progress.message["text"] = 'Reading files'
output = ""
for i in range(inpdf.getNumPages()):
output+=inpdf.getPage(i).extractText() + "\n"
progress.next()
except IOError:
logger.warn("Text extraction : Could not open PDF file {}.".format(self.file))
messagebox.showerror(title="Error",
message="Impossible to open PDF file {}".format(self.file))
except Exception as e:
logger.warn("Text extraction : Unknown error occured during PDF production. {}".format(str(e)))
messagebox.showerror(title="Error",
message="An Error occured :\n{}\nNo output produced!".format(e))
finally:
if(progress is not None):
progress.close()
try:
with open(outbasename, 'w') as out_f:
out_f.write(output)
self.message["text"] = "Done"
except IOError:
logger.warn("Text extraction : Could not open output file {}.".format(outbasename))
messagebox.showerror(title="Error",
message="Impossible to open output file {}".format(outbasename))
except Exception as e:
logger.warn("Text extraction : Unknown error occured during PDF production. {}".format(str(e)))
messagebox.showerror(title="Error",
message="An Error occured :\n{}\nNo output produced!".format(e))
def do_ppage(self, *args):
if(self.file is None):
messagebox.showwarning(title="PDF Output", message="Please open the PDF to split before !")
else:
self.message["text"] = "Splitting"
outfoldername=self.file[:-4]
outbasename=self.file[:-4]+"/"+self.file.split('/')[-1][:-4]
try:
os.mkdir(outfoldername)
except OSError as e:
logger.warn("Text extraction : Could not create folder {} :: {}".format(outfoldername, e))
messagebox.showerror(title="Error",
message="Folder {} already exists or could not be created".format(outfoldername))
progress = None
try:
with open(self.file, 'rb') as in_f:
inpdf = PyPDF2.PdfFileReader(in_f)
progress = subwindows.Progress(self, inpdf.getNumPages(), "Producing PDFs...")
progress.message["text"] = 'Reading files'
for i in range(inpdf.getNumPages()):
output = inpdf.getPage(i).extractText()
progress.next()
with open("{}_{:03d}.txt".format(outbasename, i), 'w') as out_f:
out_f.write(output)
self.message["text"] = "Done"
except IOError:
logger.warn("Text extraction : Could not open one of the files.")
messagebox.showerror(title="Error",
message="IO Error occured :\nImpossible to open one of the files\nNo output produced!")
except Exception as e:
logger.warn("Text extraction : Unknown error occured during PDF production. {}".format(str(e)))
messagebox.showerror(title="Error",
message="An Error occured :\n{}\nNo output produced!".format(e))
finally:
if(progress is not None):
progress.close()
def close(self, *args):
self.destroy()

63
pdfembannersrc/help.py Normal file
View File

@ -0,0 +1,63 @@
# -*-coding:utf-8 -*
import tkinter as tk
import logging
logger = logging.getLogger()
class Interface(tk.Toplevel):
"""
Help window
"""
def __init__(self, parent, **kwargs):
tk.Toplevel.__init__(self, parent)
self.transient(parent)
self.grab_set()
self.title("PDF Embanner : help and credits")
self.geometry("700x300")
self.protocol("WM_DELETE_WINDOW", self.close)
self.bind("<Escape>", self.close)
self.bind("<Return>", self.close)
self.f = tk.Frame(self)
self.f.pack(fill=tk.BOTH)
self.f.columnconfigure(0, weight=1)
self.f.rowconfigure(0, weight=1)
# Création de nos widgets
vsb = tk.Scrollbar(self.f, orient=tk.VERTICAL)
vsb.grid(row=0, column=1, sticky=tk.N+tk.S)
hsb = tk.Scrollbar(self.f, orient=tk.HORIZONTAL)
hsb.grid(row=1, column=0, sticky=tk.W+tk.E)
c = tk.Canvas(self.f,yscrollcommand=vsb.set, xscrollcommand=hsb.set)
c.grid(row=0, column=0, sticky=tk.W+tk.E+tk.N+tk.S)
vsb.config(command=c.yview)
hsb.config(command=c.xview)
self.frame_text = tk.Frame(c)
self.frame_text.columnconfigure(0, weight=1)
i=0
with open('pdfembannersrc/help.md', 'r') as f:
for l in f.readlines():
if(l[0]=='#'):
if(l[1]=='#'):
if(l[2]=='#'):
tk.Label(self.frame_text, text=l[3:-1], font=(None, 15)).grid(row=i, column=0, sticky=tk.W)
else:
tk.Label(self.frame_text, text=l[2:-1], font=(None,18)).grid(row=i, column=0, sticky=tk.W)
else:
tk.Label(self.frame_text, text=l[1:-1], font=(None, 23)).grid(row=i, column=0, sticky=tk.W)
else:
tk.Label(self.frame_text, text=l[:-1]).grid(row=i, column=0, sticky=tk.W)
i+=1
c.create_window(0, 0, window=self.frame_text, anchor=tk.NW)
self.frame_text.update_idletasks()
c.config(scrollregion=c.bbox("all"))
self.bouton_close = tk.Button(self.f, text="Close", command=self.close)
self.bouton_close.grid(row=2, column=0, sticky=tk.E)
def close(self, *args):
self.destroy()

45
pdfembannersrc/strings.py Normal file
View File

@ -0,0 +1,45 @@
custom ="Custom"
topleft = "Top Left"
topcenter = "Top Center"
topright = "Top Right"
top = [topleft, topcenter, topright]
botleft = "Bottom Left"
botcenter = "Bottom Center"
botright = "Bottom Right"
bot = [botleft, botcenter, botright]
positionlist = [topleft, topcenter, topright, botleft, botcenter, botright, custom]
tex_size={}
size_8 = "-2"
tex_size[size_8] = "\\footnotesize"
size_9 = "-1"
tex_size[size_9] = "\\small"
size_norm = "Normal"
tex_size[size_norm] = "\\normalsize"
size_1 = "+1"
tex_size[size_1] = "\\large"
size_2 = "+2"
tex_size[size_2] = "\\Large"
size_3 = "+3"
tex_size[size_3] = "\\LARGE"
size_4 = "+4"
tex_size[size_4] = "\\huge"
sizelist = [size_8, size_9, size_norm, size_1, size_2, size_3, size_4]
tex_entete = """\\documentclass[12pt]{report}
\\usepackage[utf8]{inputenc}
\\usepackage[T1]{fontenc}
\\usepackage[left=2cm,right=2cm,top=2cm,bottom=2cm,includeheadfoot]{geometry}
\\usepackage{lastpage}
\\usepackage[absolute]{textpos}
\\usepackage{fancyhdr}
\\pagestyle{fancy}
\\setlength{\\TPHorizModule}{1cm}
\\setlength{\\TPVertModule}{1cm}
\\begin{document}
"""
tex_end = "\n\\end{document}"

View File

@ -0,0 +1,327 @@
# -*-coding:utf-8 -*
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
import logging
logger = logging.getLogger()
class Rotate(tk.Toplevel):
"""
Window for asking rotation parameters
"""
def __init__(self, parent, fichobj):
tk.Toplevel.__init__(self, parent)
self.transient(parent)
self.grab_set()
self.bind("<Return>", self.ok)
self.bind("<Escape>", self.close)
self.fich = fichobj
self.title("Rotate {}".format(self.fich.name))
self.f = tk.Frame(self)
self.f.pack(fill=tk.BOTH)
# Création de nos widgets
self.f.columnconfigure(0, weight=1)
self.f.rowconfigure(5, weight=1)
info1 = tk.Label(self.f, text="Rotation :", fg="white", padx=20, bg="blue")
info1.grid(row=0, column=0, sticky=tk.W)
self.rota = tk.IntVar()
if(self.fich.rotate is not None):
self.rota.set(self.fich.rotate)
else:
self.rota.set(0)
bouton_sel = tk.Radiobutton(self.f, text="No rotation", variable=self.rota, value=0)
bouton_sel.grid(row=1, column=0)
bouton_sel = tk.Radiobutton(self.f, text="+Pi/2", variable=self.rota, value=1)
bouton_sel.grid(row=2, column=0)
bouton_sel = tk.Radiobutton(self.f, text="+Pi", variable=self.rota, value=2)
bouton_sel.grid(row=3, column=0)
bouton_sel = tk.Radiobutton(self.f, text="-Pi/2", variable=self.rota, value=3)
bouton_sel.grid(row=4, column=0)
bouton_do = tk.Button(self.f, text="Ok", command=self.ok)
bouton_do.grid(row=6, column=0)
def ok(self):
"""
Save and quit
"""
self.fich.rotate = self.rota.get()
self.destroy()
def close(self, *args):
self.destroy()
class Metadata(tk.Toplevel):
"""
Window for asking metadata
"""
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
self.transient(parent)
self.grab_set()
self.title("Metadata")
self.outobj = parent
self.f = tk.Frame(self)
self.f.pack(fill=tk.BOTH)
self.bind("<Return>", self.ok)
self.bind("<Escape>", self.close)
# Création de nos widgets
self.f.columnconfigure(1, weight=1)
self.f.rowconfigure(5, weight=1)
info1 = tk.Label(self.f, text="PDF Metadata :", fg="white", padx=20, bg="blue")
info1.grid(row=0, column=0, sticky=tk.W)
tk.Label(self.f, text="Title: ").grid(row=1, column=0)
tk.Label(self.f, text="Author: ").grid(row=2, column=0)
tk.Label(self.f, text="Subject: ").grid(row=3, column=0)
tk.Label(self.f, text="Creator: ").grid(row=4, column=0)
self.title = tk.StringVar()
self.author = tk.StringVar()
self.subject = tk.StringVar()
self.creator = tk.StringVar()
self.title.set(self.outobj.metadata['/Title'])
self.author.set(self.outobj.metadata['/Author'])
self.subject.set(self.outobj.metadata['/Subject'])
self.creator.set(self.outobj.metadata['/Creator'])
tk.Entry(self.f, textvariable=self.title).grid(row=1, column=1)
tk.Entry(self.f, textvariable=self.author).grid(row=2, column=1)
tk.Entry(self.f, textvariable=self.subject).grid(row=3, column=1)
tk.Entry(self.f, textvariable=self.creator).grid(row=4, column=1)
tk.Button(self.f, text="Annuler", command=self.quit).grid(row=6, column=0)
tk.Button(self.f, text="Ok", command=self.ok).grid(row=6, column=1)
def ok(self, *args):
"""
Save and quit
"""
self.outobj.metadata['/Title'] = self.title.get()
self.outobj.metadata['/Author'] = self.author.get()
self.outobj.metadata['/Subject'] = self.subject.get()
self.outobj.metadata['/Creator'] = self.creator.get()
self.quit()
def close(self, *args):
self.destroy()
class Progress(tk.Toplevel):
"""
Progress bar window for production of PDF
"""
def __init__(self, parent, nmax, text):
tk.Toplevel.__init__(self, parent)
self.transient(parent)
self.grab_set()
self.title("Producing PDF...")
self.nmax = nmax
self.outobj = parent
self.f = tk.Frame(self)
self.f.pack(fill=tk.BOTH)
info1 = tk.Label(self.f, text=text, padx=20)
info1.grid(row=0, column=0, sticky=tk.W)
self.progress = ttk.Progressbar(self.f, orient="horizontal", length=300)
self.progress["maximum"] = nmax
self.progress.grid(row=2, column=0)
self.message = tk.Label(self.f, text="Starting production...")
self.message.grid(row=3, column=0, sticky=tk.W)
def next(self):
self.progress["value"]+=1
self.progress.update_idletasks()
def close(self):
self.destroy()
class AskBlankPage(tk.Toplevel):
"""
Ask for position and number for adding blank pages
"""
def __init__(self, parent, fich, values):
tk.Toplevel.__init__(self, parent)
self.transient(parent)
self.grab_set()
self.title("Adding blank pages")
self.bind("<Return>", self.ok)
self.bind("<Escape>", self.close)
self.fich = fich
self.values = values
self.after = tk.StringVar()
self.nr = tk.StringVar()
self.nr.set("1")
self.f = tk.Frame(self)
self.f.pack(fill=tk.BOTH)
tk.Label(self.f, text="Add after page:", padx=20).grid(row=0, column=0, sticky=tk.W)
tk.Label(self.f, text="Nr of blank pages:", padx=20).grid(row=1, column=0, sticky=tk.W)
tk.Entry(self.f, textvariable=self.after).grid(row=0, column=1)
tk.Entry(self.f, textvariable=self.nr).grid(row=1, column=1)
tk.Button(self.f, text="Annuler", command=self.close).grid(row=3, column=0)
tk.Button(self.f, text="Ok", command=self.ok).grid(row=3, column=1)
def ok(self, *args):
self.values["after"] = []
self.values["nr"] = 0
for e in self.after.get().split(';'):
if(e.isdigit()):
i = int(e)
if(i<=0 or i>self.fich.npages+self.fich.nblankpages):
messagebox.showwarning(title="Warning", message="Number out of range, please correct it !")
return
else:
self.values["after"].append(i)
else:
messagebox.showwarning(title="Warning", message="Non-number entry (1st line), please correct it !")
return
nr = self.nr.get()
if(nr.isdigit()):
nr = int(nr)
if(nr>0):
self.values["nr"] = nr
else:
messagebox.showwarning(title="Warning", message="Non-number entry (2nd line), please correct it !")
return
self.close()
logger.debug("Concatenate : Add blank pages : End with values {}".format(self.values))
def close(self, *args):
self.quit()
class SetCrop(tk.Toplevel):
"""
Edition for crop box
"""
def __init__(self, parent, fich, **kwargs):
tk.Toplevel.__init__(self, parent)
self.transient(parent)
self.grab_set()
self.geometry("400x150")
self.bind("<Return>", self.ok)
self.bind("<Escape>", self.close)
self.fich = fich
self.enabled = False
self.llx = tk.StringVar()
self.lly = tk.StringVar()
self.urx = tk.StringVar()
self.ury = tk.StringVar()
if(self.fich.crop is not None):
self.enabled = True
self.llx.set(self.fich.crop[0][0])
self.lly.set(self.fich.crop[0][1])
self.urx.set(self.fich.crop[1][0])
self.ury.set(self.fich.crop[1][1])
self.title("Crop {}".format(self.fich.name))
self.f = tk.Frame(self, width=768, height=576, **kwargs)
self.f.pack(fill=tk.BOTH)
# Création de nos widgets
self.f.columnconfigure(1, weight=1)
self.f.columnconfigure(2, weight=1)
self.f.rowconfigure(4, weight=1)
self.Wenable = tk.Checkbutton(self.f, text="Enable cropping", command=self.toggle_enable)
self.Wenable.grid(row=0, column=0, columnspan=3)
if(self.enabled):
self.Wenable.select()
tk.Label(self.f, text="Lower Left bound (x and y):").grid(row=1, column=0)
tk.Label(self.f, text="Upper Right bound (x and y):").grid(row=2, column=0)
tk.Label(self.f, text="Bounds are given in fraction of page size (between 0 and 1).").grid(row=3, column=0, columnspan=3)
self.Wllx = tk.Entry(self.f, textvariable=self.llx)
self.Wllx.grid(row=1, column=1, sticky=tk.W+tk.E)
self.Wlly = tk.Entry(self.f, textvariable=self.lly)
self.Wlly.grid(row=1, column=2, sticky=tk.W+tk.E)
self.Wurx = tk.Entry(self.f, textvariable=self.urx)
self.Wurx.grid(row=2, column=1, sticky=tk.W+tk.E)
self.Wury = tk.Entry(self.f, textvariable=self.ury)
self.Wury.grid(row=2, column=2, sticky=tk.W+tk.E)
if(not self.enabled):
self.Wllx['state']='readonly'
self.Wlly['state']='readonly'
self.Wurx['state']='readonly'
self.Wury['state']='readonly'
tk.Button(self.f, text="Ok", command=self.ok).grid(row=5, column=2, sticky=tk.W+tk.E)
tk.Button(self.f, text="Close", command=self.close).grid(row=5, column=0)
def ok(self, *args):
"""
Save and quit
"""
if(self.enabled):
try:
llx = float(self.llx.get())
lly = float(self.lly.get())
urx = float(self.urx.get())
ury = float(self.ury.get())
except:
messagebox.showwarning(title="Cropping", message="You have to enter float only for x and y cropping limits !")
return
if(llx<0 or llx>1 or lly<0 or lly>1 or urx<0 or urx>1 or ury<0 or ury>1):
messagebox.showwarning(title="Cropping", message="Values have to be between 0 and 1 !")
return
if(llx>=urx or lly>=ury):
messagebox.showwarning(title="Cropping", message="Bounds not in correct order !")
return
self.fich.crop=[[llx,lly],[urx,ury]]
else:
self.fich.crop = None
self.close()
def close(self, *args):
self.quit()
def toggle_enable(self):
if(self.enabled):
self.enabled = False
self.Wenable.deselect()
self.llx.set('')
self.lly.set('')
self.urx.set('')
self.ury.set('')
self.Wllx['state']='readonly'
self.Wlly['state']='readonly'
self.Wurx['state']='readonly'
self.Wury['state']='readonly'
else:
self.enabled = True
self.Wenable.select()
self.Wllx['state']='normal'
self.Wlly['state']='normal'
self.Wurx['state']='normal'
self.Wury['state']='normal'

595
pdfembannersrc/watermark.py Normal file
View File

@ -0,0 +1,595 @@
# -*-coding:utf-8 -*
import tkinter as tk
from tkinter import filedialog
from tkinter import messagebox
from tkinter import ttk
from pdfembannersrc.wmark import Wmark
import configparser
from subprocess import Popen, CalledProcessError
from pdfembannersrc import strings
from pdfembannersrc import subwindows
import shlex
import os.path
import random
import logging
import PyPDF2
logger = logging.getLogger()
class Interface(tk.Toplevel):
"""
Main window
"""
def __init__(self, parent, **kwargs):
tk.Toplevel.__init__(self, parent)
self.transient(parent)
self.grab_set()
self.title("PDF Embanner : watermark")
self.geometry("800x400")
self.protocol("WM_DELETE_WINDOW", self.close)
self.bind("<Escape>", self.close)
self.bind("<Control-o>", self.open)
self.bind("<Control-f>", self.open_markfile)
self.bind("<Control-s>", self.saveas)
self.bind("<Control-m>", self.editmetadata)
self.bind("<Control-a>", self.add)
self.bind("<Control-Return>", self.do)
self.file = None
self.npages=0
self.box=None
self.markfile = None
self.save_file = None
self.wmarks = []
self.output_produced=False
self.metadata = {'/Title': '',
'/Author': '',
'/Subject': '',
'/Creator': 'PDF Embanner'}
self.f = tk.Frame(self, width=768, height=576, **kwargs)
self.f.pack(fill=tk.BOTH)
# Création de nos widgets
self.f.columnconfigure(1, weight=1)
self.f.rowconfigure(8, weight=1)
tk.Button(self.f, text="Open", command=self.open).grid(row=0, column=0)
tk.Button(self.f, text="Close", command=self.close).grid(row=0, column=4, sticky=tk.E)
self.file_label = tk.Label(self.f, text="-" if self.file is None else self.file)
self.file_label.grid(row=0, column=1, columnspan=4, sticky=tk.W)
ttk.Separator(self.f, orient="horizontal").grid(row=1, column=0, columnspan=6, sticky=tk.W+tk.E, padx=5, pady=5)
tk.Label(self.f, text="Watermark with an other PDF", bg="blue", fg="white", padx=20).grid(row=2, column=0, columnspan=5, sticky=tk.W)
tk.Button(self.f, text="File", command=self.open_markfile).grid(row=3, column=0)
self.markfile_label = tk.Label(self.f, text="-" if self.markfile is None else self.markfile)
self.markfile_label.grid(row=3, column=1, columnspan=2, sticky=tk.W)
tk.Label(self.f, text="Select pages").grid(row=3, column=3, sticky=tk.W)
self.select_page_markfile = tk.StringVar()
tk.Entry(self.f, textvariable=self.select_page_markfile).grid(row=3, column=4)
self.all_pages_markfile = tk.IntVar()
self.all_pages_markfile.set(0)
tk.Radiobutton(self.f, text="Apply on first page(s) only", variable=self.all_pages_markfile, value=0).grid(row=4, column=0, columnspan=2, sticky=tk.W)
tk.Radiobutton(self.f, text="Apply on all pages", variable=self.all_pages_markfile, value=1).grid(row=5, column=0, columnspan=2, sticky=tk.W)
self.markfile_bottom = tk.IntVar()
self.markfile_bottom.set(0)
tk.Radiobutton(self.f, text="Apply on top", variable=self.markfile_bottom, value=0).grid(row=4, column=2, columnspan=3, sticky=tk.W)
tk.Radiobutton(self.f, text="Apply on bottom", variable=self.markfile_bottom, value=1).grid(row=5, column=2, columnspan=3, sticky=tk.W)
ttk.Separator(self.f, orient="horizontal").grid(row=6, column=0, columnspan=6, sticky=tk.W+tk.E, padx=5, pady=5)
tk.Label(self.f, text="Text watermark", bg="blue", fg="white", padx=20).grid(row=7, column=0, columnspan=5, sticky=tk.W)
tk.Button(self.f, text="Add", command=self.add).grid(row=7, column=4)
vsb = tk.Scrollbar(self.f, orient=tk.VERTICAL)
vsb.grid(row=8, column=5, sticky=tk.N+tk.S)
self.c = tk.Canvas(self.f,yscrollcommand=vsb.set)
self.c.grid(row=8, column=0, columnspan=5, sticky="news")
vsb.config(command=self.c.yview)
self.frame_wmark = tk.Frame(self.c)
self.c.create_window(0, 0, window=self.frame_wmark, anchor=tk.NW)
self.frame_wmark.update_idletasks()
self.c.config(scrollregion=self.c.bbox("all"))
self.frame_wmark.columnconfigure(3, weight=1)
ttk.Separator(self.f, orient="horizontal").grid(row=9, column=0, columnspan=6, sticky=tk.W+tk.E, padx=5, pady=5)
tk.Button(self.f, text="Save as", command=self.saveas).grid(row=10, column=0)
self.save_label = tk.Label(self.f, text="-" if self.save_file is None else self.save_file)
self.save_label.grid(row=10, column=1, columnspan=3, sticky=tk.W)
tk.Button(self.f, text="Metadata", command=self.editmetadata).grid(row=10, column=4, sticky=tk.E)
tk.Button(self.f, text="Generate PDF", fg="blue", command=self.do).grid(row=11, column=3, columnspan=3, sticky=tk.E)
self.message = tk.Label(self.f, text="Welcome !")
self.message.grid(row=12, column=0, columnspan=6, sticky=tk.W)
def add(self, *args):
"""
Add watermark
"""
wm = Wmark(None)
i = len(self.wmarks)
self.wmarks.append(wm)
wm.add_widgets(self.frame_wmark, self, i)
self.frame_wmark.update_idletasks()
self.c.config(scrollregion=self.c.bbox("all"))
self.message["text"] = "Added watermark"
def do(self, *args):
"""
Do watermarking with correct options !
"""
if(self.file is None):
messagebox.showwarning(title="PDF Output", message="Please open an input PDF file before !")
return
elif(self.save_file is None):
messagebox.showwarning(title="PDF Output", message="Please define the 'Save As' path before !")
return
if(len(self.wmarks)>0):
# Generate PDF for watermarking with Latex
err, use_pages_generated, pdfgeneratedname = generate_pdf(self.wmarks, self.save_file, self.npages)
if(err>0 and err<100):
self.message["text"] = "Error : no output generated"
if(err==1):
logger.error("Watermark :: Could not write in output folder")
messagebox.showerror(title="PDF Output", message="Could not write in output folder")
elif(err==2):
logger.error("Watermark :: Could not parse compiler config file")
messagebox.showerror(title="PDF Output", message="Could not parse compiler config file")
elif(err==3):
logger.error("Watermark :: No latex compiler found")
messagebox.showerror(title="PDF Output", message="No latex compiler found. Check your compiler.conf file")
elif(err==4):
logger.error("Watermark :: Latex compilation error")
messagebox.showerror(title="PDF Output", message="Latex compilation error. You may have put an incorrect caracter in text area.")
elif(err==5):
logger.error("Watermark :: IOError during compilation")
messagebox.showerror(title="PDF Output", message="IOError during compilation. Check rights on output folder.")
return
elif(err==100):
logger.info("Watermark :: There were errors in your page ranges. Some page ranges not taken in consideration.")
messagebox.showinfo(title="PDF Output", message="There were errors in your page ranges. Some page ranges not taken in consideration.")
genpdf=True
else:
genpdf=False
logger.debug("Genpdf is set to {}".format(genpdf))
if(self.markfile is not None):
try:
self.message["text"] = "Opening {}".format(self.markfile)
with open(self.markfile, "rb") as in_f:
input2 = PyPDF2.PdfFileReader(in_f)
markf_npages = input2.getNumPages()
correct, use_pages_markf = parse_range(self.select_page_markfile.get(), markf_npages)
if(not correct):
messagebox.showerror(title="Watermark file", message="Incorrect 'Select page' range. Please correct it!".format(self.markfile))
return
if(len(use_pages_markf)==0):
use_pages_markf = list(range(1,markf_npages+1))
except (PyPDF2.utils.PdfReadError, EOFError, IOError, NotImplementedError):
self.message["text"] = "Failed to open {}".format(self.markfile)
messagebox.showerror(title="PDF Open", message="Impossible to read PDF input file ! {}".format(self.markfile))
logger.warning("Wartermark : Error while opening {}".format(self.markfile))
return
markf = True
else:
markf=False
use_pages_markf = []
logger.debug("Markf is set to {} with pages {}".format(markf, use_pages_markf))
genpdf_f = None
markf_f = None
try:
if(markf):
markf_f = open(self.markfile, 'rb')
input_mark = PyPDF2.PdfFileReader(markf_f)
if(genpdf):
genpdf_f = open(pdfgeneratedname, 'rb')
input_gen = PyPDF2.PdfFileReader(genpdf_f)
output = PyPDF2.PdfFileWriter()
with open(self.file,'rb') as in_f:
input_ref = PyPDF2.PdfFileReader(in_f)
for i in range(self.npages):
# Adding i-th page !
if(markf and (self.all_pages_markfile.get()==1 or i<len(use_pages_markf))):
if(self.all_pages_markfile):
print("add 1 on {}".format(i))
if(self.markfile_bottom.get()):
page = input_mark.getPage(use_pages_markf[i%len(use_pages_markf)]-1)
page.mergePage(input_ref.getPage(i))
else:
page = input_ref.getPage(i)
page2 = input_mark.getPage(use_pages_markf[i%len(use_pages_markf)]-1)
page.mergePage(page2)
else:
print("add 2 on {}".format(i))
if(self.markfile_bottom.get()):
page = input_mark.getPage(use_pages_markf[i]-1)
page.mergePage(input_ref.getPage(i))
else:
page = input_ref.getPage(i)
page2 = input_mark.getPage(use_pages_markf[i]-1)
page.mergePage(page2)
else:
page = input_ref.getPage(i)
if(genpdf and use_pages_generated[i]):
page3 = input_gen.getPage(i)
sp3 = page3.mediaBox
if(self.box is not None):
try:
sx = float(self.box.upperRight[0] - self.box.upperLeft[0])/float(sp3.upperRight[0] - sp3.upperLeft[0])
sy = float(self.box.lowerRight[1] - self.box.upperRight[1])/float(sp3.lowerRight[1] - sp3.upperRight[1])
page3.scale(sx,sy)
except:
logger.warn("Watermark :: Error during rescaling of pages")
page.mergePage(page3)
output.addPage(page)
# Writing output file
try:
with open(self.save_file, 'wb') as out_f:
output.write(out_f)
except IOError:
logger.error("Watermark :: IOError :: Could not write output file {}".format(self.save_file))
self.message["text"] = "Could not write into {}".format(self.save_file)
messagebox.showerror(title="PDF Open", message="Impossible to write in output file ! {}".format(self.save_file))
except IOError:
logger.error("Watermark :: IOError :: Could not open one of the input file")
self.message["text"] = "Failed to open input file(s)".format(self.markfile)
messagebox.showerror(title="PDF Open", message="Impossible to read PDF input file(s) !")
except PyPDF2.utils.PdfReadError:
logger.error("Watermark :: Invalid PDF")
self.message["text"] = "Invalid input PDF".format(self.markfile)
messagebox.showerror(title="PDF Open", message="Invalid PDF as input file!")
finally:
if(genpdf_f is not None):
genpdf_f.close()
# Removing genpdf file
try:
os.remove(pdfgeneratedname)
except:
logger.warn("Watermark :: Error while cleaning generated PDF")
if(markf_f is not None):
markf_f.close()
self.message["text"] = "Done"
def close(self, *args):
if(self.output_produced or messagebox.askyesno("Quit", "Are you sure you want to quit ?")):
logger.debug("Watermark: Quit")
self.destroy()
def open(self, *args):
self.message["text"] = "Open..."
ftypes = [('PDF files (Portable Document Format)', '*.pdf'), ('All files', '*')]
fl = filedialog.askopenfilename(filetypes = ftypes)
if fl != '':
try:
self.message["text"] = "Opening {}".format(fl)
with open(fl, "rb") as in_f:
input1 = PyPDF2.PdfFileReader(in_f)
self.npages = input1.getNumPages()
self.box = input1.getPage(0).mediaBox
self.file = fl
self.file_label["text"] = fl
self.message["text"] = "Opened {}".format(fl)
except (PyPDF2.utils.PdfReadError, EOFError, IOError, NotImplementedError):
self.message["text"] = "Failed to open {}".format(fl)
messagebox.showwarning(title="PDF Open", message="Impossible to read PDF input file ! {}".format(fl))
logger.warning("Watermark: Error while opening {}".format(fl))
else:
self.message["text"] = "Nothing to open"
def open_markfile(self, *args):
self.message["text"] = "File..."
ftypes = [('PDF files (Portable Document Format)', '*.pdf'), ('All files', '*')]
fl = filedialog.askopenfilename(filetypes = ftypes)
if fl != '':
self.markfile = fl
self.markfile_label["text"] = fl
self.message["text"] = "Watermark file set to {}".format(fl)
else:
self.message["text"] = "Nothing to open"
def saveas(self, *args):
self.message["text"] = "Save as..."
fsas = filedialog.asksaveasfilename()
if fsas != '':
self.save_file = fsas
self.save_label["text"] = fsas
self.message["text"] = "Save as set to {}".format(fsas)
else:
self.message["text"] = "Aborted save as"
def delete(self, ident):
i=0
for i in range(len(self.wmarks)):
if(ident==self.wmarks[i].id):
break
self.wmarks[i].destroy_widgets()
del self.wmarks[i]
for j in range(i,len(self.wmarks)):
self.wmarks[j].unset_i_widgets()
self.wmarks[j].set_i_widgets(j)
self.message["text"] = "Deleted."
def editmetadata(self, *args):
self.message["text"] = "Edit Metadata..."
interface3 = subwindows.Metadata(self)
logger.debug("Watermark : Output metadata set")
interface3.mainloop()
interface3.destroy()
logger.debug("Watermark : End output metadata set with value {}".format(self.metadata))
self.message["text"] = "Edited Metadata"
###############################################################################################
def parse_range(srange, nmax):
"""
Parse a text as a range of pages
separated by ; and -
Recognise 'e' for even pages, 'o' for odd pages and 'end'
srange : string to be parsed
nmax : page maximum number
NB : Pages are numbered from 1 to nmax
"""
correct=True
ret = []
for e in srange.split(';'):
e = e.split('-')
if(len(e)==1):
if(e[0].lower()=='e'): # Even/pair
ret+=list(range(1,nmax,2))
elif(e[0].lower()=='o'): # Odd/impair
ret+=list(range(0,nmax,2))
elif(e[0].lower()=='end'): # Odd/impair
ret.append(nmax)
elif(e[0].isdigit() and int(e[0])>0 and int(e[0])<nmax):
ret+=[int(e[0])]
else:
correct=False
elif(len(e)==2):
if(e[0].isdigit() and e[1].isdigit()):
i = int(e[0])
j = int(e[1])
if(i>0 and i<j and j<nmax):
ret+=list(range(i,j+1))
elif(i>0 and i<j):
ret+=list(range(i,nmax))
correct=False
else:
correct=False
if(e[0].isdigit() and e[1].lower()=='end'):
i = int(e[0])
if(i>0 and i<nmax):
ret+=list(range(i,nmax))
else:
correct=False
else:
correct=False
else:
correct=False
return correct, ret
def generate_pdf(wmarks, save_file, npages):
"""
Generate a PDF with Latex with appropriate marks on it
npages : total number of pages
wmarks : list of Wmark objects to be placed on pages
save_file : name of the program output file, used to
choose a name for pdf output
Return errnum, pdf_output_name
errnum:
1 : Could not write in output folder
2 : Could not parse config file
3 : No compiler available
4 : Error during compilation
5 : IOError
100 : Warning : Error in Wmark range
"""
err=0
use_pages_generated=[]
# Test file to write
texoutname = save_file + ".tex"
if(os.path.exists(texoutname)):
texoutname = save_file + "_" + str(random.randint(0,100000)) + ".tex"
if(os.path.exists(texoutname)):
return 1, None, None
f_tex = None
reussi, markbypage = wmbypage(wmarks, npages)
if(not reussi):
err = 100
# Writting Tex file
try:
f_tex = open(texoutname, 'w')
f_tex.write(strings.tex_entete)
for i in range(npages):
f_tex.write(genpagetex(markbypage[i]))
if(len(markbypage[i])==0):
use_pages_generated.append(False)
else:
use_pages_generated.append(True)
f_tex.write(strings.tex_end)
# Compiling Tex file
except IOError:
return 1, None, None
finally:
if(f_tex is not None):
f_tex.close()
erreurs, pdfgeneratedname = compiletex(texoutname, deletetex=True)
if(erreurs>0):
return erreurs+1, None, None
return err, use_pages_generated, pdfgeneratedname
def wmbypage(wmarks, npages):
"""
Return a list indexed by page number (from 0 to npages-1)
in which element i is a list of Wmark to put on the i+1 page.
"""
ret = []
correct = True
for i in range(npages):
ret.append([])
for m in wmarks:
correct1, onlyp = parse_range(m.onlypages, npages)
correct2, notp = parse_range(m.notpages, npages)
correct = correct and correct1 and correct2
if((1 not in notp and m.onlyfirstpage) or (1 in onlyp) or (len(onlyp)==0 and not m.onlyfirstpage and not m.onlylastpage and 1 not in notp)):
ret[0].append(m)
for i in range(1,npages-1):
if((i+1 not in notp and len(onlyp)==0 and not m.onlyfirstpage and not m.onlylastpage) or (i+1 in onlyp)):
ret[i].append(m)
if((npages not in notp and m.onlylastpage) or (npages in onlyp) or (len(onlyp)==0 and not m.onlyfirstpage and not m.onlylastpage and npages not in notp)):
ret[-1].append(m)
return correct, ret
def genpagetex(wmarks):
"""
Return a string to be inserted in tex file
to generate one page with marks given in
wmarks (list of Wmark)
"""
headrule = False
footrule = False
lhead =[]
chead =[]
rhead =[]
lfoot =[]
cfoot =[]
rfoot =[]
custom = []
for m in wmarks:
if(m.position==strings.topleft):
lhead.append(m.format_tex())
elif(m.position==strings.topcenter):
chead.append(m.format_tex())
elif(m.position==strings.topright):
rhead.append(m.format_tex())
elif(m.position==strings.botleft):
lfoot.append(m.format_tex())
elif(m.position==strings.botcenter):
cfoot.append(m.format_tex())
elif(m.position==strings.botright):
rfoot.append(m.format_tex())
elif(m.position==strings.custom):
custom.append([m.userposition, m.format_tex()])
else:
rhead.append(m.format_tex())
if(m.lined and m.position in [strings.top]):
headrule = True
elif(m.lined and m.position in [strings.bot]):
footrule = True
s = "\n\\newpage"
s += "\n\\lhead{" + ' '.join(lhead) + "}"
s += "\n\\chead{" + ' '.join(chead) + "}"
s += "\n\\rhead{" + ' '.join(rhead) + "}"
s += "\n\\lfoot{" + ' '.join(lfoot) + "}"
s += "\n\\cfoot{" + ' '.join(cfoot) + "}"
s += "\n\\rfoot{" + ' '.join(rfoot) + "}"
if(headrule):
s += "\n\\renewcommand{\\headrulewidth}{0.4pt}"
else:
s += "\n\\renewcommand{\\headrulewidth}{0pt}"
if(footrule):
s += "\n\\renewcommand{\\footrulewidth}{0.4pt}"
else:
s += "\n\\renewcommand{\\footrulewidth}{0pt}"
for e in custom:
s += "\n\\begin{textblock}{1}"+"({},{})".format(e[0][0]*21, e[0][1]*29.7)
s += "\n" + e[1]
s += "\n\\end{textblock}"
s += "\n\\null\n"
return s
def compiletex(texoutname, deletetex=False, deletetemp=True):
"""
Compile the tex file texoutname
Rerurns :
error number, pdf_filename
error number :
0 : No error
1 : Could not parse config file
2 : No compiler available
3 : Error during compilation
4 : IOError
"""
err = 0
pdfgeneratedname = texoutname[:-4] + ".pdf"
curdir = os.getcwd()
os.chdir(os.path.dirname(texoutname))
texbasename = os.path.basename(texoutname)
try:
config = configparser.ConfigParser()
config.read('pdfembannersrc/compiler.conf')
except configparser.Error:
logger.error("Watermark :: configparser failed to parse compiler.conf")
return 1, None
for compiler in config.sections():
if 'cmd' not in config[compiler]:
logger.error("Watermark :: cmd entry not found for compiler {}".format(compiler))
return 1, None
cmd1 = config[compiler]['cmd']
if 'args' in config[compiler]:
cmd1 += ' ' + config[compiler]['args']
cmd1+=' '+texbasename
cmd = shlex.split(cmd1)
try:
logger.debug("Try {}".format(cmd1))
p = Popen(cmd)
p.wait()
p = Popen(cmd)
p.wait()
if(p.returncode>0):
logger.error("Watermark :: Latex Error")
return 3, None
logger.info("Watermark :: Compiled successfully with {}".format(cmd1))
break # On a trouve le bon compilo
except OSError:
logger.warn("Watermark :: Failed to compile with {}. Compiler not found.".format(cmd1))
continue
except ValueError:
logger.warn("Watermark :: Failed to compile with {}. Value Error.".format(cmd1))
continue
except CalledProcessError:
return 3, None
except IOError:
logger.error("Watermark :: IOError during compilation")
return 4, None
else:
logger.error("Watermark :: No latex compilater found after trying all!")
return 2, None
# Delete temp files
if(deletetemp):
if('clean_ext' in config[compiler]):
for ext in config[compiler]['clean_ext'].split():
todel = texoutname[:-4]+'.'+ext
logger.debug("Trying to del {}".format(todel))
if(os.path.exists(todel)):
try:
os.remove(todel)
except (OSError, IOError):
logger.error("Watermark :: Error while deleting {}".format(todel))
# Delete tex file
if(deletetex):
try:
os.remove(texoutname)
except (OSError, IOError):
logger.error("Error while deleting {}".format(texoutname))
os.chdir(curdir)
return err, pdfgeneratedname

88
pdfembannersrc/wmark.py Normal file
View File

@ -0,0 +1,88 @@
# -*-coding:utf-8 -*
import tkinter as tk
from pdfembannersrc import editwmark
from pdfembannersrc import strings
import logging
logger = logging.getLogger()
class Wmark:
"""
Class to store data on a watermark
"""
count = 0
default_name = "New watermark"
def __init__(self, filename):
self.id = Wmark.count
Wmark.count += 1
self.name = Wmark.default_name + " {}".format(self.id+1)
self.position = None
self.userposition = None
self.text = None
self.size = None
self.notfirstpage = False
self.notlastpage = False
self.notpages = None
self.onlyfirstpage = False
self.onlylastpage = False
self.onlypages = None
self.bold = False
self.italic = False
self.boxed = False
self.lined = False
self.Wnum =None
self.Wlabel = None
self.Wedit = None
self.Wdel = None
def edit(self, parent):
parent.message["text"] = "Edit Watermark"
interface2 = editwmark.InterfaceEdit(parent, self)
def add_widgets(self, frame, parent, i):
self.Wnum = tk.Label(frame, text="{}.".format(i+1))
self.Wlabel = tk.Label(frame, text=self.name)
self.Wedit = tk.Button(frame, text="Edit", command=lambda: self.edit(parent))
self.Wdel = tk.Button(frame, text="Del", command=lambda: parent.delete(self.id))
self.set_i_widgets(i)
def unset_i_widgets(self):
self.Wnum.grid_forget()
self.Wlabel.grid_forget()
self.Wedit.grid_forget()
self.Wdel.grid_forget()
def set_i_widgets(self, i):
self.Wnum.grid(row=i, column=0, sticky=tk.W)
self.Wnum["text"] = "{}.".format(i+1)
self.Wlabel.grid(row=i, column=3, sticky=tk.W)
self.Wedit.grid(row=i, column=1)
self.Wdel.grid(row=i, column=2)
def destroy_widgets(self):
self.Wnum.destroy()
self.Wlabel.destroy()
self.Wedit.destroy()
self.Wdel.destroy()
def format_tex(self):
s = self.text
s = s.replace(" %p ", "~\\thepage~")
s = s.replace("%p ", "\\thepage~")
s = s.replace(" %p", "~\\thepage")
s = s.replace("%p", "\\thepage")
s = s.replace(" %P", "~\\pageref{LastPage}")
s = s.replace("%P", "\\pageref{LastPage}")
if(self.size is not None):
s = strings.tex_size[self.size]+ " " + s
if(self.bold):
s = "\\bfseries " + s
if(self.italic):
s = "\\itshape " + s
if(self.size is not None or self.bold or self.italic):
s = "{" + s + "}"
if(self.boxed):
s = "\\framebox[1.1\\width]{" + s + "}"
return s