diff --git a/pyhanko/pdf_utils/content.py b/pyhanko/pdf_utils/content.py index 8f9fa5eb..e5341914 100644 --- a/pyhanko/pdf_utils/content.py +++ b/pyhanko/pdf_utils/content.py @@ -200,7 +200,9 @@ def _ensure_writer(self) -> BasePdfFileWriter: # TODO support a set-if-not-taken mechanism, that suggests alternative names # if necessary. - def set_resource(self, category: ResourceType, name: NameObject, value: PdfObject): + def set_resource( + self, category: ResourceType, name: NameObject, value: PdfObject + ): """Set a value in the resource dictionary associated with this content fragment. diff --git a/pyhanko/pdf_utils/writer.py b/pyhanko/pdf_utils/writer.py index a04d17f3..e2c6c765 100644 --- a/pyhanko/pdf_utils/writer.py +++ b/pyhanko/pdf_utils/writer.py @@ -149,7 +149,9 @@ def __init__( if not isinstance(info, generic.IndirectObject): info_ref = self.add_object(info) else: - info_ref = generic.IndirectObject(info.idnum, info.generation, self) + info_ref = generic.IndirectObject( + info.idnum, info.generation, self + ) self._info = info_ref self._meta: DocumentMetadata = DocumentMetadata() @@ -230,7 +232,9 @@ def document_id(self) -> Tuple[bytes, bytes]: id_arr = self._document_id return id_arr[0].original_bytes, id_arr[1].original_bytes - def mark_update(self, obj_ref: Union[generic.Reference, generic.IndirectObject]): + def mark_update( + self, obj_ref: Union[generic.Reference, generic.IndirectObject] + ): """ Mark an object reference to be updated. This is only relevant for incremental updates, but is included @@ -373,7 +377,9 @@ def register_extension(self, ext: DeveloperExtension): def get_object(self, ido, as_metadata_stream: bool = False): if ido.pdf not in self._resolves_objs_from: - raise PdfError(f'Reference {ido} has no relation to this PDF writer.') + raise PdfError( + f'Reference {ido} has no relation to this PDF writer.' + ) idnum = ido.idnum generation = ido.generation try: @@ -576,7 +582,10 @@ def _update_meta(self): ) sh = self.security_handler meta_stm._handler = sh - if sh is not None and not self.security_handler.encrypt_metadata: + if ( + sh is not None + and not self.security_handler.encrypt_metadata + ): # note: this will add the /Identity crypt filter, hence # metadata encryption will be omitted meta_stm.add_crypt_filter() @@ -671,7 +680,9 @@ def _write(self, stream, skip_header: bool = False): xref_location = write_xref_table( stream, cast(Dict[Tuple[int, int], int], object_positions) ) - trailer[pdf_name('/Size')] = generic.NumberObject(self._lastobj_id + 1) + trailer[pdf_name('/Size')] = generic.NumberObject( + self._lastobj_id + 1 + ) # write trailer stream.write(b'trailer\n') trailer.write_to_stream(stream, None) @@ -824,7 +835,9 @@ def import_page_as_xobject( try: pagetree_obj = pagetree_obj['/Parent'] except KeyError: # pragma: nocover - raise PdfReadError(f'Page {page_ix} does not have a /MediaBox') + raise PdfReadError( + f'Page {page_ix} does not have a /MediaBox' + ) stream_dict = { pdf_name('/BBox'): mb, @@ -866,12 +879,16 @@ def import_page_as_xobject( stream_dict, encoded_data=command_stream.encoded_data ) else: - result = generic.StreamObject(stream_dict, stream_data=command_stream.data) + result = generic.StreamObject( + stream_dict, stream_data=command_stream.data + ) return self.add_object(result) # TODO these can be simplified considerably using the new update_container - def add_stream_to_page(self, page_ix, stream_ref, resources=None, prepend=False): + def add_stream_to_page( + self, page_ix, stream_ref, resources=None, prepend=False + ): """Append an indirect stream object to a page in a PDF as a content stream. @@ -949,7 +966,9 @@ def add_stream_to_page(self, page_ix, stream_ref, resources=None, prepend=False) # don't bother trying to update the resource object, just # clone it and add it to the current page object. orig_resource_dict = generic.DictionaryObject(res_ref) - page_obj[pdf_name('/Resources')] = self.add_object(orig_resource_dict) + page_obj[pdf_name('/Resources')] = self.add_object( + orig_resource_dict + ) self.merge_resources(orig_resource_dict, resources) return page_obj_ref @@ -984,7 +1003,10 @@ def merge_resources( orig_value = orig_value_ref update_needed = True - if isinstance(orig_value, generic.ArrayObject) and key == '/ProcSet': + if ( + isinstance(orig_value, generic.ArrayObject) + and key == '/ProcSet' + ): orig_value.extend( x for x in value.get_object() @@ -1112,7 +1134,9 @@ def encrypt(self, owner_pass, user_pass=None, **kwargs): Other keyword arguments to be passed to :meth:`.StandardSecurityHandler.build_from_pw`. """ - sh = StandardSecurityHandler.build_from_pw(owner_pass, user_pass, **kwargs) + sh = StandardSecurityHandler.build_from_pw( + owner_pass, user_pass, **kwargs + ) self._assign_security_handler(sh) def encrypt_pubkey(self, recipients: List[x509.Certificate], **kwargs): @@ -1177,7 +1201,9 @@ def __init__( self.source = source self.target = target self.obj_stream = obj_stream - self.queued_references: List[Tuple[generic.Reference, generic.Reference]] = [] + self.queued_references: List[ + Tuple[generic.Reference, generic.Reference] + ] = [] self.reference_map = reference_map def import_object(self, obj: generic.PdfObject) -> generic.PdfObject: @@ -1208,12 +1234,16 @@ def _ingest(self, obj: generic.PdfObject): if isinstance(obj, generic.IndirectObject): return self.process_reference(obj.reference) elif isinstance(obj, generic.DictionaryObject): - raw_dict = {k: self._ingest(v) for k, v in obj.items() if k != '/Metadata'} + raw_dict = { + k: self._ingest(v) for k, v in obj.items() if k != '/Metadata' + } try: # make sure to import metadata streams as such meta_ref = obj.get_value_as_reference('/Metadata') # ensure a MetadataStream object ends up in the cache - meta_ref.get_pdf_handler().get_object(meta_ref, as_metadata_stream=True) + meta_ref.get_pdf_handler().get_object( + meta_ref, as_metadata_stream=True + ) # ...then import the reference raw_dict['/Metadata'] = self.process_reference(meta_ref) except (KeyError, IndirectObjectExpected): @@ -1276,8 +1306,8 @@ def preprocess_signature_data(self): sig_dict = ref.get_object() assert isinstance(sig_dict, generic.DictionaryObject) raw_dict = { - k: self._ingest(v) - for k, v in sig_dict.items() + k: self._ingest(v) + for k, v in sig_dict.items() if k != '/Contents' } raw_dict['/Contents'] = generic.ByteStringObject( diff --git a/pyhanko/sign/signers/cms_embedder.py b/pyhanko/sign/signers/cms_embedder.py index 6e5a2165..c3e5f2b1 100644 --- a/pyhanko/sign/signers/cms_embedder.py +++ b/pyhanko/sign/signers/cms_embedder.py @@ -46,7 +46,9 @@ def docmdp_reference_dictionary(permission_level: MDPPerm): { pdf_name('/Type'): pdf_name('/TransformParams'), pdf_name('/V'): pdf_name('/1.2'), - pdf_name('/P'): generic.NumberObject(permission_level.value), + pdf_name('/P'): generic.NumberObject( + permission_level.value + ), } ), } @@ -102,7 +104,8 @@ def _get_or_create_sigfield( if others: raise SigningError( 'There are several empty signature fields. Please specify ' - 'a field name. The options are %s, %s.' % (found_field_name, others) + 'a field name. The options are %s, %s.' + % (found_field_name, others) ) else: # grab or create a sig field @@ -199,7 +202,9 @@ def apply(self, sig_obj_ref, writer): reference_array.append(docmdp_reference_dictionary(docmdp_perms)) if lock is not None: - fieldmdp_ref = fieldmdp_reference_dictionary(lock, data_ref=writer.root_ref) + fieldmdp_ref = fieldmdp_reference_dictionary( + lock, data_ref=writer.root_ref + ) reference_array.append(fieldmdp_ref) if docmdp_perms is not None: @@ -265,7 +270,9 @@ def apply(self, sig_annot, writer, rotate): and '/N' in sig_annot['/AP'] and '/Matrix' in sig_annot['/AP']['/N'] ): - normalappearence['/N']['/Matrix'] = sig_annot['/AP']['/N']['/Matrix'] + normalappearence['/N']['/Matrix'] = sig_annot['/AP']['/N'][ + '/Matrix' + ] sig_annot['/AP'] = normalappearence try: # if there was an entry like this, it's meaningless now @@ -446,7 +453,9 @@ def write_cms( A generator coroutine implementing the protocol described above. """ - new_field_spec = self.new_field_spec if not existing_fields_only else None + new_field_spec = ( + self.new_field_spec if not existing_fields_only else None + ) # start by creating or fetching the appropriate signature field field_created, sig_field_ref = _get_or_create_sigfield( field_name, @@ -466,7 +475,9 @@ def write_cms( appearance_setup = sig_obj_setup.appearance_setup if appearance_setup is not None: sig_annot = get_sig_field_annot(sig_field) - page_ref = writer.find_page_for_modification(new_field_spec.on_page)[0] + page_ref = writer.find_page_for_modification( + new_field_spec.on_page + )[0] ref = page_ref.get_object() obj = ref.get('/Rotate', 0) rotate = obj if isinstance(obj, int) else obj.get_object() diff --git a/pyhanko/stamp.py b/pyhanko/stamp.py index a37c9035..0f257ca0 100644 --- a/pyhanko/stamp.py +++ b/pyhanko/stamp.py @@ -195,7 +195,9 @@ class StaticStampStyle(BaseStampStyle): """ @classmethod - def from_pdf_file(cls, file_name, page_ix=0, **kwargs) -> 'StaticStampStyle': + def from_pdf_file( + cls, file_name, page_ix=0, **kwargs + ) -> 'StaticStampStyle': """ Create a :class:`StaticStampStyle` from a page from an external PDF document. This is a convenience wrapper around @@ -221,7 +223,9 @@ def create_stamp( text_params: dict, rotate: int, ) -> 'StaticContentStamp': - return StaticContentStamp(writer=writer, style=self, box=box, rotate=rotate) + return StaticContentStamp( + writer=writer, style=self, box=box, rotate=rotate + ) @dataclass(frozen=True) @@ -271,7 +275,11 @@ def create_stamp( rotate: int, ) -> 'TextStamp': return TextStamp( - writer=writer, style=self, box=box, text_params=text_params, rotate=rotate + writer=writer, + style=self, + box=box, + text_params=text_params, + rotate=rotate, ) @@ -351,8 +359,8 @@ class QRStampStyle(TextStampStyle): """ stamp_text: str = ( - "Digital version available at\n" - "this url: %(url)s\n" + "Digital version available at\n" + "this url: %(url)s\n" "Timestamp: %(ts)s" ) """ @@ -406,7 +414,12 @@ def create_stamp( "Using a QR stamp style requires a 'url' text parameter." ) return QRStamp( - writer, style=self, url=url, text_params=text_params, box=box, rotate=rotate + writer, + style=self, + url=url, + text_params=text_params, + box=box, + rotate=rotate, ) @@ -685,7 +698,9 @@ def __init__( text_params=None, box: Optional[layout.BoxConstraints] = None, ): - super().__init__(writer, style, text_params=text_params, box=box, rotate=rotate) + super().__init__( + writer, style, text_params=text_params, box=box, rotate=rotate + ) self.url = url self._qr_size = None @@ -774,13 +789,21 @@ def _inner_layout_natural_size(self) -> Tuple[List[bytes], Tuple[int, int]]: # Time to put in the text box now if style.qr_position == QRPosition.LEFT_OF_TEXT: - tb_margins = layout.Margins(left=qr_padded, right=0, top=0, bottom=0) + tb_margins = layout.Margins( + left=qr_padded, right=0, top=0, bottom=0 + ) elif style.qr_position == QRPosition.RIGHT_OF_TEXT: - tb_margins = layout.Margins(right=qr_padded, left=0, top=0, bottom=0) + tb_margins = layout.Margins( + right=qr_padded, left=0, top=0, bottom=0 + ) elif style.qr_position == QRPosition.BELOW_TEXT: - tb_margins = layout.Margins(bottom=qr_padded, right=0, left=0, top=0) + tb_margins = layout.Margins( + bottom=qr_padded, right=0, left=0, top=0 + ) else: - tb_margins = layout.Margins(top=qr_padded, right=0, left=0, bottom=0) + tb_margins = layout.Margins( + top=qr_padded, right=0, left=0, bottom=0 + ) tb_layout_rule = layout.SimpleBoxLayoutRule( # flip around the alignment conventions of the default layout @@ -957,7 +980,7 @@ def qr_stamp_file( STAMP_ART_CONTENT = content.RawContent( box=layout.BoxConstraints(width=100, height=100), - data=b""" + data=b''' q 1 0 0 -1 0 100 cm 0.603922 0.345098 0.54902 rg 3.699 65.215 m 3.699 65.215 2.375 57.277 7.668 51.984 c 12.957 46.695 27.512 @@ -972,7 +995,7 @@ def qr_stamp_file( 3.801 79.512 92.398 7.391 re f 3.801 90.289 92.398 7.391 re f Q -""", +''', ) """ Hardcoded stamp background that will render a stylised image of a stamp using