File size: 10,042 Bytes
7c08dc3 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 |
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
|