"""Figma 프레임을 HTML/CSS로 변환.
기존 블록 템플릿(templates/blocks/)과 동일한 방식으로
Figma API 데이터에서 정확한 HTML을 생성한다.
Usage:
python scripts/figma_to_html.py data/runs/figma_beps_full.json templates/blocks/BEPs/
"""
from __future__ import annotations
import json
import math
import sys
from pathlib import Path
def get_fill_css(fills: list[dict]) -> tuple[str, str]:
"""fills 배열에서 CSS background와 color를 추출.
Returns: (background_css, color_css)
"""
bg = ""
color = ""
for f in fills:
if not f.get("visible", True):
continue
ftype = f.get("type", "")
if ftype == "SOLID":
c = f["color"]
r, g, b = int(c["r"] * 255), int(c["g"] * 255), int(c["b"] * 255)
a = f.get("opacity", c.get("a", 1))
if a < 0.99:
val = f"rgba({r},{g},{b},{a:.2f})"
else:
val = f"#{r:02x}{g:02x}{b:02x}"
bg = val
color = val
elif "GRADIENT" in ftype:
stops = f.get("gradientStops", [])
handles = f.get("gradientHandlePositions", [])
if len(stops) < 2:
continue
# 각도 계산
if len(handles) >= 2:
dx = handles[1]["x"] - handles[0]["x"]
dy = handles[1]["y"] - handles[0]["y"]
angle = math.degrees(math.atan2(dy, dx)) + 90
else:
angle = 180
stop_str = ",".join(
f"#{int(s['color']['r']*255):02x}{int(s['color']['g']*255):02x}{int(s['color']['b']*255):02x} {s['position']*100:.0f}%"
for s in stops
)
bg = f"linear-gradient({angle:.0f}deg,{stop_str})"
color = bg
elif ftype == "IMAGE":
bg = "__IMAGE__"
return bg, color
def node_to_html(node: dict, ox: float, oy: float, scale: float) -> str:
"""단일 노드를 HTML div로 변환."""
ntype = node.get("type", "")
name = node.get("name", "")
bb = node.get("absoluteBoundingBox")
if not bb or not bb.get("width"):
return ""
x = (bb["x"] - ox) * scale
y = (bb["y"] - oy) * scale
w = bb["width"] * scale
h = bb["height"] * scale
fills = node.get("fills", [])
visible_fills = [f for f in fills if f.get("visible", True)]
if ntype == "TEXT":
chars = node.get("characters", "")
if not chars:
return ""
style = node.get("style", {})
fs = style.get("fontSize", 12) * scale
fw = style.get("fontWeight", 400)
align_h = style.get("textAlignHorizontal", "LEFT").lower()
align_v = style.get("textAlignVertical", "TOP")
lh_px = style.get("lineHeightPx", 0)
lh = lh_px * scale if lh_px else fs * 1.5
ls = style.get("letterSpacing", 0) * scale
# 텍스트 fill 처리
has_gradient = any(
"GRADIENT" in f.get("type", "") for f in visible_fills
)
if has_gradient:
bg, _ = get_fill_css(
[f for f in visible_fills if "GRADIENT" in f.get("type", "")]
)
text_style = (
f"background:{bg};"
f"-webkit-background-clip:text;"
f"-webkit-text-fill-color:transparent;"
)
else:
_, c = get_fill_css(visible_fills)
text_style = f"color:{c or '#000'};"
lh_css = f"line-height:{lh:.1f}px;"
ls_css = f"letter-spacing:{ls:.1f}px;" if ls > 0.1 else ""
align_css = f"text-align:{align_h};" if align_h != "left" else ""
valign_css = (
"display:flex;align-items:center;" if align_v == "CENTER" else ""
)
text_html = chars.replace("\n", "
")
return (
f'