Skip Cell calculations with unchanged inputs

Add class Volatile to force recalculation after file contents change.
Dieser Commit ist enthalten in:
Axel Svensson 2024-06-02 01:02:09 +02:00
Ursprung f58854ff5d
Commit 07b9e61bf3

Datei anzeigen

@ -10,26 +10,32 @@ def main(args):
if not isPdfFilename(filePath): if not isPdfFilename(filePath):
die("Input file must end with .pdf (case insensitive)") die("Input file must end with .pdf (case insensitive)")
with tempfile.TemporaryDirectory() as tempdir: with tempfile.TemporaryDirectory() as tempdir:
intmp=lambda fileName: os.path.join(tempdir, fileName) intmp=lambda fileName: Volatile(os.path.join(tempdir, fileName))
# Maybe flatten (make forms non-editable) before signing # Maybe flatten (make forms non-editable) before signing
@Cell
def inputPDF():
if args.flatten: if args.flatten:
outFile=intmp('input.pdf') inputPDF=str(intmp('input.pdf'))
subprocess.run([ subprocess.run([
'pdftk', filePath, 'pdftk', filePath,
'output', outFile, 'output', inputPDF,
'flatten' 'flatten'
], check=True) ], check=True)
return outFile else:
return filePath inputPDF=filePath
# The chosen page # The chosen page
pageCount=pdfCountPages(inputPDF()) pageCount=pdfCountPages(inputPDF)
if args.page < -pageCount or args.page==0 or pageCount < args.page: if args.page < -pageCount or args.page==0 or pageCount < args.page:
die('Page number out of range') die('Page number out of range')
pageNumber=Cell(args.page if 0 < args.page else pageCount+args.page+1) pageNumber=Cell(args.page if 0 < args.page else pageCount+args.page+1)
pagePDF=Cell(lambda: subprocess.run(['pdftk', inputPDF(), 'cat', str(pageNumber()), 'output', intmp('page.pdf')], check=True) and intmp('page.pdf')) @Cell
pageSize=Cell(lambda: pdfGetSize(pagePDF())) def pagePDF():
outFile=intmp('page.pdf')
subprocess.run([
'pdftk', inputPDF,
'cat', str(pageNumber()),
'output', str(outFile)
], check=True)
return outFile
pageSize=Cell(lambda: pdfGetSize(str(pagePDF())))
# The chosen signature # The chosen signature
if not args.signature and args.batch: if not args.signature and args.batch:
die('In batch mode, signature must be specified.') die('In batch mode, signature must be specified.')
@ -62,12 +68,15 @@ def main(args):
], check=True) ], check=True)
return outFile return outFile
# The signed page # The signed page
signedPagePDF=Cell(lambda: subprocess.run([ @Cell
'pdftk', def signedPagePDF():
pagePDF(), outFile=intmp('signed-page.pdf')
'stamp', signaturePositionedPDF(), subprocess.run([
'output', intmp('signed-page.pdf'), 'pdftk', str(pagePDF()),
], check=True) and intmp('signed-page.pdf')) 'stamp', str(signaturePositionedPDF()),
'output', str(outFile)
], check=True)
return outFile
# The signed page as PNG, for GUI use # The signed page as PNG, for GUI use
displayMaxSize=Cell((400, 800)) displayMaxSize=Cell((400, 800))
@Cell @Cell
@ -86,7 +95,7 @@ def main(args):
'-sDEVICE=pngalpha', '-sDEVICE=pngalpha',
'-dMaxBitmap=2147483647', '-dMaxBitmap=2147483647',
f'-dDEVICEWIDTHPOINTS={w}', f'-dDEVICEHEIGHTPOINTS={h}', '-dFIXEDMEDIA', '-dPDFFitPage', f'-dDEVICEWIDTHPOINTS={w}', f'-dDEVICEHEIGHTPOINTS={h}', '-dFIXEDMEDIA', '-dPDFFitPage',
'-f', signedPagePDF(), '-f', str(signedPagePDF()),
], check=True) ], check=True)
return outFile return outFile
# GUI # GUI
@ -240,7 +249,7 @@ def main(args):
@tkthrottle(100, root) @tkthrottle(100, root)
def update(): def update():
(w, h) = displaySize() (w, h) = displaySize()
root._docImg = tk.PhotoImage(file=displayPNG()) root._docImg = tk.PhotoImage(file=str(displayPNG()))
root._docView.itemconfig(root._docViewIndex, image=root._docImg) root._docView.itemconfig(root._docViewIndex, image=root._docImg)
root._docView.configure(width=w, height=h) root._docView.configure(width=w, height=h)
updateTitle() updateTitle()
@ -276,7 +285,7 @@ def main(args):
pnr=pageNumber() pnr=pageNumber()
subprocess.run([ subprocess.run([
'pdftk', 'pdftk',
f'A={inputPDF()}', f'A={inputPDF}',
f'B={signedPagePDF()}', f'B={signedPagePDF()}',
'cat', 'cat',
*([f'A1-{pnr-1}'] if 1 < pnr else []), *([f'A1-{pnr-1}'] if 1 < pnr else []),
@ -288,6 +297,12 @@ def main(args):
else: else:
print(f'Aborted') print(f'Aborted')
# Used for file names that don't change but represents changed content
class Volatile():
def __init__(self, underlying): self._underlying = underlying
def __eq__(self, other): return self is other
def __str__(self): return str(self._underlying)
def getSignatureDir(): def getSignatureDir():
if 'PDF_SIGNATURE_DIR' in os.environ: if 'PDF_SIGNATURE_DIR' in os.environ:
sd=os.environ['PDF_SIGNATURE_DIR'] sd=os.environ['PDF_SIGNATURE_DIR']
@ -308,33 +323,54 @@ def getSignatureDir():
# Init with a value or function to calculate the value. # Init with a value or function to calculate the value.
# Update by calling with one argument (as in init). # Update by calling with one argument (as in init).
# To retrieve an up-to-date value, call with no arguments. # To retrieve an up-to-date value, call with no arguments.
# Calculations with unchanged inputs are skipped.
class Cell(): class Cell():
currentCell=None currentCell=None
def __init__(self, arg): def __init__(self, arg):
self._arg=arg self._arg=arg
self._isuptodate=False self._isuptodate=False
self._needEval=True
self._dependents=[] self._dependents=[]
self._precedents=[]
self._precedentvalues=[]
def __call__(self, *args): def __call__(self, *args):
if(len(args)==1): if(len(args)==1):
if self._arg != args[0]:
self._arg=args[0] self._arg=args[0]
self.dirty() self._needEval=True
self._dirty()
return return
assert len(args)==0 assert len(args)==0
if(Cell.currentCell):
self._dependents.append(Cell.currentCell)
if not self._isuptodate: if not self._isuptodate:
oldcell=Cell.currentCell oldcell=Cell.currentCell
Cell.currentCell=None
for i in range(len(self._precedents)):
p=self._precedents[i]
oldval=self._precedentvalues[i]
newval=p()
if oldval!=newval:
self._needEval=True
break
if self._needEval:
Cell.currentCell=self Cell.currentCell=self
for p in self._precedents:
p._dependents.remove(self)
self._precedents=[]
self._precedentvalues=[]
self._value=self._arg() if callable(self._arg) else self._arg self._value=self._arg() if callable(self._arg) else self._arg
self._needEval=False
self._isuptodate=True self._isuptodate=True
Cell.currentCell=oldcell Cell.currentCell=oldcell
if(Cell.currentCell):
self._dependents.append(Cell.currentCell)
Cell.currentCell._precedents.append(self)
Cell.currentCell._precedentvalues.append(self._value)
return self._value return self._value
def dirty(self): def _dirty(self):
if self._isuptodate: if self._isuptodate:
self._isuptodate=False self._isuptodate=False
for d in self._dependents: for d in self._dependents:
d.dirty() d._dirty()
self._dependents=[]
def tkthrottle(frequency, root): def tkthrottle(frequency, root):
wait=1/frequency wait=1/frequency
@ -358,7 +394,7 @@ def tkthrottle(frequency, root):
return decorator return decorator
def pdfCountPages(filePath): def pdfCountPages(filePath):
return int(fromCmdOutput(["pdfinfo", filePath], "^.*\nPages: +([0-9]+)\n.*$")[1]) return int(fromCmdOutput(["pdfinfo", str(filePath)], "^.*\nPages: +([0-9]+)\n.*$")[1])
def pdfGetSize(filePath): def pdfGetSize(filePath):
[ignored, w, h, *ignored2]=fromCmdOutput(['pdfinfo', filePath], '^.*\nPage size: +([0-9.]+) x ([0-9.]+) pts( \([A-Za-z0-9]+\))?\n.*$') [ignored, w, h, *ignored2]=fromCmdOutput(['pdfinfo', filePath], '^.*\nPage size: +([0-9.]+) x ([0-9.]+) pts( \([A-Za-z0-9]+\))?\n.*$')