79762299

Date: 2025-09-11 19:12:19
Score: 0.5
Natty:
Report link

Thanks to KJ for pointing me in the right direction!

A coworker wrote up a different way to fill the fields using pdfrw, example below:

from pdfrw import PdfReader, PdfWriter, PdfDict, PdfObject, PdfName, PageMerge
from pdfrw.objects.pdfstring import PdfString

def fill_pdf_fields(input_path, output_path):
    pdf = PdfReader(input_path)

    # Ensure viewer regenerates appearances
    if not pdf.Root.AcroForm:
        pdf.Root.AcroForm = PdfDict(NeedAppearances=PdfObject('true'))
    else:
        pdf.Root.AcroForm.update(PdfDict(NeedAppearances=PdfObject('true')))

    for page in pdf.pages:
        annotations = page.Annots
        if annotations:
            for annot in annotations:
                if annot.Subtype == PdfName('Widget') and annot.T:
                    field_name = str(annot.T)[1:-1]


                    if field_name == "MemberName": annot.V = PdfObject(f'(Test)')
                    if field_name == "Address": annot.V = PdfObject(f'(123 Sesame St)')
                    if field_name == "CityStateZip": annot.V = PdfObject(f'(Birmingham, AK 12345-6789)')
                    if field_name == "Level": annot.V = PdfObject(f'(1)')
                    if field_name == "OfficialsNumber": annot.V = PdfObject(f'(9999999)')
                    if field_name == "Season2": annot.V = PdfObject(f'(2025-26)')
                    if field_name == "Season1": annot.V = PdfObject(f'(2025-2026)')


    PdfWriter().write(output_path, pdf)
    print(f"Filled PDF saved to: {output_path}")


def flatten_pdf_fields(input_path, output_path):
    template_pdf = PdfReader(input_path)

    for page in template_pdf.pages:
        annotations = page.Annots
        if annotations:
            for annot in annotations:
                if annot.Subtype == PdfName('Widget') and annot.T and annot.V:
                    # Remove interactive field appearance
                    annot.update({
                        PdfName('F'): PdfObject('4'),  # Make field read-only
                        PdfName('AP'): None  # Remove appearance stream
                    })

        # Flatten page by merging its own content (no overlay)
        PageMerge(page).render()

    PdfWriter(output_path, trailer=template_pdf).write()
    print(f"Flattened PDF saved to: {output_path}")


if __name__ == "__main__":
    fill_pdf_fields(template_pdf, filled_pdf)
    flatten_pdf_fields(filled_pdf, flattened_pdf)

I researched interactions with NeedAppearances, and found this Stack post:
NeedAppearances=pdfrw.PdfObject('true') forces manual pdf save in Acrobat Reader

The answer provides a code snippet that from what I can tell, acts as a reader generating those appearance streams so the filled in fields actually show their contents.

Code snippet for reference:

from pikepdf import Pdf

with Pdf.open('source_pdf.pdf') as pdf:
    pdf.generate_appearance_streams()
    pdf.save('output.pdf')
Reasons:
  • Blacklisted phrase (0.5): Thanks
  • Long answer (-1):
  • Has code block (-0.5):
  • Self-answer (0.5):
  • Low reputation (1):
Posted by: Ben B