Files
aiturk-hermes-ide/skills/productivity/pdf/scripts/pdf_create.py
T

131 lines
4.5 KiB
Python

#!/usr/bin/env python3
"""Create a PDF from a JSON spec using reportlab platypus.
Spec format (UTF-8 JSON):
{
"title": "Example Report",
"author": "example-author",
"page_size": "A4", // or "letter" (default: A4)
"page_numbers": true, // default true
"elements": [
{"type": "heading", "text": "Section 1", "level": 1},
{"type": "paragraph", "text": "Body text..."},
{"type": "table", "rows": [["H1", "H2"], ["a", "b"]], "header": true},
{"type": "image", "path": "chart.png", "width": 400},
{"type": "pagebreak"}
]
}
"""
from __future__ import annotations
import argparse
import json
import sys
def _reconfigure_stdio() -> None:
for stream in (sys.stdout, sys.stderr):
try:
stream.reconfigure(encoding="utf-8")
except Exception:
pass
def build_pdf(spec: dict, out_path: str) -> int:
try:
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4, letter
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import inch
from reportlab.platypus import (
Image,
PageBreak,
Paragraph,
SimpleDocTemplate,
Spacer,
Table,
TableStyle,
)
except ImportError:
print("Missing dependency: install with 'python3 -m pip install reportlab'", file=sys.stderr)
return 2
page_size = letter if str(spec.get("page_size", "A4")).lower() == "letter" else A4
styles = getSampleStyleSheet()
story = []
for el in spec.get("elements", []):
etype = el.get("type")
if etype == "heading":
level = min(max(int(el.get("level", 1)), 1), 3)
story.append(Paragraph(el.get("text", ""), styles[f"Heading{level}"]))
elif etype == "paragraph":
story.append(Paragraph(el.get("text", ""), styles["BodyText"]))
story.append(Spacer(1, 6))
elif etype == "table":
rows = el.get("rows", [])
if not rows:
continue
table = Table(rows, repeatRows=1 if el.get("header", True) else 0)
style = [
("GRID", (0, 0), (-1, -1), 0.5, colors.grey),
("VALIGN", (0, 0), (-1, -1), "TOP"),
]
if el.get("header", True):
style += [
("BACKGROUND", (0, 0), (-1, 0), colors.lightgrey),
("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
]
table.setStyle(TableStyle(style))
story.append(table)
story.append(Spacer(1, 10))
elif etype == "image":
kwargs = {}
if el.get("width"):
kwargs["width"] = float(el["width"])
if el.get("height"):
kwargs["height"] = float(el["height"])
img = Image(el["path"], **kwargs)
if "width" in kwargs and "height" not in kwargs:
# keep aspect ratio
ratio = img.imageHeight / img.imageWidth
img.drawWidth = kwargs["width"]
img.drawHeight = kwargs["width"] * ratio
story.append(img)
story.append(Spacer(1, 10))
elif etype == "pagebreak":
story.append(PageBreak())
else:
print(f"Warning: unknown element type {etype!r}, skipped", file=sys.stderr)
def draw_page_number(canvas, doc):
if spec.get("page_numbers", True):
canvas.saveState()
canvas.setFont("Helvetica", 9)
canvas.drawCentredString(page_size[0] / 2.0, 0.5 * inch, f"Page {doc.page}")
canvas.restoreState()
doc = SimpleDocTemplate(
out_path,
pagesize=page_size,
title=spec.get("title", ""),
author=spec.get("author", ""),
)
doc.build(story, onFirstPage=draw_page_number, onLaterPages=draw_page_number)
print(json.dumps({"output": out_path, "elements": len(spec.get("elements", []))}))
return 0
def main() -> int:
_reconfigure_stdio()
parser = argparse.ArgumentParser(description="Create a PDF from a JSON spec (reportlab).")
parser.add_argument("spec", help="Path to UTF-8 JSON spec file")
parser.add_argument("-o", "--output", required=True, help="Output PDF path")
args = parser.parse_args()
with open(args.spec, encoding="utf-8") as fh:
spec = json.load(fh)
return build_pdf(spec, args.output)
if __name__ == "__main__":
sys.exit(main())