콘텐츠로 이동
quickthumb
전체 문서

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

"Layouts that build themselves."라는 문구와 오른쪽 파란 원이 있는 출시 카드

텍스트 좌표를 일일이 지정하지 않고 만드는 출시 카드입니다. 제목은 열 그룹으로, 기능 목록은 행 그룹으로 자동 배치합니다. 색상과 글꼴 크기는 모두 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}")