서브셋 파일 크기를 자수로 미리 계산하는 법

Pretendard 전체 폰트를 서브셋으로 만들면 814KB에서 273KB로 줄어듭니다. 이 정도면 충분히 쓸 만하죠. 그런데 문제는 이 숫자를 서브셋을 만들고 난 뒤에야 알게 된다는 점입니다.
"내 프로젝트에서 1,200자쯤 쓰는데, 파일이 얼마나 나올까?" 서브셋을 직접 만들어보기 전에 이 질문에 답할 수 있다면 결정이 훨씬 쉬워집니다. 자수(글리프 수)와 WOFF2 파일 크기 사이에는 분명한 비례 관계가 있고, 그 관계를 역으로 풀면 됩니다.
아래는 내 숫자를 직접 채워가며 완성하는 3단계 워크시트입니다. 중간에 계산기나 코드 한 줄이면 충분합니다.
워크시트 전에 잡아두는 실측 기준점
계산식에 넣을 기준값이 먼저 있어야 역산이 됩니다. 공개된 실측 데이터 두 가지입니다.
첫 번째는 Pretendard Regular입니다. 전체 11,172자 WOFF2가 814KB이고, KS X 1001 기준 2,350자 서브셋 WOFF2는 273KB입니다. 글리프가 약 21%로 줄었는데 파일 크기는 약 34%까지 내려옵니다. 단순 비례보다 더 잘 줄어드는 이유가 있습니다. WOFF2 내부에서 쓰는 brotli 계열 압축이 파일이 작을수록 효율이 높아지기 때문입니다.
두 번째는 Source Han Sans 서브셋 실험입니다. 15MB에 달하던 원본이 2,350자 수준 서브셋에 WOFF2 변환을 거치니 379KB까지 줄었습니다. 폰트 종류가 달라도 한글 2,350자 서브셋 WOFF2는 200~400KB 구간에 수렴하는 경향을 두 데이터 모두 가리킵니다.
두 데이터 모두 그 구간을 가리킨다는 게 핵심이고, 아래 식에서 쓸 기준값은 이 Pretendard 실측치입니다.
1단계. 내 프로젝트에서 실제로 쓰는 글자 수를 확정한다
워크시트의 첫 칸에는 내 서비스의 고유 글리프 수(N)를 채웁니다.
가장 정확한 방법은 사이트 전체 텍스트를 수집해 고유 글자 수를 직접 세는 것입니다.
text = open("all_text.txt", encoding="utf-8").read()
hangul = {ch for ch in text if '가' <= ch <= '힣'}
print(f"한글 고유 글자 수: {len(hangul)}")
도구를 쓰기 어려운 상황이라면 서비스 유형별 경험치로 시작점을 잡을 수 있습니다.
- 소개 페이지·일반 블로그 — 1,000~1,500자 안팎
- 뉴스·쇼핑몰처럼 텍스트가 많은 서비스 — 2,000~3,000자 구간
- 타이핑 입력·문서 편집기 — 전체 11,172자가 필요할 수 있음
막상 직접 세보면 "생각보다 적다"는 느낌이 드는 경우가 많습니다. 블로그나 소개 페이지는 1,000자를 채 넘지 않는 경우도 흔하거든요.
담을 글자 범위도 이 단계에서 함께 정합니다. KS X 1001 기준 2,350자 목록은 범용 서비스에서 가장 흔히 쓰는 출발점이고, 직접 추출한 고유 글자 목록을 쓰면 사이트 텍스트가 1,200자라면 파일도 그에 맞게 작아집니다. 후자라면 향후 추가될 텍스트를 고려해 100~200자 버퍼를 두는 것이 안전합니다.
2단계. 자수를 넣어 예상 파일 크기를 직접 계산한다
1단계에서 확정한 N을 아래 식에 바로 넣습니다.
예상 WOFF2 크기(KB) ≈ 273 × (N / 2350)^0.75
지수를 0.75로 잡은 이유는 brotli 압축 효율 때문입니다. 글리프가 줄어들수록 압축률이 더 올라가는 경향이 있어, 선형(지수 1.0)보다 낮게 잡아야 실측에 가까워집니다.
계산기로 따라해보면 이렇습니다.
- N = 1,000 → 273 × (1000/2350)^0.75 → 273 × 0.527 ≈ 약 144KB
- N = 2,350 → 273 × (2350/2350)^0.75 → 273 × 1.000 = 273KB (실측 기준점과 일치)
- N = 5,000 → 273 × (5000/2350)^0.75 → 273 × 1.762 ≈ 약 481KB
두 번째 줄이 실측 앵커와 정확히 맞아떨어지니, 식이 제대로 세팅됐다는 확인이 됩니다. 본인 N 값을 넣고 나온 수치가 허용 범위인지 판단하면 됩니다. 폰트 종류에 따라 ±30% 이상 오차가 날 수 있으니 개략적인 상한선 추정으로 씁니다.
3단계. 파일 크기를 로딩 시간으로 환산해 허용 여부를 판단한다
2단계에서 나온 예상 크기(KB)를 아래 식에 넣습니다.
로딩 시간(ms) = 파일 크기(KB) × 8 / 네트워크 속도(Mbps)
2,350자 서브셋(273KB)을 기준으로 직접 계산하면 LTE(30Mbps 가정)에서 273 × 8 / 30 ≈ 약 73ms, 3G(4Mbps 가정)에서 273 × 8 / 4 ≈ 약 546ms입니다.
본인 예상 크기가 나왔으면 같은 식에 그대로 넣으면 됩니다. 기준점은 하나입니다. 3G에서 500ms를 넘기면 글자가 바뀌는 게 눈에 보입니다. 브라우저는 폰트를 받는 동안 텍스트를 숨기거나(FOIT) 기본 폰트로 먼저 보여주는데(FOUT), 이 전환이 눈에 보이면 사용자 경험이 나빠집니다.
이 계산이 와닿는 순간은 실제로 폰트를 올리고 나서 모바일 3G로 접속해볼 때거든요. "왜 글자가 버벅이지?" 싶은 그 순간에 수치가 없으면 원인도 파악이 안 됩니다. 자수를 어디까지 줄이는 게 의미 있는지, 미리 숫자로 판단해두는 이유가 거기 있습니다.
워크시트가 완성됐으면 실제 서브셋을 만드는 법
숫자가 나왔으면 이제 실제로 만들 차례입니다. 어떤 글자 범위를 쓰든 pyftsubset 명령 구조는 같습니다.
pyftsubset NotoSansKR-Regular.ttf \
--text-file=my_chars.txt \
--output-file=NotoSansKR-subset.woff2 \
--flavor=woff2
서브셋을 만들고 CSS에 적용할 때는 폰트비에서 해당 폰트를 검색하면 @font-face 웹폰트 코드를 바로 복사할 수 있습니다. 직접 만든 서브셋이라면 src URL만 교체해서 쓰면 됩니다.
계산 결과가 마음에 안 들면 확인할 것
자수를 이미 최소화했는데도 파일이 크다면 웨이트 수를 줄이는 것이 다음 선택지입니다. Regular 하나와 Regular + Bold 두 개는 파일 크기가 두 배 가까이 차이 납니다. 실제로 쓰는 웨이트를 따져보면 대부분 Regular + 하나 정도로 충분한 경우가 많습니다.
가변 폰트(variable font)는 WOFF2 파일 하나로 모든 웨이트를 커버하지만, 전체 자수가 그대로 담겨 있으면 파일이 클 수 있습니다. Pretendard Variable을 fonttools의 varLib.instancer로 웨이트 축을 좁히고 pyftsubset으로 글리프를 정리하면 80% 전후 절감이 가능하다는 실험 보고도 있습니다.
예측값과 실제 파일 크기 차이가 크다면 글리프 윤곽선 복잡도를 의심합니다. 한자나 획이 많은 글리프가 포함되면 같은 자수라도 파일이 더 크고, 획이 단순한 폰트는 예측값보다 작게 나옵니다. ±30% 오차는 정상 범위이며, 이 역산의 목적은 정밀한 수치가 아니라 "자수를 500자 줄였을 때 파일이 의미 있게 줄어드는지"를 만들기 전에 판단하는 것입니다. ✦
이 글이 도움이 됐나요?
여러분의 반응이 다음 글의 방향이 돼요


