From 171db182aa8f166f871a34b51d70c60d393259f0 Mon Sep 17 00:00:00 2001 From: Leo VIALLON-GALINIER Date: Fri, 18 May 2018 08:49:19 +0200 Subject: [PATCH] First commit --- .gitignore | 4 + README.md | 20 ++ pdfembanner | 62 ++++ pdfembannersrc/__init__.py | 0 pdfembannersrc/cat.py | 257 +++++++++++++++ pdfembannersrc/choice.py | 91 ++++++ pdfembannersrc/editfich.py | 304 ++++++++++++++++++ pdfembannersrc/editwmark.py | 260 +++++++++++++++ pdfembannersrc/encrypt.py | 199 ++++++++++++ pdfembannersrc/fich.py | 93 ++++++ pdfembannersrc/full_split.py | 99 ++++++ pdfembannersrc/get_text.py | 139 ++++++++ pdfembannersrc/help.py | 63 ++++ pdfembannersrc/strings.py | 45 +++ pdfembannersrc/subwindows.py | 327 +++++++++++++++++++ pdfembannersrc/watermark.py | 595 +++++++++++++++++++++++++++++++++++ pdfembannersrc/wmark.py | 88 ++++++ 17 files changed, 2646 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100755 pdfembanner create mode 100644 pdfembannersrc/__init__.py create mode 100644 pdfembannersrc/cat.py create mode 100644 pdfembannersrc/choice.py create mode 100644 pdfembannersrc/editfich.py create mode 100644 pdfembannersrc/editwmark.py create mode 100644 pdfembannersrc/encrypt.py create mode 100644 pdfembannersrc/fich.py create mode 100644 pdfembannersrc/full_split.py create mode 100644 pdfembannersrc/get_text.py create mode 100644 pdfembannersrc/help.py create mode 100644 pdfembannersrc/strings.py create mode 100644 pdfembannersrc/subwindows.py create mode 100644 pdfembannersrc/watermark.py create mode 100644 pdfembannersrc/wmark.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7d3c069 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.pyc +*.pdf +.*.swp +*__pycache__* diff --git a/README.md b/README.md new file mode 100644 index 0000000..2c98dbf --- /dev/null +++ b/README.md @@ -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/ diff --git a/pdfembanner b/pdfembanner new file mode 100755 index 0000000..972b9cf --- /dev/null +++ b/pdfembanner @@ -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("", 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") + diff --git a/pdfembannersrc/__init__.py b/pdfembannersrc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pdfembannersrc/cat.py b/pdfembannersrc/cat.py new file mode 100644 index 0000000..958942e --- /dev/null +++ b/pdfembannersrc/cat.py @@ -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("", self.close) + self.bind("", self.open) + self.bind("", self.saveas) + self.bind("", self.editmetadata) + self.bind("", 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)) + diff --git a/pdfembannersrc/choice.py b/pdfembannersrc/choice.py new file mode 100644 index 0000000..efde97d --- /dev/null +++ b/pdfembannersrc/choice.py @@ -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() diff --git a/pdfembannersrc/editfich.py b/pdfembannersrc/editfich.py new file mode 100644 index 0000000..0e43a1d --- /dev/null +++ b/pdfembannersrc/editfich.py @@ -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("", 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 pos0 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 i0 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 i0): + 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() diff --git a/pdfembannersrc/editwmark.py b/pdfembannersrc/editwmark.py new file mode 100644 index 0000000..f907178 --- /dev/null +++ b/pdfembannersrc/editwmark.py @@ -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("", self.ok) + self.bind("", self.close) + self.bind('<>', 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' diff --git a/pdfembannersrc/encrypt.py b/pdfembannersrc/encrypt.py new file mode 100644 index 0000000..91025b7 --- /dev/null +++ b/pdfembannersrc/encrypt.py @@ -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("", 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() + diff --git a/pdfembannersrc/fich.py b/pdfembannersrc/fich.py new file mode 100644 index 0000000..5dc7798 --- /dev/null +++ b/pdfembannersrc/fich.py @@ -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() diff --git a/pdfembannersrc/full_split.py b/pdfembannersrc/full_split.py new file mode 100644 index 0000000..974056c --- /dev/null +++ b/pdfembannersrc/full_split.py @@ -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("", 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() + diff --git a/pdfembannersrc/get_text.py b/pdfembannersrc/get_text.py new file mode 100644 index 0000000..53bcdb7 --- /dev/null +++ b/pdfembannersrc/get_text.py @@ -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("", 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() + diff --git a/pdfembannersrc/help.py b/pdfembannersrc/help.py new file mode 100644 index 0000000..07f3da6 --- /dev/null +++ b/pdfembannersrc/help.py @@ -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("", self.close) + self.bind("", 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() + diff --git a/pdfembannersrc/strings.py b/pdfembannersrc/strings.py new file mode 100644 index 0000000..f04d900 --- /dev/null +++ b/pdfembannersrc/strings.py @@ -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}" diff --git a/pdfembannersrc/subwindows.py b/pdfembannersrc/subwindows.py new file mode 100644 index 0000000..6456d15 --- /dev/null +++ b/pdfembannersrc/subwindows.py @@ -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("", self.ok) + self.bind("", 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("", self.ok) + self.bind("", 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("", self.ok) + self.bind("", 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("", self.ok) + self.bind("", 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' diff --git a/pdfembannersrc/watermark.py b/pdfembannersrc/watermark.py new file mode 100644 index 0000000..958ae56 --- /dev/null +++ b/pdfembannersrc/watermark.py @@ -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("", self.close) + self.bind("", self.open) + self.bind("", self.open_markfile) + self.bind("", self.saveas) + self.bind("", self.editmetadata) + self.bind("", self.add) + self.bind("", 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 i0 and int(e[0])0 and i0 and i0 and i0): + 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 + + diff --git a/pdfembannersrc/wmark.py b/pdfembannersrc/wmark.py new file mode 100644 index 0000000..9ee7f98 --- /dev/null +++ b/pdfembannersrc/wmark.py @@ -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