From 24c32614b284b08081b9c18359e7e1968df46beb Mon Sep 17 00:00:00 2001 From: mpmedia Date: Mon, 20 Jul 2026 15:00:27 -0500 Subject: [PATCH] Add scripts/fill_flat_pdf.py (v1.0.0) --- scripts/fill_flat_pdf.py | 100 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 scripts/fill_flat_pdf.py diff --git a/scripts/fill_flat_pdf.py b/scripts/fill_flat_pdf.py new file mode 100644 index 0000000..1464884 --- /dev/null +++ b/scripts/fill_flat_pdf.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +""" +fill_flat_pdf.py — overlay text onto a flat (non-fillable) PDF. + +For forms that look like forms but have no AcroForm fields. You supply the +placements (page index, x, y in PDF points from the bottom-left, and the text); +this stamps them onto a copy of the PDF. + +Usage: + python fill_flat_pdf.py INPUT.pdf OUTPUT.pdf PLACEMENTS.json + # or pass placements inline: + python fill_flat_pdf.py INPUT.pdf OUTPUT.pdf '[{"page":0,"x":150,"y":690,"text":"Message Point Media of Alabama Inc."}]' + +PLACEMENTS.json format: + [ + {"page": 0, "x": 150, "y": 690, "text": "Message Point Media of Alabama Inc.", "size": 10}, + {"page": 0, "x": 150, "y": 668, "text": "4628 Montevallo Rd, Suite 204"} + ] + +Coordinates: PDF origin is bottom-left; y increases upward; 72 points = 1 inch. +If your source coordinates are top-left, convert with y_pdf = page_height - y_top. +Use --list-sizes to print each page's width/height in points to help you place text. + +Requires: pypdf and reportlab (pip install pypdf reportlab --break-system-packages) +""" +import sys +import json +import io + + +def page_sizes(input_pdf): + from pypdf import PdfReader + reader = PdfReader(input_pdf) + for i, page in enumerate(reader.pages): + box = page.mediabox + print(f"page {i}: width={float(box.width):.1f} height={float(box.height):.1f} (points)") + + +def fill(input_pdf, output_pdf, placements): + from pypdf import PdfReader, PdfWriter + from reportlab.pdfgen import canvas + + reader = PdfReader(input_pdf) + writer = PdfWriter() + + # group placements by page + by_page = {} + for p in placements: + by_page.setdefault(int(p.get("page", 0)), []).append(p) + + for i, page in enumerate(reader.pages): + if i in by_page: + box = page.mediabox + w, h = float(box.width), float(box.height) + buf = io.BytesIO() + c = canvas.Canvas(buf, pagesize=(w, h)) + for p in by_page[i]: + size = float(p.get("size", 10)) + c.setFont(p.get("font", "Helvetica"), size) + c.drawString(float(p["x"]), float(p["y"]), str(p["text"])) + c.save() + buf.seek(0) + overlay = PdfReader(buf).pages[0] + page.merge_page(overlay) + writer.add_page(page) + + with open(output_pdf, "wb") as f: + writer.write(f) + print(f"Wrote {output_pdf} with {sum(len(v) for v in by_page.values())} text placements.") + + +def main(): + args = sys.argv[1:] + if args and args[0] == "--list-sizes": + if len(args) != 2: + print("Usage: python fill_flat_pdf.py --list-sizes INPUT.pdf") + sys.exit(1) + page_sizes(args[1]) + return + + if len(args) != 3: + print(__doc__) + sys.exit(1) + + input_pdf, output_pdf, placements_arg = args + try: + placements = json.loads(placements_arg) + except json.JSONDecodeError: + with open(placements_arg) as f: + placements = json.load(f) + + if not isinstance(placements, list): + print("Placements must be a JSON array of {page,x,y,text} objects.") + sys.exit(1) + + fill(input_pdf, output_pdf, placements) + + +if __name__ == "__main__": + main()