MatthiasValvekens / pyHanko

pyHanko: sign and stamp PDF files
MIT License
520 stars 75 forks source link

Signature faulty with rotate_with_page = False on rotated pages/pdfs #265

Open DominikDostal opened 1 year ago

DominikDostal commented 1 year ago

Describe the bug When you create a signature on a page that has the Rotate Parameter (e.g. /Rotate 90) with the rotate_with_page paramter set to False then the visible Signature will correctly stay horizontal like it should when its opened in a browser.

Should you then open the same file in Adobe Acrobat Reader instead, it will not be visible at all, but still have a clickable invisible box where it should have been visible, and a "selected" box that was rotated with the page.

To Reproduce Steps to reproduce the behavior: 1) Create any pdf (I used MS Word) 2) Rotate the PDF (I used https://smallpdf.com/rotate-pdf) 3) Sign the pdf with pyHanko:

input_file = 'input path here'
output_file = 'output path here'
signer = signers.SimpleSigner.load(certificate_key, certificate_cert)
    inFile = open(input_file, 'rb')
    w = IncrementalPdfFileWriter(inFile, strict=False)
    fields.append_signature_field(
        w, sig_field_spec=fields.SigFieldSpec(
            'Signature',
            box=(31, 195, 564, 99),
            on_page=0,
            visible_sig_settings=fields.VisibleSigSettings(False)
        )
    )
    meta = signers.PdfSignatureMetadata(field_name='Signature')
    stamp_style = stamp.StaticStampStyle.from_pdf_file(stamp_file,
        border_width=0,
        background_layout=SimpleBoxLayoutRule(
            margins=Margins(0,0,0,0),
            x_align=AxisAlignment.ALIGN_MID,
            y_align=AxisAlignment.ALIGN_MID,
            inner_content_scaling=InnerScaling.STRETCH_FILL
        ),
    )
    pdf_signer = signers.PdfSigner(
        # stamp style: https://pyhanko.readthedocs.io/en/latest/api-docs/pyhanko.html#pyhanko.stamp.StaticStampStyle
        meta, signer=signer, stamp_style=stamp_style
    )

    with open(output_file, 'wb') as outFile:
        # with QR stamps, the 'url' text parameter is special-cased and mandatory, even if it
        # doesn't occur in the stamp text: this is because the value of the 'url' parameter is
        # also used to render the QR code.
        pdf_signer.sign_pdf(
            w, output=outFile,
        )

    inFile.close()

Expected behavior Signature is visible in Adobe Acrobat reader.

Based on my research, which could very well be wrong, i think the Appearence Annotations need to have either the norotate flag set (if that is even possible) or /Matrix added to rotate it against the page rotation. Here are the values you would have to add as a /Matrix parameter based on the /Rotate parameter of the page:

                match pagetree_obj['/Rotate']:
                    case 0:
                        matrix = (1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
                    case 90:
                        matrix = (0.0, 1.0, -1.0, 0.0, 0.0, 0.0)
                    case 180:
                        matrix = (-1.0, 0.0, 0.0, -1.0, 0.0, 0.0)
                    case 270:
                        matrix = (0.0, -1.0, 1.0, 0.0, 0.0, 0.0)

Screenshots When opened in Browser (Opera): image

When opened in Adobe Acrobat Reader: image

Environment (please complete the following information):

Here is the file that i used to create the screenshots:

faulty signature pyhanko.pdf

MatthiasValvekens commented 1 year ago

This is a side effect of the fact that pyHanko doesn't really look at page rotation settings. See these threads, for example:

You could try with NoRotate, but given that that's notoriously finicky it might be better to hardwire the rotation into the annotation content stream after all... Somewhat ironically, this is what Acrobat actually does these days when putting signatures on rotated pages, instead of trying to use the format's "native" way of coping with these things.

I'll check how much effort this would be to implement.