출시 알림(자동 레이아웃)

텍스트 좌표를 일일이 지정하지 않고 만드는 출시 카드입니다. 제목은 열 그룹으로, 기능 목록은 행 그룹으로 자동 배치합니다. 색상과 글꼴 크기는 모두 theme에서 가져오므로 문구나 브랜드를 바꿔도 크기와 위치를 다시 측정할 필요가 없습니다.
예제를 실행하려면 uv를 설치하고 저장소를 복제하세요.
git clone https://github.com/sjquant/quickthumb
cd quickthumb
uv run --extra svg python examples/launch_announcement.py
스크립트는 examples/launch_announcement.png를 기록하며 같은 이름의 기존 파일을 덮어씁니다.
반짝임 장식은 SVG 파일이므로 svg 추가 기능이 필요합니다.
구성 방식
- 테마 토큰.
theme아래에 색상과 크기를 한 번 정의하고"$theme.colors.blue","$theme.sizes.title"로 사용합니다. 값 전체가 토큰이면 JSON 자료형을 유지하므로"$theme.sizes.title"은 문자열이 아닌 숫자116이 됩니다. - 텍스트용 열 그룹. 출시 라벨, 제목, 부제는
"direction": "column","gap": 22인group하나의 자식입니다. 그룹만["7%", "44%"]에 한 번 배치하며 자식에는position이 없습니다. 부제에 줄을 추가하면 열이 그에 맞춰 재배치됩니다. - 기능용 행 그룹. "01 GROUPS"부터 "04 DIAGNOSTICS"는
"direction": "row"인 두 번째 그룹입니다. 이름을 바꿔도 간격이 균일하게 유지됩니다. - 기본 도형으로 만든 장식. 원, 막대 네 개, 별은 일반
ellipse,rectangle,star도형입니다. 반짝임은assets/images/spark.svg에Filter(saturation=0, brightness=3)을 적용해 흰색으로 만듭니다. - 렌더링 전 검사. 스크립트는 렌더링 전에
canvas.diagnose()를 호출하고 진단 항목을 출력합니다. 터미널에서도uv run --extra cli quickthumb lint examples/launch_announcement.json으로 같은 검사를 실행할 수 있습니다.
명세
{
"kind": "canvas",
"width": 1280,
"height": 720,
"theme": {
"colors": {
"paper": "#F5F5F7",
"ink": "#1D1D1F",
"blue": "#0066CC",
"muted": "#515154",
"rule": "#1D1D1F26"
},
"sizes": {
"title": 116,
"subtitle": 28,
"meta": 18,
"chip": 18
}
},
"layers": [
{
"type": "background",
"color": "$theme.colors.paper",
"effects": [
{ "type": "grain", "intensity": 0.006, "monochrome": true, "seed": 7 }
]
},
{
"type": "shape",
"shape": "ellipse",
"position": ["94%", "50%"],
"width": 460,
"height": 460,
"color": "$theme.colors.blue",
"align": "center"
},
{
"type": "shape",
"shape": "rectangle",
"position": ["82%", "35%"],
"width": 170,
"height": 12,
"color": "$theme.colors.paper"
},
{
"type": "shape",
"shape": "rectangle",
"position": ["82%", "43%"],
"width": 112,
"height": 6,
"color": "$theme.colors.paper"
},
{
"type": "shape",
"shape": "rectangle",
"position": ["82%", "51%"],
"width": 214,
"height": 6,
"color": "$theme.colors.paper"
},
{
"type": "shape",
"shape": "rectangle",
"position": ["82%", "59%"],
"width": 142,
"height": 6,
"color": "#FFFFFFB3"
},
{
"type": "shape",
"shape": "rectangle",
"position": ["80.5%", "32%"],
"width": 1,
"height": 252,
"color": "#FFFFFF80"
},
{
"type": "svg",
"path": "assets/images/spark.svg",
"position": ["80.6%", "35.5%"],
"width": 16,
"align": "center",
"effects": [{ "type": "filter", "saturation": 0.0, "brightness": 3.0 }]
},
{
"type": "shape",
"shape": "star",
"position": ["80.6%", "59.5%"],
"width": 16,
"height": 16,
"color": "$theme.colors.paper",
"align": "center",
"star_points": 5,
"inner_radius": 0.42
},
{
"type": "group",
"direction": "column",
"gap": 22,
"position": ["7%", "44%"],
"align": ["left", "middle"],
"children": [
{
"type": "text",
"content": "QUICKTHUMB 0.5 / RELEASE",
"size": "$theme.sizes.meta",
"color": "$theme.colors.muted",
"weight": 700,
"letter_spacing": 3,
"align": "left"
},
{
"type": "text",
"content": [
{ "text": "Layouts that\n", "color": "$theme.colors.ink", "weight": 700 },
{ "text": "build themselves.", "color": "$theme.colors.blue", "weight": 700 }
],
"size": "$theme.sizes.title",
"color": "$theme.colors.ink",
"weight": 700,
"line_height": 1.02,
"letter_spacing": -3,
"align": "left"
},
{
"type": "text",
"content": "Auto-layout groups, theme tokens, and built-in lint —\nno pixel coordinates required.",
"size": "$theme.sizes.subtitle",
"color": "$theme.colors.muted",
"line_height": 1.35,
"align": "left"
}
]
},
{
"type": "shape",
"shape": "rectangle",
"position": ["7%", "83%"],
"width": 860,
"height": 1,
"color": "$theme.colors.rule"
},
{
"type": "group",
"direction": "row",
"gap": 54,
"position": ["7%", "88%"],
"align": ["left", "middle"],
"children": [
{
"type": "text",
"content": "01 GROUPS",
"size": "$theme.sizes.chip",
"color": "$theme.colors.ink",
"weight": 700,
"letter_spacing": 1
},
{
"type": "text",
"content": "02 TOKENS",
"size": "$theme.sizes.chip",
"color": "$theme.colors.ink",
"weight": 700,
"letter_spacing": 1
},
{
"type": "text",
"content": "03 SVG",
"size": "$theme.sizes.chip",
"color": "$theme.colors.ink",
"weight": 700,
"letter_spacing": 1
},
{
"type": "text",
"content": "04 DIAGNOSTICS",
"size": "$theme.sizes.chip",
"color": "$theme.colors.ink",
"weight": 700,
"letter_spacing": 1
}
]
}
]
}
스크립트
"""
JSON-first launch announcement card.
Exercises the quickthumb 0.5 feature set in a single themed spec:
- A top-level `theme` block with `$theme.*` token references across layers
- Auto-layout `group` layers for the headline column and feature row — no
hand-placed coordinates inside either text group
- `star` shape primitives and decorative `svg` layers
- Film grain on the base background
- `canvas.diagnose()` before rendering, mirroring `quickthumb lint`
"""
import os
from quickthumb import Canvas
FILE_DIR = os.path.dirname(__file__)
REPO_DIR = os.path.abspath(os.path.join(FILE_DIR, ".."))
SPEC_PATH = os.path.join(FILE_DIR, "launch_announcement.json")
OUTPUT_PATH = os.path.join(FILE_DIR, "launch_announcement.png")
os.environ["QUICKTHUMB_FONT_DIR"] = os.path.join(REPO_DIR, "assets", "fonts")
os.environ["QUICKTHUMB_DEFAULT_FONT"] = "Roboto"
with open(SPEC_PATH, encoding="utf-8") as f:
json_spec = f.read()
previous_cwd = os.getcwd()
try:
# The checked-in JSON spec uses repo-relative asset paths to stay easy to read.
os.chdir(REPO_DIR)
canvas = Canvas.from_json(json_spec)
findings = canvas.diagnose().findings
for finding in findings:
print(
f"[{finding.severity}] layer {finding.layer_index}: {finding.code} — {finding.message}"
)
if not findings:
print("✓ diagnose(): no layout or legibility issues")
canvas.render(OUTPUT_PATH)
finally:
os.chdir(previous_cwd)
print(f"✓ Launch announcement card created: {OUTPUT_PATH}")
print(f" Spec source: {SPEC_PATH}")