Cmd-line options and help
Dieser Commit ist enthalten in:
Ursprung
823ec4b9cb
Commit
e8eab02591
1 geänderte Dateien mit 66 neuen und 29 gelöschten Zeilen
95
pdf-sign
95
pdf-sign
|
@ -2,35 +2,49 @@
|
|||
|
||||
#Dependencies: python3, pdftk, gs, mv, pdfinfo
|
||||
|
||||
import os, queue, re, subprocess, sys, tempfile, threading, time, tkinter as tk
|
||||
import argparse, os, queue, re, subprocess, sys, tempfile, threading, time, tkinter as tk
|
||||
|
||||
signatureDir=os.path.expanduser(os.environ['PDF_SIGNATURE_DIR'] if 'PDF_SIGNATURE_DIR' in os.environ else "~/.pdf_signatures")
|
||||
|
||||
# Inspired by https://unix.stackexchange.com/a/141496
|
||||
def main(filePath, pagestr=None):
|
||||
#filePath=os.path.expanduser(filePath)
|
||||
def main(args):
|
||||
filePath=args.input
|
||||
with tempfile.TemporaryDirectory() as tempdir:
|
||||
intmp=lambda fileName: os.path.join(tempdir, fileName)
|
||||
# Flatten (make forms non-editable) before signing
|
||||
flatPDF=Cell(lambda: subprocess.run(['pdftk', filePath, 'output', intmp('flat.pdf'), 'flatten'], check=True) and intmp('flat.pdf'))
|
||||
# Maybe flatten (make forms non-editable) before signing
|
||||
@Cell
|
||||
def inputPDF():
|
||||
if args.flatten:
|
||||
outFile=intmp('input.pdf')
|
||||
subprocess.run([
|
||||
'pdftk', filePath,
|
||||
'output', outFile,
|
||||
'flatten'
|
||||
], check=True)
|
||||
return outFile
|
||||
return filePath
|
||||
# The chosen page
|
||||
pageCount=pdfCountPages(flatPDF())
|
||||
pageNumber=Cell(int(pagestr) if pagestr else pageCount)
|
||||
pagePDF=Cell(lambda: subprocess.run(['pdftk', flatPDF(), 'cat', str(pageNumber()), 'output', intmp('page.pdf')], check=True) and intmp('page.pdf'))
|
||||
pageCount=pdfCountPages(inputPDF())
|
||||
if args.page < -pageCount or args.page==0 or pageCount < args.page:
|
||||
die('Page number out of range')
|
||||
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'))
|
||||
pageSize=Cell(lambda: pdfGetSize(pagePDF()))
|
||||
# The chosen signature
|
||||
signatures=[*filter(lambda x: m("^.*\.pdf$", x), os.listdir(signatureDir))]
|
||||
if not args.signature and args.batch:
|
||||
die('In batch mode, signature must be specified.')
|
||||
signatures=[*filter(lambda x: m("^.*\.pdf$", x), os.listdir(signatureDir))] if not args.signature else [None]
|
||||
signatureIndex=Cell(0)
|
||||
signatureAbsPath=Cell(lambda: os.path.join(signatureDir, signatures[signatureIndex()]))
|
||||
signatureSize=Cell(lambda: pdfGetSize(signatureAbsPath()))
|
||||
signaturePositionX=Cell(0.5)
|
||||
signaturePositionY=Cell(0.75)
|
||||
signaturePath=Cell(lambda: args.signature if args.signature else os.path.join(signatureDir, signatures[signatureIndex()]))
|
||||
signatureSize=Cell(lambda: pdfGetSize(signaturePath()))
|
||||
signaturePositionX=Cell(args.x_coordinate)
|
||||
signaturePositionY=Cell(args.y_coordinate)
|
||||
signatureScale=Cell(0)
|
||||
@Cell
|
||||
def signaturePositionedPDF():
|
||||
(w, h)=pageSize()
|
||||
(sw, sh)=signatureSize()
|
||||
resize=1.1**signatureScale()*min(w/sw, h/sh)/3
|
||||
resize=1.1**signatureScale()*min(args.width*w/sw, args.height*h/sh)
|
||||
dx=w*signaturePositionX()/resize - sw/2
|
||||
dy=h*(1-signaturePositionY())/resize - sh/2
|
||||
outFile=intmp('signature-positioned.pdf')
|
||||
|
@ -40,7 +54,7 @@ def main(filePath, pagestr=None):
|
|||
'-sDEVICE=pdfwrite',
|
||||
f'-dDEVICEWIDTHPOINTS={w}', f'-dDEVICEHEIGHTPOINTS={h}', '-dFIXEDMEDIA',
|
||||
'-c', f'<</BeginPage{{{resize} {resize} scale {dx} {dy} translate}}>> setpagedevice',
|
||||
'-f', signatureAbsPath(),
|
||||
'-f', signaturePath(),
|
||||
], check=True)
|
||||
return outFile
|
||||
# The signed page
|
||||
|
@ -73,7 +87,7 @@ def main(filePath, pagestr=None):
|
|||
return outFile
|
||||
# GUI
|
||||
doSign=True
|
||||
gui=True
|
||||
gui=not args.batch
|
||||
if gui:
|
||||
doSign=False
|
||||
# Commands
|
||||
|
@ -126,9 +140,10 @@ def main(filePath, pagestr=None):
|
|||
pagemenu.add_command(label='Last page', underline=0, accelerator='End', command=cmd_lastPage)
|
||||
placemenu = tk.Menu(rootmenu, tearoff=0)
|
||||
rootmenu.add_cascade(label="Signature", underline=1, menu=placemenu)
|
||||
placemenu.add_command(label='Previous signature', underline=0, accelerator='Ctrl-Left', command=cmd_prevSignature)
|
||||
placemenu.add_command(label='Next signature', underline=0, accelerator='Ctrl-Right', command=cmd_nextSignature)
|
||||
placemenu.add_separator()
|
||||
if not args.signature:
|
||||
placemenu.add_command(label='Previous signature', underline=0, accelerator='Ctrl-Left', command=cmd_prevSignature)
|
||||
placemenu.add_command(label='Next signature', underline=0, accelerator='Ctrl-Right', command=cmd_nextSignature)
|
||||
placemenu.add_separator()
|
||||
placemenu.add_command(label='Enlarge signature', underline=0, accelerator='+', command=cmd_enlargeSignature)
|
||||
placemenu.add_command(label='Shrink signature', underline=0, accelerator='-', command=cmd_shrinkSignature)
|
||||
placemenu.add_separator()
|
||||
|
@ -208,19 +223,24 @@ def main(filePath, pagestr=None):
|
|||
# End of GUI
|
||||
if doSign:
|
||||
[ignored, pathPart1, pathPart2] = m("^(.*)(\.[Pp][Dd][Ff])$", filePath)
|
||||
signedFilePath=f'{pathPart1}.signed{pathPart2}'
|
||||
signedFilePath=args.output if args.output else f'{pathPart1}.signed{pathPart2}'
|
||||
if os.path.exists(signedFilePath):
|
||||
backupFilePath=f'{pathPart1}.signed.backup{time.strftime("%Y%m%d_%H%M%S")}{pathPart2}'
|
||||
subprocess.run([
|
||||
'mv',
|
||||
signedFilePath,
|
||||
backupFilePath,
|
||||
], check=True)
|
||||
print(f'Renamed {signedFilePath} to {backupFilePath}')
|
||||
if args.existing=='backup':
|
||||
backupFilePath=f'{signedFilePath}.backup{time.strftime("%Y%m%d_%H%M%S")}'
|
||||
subprocess.run([
|
||||
'mv',
|
||||
signedFilePath,
|
||||
backupFilePath,
|
||||
], check=True)
|
||||
print(f'Renamed {signedFilePath} to {backupFilePath}')
|
||||
elif args.existing=='fail':
|
||||
die(f'Output file {signedFilePath} already exists')
|
||||
else:
|
||||
assert args.existing=='overwrite'
|
||||
pnr=pageNumber()
|
||||
subprocess.run([
|
||||
'pdftk',
|
||||
f'A={flatPDF()}',
|
||||
f'A={inputPDF()}',
|
||||
f'B={signedPagePDF()}',
|
||||
'cat',
|
||||
*([f'A1-{pnr-1}'] if 1 < pnr else []),
|
||||
|
@ -310,4 +330,21 @@ class NonBlockingIterable:
|
|||
except queue.Empty:
|
||||
yield None
|
||||
|
||||
main(*sys.argv[1:])
|
||||
def die(reason):
|
||||
print(reason, file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
parser = argparse.ArgumentParser(description='Sign a PDF file.')
|
||||
parser.add_argument('input', metavar='input.pdf', type=str, help='Input PDF file.')
|
||||
parser.add_argument('-p', '--page', type=int, default=-1, help='The page to sign, negative for counting from the end. (default: -1)')
|
||||
parser.add_argument('-s', '--signature', type=str, help='Path to file used as signature. Required in batch mode. In GUI mode, the user can choose among files in $PDF_SIGNATURE_DIR or ~/.pdf_signatures.')
|
||||
parser.add_argument('-x', '--x-coordinate', type=float, default=0.5, help='Horizontal coordinate of signature center, in page width units. (default: 0.5)')
|
||||
parser.add_argument('-y', '--y-coordinate', type=float, default=0.75, help='Vertical coordinate of signature center, in page height units. (default: 0.75)')
|
||||
parser.add_argument('-W', '--width', type=float, default=0.28, help='Width of box to fit signature to, in page width units. (default: 0.28)')
|
||||
parser.add_argument('-H', '--height', type=float, default=0.28, help='Height of box to fit signature to, in page height units. (default: 0.28)')
|
||||
parser.add_argument('-o', '--output', type=str, help='Output file. (default: Add ".signed" before the extension)')
|
||||
parser.add_argument('-b', '--batch', action=argparse.BooleanOptionalAction, default=False, help='Batch mode: do not show GUI.')
|
||||
parser.add_argument('-f', '--flatten', action=argparse.BooleanOptionalAction, default=True, help='Flatten before signing.')
|
||||
parser.add_argument('-e', '--existing', type=str, choices=['backup', 'overwrite', 'fail'], default='backup', help='What to do if output file exists. (default: backup)')
|
||||
|
||||
main(parser.parse_args(sys.argv[1:] or ['-h']))
|
||||
|
|
Laden …
Tabelle hinzufügen
In neuem Issue referenzieren