V'-2/V'-4 수정: 표 행 수 계산 + footer 최소 높이 + 이미지 비율

- 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) <noreply@anthropic.com>
This commit is contained in:
2026-04-06 09:38:28 +09:00
parent 1f7579cf64
commit c9677a69f8
3 changed files with 36 additions and 7 deletions

View File

@@ -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))

View File

@@ -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 # 공간 부족하면 건너뜀

View File

@@ -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))