import unicodedata import uuid from io import BytesIO from pathlib import Path from typing import Dict, List, Optional import requests from autogen_core.code_executor import ImportFromModule from autogen_core.tools import FunctionTool from fpdf import FPDF from PIL import Image, ImageDraw, ImageOps async def generate_pdf( sections: List[Dict[str, Optional[str]]], output_file: str = "report.pdf", report_title: str = "PDF Report" ) -> str: """ Generate a PDF report with formatted sections including text and images. Args: sections: List of dictionaries containing section details with keys: - title: Section title - level: Heading level (title, h1, h2) - content: Section text content - image: Optional image URL or file path output_file: Name of output PDF file report_title: Title shown at top of report Returns: str: Path to the generated PDF file """ def normalize_text(text: str) -> str: """Normalize Unicode text to ASCII.""" return unicodedata.normalize("NFKD", text).encode("ascii", "ignore").decode("ascii") def get_image(image_url_or_path): """Fetch image from URL or local path.""" if image_url_or_path.startswith(("http://", "https://")): response = requests.get(image_url_or_path) if response.status_code == 200: return BytesIO(response.content) elif Path(image_url_or_path).is_file(): return open(image_url_or_path, "rb") return None def add_rounded_corners(img, radius=6): """Add rounded corners to an image.""" mask = Image.new("L", img.size, 0) draw = ImageDraw.Draw(mask) draw.rounded_rectangle([(0, 0), img.size], radius, fill=255) img = ImageOps.fit(img, mask.size, centering=(0.5, 0.5)) img.putalpha(mask) return img class PDF(FPDF): """Custom PDF class with header and content formatting.""" def header(self): self.set_font("Arial", "B", 12) normalized_title = normalize_text(report_title) self.cell(0, 10, normalized_title, 0, 1, "C") def chapter_title(self, txt): self.set_font("Arial", "B", 12) normalized_txt = normalize_text(txt) self.cell(0, 10, normalized_txt, 0, 1, "L") self.ln(2) def chapter_body(self, body): self.set_font("Arial", "", 12) normalized_body = normalize_text(body) self.multi_cell(0, 10, normalized_body) self.ln() def add_image(self, img_data): img = Image.open(img_data) img = add_rounded_corners(img) img_path = Path(f"temp_{uuid.uuid4().hex}.png") img.save(img_path, format="PNG") self.image(str(img_path), x=None, y=None, w=190 if img.width > 190 else img.width) self.ln(10) img_path.unlink() # Initialize PDF pdf = PDF() pdf.add_page() font_size = {"title": 16, "h1": 14, "h2": 12, "body": 12} # Add sections for section in sections: title = section.get("title", "") level = section.get("level", "h1") content = section.get("content", "") image = section.get("image") pdf.set_font("Arial", "B" if level in font_size else "", font_size.get(level, font_size["body"])) pdf.chapter_title(title) if content: pdf.chapter_body(content) if image: img_data = get_image(image) if img_data: pdf.add_image(img_data) if isinstance(img_data, BytesIO): img_data.close() pdf.output(output_file) return output_file # Create the PDF generation tool generate_pdf_tool = FunctionTool( func=generate_pdf, description="Generate PDF reports with formatted sections containing text and images", global_imports=[ "uuid", "requests", "unicodedata", ImportFromModule("typing", ("List", "Dict", "Optional")), ImportFromModule("pathlib", ("Path",)), ImportFromModule("fpdf", ("FPDF",)), ImportFromModule("PIL", ("Image", "ImageDraw", "ImageOps")), ImportFromModule("io", ("BytesIO",)), ], )