import re import json import os from typing import List, Dict, Any def sanitize_for_latex(name): """Convert any character that is not alphanumeric into underscore for LaTeX compatibility.""" return re.sub(r'[^0-9a-zA-Z_]+', '_', name) def initialize_beamer_document(width_cm=120, height_cm=90, theme="default"): """ Initialize a Beamer document with specified dimensions and theme. Args: width_cm: Width in centimeters (default 120cm for poster) height_cm: Height in centimeters (default 90cm for poster) theme: Beamer theme name (default, Madrid, Warsaw, etc.) """ code = f'''\\documentclass[aspectratio=169]{{beamer}} \\usepackage[utf8]{{inputenc}} \\usepackage[T1]{{fontenc}} \\usepackage{{graphicx}} \\usepackage{{tikz}} \\usepackage{{xcolor}} \\usepackage{{geometry}} \\usepackage{{multicol}} \\usepackage{{array}} \\usepackage{{booktabs}} \\usepackage{{adjustbox}} % Set page dimensions for poster \\geometry{{paperwidth={width_cm}cm, paperheight={height_cm}cm, margin=1cm}} % Beamer theme \\usetheme{{{theme}}} \\usecolortheme{{default}} % Custom colors \\definecolor{{titlecolor}}{{RGB}}{{47, 85, 151}} \\definecolor{{textcolor}}{{RGB}}{{0, 0, 0}} \\definecolor{{bgcolor}}{{RGB}}{{255, 255, 255}} % Remove navigation symbols \\setbeamertemplate{{navigation symbols}}{{}} % Custom title page \\setbeamertemplate{{title page}}{{ \\begin{{center}} \\vspace{{1cm}} {{\\color{{titlecolor}}\\Huge\\textbf{{\\inserttitle}}}} \\vspace{{0.5cm}} \\Large{{\\insertauthor}} \\vspace{{0.3cm}} \\normalsize{{\\insertinstitute}} \\end{{center}} }} % Custom frame title \\setbeamertemplate{{frametitle}}{{ \\vspace{{0.5cm}} \\begin{{flushleft}} {{\\color{{titlecolor}}\\Large\\textbf{{\\insertframetitle}}}} \\end{{flushleft}} \\vspace{{0.3cm}} }} \\begin{{document}} % Title frame \\title{{POSTER_TITLE_PLACEHOLDER}} \\author{{POSTER_AUTHOR_PLACEHOLDER}} \\institute{{POSTER_INSTITUTE_PLACEHOLDER}} \\date{{\\today}} \\begin{{frame}}[plain] \\titlepage \\end{{frame}} ''' return code def generate_beamer_section_code(section_data: Dict[str, Any], section_index: int): """ 兼容 Paper2Poster bullet JSON: - section_data 包含 title_blocks / textbox1_blocks / textbox2_blocks - 每个 *_blocks 是 list[ {bullet: bool, runs: [{text: str, ...}], ...} ] """ def blocks_to_lines(blocks): """把 blocks 转成 list[str],并标注是否 bullet""" lines = [] for blk in blocks or []: text = " ".join([r.get("text","") for r in blk.get("runs", [])]).strip() if not text: continue lines.append({ "text": text, "bullet": bool(blk.get("bullet", False)) }) return lines # Frame title 优先用 title_blocks 的文本,否则用 title_str,否则 Untitled if isinstance(section_data.get("title_blocks"), list) and section_data["title_blocks"]: frame_title = " ".join([r.get("text","") for r in section_data["title_blocks"][0].get("runs", [])]).strip() else: frame_title = section_data.get("title_str") or "Untitled" frame_title = frame_title.replace("{","\\{").replace("}","\\}") # 简单转义以防标题含花括号 code = f"\n% ===== Section {section_index} =====\n" code += f"\\begin{{frame}}[t]{{{frame_title}}}\n" code += " \\vspace{-0.5cm}\n" for key in ["textbox1_blocks", "textbox2_blocks"]: lines = blocks_to_lines(section_data.get(key, [])) if not lines: continue # 如果全是 bullet,就合并成一个 itemize;否则分别处理 if all(l["bullet"] for l in lines): code += " \\begin{itemize}\n" for l in lines: code += f" \\item {l['text']}\n" code += " \\end{itemize}\n" else: for l in lines: if l["bullet"]: code += f" \\begin{{itemize}}\\item {l['text']}\\end{{itemize}}\n" else: code += f" {l['text']}\\\\\n" code += "\\end{frame}\n\n" return code def generate_beamer_figure_code(figure_data: Dict[str, Any], figure_index: int): """ Generate Beamer code for including figures. Args: figure_data: Dictionary containing figure information figure_index: Index of the figure """ figure_name = sanitize_for_latex(figure_data.get('figure_name', f'figure_{figure_index}')) figure_path = figure_data.get('figure_path', '') # Convert inches to centimeters (1 inch = 2.54 cm) width_cm = figure_data.get('width', 10) * 2.54 height_cm = figure_data.get('height', 8) * 2.54 code = f''' % Figure: {figure_name} \\begin{{frame}}[t]{{{figure_data.get('title', 'Figure')}}} \\vspace{{-0.5cm}} \\begin{{center}} \\includegraphics[width={width_cm:.2f}cm, height={height_cm:.2f}cm]{{{figure_path}}} \\end{{center}} \\vspace{{0.3cm}} \\begin{{center}} \\small{{\\textbf{{{figure_data.get('caption', 'Figure Caption')}}}}} \\end{{center}} \\end{{frame}} ''' return code def generate_beamer_poster_code( sections: List[Dict[str, Any]], figures: List[Dict[str, Any]], poster_info: Dict[str, str], width_cm: float = 120, height_cm: float = 90, theme: str = "default", output_path: str = "poster.tex" ): """ Generate complete Beamer poster code. Args: sections: List of section dictionaries figures: List of figure dictionaries poster_info: Dictionary with title, author, institute width_cm: Poster width in centimeters height_cm: Poster height in centimeters theme: Beamer theme name output_path: Output .tex file path """ code = initialize_beamer_document(width_cm, height_cm, theme) # Replace placeholders with actual content code = code.replace('POSTER_TITLE_PLACEHOLDER', poster_info.get('title', 'Poster Title')) code = code.replace('POSTER_AUTHOR_PLACEHOLDER', poster_info.get('author', 'Author Name')) code = code.replace('POSTER_INSTITUTE_PLACEHOLDER', poster_info.get('institute', 'Institute Name')) # Add sections for i, section in enumerate(sections): code += generate_beamer_section_code(section, i) # Add figures for i, figure in enumerate(figures): code += generate_beamer_figure_code(figure, i) # Close document code += ''' \\end{document} ''' return code def save_beamer_code(code: str, output_path: str): """Save Beamer code to file.""" with open(output_path, 'w', encoding='utf-8') as f: f.write(code) def compile_beamer_to_pdf(tex_path: str, output_dir: str = "."): """ Compile Beamer .tex file to PDF using pdflatex. Args: tex_path: Path to .tex file output_dir: Output directory for PDF """ import subprocess try: # Run pdflatex twice for proper cross-references result1 = subprocess.run( ['pdflatex', '-output-directory', output_dir, tex_path], capture_output=True, text=True, timeout=60 ) result2 = subprocess.run( ['pdflatex', '-output-directory', output_dir, tex_path], capture_output=True, text=True, timeout=60 ) if result1.returncode == 0 and result2.returncode == 0: print(f"Successfully compiled {tex_path} to PDF") return True else: print(f"Error compiling {tex_path}:") print(result1.stderr) print(result2.stderr) return False except subprocess.TimeoutExpired: print(f"Timeout while compiling {tex_path}") return False except Exception as e: print(f"Error compiling {tex_path}: {e}") return False # Example usage functions def convert_pptx_layout_to_beamer(pptx_layout_data: Dict[str, Any]) -> Dict[str, Any]: """ Convert PowerPoint layout data to Beamer-compatible format. Args: pptx_layout_data: Layout data from PowerPoint generation """ beamer_data = { 'sections': [], 'figures': [], 'poster_info': { 'title': 'Default Title', 'author': 'Default Author', 'institute': 'Default Institute' } } # Convert text arrangements to sections if 'text_arrangement' in pptx_layout_data: for i, text_item in enumerate(pptx_layout_data['text_arrangement']): section = { 'section_name': text_item.get('textbox_name', f'section_{i}'), 'title': text_item.get('title', f'Section {i+1}'), 'content': text_item.get('content', 'Content placeholder') } beamer_data['sections'].append(section) # Convert figure arrangements to figures if 'figure_arrangement' in pptx_layout_data: for i, figure_item in enumerate(pptx_layout_data['figure_arrangement']): figure = { 'figure_name': figure_item.get('figure_name', f'figure_{i}'), 'figure_path': figure_item.get('figure_path', ''), 'width': figure_item.get('width', 10), 'height': figure_item.get('height', 8), 'title': figure_item.get('title', f'Figure {i+1}'), 'caption': figure_item.get('caption', 'Figure caption') } beamer_data['figures'].append(figure) return beamer_data