Add scripts/fill_flat_pdf.py (v1.0.0)
This commit is contained in:
@@ -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()
|
||||||
Reference in New Issue
Block a user