/ pypdftools / sign.py
sign.py
1 ''' 2 Simple tool for filling and signing pdfs 3 ''' 4 import os 5 import typer 6 import PyPDF2 7 from sys import exit 8 9 from pyhanko import stamp 10 from pyhanko.pdf_utils import text, images 11 from pyhanko.pdf_utils.font import opentype 12 from pyhanko.pdf_utils.incremental_writer import IncrementalPdfFileWriter 13 from pyhanko.sign import signers 14 from pyhanko.sign import fields 15 16 17 FONT = os.path.join(os.path.dirname(__file__), 18 'resources', 'NotoSans-Regular.ttf') 19 20 app = typer.Typer() 21 22 23 def get_signature_fields(pdf: str) -> list[str]: 24 sig_fields = [] 25 with open(pdf, 'rb') as file: 26 reader = PyPDF2.PdfReader(file) 27 28 i = 0 29 for page in reader.pages: 30 if '/Annots' not in page: 31 i += 1 32 continue 33 34 for annotation in page['/Annots']: 35 annotation_dict = reader.get_object(annotation) 36 if annotation_dict is None: 37 continue 38 39 if annotation_dict.get('/Subtype') != '/Widget' \ 40 or annotation_dict.get('/FT') != '/Sig': 41 continue 42 43 sig_fields.append({ 44 'name': annotation_dict.get('/T'), 45 'rect': annotation_dict.get('/Rect'), 46 'page': i, 47 }) 48 49 i += 1 50 return sig_fields 51 52 53 def add_sig(w, field_name, coordinates, page): 54 fields.append_signature_field( 55 w, sig_field_spec=fields.SigFieldSpec( 56 field_name, on_page=page, box=coordinates 57 ) 58 ) 59 60 return w 61 62 63 def sign(w, outf, signer, field_name: str, stamp_path: str) -> None: 64 meta = signers.PdfSignatureMetadata(field_name=field_name) 65 pdf_signer = signers.PdfSigner( 66 meta, signer=signer, stamp_style=stamp.TextStampStyle( 67 stamp_text='%(signer)s\nTime: %(ts)s', 68 text_box_style=text.TextBoxStyle( 69 font=opentype.GlyphAccumulatorFactory(FONT) 70 ), 71 background=images.PdfImage(stamp_path), 72 border_width=1, 73 background_opacity=0.3 74 ), 75 ) 76 77 pdf_signer.sign_pdf(w, output=outf) 78 79 80 @app.command('sign') 81 def sign_pdf_from_p12( 82 pdf: str, 83 cert: str, 84 pwd: str, 85 86 automatic: bool = typer.Option( 87 False, '--automagic', '-a', help='Sign the existing field'), 88 89 field_name: str = typer.Option( 90 None, '--field', '-f', help='Output file name'), 91 out: str = typer.Option(None, '--output', '-o', 92 help='Output file name'), 93 page: int = typer.Option( 94 0, '--page', '-p', help='page where the stamp will be applied'), 95 dots: str = typer.Option( 96 None, '--dots', '-d', help='Coordinates in pdf. Axis at left bottom corner'), 97 stamp: str = typer.Option(None, '--stamp', '-s', help='Stamp image PNG')) -> None: 98 99 # output name if not given 100 if out is None: 101 out = pdf[0:-4] + '_signed.pdf' 102 103 if not os.path.exists(pdf): 104 print('File does not exist') 105 exit(1) 106 107 if not os.path.exists(cert): 108 print('Certificate does not exist') 109 exit(1) 110 111 if not stamp or not os.path.exists(stamp): 112 print('Stamp does not exist') 113 exit(1) 114 115 fields = [] 116 if automatic: 117 fields = get_signature_fields(pdf) 118 119 if len(fields) == 0: 120 print('No signature fields found') 121 exit(1) 122 123 print(fields) 124 125 signer = signers.SimpleSigner.load_pkcs12( 126 pfx_file=cert, passphrase=bytes(pwd, 'utf-8')) 127 w = IncrementalPdfFileWriter(open(pdf, 'rb'), strict=False) 128 outf = open(out, 'wb') 129 130 # try to sign until signed 131 for field in fields: 132 coordinates = tuple(field['rect']) 133 field_name = field['name'] 134 page = field['page'] 135 136 try: 137 w = add_sig(w, field_name, coordinates, page) 138 except Exception as e: 139 print(e, 'Trying to sign it anyway') 140 141 try: 142 sign(w, outf, signer, field_name) 143 return 0 144 except Exception as e: 145 pass 146 147 elif field_name is not None and page is not None and dots is not None: 148 coordinates = tuple(map(lambda x: int(x), dots.split(','))) 149 150 else: 151 print('Not enough information given') 152 exit(1) 153 154 # open files for rw 155 signer = signers.SimpleSigner.load_pkcs12( 156 pfx_file=cert, passphrase=bytes(pwd, 'utf-8')) 157 w = IncrementalPdfFileWriter(open(pdf, 'rb'), strict=False) 158 outf = open(out, 'wb') 159 160 # sign the pdf 161 print( 162 f"Signing {pdf} with {cert} at {coordinates} at page {page} at {field_name}") 163 164 try: 165 w = add_sig(w, field_name, coordinates, page) 166 except Exception as e: 167 print(e, 'Trying to sign it anyway') 168 169 sign(w, outf, signer, field_name, stamp) 170 print(out) 171 172 173 if __name__ == '__main__': 174 app()