"""HTML Export and Browser PDF for Opus Orchestrator. Uses browser for PDF generation - no LaTeX required! """ import json from pathlib import Path from typing import Optional from dataclasses import dataclass from datetime import datetime @dataclass class HTMLOptions: """Options for HTML export.""" template: str = "memoir" # memoir, academic, minimal theme: str = "light" # light, dark, sepia font: str = "serif" # serif, sans include_toc: bool = True author: str = "" dedication: str = "" date: str = "" def __post_init__(self): if not self.date: self.date = datetime.now().strftime("%Y") # HTML Templates TEMPLATES = { "memoir": { "name": "Memoir", "description": "Novel, memoir, personal narrative", "fonts": ["Merriweather", "Lora"], "background": "#fdfbf7", "text": "#2c2c2c", }, "academic": { "name": "Academic", "description": "Technical, textbook, educational", "fonts": ["Roboto", "Open Sans"], "background": "#ffffff", "text": "#1a1a1a", }, "minimal": { "name": "Minimal", "description": "Clean, simple design", "fonts": ["Inter", "System UI"], "background": "#ffffff", "text": "#000000", }, } class HTMLExporter: """Export manuscript to HTML and PDF via browser.""" def __init__(self, template_dir: Optional[str] = None): if template_dir: self.template_dir = Path(template_dir) else: self.template_dir = Path(__file__).parent / "templates" / "html" def export( self, manuscript, book_title: str, options: Optional[HTMLOptions] = None, ) -> str: """Export manuscript to HTML. Args: manuscript: The Manuscript to export book_title: Title for the book options: HTMLOptions Returns: HTML string """ opts = options or HTMLOptions() template_info = TEMPLATES.get(opts.template, TEMPLATES["memoir"]) # Build HTML html_parts = [ self._build_head(book_title, template_info, opts), self._build_body(manuscript, book_title, opts), ] return "\n".join(html_parts) def export_to_file( self, manuscript, book_title: str, output_path: str, options: Optional[HTMLOptions] = None, ) -> dict: """Export to HTML file.""" html = self.export(manuscript, book_title, options) output_file = Path(output_path) output_file.parent.mkdir(parents=True, exist_ok=True) output_file.write_text(html) return { "output_file": str(output_file), "template": options.template if options else "memoir", "size": len(html), } def _build_head( self, book_title: str, template_info: dict, options: HTMLOptions, ) -> str: """Build HTML head with styles.""" font_import = self._get_font_import(template_info["fonts"]) return f"""
\1', text, flags=re.DOTALL)
text = re.sub(r'`(.+?)`', r'\1', text)
# Lists
text = re.sub(r'^- (.+)$', r'{para}
' paragraphs.append(para) text = '\n'.join(paragraphs) return text def _get_font_import(self, fonts: list) -> str: """Get Google Fonts import URL.""" font_query = "|".join(fonts).replace(" ", "+") return f'' def export_to_html( manuscript, book_title: str, output_path: str = "", template: str = "memoir", **options, ) -> dict: """Convenience function to export to HTML. Args: manuscript: The Manuscript book_title: Book title output_path: Output .html path (optional) template: Template name **options: Additional HTMLOptions Returns: Export result with HTML string or file path """ opts = HTMLOptions(template=template, **options) exporter = HTMLExporter() if output_path: return exporter.export_to_file(manuscript, book_title, output_path, opts) else: html = exporter.export(manuscript, book_title, opts) return {"html": html, "template": template} def export_to_pdf( manuscript, book_title: str, output_path: str, template: str = "memoir", **options, ) -> dict: """Export to PDF via browser. Args: manuscript: The Manuscript book_title: Book title output_path: Output .pdf path template: Template name **options: Additional HTMLOptions Returns: Export result """ # First export to HTML opts = HTMLOptions(template=template, **options) exporter = HTMLExporter() html = exporter.export(manuscript, book_title, opts) # Save HTML temporarily html_path = output_path.replace(".pdf", ".html") Path(html_path).write_text(html) return { "html_file": html_path, "pdf_file": output_path, "template": template, "html": html, }