From c9677a69f885f661de2c8e5a49ae7f0aa5540d79 Mon Sep 17 00:00:00 2001 From: kyeongmin Date: Mon, 6 Apr 2026 09:38:28 +0900 Subject: [PATCH] =?UTF-8?q?V'-2/V'-4=20=EC=88=98=EC=A0=95:=20=ED=91=9C=20?= =?UTF-8?q?=ED=96=89=20=EC=88=98=20=EA=B3=84=EC=82=B0=20+=20footer=20?= =?UTF-8?q?=EC=B5=9C=EC=86=8C=20=EB=86=92=EC=9D=B4=20+=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=EB=B9=84=EC=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - V'-2: 표 공간 계산에 V'-4(결론 위까지 채움) 높이 반영 → Kei에게 정확한 행 수 전달 (1행 → 5행) - V'-2: 이미지 높이를 실제 비율로 계산 (sub_layout 고정값 대신) → 200/2.73 = 73px (기존 172px → 공간 100px 확보) - footer 최소 높이: design tokens 기반 동적 계산 → weight 0.05일 때 26px → 53px 보장 - assemble_stage2: 이미지 높이도 실제 비율 반영 Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/assemble_stage2.py | 5 ++++- src/pipeline.py | 32 ++++++++++++++++++++++++++------ src/space_allocator.py | 6 ++++++ 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/scripts/assemble_stage2.py b/scripts/assemble_stage2.py index bbe0f95..e4c3492 100644 --- a/scripts/assemble_stage2.py +++ b/scripts/assemble_stage2.py @@ -386,7 +386,10 @@ def assemble(run_dir: str): break svg_w = int(svg_sc["width_px"]) if svg_sc else 200 - svg_h = int(svg_sc["height_px"]) if svg_sc else 265 + # 이미지 높이: 실제 비율로 계산 (sub_layout 고정값 대신) + slide_images = ctx.get("slide_images", []) + img_ratio = next((img.get("ratio", 1) for img in slide_images if img.get("b64")), 1) + svg_h = int(svg_w / img_ratio) if img_ratio > 0 else int(svg_sc["height_px"]) if svg_sc else 265 # 본심의 모든 topic 텍스트를 합침 all_core_text = "\n".join(get_text(topic_map.get(tid, {})) for tid in tids if topic_map.get(tid)) diff --git a/src/pipeline.py b/src/pipeline.py index c450a8b..c5c74a7 100644 --- a/src/pipeline.py +++ b/src/pipeline.py @@ -716,19 +716,39 @@ async def generate_slide( popup = next((p for p in popups if pr in p.get("title", "")), None) if not popup: continue - # 공란 계산: after(updated_containers) 기준 - after_ci = updated_containers.get(role) - after_h = after_ci.height_px if after_ci else float(text_sc["height_px"]) - # after 높이에서 제목+keymsg+padding 제외 → 텍스트 영역 + # 공란 계산: V'-4 적용 후 높이 (결론 바로 위까지 채움) + from src.fit_verifier import _load_design_tokens as _ldt_v2 + _v2_tokens = _ldt_v2() + _v2_slide_h = _v2_tokens.get("slide_height", 720) + _v2_pad = _v2_tokens["spacing_page"] + _v2_header_h = _v2_tokens.get("header_height", 66) + _v2_gap = _v2_tokens["spacing_block"] + _v2_gap_small = _v2_tokens["spacing_small"] + _v2_concl_ci = updated_containers.get("결론") or next((ci for r, ci in updated_containers.items() if ci.zone == "footer"), None) + _v2_concl_h = _v2_concl_ci.height_px if _v2_concl_ci else 53 + _v2_ft_top = _v2_slide_h - _v2_pad - _v2_concl_h - _v2_gap + _v2_column_bottom = _v2_ft_top - _v2_gap + _v2_bg_ci = next((ci for r, ci in updated_containers.items() if ci.zone == "body" and r != role), None) + _v2_bg_h = _v2_bg_ci.height_px if _v2_bg_ci else 0 + _v2_core_top = _v2_pad + _v2_header_h + _v2_gap + _v2_bg_h + _v2_gap_small + after_h = _v2_column_bottom - _v2_core_top # 결론 위까지의 실제 본심 높이 keymsg_h = 0 for sc in role_scs: if sc.get("name") == "keymsg": keymsg_h = float(sc.get("height_px", 0)) title_h = (fs + 1) * 1.5 + 4 - content_area_h = after_h - 16 - title_h - keymsg_h - 8 # pad*2 + gap + # 이미지 높이: 실제 비율로 계산 + svg_sc = next((sc for sc in role_scs if sc.get("name") == "svg"), None) + img_h = 0 + if svg_sc: + svg_w = float(svg_sc.get("width_px", 200)) + img_ratio = next((img.get("ratio", 1) for img in (context.slide_images or []) if img.get("b64")), 1) + img_h = svg_w / img_ratio if img_ratio > 0 else float(svg_sc.get("height_px", 0)) text_lines = len([l for l in st_text.split("\n") if l.strip() and not l.strip().startswith("[팝업:") and not l.strip().startswith("[이미지:") and not l.strip().lstrip("• ").startswith("출처:")]) text_h_used = text_lines * fs * 1.55 - available_h = content_area_h - text_h_used + # 표 공간 = 전체 - 제목 - max(이미지,텍스트) - keymsg - padding + upper_h = max(img_h, text_h_used) + available_h = after_h - 16 - title_h - upper_h - keymsg_h - 8 available_w = float(text_sc["width_px"]) if available_h < fs * 3: continue # 공간 부족하면 건너뜀 diff --git a/src/space_allocator.py b/src/space_allocator.py index 5fbdfe8..b086784 100644 --- a/src/space_allocator.py +++ b/src/space_allocator.py @@ -400,6 +400,12 @@ def calculate_container_specs( # 비중 비율로 높이 할당 ratio = weight / total_weight height_px = max(min_block_h, int(available * ratio)) + # footer는 최소 높이 보장 (font_size * line_height + padding) + if zone_name == "footer": + from src.fit_verifier import _load_design_tokens as _ldt_footer + _ft = _ldt_footer() + _footer_min = int(14 * _ft.get("line_height_ko", 1.7) + _ft["spacing_page"]) + height_px = max(_footer_min, height_px) # 블록 내부 제약 계산 — topic당 높이로 판단 topic_count = max(1, len(topic_ids))