PDF-Embanner/pdfembannersrc/watermark.py

596 lines
25 KiB
Python

# -*-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