"""Step 1~4를 같은 슬라이드 레이아웃 위에 레이어로 쌓아 PNG 생성."""
import json, urllib.parse, time, sys
from pathlib import Path
sys.path.insert(0, ".")
run_dir = Path("data/runs/20260402_091318")
ctx_1a = json.loads((run_dir / "stage_1a_context.json").read_text(encoding="utf-8"))
ctx_1b = json.loads((run_dir / "stage_1b_context.json").read_text(encoding="utf-8"))
ctx_15a = json.loads((run_dir / "stage_1_5a_context.json").read_text(encoding="utf-8"))
ctx_17 = json.loads((run_dir / "stage_1_7_context.json").read_text(encoding="utf-8"))
ctx_15b = json.loads((run_dir / "stage_1_5b_context.json").read_text(encoding="utf-8"))
topics = ctx_1b.get("topics", [])
containers = ctx_15a.get("containers", {})
fh = ctx_15a.get("font_hierarchy", {})
ratio = ctx_15a.get("container_ratio", [72, 28])
refs = ctx_17.get("references", {})
ps = ctx_1a.get("page_structure", {})
if "roles" in ps:
ps = ps["roles"]
containers_b = ctx_15b.get("containers", {})
topic_map = {t["id"]: t for t in topics}
slide_w, slide_h = 1280, 720
pad = 40
header_h = 66
gap = 20
footer_h = containers.get("결론", {}).get("height_px", 60)
inner_w = slide_w - pad * 2
body_pct = ratio[0] if ratio else 72
sidebar_pct = ratio[1] if len(ratio) > 1 else 28
body_w = int(inner_w * body_pct / 100)
sidebar_w = inner_w - body_w - gap
body_zone_h = slide_h - pad * 2 - header_h - footer_h - gap * 2
bg_h = containers.get("배경", {}).get("height_px", 117)
core_h = body_zone_h - bg_h - 12
L = {
"배경": {"x": pad, "y": pad+header_h+gap, "w": body_w, "h": bg_h},
"본심": {"x": pad, "y": pad+header_h+gap+bg_h+12, "w": body_w, "h": core_h},
"첨부": {"x": pad+body_w+gap, "y": pad+header_h+gap, "w": sidebar_w, "h": body_zone_h},
"결론": {"x": pad, "y": slide_h-pad-footer_h, "w": inner_w, "h": footer_h},
}
C = {"배경": "#dc2626", "본심": "#2563eb", "첨부": "#16a34a", "결론": "#7c3aed"}
def area(role, inner):
p = L[role]; c = C[role]
return (f'
{inner}
')
def header(title):
return (f'건설산업 DX의 올바른 이해
')
def slide(step_title, areas_html):
return (f''
f''
f'{step_title}
'
f''
f'{header(step_title)}{areas_html}
')
# Step 1
a1 = ""
for role in L:
c = C[role]; p = L[role]
fk = {"배경":"bg","본심":"core","첨부":"sidebar","결론":"key_msg"}.get(role,"core")
fv = fh.get(fk, 12)
a1 += area(role,
f''
f'{role}
'
f'{p["w"]}x{p["h"]}px / font:{fv}px
')
# Step 2
a2 = ""
for role in L:
c = C[role]; info = ps.get(role, {}); tids = info.get("topic_ids", [])
w = info.get("weight", 0)
inner = f'{role} (w:{w})
'
for tid in tids:
t = topic_map.get(tid, {})
inner += (f''
f'T{tid}: {t.get("title","")[:25]}
'
f''
f'{t.get("purpose","")} / {t.get("relation_type","")}
')
a2 += area(role, inner)
# Step 3
a3 = ""
for role in L:
c = C[role]; ref = refs.get(role, {}); p = L[role]
bid = ref.get("block_id", "?"); vtype = ref.get("visual_type", "?")
info = ps.get(role, {}); tids = info.get("topic_ids", [])
tnames = ", ".join(f"T{tid}" for tid in tids)
mt = max(0, p["h"]//2-25)
a3 += area(role,
f''
f'
{role} ({tnames})
'
f'
📦
'
f'
{bid}
'
f'
type: {vtype}
')
# Step 4
a4 = ""
for role in L:
c = C[role]; ref = refs.get(role, {}); p = L[role]
bid = ref.get("block_id", "?")
cb = containers_b.get(role, {}); db = cb.get("design_budget") or {}
text_h = db.get("text_height_px", 0)
avail_h = db.get("available_height_px", 0)
fits = db.get("fits", False)
total = max(text_h + avail_h, 1)
tp = int(text_h / total * 100)
bw = p["w"] - 20
fc = "green" if fits else "red"
a4 += area(role,
f''
f'
{role}: {bid}
'
f'
'
f'
텍스트{text_h}px
'
f'
여유{avail_h}px
'
f'
'
f'
fits:{fits} / {p["w"]}x{p["h"]}px
')
htmls = {
"viz_1_containers": slide(f"Step 1: 컨테이너 포션과 위치 (비율 {body_pct}:{sidebar_pct})", a1),
"viz_2_content": slide("Step 2: 각 영역별 내용 배치", a2),
"viz_3_blocks": slide("Step 3: 블록 선택 결과", a3),
"viz_4_budget": slide("Step 4: 블록별 디자인 예산", a4),
}
for name, html in htmls.items():
(run_dir / f"{name}.html").write_text(html, encoding="utf-8")
# PNG
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
opts = Options()
opts.add_argument("--headless")
opts.add_argument("--no-sandbox")
opts.add_argument("--force-device-scale-factor=2")
driver = webdriver.Chrome(options=opts)
driver.set_window_size(1380, 820)
for name in htmls:
html = (run_dir / f"{name}.html").read_text(encoding="utf-8")
encoded = urllib.parse.quote(html, safe="")
driver.get(f"data:text/html;charset=utf-8,{encoded}")
time.sleep(2)
driver.save_screenshot(str(run_dir / f"{name}.png"))
print(f"{name}.png")
driver.quit()
print("완료")