"""DA-11: 슬라이드 조합 렌더러. 블록 배치 명세(JSON)를 받아 Jinja2로 HTML을 생성한다. 다중 페이지 지원: pages 배열의 각 페이지를 .slide div로 렌더링. """ from __future__ import annotations import logging from pathlib import Path from typing import Any from jinja2 import Environment, FileSystemLoader logger = logging.getLogger(__name__) TEMPLATES_DIR = Path(__file__).parent.parent / "templates" STATIC_DIR = Path(__file__).parent.parent / "static" def create_jinja_env() -> Environment: """Jinja2 환경 생성. templates/ 폴더를 로더로 사용.""" return Environment( loader=FileSystemLoader(str(TEMPLATES_DIR)), autoescape=False, ) def render_multi_page(layout_concept: dict[str, Any]) -> str: """다중 페이지 레이아웃 컨셉으로 완성 HTML을 생성한다. Args: layout_concept: 디자인 팀장 + 텍스트 편집자가 완성한 구조: { "title": "슬라이드 제목", "pages": [ { "grid_areas": "...", "grid_columns": "...", "grid_rows": "...", "blocks": [{"area": "...", "type": "...", "data": {...}}] }, ... ] } Returns: 완성된 HTML 문자열 (다중 페이지 시 .slide div 여러 개). """ env = create_jinja_env() title = layout_concept.get("title", "슬라이드") pages = layout_concept.get("pages", []) if not pages: logger.warning("페이지가 없습니다. 빈 HTML 반환.") return "

페이지가 없습니다.

" # 각 페이지의 블록을 개별 렌더링 pages_rendered = [] for page_idx, page in enumerate(pages): blocks_rendered = [] for block in page.get("blocks", []): block_type = block.get("type", "") block_data = block.get("data", {}) template_path = f"blocks/{block_type}.html" try: block_template = env.get_template(template_path) rendered_html = block_template.render(**block_data) except Exception as e: logger.warning(f"블록 렌더링 실패 ({block_type}): {e}") rendered_html = f'
블록 렌더링 실패: {block_type}
' blocks_rendered.append({ "area": block.get("area", "main"), "html": rendered_html, }) pages_rendered.append({ "grid_areas": page.get("grid_areas", "'main'"), "grid_columns": page.get("grid_columns", "1fr"), "grid_rows": page.get("grid_rows", "auto"), "blocks": blocks_rendered, "page_number": page_idx + 1, }) # base 템플릿 렌더링 base_template = env.get_template("slide-base.html") html = base_template.render( slide_title=title, pages=pages_rendered, total_pages=len(pages_rendered), ) # CSS를 인라인으로 삽입 tokens_css = (STATIC_DIR / "tokens.css").read_text(encoding="utf-8") base_css = (STATIC_DIR / "base.css").read_text(encoding="utf-8") base_css = base_css.replace("@import url('./tokens.css');", "") inline_css = f"" html = html.replace( '', inline_css, ) logger.info(f"슬라이드 렌더링 완료: {title}, {len(pages_rendered)}페이지") return html # 하위 호환: 기존 render_slide도 유지 def render_slide(layout: dict[str, Any]) -> str: """기존 단일 페이지 렌더링 (하위 호환). pages 구조가 있으면 render_multi_page로 위임. 없으면 기존 방식으로 단일 페이지 렌더링. """ if "pages" in layout: return render_multi_page(layout) # 기존 단일 페이지 로직 env = create_jinja_env() blocks_rendered = [] for block in layout.get("blocks", []): block_type = block["type"] block_data = block.get("data", {}) template_path = f"blocks/{block_type}.html" try: block_template = env.get_template(template_path) rendered_html = block_template.render(**block_data) except Exception as e: logger.warning(f"블록 렌더링 실패 ({block_type}): {e}") rendered_html = f'
블록 렌더링 실패: {block_type}
' blocks_rendered.append({ "area": block["area"], "html": rendered_html, }) base_template = env.get_template("slide-base.html") html = base_template.render( slide_title=layout.get("title", ""), pages=[{ "grid_areas": layout.get("grid_areas", "'header' 'main' 'footer'"), "grid_columns": layout.get("grid_columns", "1fr"), "grid_rows": layout.get("grid_rows", "auto 1fr auto"), "blocks": blocks_rendered, "page_number": 1, }], total_pages=1, ) tokens_css = (STATIC_DIR / "tokens.css").read_text(encoding="utf-8") base_css = (STATIC_DIR / "base.css").read_text(encoding="utf-8") base_css = base_css.replace("@import url('./tokens.css');", "") inline_css = f"" html = html.replace( '', inline_css, ) logger.info(f"슬라이드 렌더링 완료: {layout.get('title', 'untitled')}") return html def render_standalone_block(block_type: str, data: dict[str, Any]) -> str: """단일 블록을 독립 HTML로 렌더링 (테스트/미리보기용).""" env = create_jinja_env() template = env.get_template(f"blocks/{block_type}.html") return template.render(**data)