MatthiasValvekens / pyHanko

pyHanko: sign and stamp PDF files
MIT License
483 stars 71 forks source link

stamp font and position is inverted for some PDFs. #312

Open aaabhilash97 opened 1 year ago

aaabhilash97 commented 1 year ago

The stamp font and box position are vertically inverted from what is expected. This issue only occurs in PDFs that are converted from HTML. For other PDFs, the font and position are in the expected manner.

Code sample to reproduce the issue.

import concurrent.futures

import asyncio
from loguru import logger
# import aiofiles
from pyhanko import stamp
from pyhanko.pdf_utils import text, images
from pyhanko.pdf_utils.incremental_writer import IncrementalPdfFileWriter
from pyhanko.sign import fields, signers
import secrets
import string

async def stamp2():
    with open("./input2.pdf", "rb") as fin:
        pdf_out = IncrementalPdfFileWriter(fin, strict=False)
        stamper = stamp.TextStamp(
            writer=pdf_out,
            style=stamp.TextStampStyle(
                stamp_text="Digitally signed by: %(signer)s\nTime: %(ts)s\nDevice ip: 129.001.11.1\nDevice os: Android\nBorrower name:Abhilash km",
                text_box_style=text.TextBoxStyle(
                    font_size=10,
                ),
                background=images.PdfImage("logo.png"),

            ),
            text_params={"signer": "OKOK"},
            # box=(200, 600, 400, 660),
        )
        stamper.apply(0, 200, 1)

        with open("./output2.pdf", "wb") as out:
            pdf_out.write(out)

Expected behaviour is stamp affixed bottom of page.

Issue screenshot: (HTML converted pdf)(issue happening in wkhtmltopdf pdf, pdf created from chrome)

Screenshot 2023-08-29 at 9 41 03 AM

Tried python 3.11.4 macOS and Linux

Working as expected in other pdf:

Screenshot 2023-08-29 at 9 43 43 AM

@MatthiasValvekens Do you have any idea why this is happening?

MatthiasValvekens commented 1 year ago

Is the "bad" PDF rotated? If so, it's a known issue; see discussion and links in #265.

There's an open PR purporting to fix the problem, but it needs a bit of work...

aaabhilash97 commented 1 year ago

@MatthiasValvekens probably this might be the issue. Will explore this.

In my case visible signature working fine(visible_sig_settings=fields.VisibleSigSettings(rotate_with_page=True)).

When I'm trying to put stamp alone issue is happening. Couldn't able to find this rotate_with_page option in stamp functions.

aaabhilash97 commented 1 year ago

Hi @MatthiasValvekens Anyway we can specify rotate with page option in stamp function. Or does that require changes in module?

MatthiasValvekens commented 1 year ago

The rotate_with_page option in signatures works by setting an annotation flag; that's not possible in this case because the stamp function (when used in the most straightforward way) directly acts on page content.

There are obviously ways to fix this ad-hoc without making too many code changes, but they all require some knowledge about the PDF graphics model... :/ The approach proposed in #266 can be made to cover your use case, but not in the way it's implemented there.

I'm currently very strapped for time, but I'll flag this as a feature request to be completed together with the fix for #265.

aaabhilash97 commented 1 year ago

We can fix the rotation issue of a PDF using the transfer_rotation_to_content() option in https://pypdf.readthedocs.io/en/3.15.4/user/add-watermark.html.

https://pypdf.readthedocs.io/en/3.15.4/modules/PageObject.html?highlight=transfer_rotation_to_content#pypdf._page.PageObject.transfer_rotation_to_content

I am adding it here because it may be helpful to someone looking for a solution.

import io
from io import BytesIO

from loguru import logger
from pyhanko import stamp
from pyhanko.pdf_utils import images, text
from pyhanko.pdf_utils.incremental_writer import IncrementalPdfFileWriter
from pyhanko.sign import signers
from pypdf import PdfReader, PdfWriter

cms_signer = signers.SimpleSigner.load(
    "./key.pem",
    "./cert.pem",
)

async def all_page_stamp_single_sign(
    inf: bytes,
    stamp_text: str,
):
    rotation_fixer = PdfWriter(clone_from=PdfReader(BytesIO(inf)))
    for page in rotation_fixer.pages:
        page.transfer_rotation_to_content()
        page.compress_content_streams(level=9)

    rotation_fixed_pdf_out = io.BytesIO()
    rotation_fixer.write(rotation_fixed_pdf_out)
    stamp_pdf_writer = IncrementalPdfFileWriter(rotation_fixed_pdf_out, strict=False)
    stamper = stamp.TextStamp(
        writer=stamp_pdf_writer,
        style=stamp.TextStampStyle(
            stamp_text=stamp_text,
            text_box_style=text.TextBoxStyle(
                font_size=7,
            ),
            background=images.PdfImage("logo.png"),
        ),
    )
    try:
        for i in range(40):
            stamper.apply(i, 180, 1)
    except Exception as e:
        logger.debug("Stamp apply exception", exception=e)

    stamped_pdf_out = io.BytesIO()
    stamp_pdf_writer.write(stamped_pdf_out)
    signed_pdf_writer = IncrementalPdfFileWriter(stamped_pdf_out, strict=False)
    return await signers.async_sign_pdf(
        signed_pdf_writer,
        signers.PdfSignatureMetadata(field_name="Signature"),
        signer=cms_signer,
    )