From 17ccf8b527057a8584c306a38f8286bb5a38b1f5 Mon Sep 17 00:00:00 2001 From: Axel Svensson Date: Wed, 17 Jul 2024 16:15:34 +0000 Subject: [PATCH 1/4] Revert "Minimize empty-3inx2in.pdf" This reverts commit 2e9395cee3845d0152a85c5d00065d4de930a76c. --- empty-3inx2in.pdf | Bin 196 -> 2190 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/empty-3inx2in.pdf b/empty-3inx2in.pdf index 301d503a0e713b8e4fa547ebb89b866fc9047142..21cba6c4afaaa3933a938b8f56b81a3f458be77d 100644 GIT binary patch literal 2190 zcmb_e&u*he7~eyC;okcwRYa;So*6KdpHn@IE`A^e$!tpk}?{U<&UaFGhno(u(s^WThC!33CqN(;NIW>JOYO zjMjN97zl1TZ${qyWO6<_o6j7bw(j{SgVFKHygs1KreY$3`1L!b+r_6WU5Fdh0?rhJ z2f=cLhlwwk#{wQQ*HsFUGk>LQ-oKuce~dYK)jRn0{omjG_4O-r^c7=iSPK9pjY?DU zts6?sX+F4{-)0O?`~|~f7RLS&-=Hf3$1+@q?5zTaIZFjHWIzg@vVxa+zzPU;lJj83 z#1#fa-zpFo?(wAKLmU?9N|6ywmA42il;Dg$Ql?!6Lnx?Cj1aiQ2U8%`CVgMCA$+;@T|NRGX2Z(=?->EJG$%E_;n9AnI9~f zK-X*$r;hsH?|-N$4juK4IVR(*&u-$AyPVDL&gQ}0GO$AR&{evJKETejjh|^zb$Da`DX_6Ot84}FqRlEv3wz_Jm5L3tWMdUoB4X*Nm*WW%oF~gzAiZHVs78(N zQIz{Dw&wW~(MC%JlX)1~Q*T%sgfBWf_O|wLv?k6&PA8Xa*Xk zk);uMG|OlM9Y)L#te4(L7~IE^m3dNCUKn7OFzK>@E1?ib7}ycdSH6JSf4~DnVW@5w z4n-^y=2msxOT?B)Vp(B!;-?Eo-Dn|;{4xj}TfS)>ruQDot4AJj5U!$c9<@tOvNC?AyWc(iT8?EjEQj5ZAQ zCp_J>KH|}4=OdopgoW`$w`su+-0?*2!`{we*CITN?-=SJJmnmgj$9lFkJ5-E2_UEBcL9+>MVbXr@NH@)vuDhR?sJ3Gv{9IHp(N5cfACJNLQ28JH Ccz34& delta 132 zcmeAZJi;ib8sOrlYp7?=Wujo9ke`&LFqxZCS;WpxAvG@r$mKGI3ftIBZeSEqRsbmr zQixU1cg`=(D^V~+s5FEbu!B)n10h^el$e>5TBKlOqaT!?57YrNz|M|KRn^tsjSB$d C;3EzI From bcc37b0929e6c58e15121534c40c6ff5ba6b8604 Mon Sep 17 00:00:00 2001 From: Axel Svensson Date: Wed, 17 Jul 2024 16:16:03 +0000 Subject: [PATCH 2/4] Refactor signature path logic --- pdf-sign | 109 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 58 insertions(+), 51 deletions(-) diff --git a/pdf-sign b/pdf-sign index 868836a..ff0c02e 100755 --- a/pdf-sign +++ b/pdf-sign @@ -65,41 +65,44 @@ def main(args): return outFile pageSize=Cell(lambda: pdfGetSize(pagePDF())) # The chosen signature - if args.batch: - if not args.signature and not args.text: + if args.signature: + if args.text: + die('--signature and --text cannot be specified together.') + if not os.path.exists(args.signature): + die(f'File not found: {args.signature}') + signatures=[('file', args.signature, args.signature)] + elif args.batch: + assert not args.signature + if not args.text: die('In batch mode, --signature or --text must be specified.') - if args.text and len(args.text) > 1: + if len(args.text) > 1: die('In batch mode, --text must be given only once.') - if args.signature and args.text: - die('--signature and --text cannot be specified together.') - if args.signature and not os.path.exists(args.signature): - die(f'File not found: {args.signature}') - if args.text: - assert(len(args.text) > 0) - latin1_chars=set([*range(0x20, 0x7f), *range(0xa0, 0x100)]) - for text in args.text: - text_chars=set(map(ord, text)) - if not text_chars.issubset(latin1_chars): - die("Error: Only non-control latin-1 characters are supported in --text." + - " Unsupported characters: " + ', '.join(map(hex, text_chars - latin1_chars))) - signatureDir=getSignatureDir() - signatures=[('file', x) for x in filter(isPdfFilename, os.listdir(signatureDir))] if not args.signature else [None] - if not signatures and not args.text: - die(f'Could not find anything usable as signature, since no .pdf files found in {signatureDir} and no --text option given.') - signatures.sort() - if args.text: - signatures=[('text', x) for x in args.text] + signatures + validateText(args.text[0], True) + signatures=[('text', textLabel(args.text[0]), args.text[0])] + else: + (signatureDir, signatureDirHelp)=getSignatureDirAndHelp() + signatures=([('file', x, os.path.join(signatureDir, x)) + for x in filter(isPdfFilename, os.listdir(signatureDir))] + if signatureDir else []) + signatures.sort() + if not signatures and not args.text: + die('Could not find any usable signatures. No --signature, no --text, and ' + + ('no .pdf files found in {signatureDir}.' + if signatureDir else + f'no signature directory. The options considered for signature directory are: {signatureDirHelp}')) + if args.text: + assert(len(args.text) > 0) + for text in args.text: + validateText(text, True) + signatures=[('text', textLabel(x), x) for x in args.text] + signatures signatureIndex=Cell(0) - customText=Cell(('text', time.strftime('%Y-%m-%d'))) @Cell def signaturePath(): - if args.signature: - return args.signature - (signType, content)=signatures[signatureIndex()] + (signType, _, content)=signatures[signatureIndex()] if signType=='cell': - (signType, content)=content() + (signType, _, content)=content() if signType=='file': - return os.path.join(signatureDir, content) + return content assert signType=='text' cache = signaturePath._cache if content in cache: @@ -220,6 +223,7 @@ def main(args): except ModuleNotFoundError: die('Cannot find Python module `tkinter`, which is needed for interactive use.') doSign=False + customText=Cell(('text', None, time.strftime('%Y-%m-%d'))) customTextIndex = len(signatures) # Commands def uf(fun): @@ -260,32 +264,25 @@ def main(args): text = simpledialog.askstring( "Custom Text", "Input the text you want to stamp this PDF file with", - initialvalue=customText()[1]) + initialvalue=customText()[2]) if text == None: return # Validate text - text_chars=set(map(ord, text)) - latin1_chars=set([*range(0x20, 0x7f), *range(0xa0, 0x100)]) - if not text_chars.issubset(latin1_chars): + validateMsg = validateText(text, False) + if validateMsg: simpledialog.messagebox.showerror( parent=root, title="Invalid text", - message=f"Only non-control latin-1 characters are supported. Unsupported characters: {', '.join(map(hex, text_chars - latin1_chars))}" - ) - return - if not (1 <= len(text) and len(text) <= 100): - simpledialog.messagebox.showerror( - parent=root, - title="Invalid text", - message="Text must be between 1 and 100 characters long." + message=validateMsg ) return # Set text - customText(('text', text)) + customText(('text', None, text)) signatureIndex(customTextIndex) - label='Custom text: ' + (text if len(text) <= 20 else (text[:17] + '...')) + label='Custom text: ' + textLabel(text) placemenu.entryconfig(len(signatures) + 2, label=label) update() + def textLabel(text): return f'"{text}"' if len(text) <= 20 else f'Custom text: "{text[:17]}"...' def cmd_abort(): root.destroy() def cmd_sign(): @@ -323,18 +320,18 @@ def main(args): if root.signatureControlVar.get() != signatureIndex(): signatureIndex(root.signatureControlVar.get()) update() - for index, (_, signature) in enumerate(signatures): + for index, (_, signatureText, _) in enumerate(signatures): placemenu.add_radiobutton( value=index, - label=f'{index+1}: {signature}', + label=f'{index+1}: {signatureText}', underline=0 if index<9 else None, variable=root.signatureControlVar, accelerator=(str(index+1) if index<9 else None), command=updateFromSignatureRadio) - signatures.append(('cell', customText)) + signatures.append(('cell', None, customText)) placemenu.add_radiobutton( value=customTextIndex, - label='Custom text: ' + customText()[1], + label='Custom text: ' + textLabel(customText()[2]), underline=7, variable=root.signatureControlVar, accelerator='T', @@ -487,11 +484,21 @@ def qpdfOrPdftk(qpdfCmd, pdftkCmd): subprocess.run(cmd, check=True) return True # Some lambdas above rely on this -def getSignatureDir(): - (path, helptxt) = getSignatureDirAndHelp() - if not path: - die(f"Could not find a valid signature directory. The options considered are: {helptxt}") - return path +def validateText(text, do_die): + latin1_chars=set([*range(0x20, 0x7f), *range(0xa0, 0x100)]) + text_chars=set(map(ord, text)) + msg = "" + if not text_chars.issubset(latin1_chars): + msg = ("Error: Only non-control latin-1 characters are supported" + + (" in --text" if do_die else "") + + ". Unsupported characters: " + ', '.join(map(hex, text_chars - latin1_chars))) + elif not (1 <= len(text) and len(text) <= 100): + msg = "Text must be between 1 and 100 characters long." + if not msg: + return None + if do_die: + die(msg) + return msg def getSignatureDirAndHelp(): def candidate(prevpath, prevhelptxt, nr, lbl, envvar, path): From 9c7ba6da502f2369418397bbe7f40c779df5a1f1 Mon Sep 17 00:00:00 2001 From: Axel Svensson Date: Sun, 8 Sep 2024 04:03:48 +0200 Subject: [PATCH 3/4] Improve signature sizing Fixes #13. - Improve README.md to clarify signature anchor point. - Change -x and -y - Require % unit - Support specifying coordinate relative both edges. - Add -r / --resize - Change -W and -H - Add support for units - Change default to 50% --- README-example-signature.gif | Bin 0 -> 22164 bytes README-example.gif => README-example-use.gif | Bin README.md | 7 +++- pdf-sign | 42 +++++++++++++++---- 4 files changed, 41 insertions(+), 8 deletions(-) create mode 100644 README-example-signature.gif rename README-example.gif => README-example-use.gif (100%) diff --git a/README-example-signature.gif b/README-example-signature.gif new file mode 100644 index 0000000000000000000000000000000000000000..f8bc214cdf763d6bb22dc360b1ba6f6669a1e7d8 GIT binary patch literal 22164 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 VKH(L9;TgW+9sc1V{@nut06Tk$JRSf5 literal 0 HcmV?d00001 diff --git a/README-example.gif b/README-example-use.gif similarity index 100% rename from README-example.gif rename to README-example-use.gif diff --git a/README.md b/README.md index 0520279..cbaf876 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ A tool to sign PDF files, with Linux support. We are here referring to the visible, non-cryptographic squiggles. -![](README-example.gif) +![](README-example-use.gif) ## How @@ -16,6 +16,11 @@ The recommended way is: * Use an application of your choice to sign it. You can for example use Okular's Freehand Line, or transfer it to your smartphone and use Adobe Acrobat Reader. Keep in mind that it's the center of this mini-page that will be used for positioning the signature. + + + + It's a good idea to write your signature on an imagined line through the center of the mini-page. + That way, it can be positioned correctly by clicking on the signature line. * Put the signed file in your signature directory. The signature directory is `$PDF_SIGNATURE_DIR`, `$XDG_CONFIG_HOME/pdf_signatures`, `$HOME/.config/pdf_signatures` or `$HOME/.pdf_signatures/`; the first one that exists. Use `pdf-sign -h` to confirm which one will be used on your system. diff --git a/pdf-sign b/pdf-sign index ff0c02e..9f6b9d7 100755 --- a/pdf-sign +++ b/pdf-sign @@ -113,8 +113,18 @@ def main(args): return fileName signaturePath._cache={} signatureSize=Cell(lambda: pdfGetSize(signaturePath())) - signaturePositionX=Cell(args.x_coordinate) - signaturePositionY=Cell(args.y_coordinate) + try: + xm=m("^(L\\+|L-|R\\+|R-|-|)([0-9.]+)%$", args.x_coordinate) + default_x=(lambda x:{'L+':x,'L-':-x,'R+':1+x,'R-':1-x,'-':-x,'':x}[xm[1]])(float(xm[2])/100) + except: + die('Invalid -x option') + try: + ym=m("^(T\\+|T-|B\\+|B-|-|)([0-9.]+)%$", args.y_coordinate) + default_y=(lambda y:{'T+':y,'T-':-y,'B+':1+y,'B-':1-y,'-':-y,'':y}[ym[1]])(float(ym[2])/100) + except: + die('Invalid -y option') + signaturePositionX=Cell(default_x) + signaturePositionY=Cell(default_y) signatureScale=Cell(0) def translatablePDF(path): cache = translatablePDF._cache @@ -162,11 +172,28 @@ def main(args): die(f"The PDF at {path} is unusable as a signature. Reason unknown.") return cache[path] translatablePDF._cache={} + @Cell + def defaultResizeFactor(): + (pageWidth, pageHeight)=pageSize() + try: + wm=m("^([0-9.]+)(pts|pt|in|cm|mm|%)$", args.width) + maxSignatureWidth=int(float(wm[1])*({'pts':1,'pt':1,'in':72,'cm':28.3,'mm':2.83,'%':pageWidth/100}[wm[2]])) + except: + die('Invalid -W option') + try: + hm=m("^([0-9.]+)(pts|pt|in|cm|mm|%)$", args.height) + maxSignatureHeight=int(float(hm[1])*({'pts':1,'pt':1,'in':72,'cm':28.3,'mm':2.83,'%':pageHeight/100}[hm[2]])) + except: + die('Invalid -H option') + (signatureWidth, signatureHeight)=signatureSize() + return min(args.resize_factor, + maxSignatureWidth / signatureWidth, + maxSignatureHeight / signatureHeight) @VolatileCell def signaturePositionedPDF(): (w, h)=pageSize() (sw, sh)=signatureSize() - resize=1.1**signatureScale()*min(args.width*w/sw, args.height*h/sh) + resize=1.1**signatureScale()*defaultResizeFactor() dx=w*signaturePositionX()/resize - sw/2 dy=h*(1-signaturePositionY())/resize - sh/2 outFile=intmp('signature-positioned.pdf') @@ -749,10 +776,11 @@ parser = argparse.ArgumentParser(description='Sign a PDF file, with a non-crypto 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=getSignatureHelp()) -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.11, help='Height of box to fit signature to, in page height units. (default: 0.11)') +parser.add_argument('-x', '--x-coordinate', type=str, default="50%", help='Horizontal coordinate of signature center. Requires unit %% (meaning percent of page width). May be preceded by L+ (default), L-, R+ or R- to give coordinate relative left of right edge of the page. For example, 25%% means a quarter page width from left edge, R-3%% means 3%% of page width from right edge. (default: 50%%)') +parser.add_argument('-y', '--y-coordinate', type=str, default="B-25%", help='Vertical coordinate of signature center. Requires unit %% (meaning percent of page height). May be preceded by T+ (default), T-, B+ or B- to give coordinate relative top or bottom edge of the page. For example, 3%% means 3%% of page height from top edge, B-10%% means 10%% of page height from bottom edge. (default: B-25%%)') +parser.add_argument('-r', '--resize-factor', type=float, default=1.0, help='Resize signature by this factor. If this would make the signature larger than allowed by -W or -H, then instead use the maximum allowed size.') +parser.add_argument('-W', '--width', type=str, default="50%", help='Maximum width of signature. Supports units pts, in, cm, mm and %% (meaning percent of page width). (default: 50%%)') +parser.add_argument('-H', '--height', type=str, default="50%", help='Maximum height of signature. Supports units pts, in, cm, mm and %% (meaning percent of page height). (default: 50%%)') 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. (default: False)') parser.add_argument('-f', '--flatten', action=argparse.BooleanOptionalAction, default=True, help='Flatten before signing, preventing subsequent changes in PDF forms. (default: True)') From b3a9474e4e91fb2e5999b5dc80509638e68dcf1d Mon Sep 17 00:00:00 2001 From: Axel Svensson Date: Sun, 8 Sep 2024 04:53:21 +0200 Subject: [PATCH 4/4] Fix: Account for imprecision in ghostscript Fixes #14. --- pdf-sign | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pdf-sign b/pdf-sign index 9f6b9d7..f0e9e5b 100755 --- a/pdf-sign +++ b/pdf-sign @@ -130,6 +130,8 @@ def main(args): cache = translatablePDF._cache if path not in cache: (w, h) = pdfGetSize(path) + if not (1 < w and 1 < h): + die(f"The PDF at {path} is unusable as a signature due to too small dimensions.") (double_w, double_h) = (2*w, 2*h) testFile = intmp('translateTest.pdf') subprocess.run([ @@ -141,7 +143,7 @@ def main(args): '-f', path, ], check=True) (test_w, test_h) = pdfGetSize(testFile) - if (test_w, test_h) == (double_w, double_h): + if abs(test_w - double_w) < 0.01 and abs(test_h - double_h) < 0.01: # The pdf file at path can be translated correctly cache[path] = path elif (test_w, test_h) == (w, h):