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