요구사항

Intuitive, Lightweight, Open Source

작업 흐름

Bitmap→HTML5 CanvasStep 1: drawing2pngPNG→VTracerStep 2: png2svgSVG→fonttoolsStep 3: svg2ttfTTF (설치용)→pyftsubsetStep 4: ttf2woff2WOFF2 (CDN 배포용)\text{Bitmap} \xrightarrow[\text{HTML5 Canvas}]{\text{Step 1: drawing2png}} \text{PNG} \xrightarrow[\text{VTracer}]{\text{Step 2: png2svg}} \text{SVG} \xrightarrow[\text{fonttools}]{\text{Step 3: svg2ttf}} \text{TTF (설치용)} \xrightarrow[\text{pyftsubset}]{\text{Step 4: ttf2woff2}} \text{WOFF2 (CDN 배포용)}

자료 구조

plain text
font-pipeline/
├── 01_canvas.html        # [Step 1: drawing2png] 브라우저 드로잉 웹앱
├── 02_png/               # Step 1 결과물 (0041.png 등 비트맵 덤프)
├── 03_svg/               # [Step 2: png2svg] VTracer로 추출된 벡터 패스
├── 04_build.py           # [Step 2, 3, 4] 파이프라인 일괄 실행 스크립트
├── 05_dist/              # [Step 3, 4 최종물] TTF (설치용) 및 WOFF2 (웹 CDN용)
└── README.md

단계별 상세 사양

Step 1: drawing2png (01_canvas.html)

  • 입력: 브라우저 캔버스 내 마우스 / 스타일러스 펜 드로잉
  • 기술 도구: HTML5 Canvas API (Vanilla JS)
  • 출력 위치: 02_png/[Unicode_HEX].png (예: 0041.png, 1000×1000, 32-bit RGBA)
  • 상세 규격 (1000 UPM 기준):
  • 동작 방식: Enter 입력 시 가이드라인 레이어를 제외한 블랙(#000000) 획 레이어만 투명 배경 PNG로 자동 덤프 후 다음 글자로 전환.

Step 2: png2svg (04_build.py 내부 호출)

  • 입력: 02_png/*.png
  • 기술 도구: vtracer (Rust CLI)
  • 출력 위치: 03_svg/*.svg
  • 실행 명령:
  • 동작 방식: 픽셀 손떨림 노이즈 필터링 후 단일 <path> 형태의 벡터 패스 추출.

Step 3: svg2ttf (04_build.py 내부 처리)

  • 입력: 03_svg/*.svg
  • 기술 도구: Python fonttools (fontTools.fontBuilder + fontTools.pens.ttGlyphPen)
  • 출력 위치: 05_dist/MyFont-Regular.ttf
  • 동작 방식:

Step 4: ttf2woff2 (04_build.py 내부 호출)

  • 입력: 05_dist/MyFont-Regular.ttf
  • 기술 도구: pyftsubset (fonttools 내장) + brotli
  • 출력 위치: 05_dist/MyFont.woff2
  • 실행 명령:
  • 동작 방식: 불필요한 메타데이터 제거 및 Brotli 압축을 적용하여 20~30KB 내외의 초경량 웹폰트 생성.

Optional: CDN 호스팅 및 엣지 배포

  • 스토리지: Cloudflare R2 (assets/fonts/MyFont.woff2)
  • 엔드포인트: [https://cdn.mydomain.com/fonts/](https://cdn.mydomain.com/fonts/)
  • Cloudflare 설정:

Step 2, 3, 4 자동화 스크립트 (04_build.py)

plain text
#!/usr/bin/env python3
import os
import glob
import subprocess
from fontTools.fontBuilder import FontBuilder
from fontTools.pens.ttGlyphPen import TTGlyphPen
from fontTools.svgLib.path import SVGPath

def main():
    os.makedirs("./03_svg", exist_ok=True)
    os.makedirs("./05_dist", exist_ok=True)

    # Step 2: png2svg (vtracer)
    print(">> [Step 2: png2svg] VTracer 실행 중...")
    subprocess.run([
        "vtracer",
        "--input", "./02_png",
        "--output", "./03_svg",
        "--colormode", "bw",
        "--filter_speckle", "4"
    ], check=True)

    # Step 3: svg2ttf (fonttools)
    print(">> [Step 3: svg2ttf] TTF 마스터 컴파일 중...")
    fb = FontBuilder(unitsPerEm=1000, isTTF=True)
    svg_files = sorted(glob.glob("./03_svg/*.svg"))
    glyph_names = [".notdef"] + [f"uni{os.path.splitext(os.path.basename(f))[0].upper()}" for f in svg_files]
    fb.setupGlyphOrder(glyph_names)

    # (좌표계 변환: Canvas Y하향 -> Font Y상향 및 Glyph Pen 바인딩)
    # ...
    fb.save("./05_dist/MyFont-Regular.ttf")

    # Step 4: ttf2woff2 (pyftsubset)
    print(">> [Step 4: ttf2woff2] WOFF2 압축 변환 중...")
    subprocess.run([
        "pyftsubset", "./05_dist/MyFont-Regular.ttf",
        "--unicodes=U+0020-007E",
        "--flavor=woff2",
        "--output-file=./05_dist/MyFont.woff2"
    ], check=True)

    print(">> 전체 빌드 완료: 05_dist/MyFont-Regular.ttf, 05_dist/MyFont.woff2")

if __name__ == "__main__":
    main()