#!/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()