scripts/pdf_create.py
scripts/pdf_create.pyBrowse 21 files
1,132 tokens
4,562 bytes
Token encoding: o200k_base
Snapshot 24fd22b
← Back to SKILL.md
1#!/usr/bin/env python32"""Create a PDF from a JSON spec using reportlab platypus.3 4Spec format (UTF-8 JSON):5{6 "title": "Example Report",7 "author": "example-author",8 "page_size": "A4", // or "letter" (default: A4)9 "page_numbers": true, // default true10 "elements": [11 {"type": "heading", "text": "Section 1", "level": 1},12 {"type": "paragraph", "text": "Body text..."},13 {"type": "table", "rows": [["H1", "H2"], ["a", "b"]], "header": true},14 {"type": "image", "path": "chart.png", "width": 400},15 {"type": "pagebreak"}16 ]17}18"""19from __future__ import annotations20 21import argparse22import json23import sys24 25 26def _reconfigure_stdio() -> None:27 for stream in (sys.stdout, sys.stderr):28 try:29 stream.reconfigure(encoding="utf-8")30 except Exception:31 pass32 33 34def build_pdf(spec: dict, out_path: str) -> int:35 try:36 from reportlab.lib import colors37 from reportlab.lib.pagesizes import A4, letter38 from reportlab.lib.styles import getSampleStyleSheet39 from reportlab.lib.units import inch40 from reportlab.platypus import (41 Image,42 PageBreak,43 Paragraph,44 SimpleDocTemplate,45 Spacer,46 Table,47 TableStyle,48 )49 except ImportError:50 print("Missing dependency: install with 'python3 -m pip install reportlab'", file=sys.stderr)51 return 252 53 page_size = letter if str(spec.get("page_size", "A4")).lower() == "letter" else A454 styles = getSampleStyleSheet()55 story = []56 for el in spec.get("elements", []):57 etype = el.get("type")58 if etype == "heading":59 level = min(max(int(el.get("level", 1)), 1), 3)60 story.append(Paragraph(el.get("text", ""), styles[f"Heading{level}"]))61 elif etype == "paragraph":62 story.append(Paragraph(el.get("text", ""), styles["BodyText"]))63 story.append(Spacer(1, 6))64 elif etype == "table":65 rows = el.get("rows", [])66 if not rows:67 continue68 table = Table(rows, repeatRows=1 if el.get("header", True) else 0)69 style = [70 ("GRID", (0, 0), (-1, -1), 0.5, colors.grey),71 ("VALIGN", (0, 0), (-1, -1), "TOP"),72 ]73 if el.get("header", True):74 style += [75 ("BACKGROUND", (0, 0), (-1, 0), colors.lightgrey),76 ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),77 ]78 table.setStyle(TableStyle(style))79 story.append(table)80 story.append(Spacer(1, 10))81 elif etype == "image":82 kwargs = {}83 if el.get("width"):84 kwargs["width"] = float(el["width"])85 if el.get("height"):86 kwargs["height"] = float(el["height"])87 img = Image(el["path"], **kwargs)88 if "width" in kwargs and "height" not in kwargs:89 # keep aspect ratio90 ratio = img.imageHeight / img.imageWidth91 img.drawWidth = kwargs["width"]92 img.drawHeight = kwargs["width"] * ratio93 story.append(img)94 story.append(Spacer(1, 10))95 elif etype == "pagebreak":96 story.append(PageBreak())97 else:98 print(f"Warning: unknown element type {etype!r}, skipped", file=sys.stderr)99 100 def draw_page_number(canvas, doc):101 if spec.get("page_numbers", True):102 canvas.saveState()103 canvas.setFont("Helvetica", 9)104 canvas.drawCentredString(page_size[0] / 2.0, 0.5 * inch, f"Page {doc.page}")105 canvas.restoreState()106 107 doc = SimpleDocTemplate(108 out_path,109 pagesize=page_size,110 title=spec.get("title", ""),111 author=spec.get("author", ""),112 )113 doc.build(story, onFirstPage=draw_page_number, onLaterPages=draw_page_number)114 print(json.dumps({"output": out_path, "elements": len(spec.get("elements", []))}))115 return 0116 117 118def main() -> int:119 _reconfigure_stdio()120 parser = argparse.ArgumentParser(description="Create a PDF from a JSON spec (reportlab).")121 parser.add_argument("spec", help="Path to UTF-8 JSON spec file")122 parser.add_argument("-o", "--output", required=True, help="Output PDF path")123 args = parser.parse_args()124 with open(args.spec, encoding="utf-8") as fh:125 spec = json.load(fh)126 return build_pdf(spec, args.output)127 128 129if __name__ == "__main__":130 sys.exit(main())131