Refactor dependency tracking for files

Dieser Commit ist enthalten in:
Axel Svensson 2024-07-16 08:01:05 +00:00
Ursprung c0dfb3ec4c
Commit ed54c79aa7

Datei anzeigen

@ -23,10 +23,10 @@ def main(args):
# A cheap solution to a rare problem # A cheap solution to a rare problem
die("Input file may not start with a dash (-)") die("Input file may not start with a dash (-)")
with tempfile.TemporaryDirectory() as tempdir: with tempfile.TemporaryDirectory() as tempdir:
intmp=lambda fileName: Volatile(os.path.join(tempdir, fileName)) intmp=lambda fileName: os.path.join(tempdir, fileName)
# Maybe flatten (make forms non-editable) before signing # Maybe flatten (make forms non-editable) before signing
if args.flatten: if args.flatten:
inputPDF=str(intmp('input.pdf')) inputPDF=intmp('input.pdf')
qpdfOrPdftk([ qpdfOrPdftk([
'qpdf', 'qpdf',
'--flatten-annotations=all', '--flatten-annotations=all',
@ -44,18 +44,18 @@ def main(args):
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)
@Cell @VolatileCell
def pagePDF(): def pagePDF():
outFile=intmp('page.pdf') outFile=intmp('page.pdf')
qpdfOrPdftk([ qpdfOrPdftk([
'qpdf', '--pages', '.', f'{pageNumber()}', '--', 'qpdf', '--pages', '.', f'{pageNumber()}', '--',
inputPDF, str(outFile) inputPDF, outFile
],[ ],[
'pdftk', inputPDF, 'pdftk', inputPDF,
'cat', str(pageNumber()), 'cat', str(pageNumber()),
'output', str(outFile)]) 'output', outFile])
return outFile return outFile
pageSize=Cell(lambda: pdfGetSize(str(pagePDF()))) pageSize=Cell(lambda: pdfGetSize(pagePDF()))
# The chosen signature # The chosen signature
if args.batch: if args.batch:
if not args.signature and not args.text: if not args.signature and not args.text:
@ -96,7 +96,7 @@ def main(args):
cache = signaturePath._cache cache = signaturePath._cache
if content in cache: if content in cache:
return cache[content] return cache[content]
fileName=os.path.join(tempdir, f"text{len(cache)}.pdf") fileName=intmp(f"text{len(cache)}.pdf")
text_to_pdf(content, fileName, 12, 1) text_to_pdf(content, fileName, 12, 1)
cache[content]=fileName cache[content]=fileName
return fileName return fileName
@ -110,7 +110,7 @@ def main(args):
if path not in cache: if path not in cache:
(w, h) = pdfGetSize(path) (w, h) = pdfGetSize(path)
(double_w, double_h) = (2*w, 2*h) (double_w, double_h) = (2*w, 2*h)
testFile = os.path.join(tempdir, 'translateTest.pdf') testFile = intmp('translateTest.pdf')
subprocess.run([ subprocess.run([
'gs', '-dBATCH', '-dNOPAUSE', '-dSAFER', '-dQUIET', 'gs', '-dBATCH', '-dNOPAUSE', '-dSAFER', '-dQUIET',
f'-sOutputFile={testFile}', f'-sOutputFile={testFile}',
@ -130,8 +130,8 @@ def main(args):
# gs -c '[/CropBox [0 0 100 50] /PAGES pdfmark'. We have to # gs -c '[/CropBox [0 0 100 50] /PAGES pdfmark'. We have to
# remove it to make the PDF translatable and usable as a # remove it to make the PDF translatable and usable as a
# signature. # signature.
translatableFileName = os.path.join(tempdir, f'translatable{len(cache)}.pdf') translatableFileName = intmp(f'translatable{len(cache)}.pdf')
emptyFileName = os.path.join(tempdir, f'empty{len(cache)}.pdf') emptyFileName = intmp(f'empty{len(cache)}.pdf')
subprocess.run([ subprocess.run([
'gs', '-dBATCH', '-dNOPAUSE', '-dSAFER', '-dQUIET', 'gs', '-dBATCH', '-dNOPAUSE', '-dSAFER', '-dQUIET',
f'-sOutputFile={emptyFileName}', f'-sOutputFile={emptyFileName}',
@ -151,7 +151,7 @@ def main(args):
die(f"The PDF at {path} is unusable as a signature. Reason unknown.") die(f"The PDF at {path} is unusable as a signature. Reason unknown.")
return cache[path] return cache[path]
translatablePDF._cache={} translatablePDF._cache={}
@Cell @VolatileCell
def signaturePositionedPDF(): def signaturePositionedPDF():
(w, h)=pageSize() (w, h)=pageSize()
(sw, sh)=signatureSize() (sw, sh)=signatureSize()
@ -169,16 +169,16 @@ def main(args):
], check=True) ], check=True)
return outFile return outFile
# The signed page # The signed page
@Cell @VolatileCell
def signedPagePDF(): def signedPagePDF():
outFile=intmp('signed-page.pdf') outFile=intmp('signed-page.pdf')
qpdfOrPdftk([ qpdfOrPdftk([
'qpdf', '--overlay', str(signaturePositionedPDF()), '--', 'qpdf', '--overlay', signaturePositionedPDF(), '--',
str(pagePDF()), str(outFile), pagePDF(), outFile,
],[ ],[
'pdftk', str(pagePDF()), 'pdftk', pagePDF(),
'stamp', str(signaturePositionedPDF()), 'stamp', signaturePositionedPDF(),
'output', str(outFile) 'output', outFile
]) ])
return outFile return outFile
# The signed page as PNG, for GUI use # The signed page as PNG, for GUI use
@ -189,7 +189,7 @@ def main(args):
(pageWidth, pageHeight)=pageSize() (pageWidth, pageHeight)=pageSize()
scale=min(maxWidth/pageWidth, maxHeight/pageHeight) scale=min(maxWidth/pageWidth, maxHeight/pageHeight)
return (round(pageWidth*scale), round(pageHeight*scale)) return (round(pageWidth*scale), round(pageHeight*scale))
@Cell @VolatileCell
def displayPNG(): def displayPNG():
(w, h)=displaySize() (w, h)=displaySize()
outFile=intmp('display.png') outFile=intmp('display.png')
@ -199,7 +199,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', str(signedPagePDF()), '-f', signedPagePDF(),
], check=True) ], check=True)
return outFile return outFile
# GUI # GUI
@ -414,7 +414,7 @@ def main(args):
if not updateActive: if not updateActive:
return return
(w, h) = displaySize() (w, h) = displaySize()
root._docImg = tk.PhotoImage(file=str(displayPNG())) root._docImg = tk.PhotoImage(file=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()
@ -451,7 +451,7 @@ def main(args):
qpdfOrPdftk([ qpdfOrPdftk([
'qpdf', '--pages', 'qpdf', '--pages',
*(['.', f'1-{pnr-1}'] if 1 < pnr else []), *(['.', f'1-{pnr-1}'] if 1 < pnr else []),
str(signedPagePDF()), '1', signedPagePDF(), '1',
*(['.', f'{pnr+1}-z'] if pnr < pageCount else []), *(['.', f'{pnr+1}-z'] if pnr < pageCount else []),
'--', '--',
inputPDF, inputPDF,
@ -470,12 +470,6 @@ 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 has(cmd): def has(cmd):
return subprocess.run(["which", cmd], check=False, capture_output=True).returncode == 0 return subprocess.run(["which", cmd], check=False, capture_output=True).returncode == 0
hasQpdf = has("qpdf") hasQpdf = has("qpdf")
@ -553,7 +547,8 @@ class Cell():
for i in range(len(self._precedents)): for i in range(len(self._precedents)):
p=self._precedents[i] p=self._precedents[i]
oldval=self._precedentvalues[i] oldval=self._precedentvalues[i]
newval=p() p()
newval=p._cacheid()
if oldval!=newval: if oldval!=newval:
self._needEval=True self._needEval=True
break break
@ -570,13 +565,23 @@ class Cell():
if(Cell.currentCell): if(Cell.currentCell):
self._dependents.append(Cell.currentCell) self._dependents.append(Cell.currentCell)
Cell.currentCell._precedents.append(self) Cell.currentCell._precedents.append(self)
Cell.currentCell._precedentvalues.append(self._value) Cell.currentCell._precedentvalues.append(self._cacheid())
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()
def _cacheid(self):
assert self._isuptodate
return self._value
# When the return value is just a file name, the contents of that file needs to
# be taken into account in order to decide whether the "value" has changed. In
# these cases, we track the precedent values along the cell's own value.
class VolatileCell(Cell):
def _cacheid(self):
assert self._isuptodate
return (self._value, self._precedentvalues)
# Keep this function in sync with pdf-from-text # Keep this function in sync with pdf-from-text
def text_to_pdf(text, output, size, margin): def text_to_pdf(text, output, size, margin):
@ -666,7 +671,7 @@ def tkthrottle(frequency, root):
return decorator return decorator
def pdfCountPages(filePath): def pdfCountPages(filePath):
return int(fromCmdOutput(["pdfinfo", str(filePath)], "^.*\nPages: +([0-9]+)\n.*$")[1]) return int(fromCmdOutput(["pdfinfo", 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.*$')