볼드 이즈 더 뉴 블랙: 웹 폰트

로컬 폰트만 사용하는 가벼운 웹사이트로 만들기 위해 웹 폰트는 잘 사용하지 않아 왔다. macOS와 iOS의 기본 고딕체인 애플 산돌 고딕 네오Apple SD Gothic Neo가 기본 글꼴로 충분히 마음에 드는 이유도 있었다. 다양한 환경에서 디자인 일관성을 가지는 것이 중요하기 때문에 웹 폰트를 적용한 내용에 대하여 기록해 놓는다.

좋은 명조체

나는 본문에 명조체를 사용해 책을 읽는 것처럼 편한 것을 좋아한다. 본문에 명조체를 사용할 때는 좋은 글꼴과 크기, 행간의 간격이 잘 맞춰지지 않으면 가독성이 크게 떨어진다.

본문이나 특징적인 부분에 명조체를 써보려 하면 먼저 일관성의 문제에 부딪힌다. OS 간의 내장된 명조체 수준이 크게 차이 나는 것이다. 심지어 iOS와 안드로이드에는 내장된 한글 명조체가 없다. macOS의 애플 명조는 글꼴 자체는 마음에 들지만, 일본 글꼴에 기반해 만들어진 것처럼 몇몇 기본적인 기호들이 전각문자로 되어있다. Mac OS X 10.7 Lion부터 네이버가 만든 나눔명조가 포함되어 있지만, 세리프가 날카로워 편한 느낌이 부족하다.

본 고딕과 본 명조

웹에서 사용해볼 수 있는 글꼴 중에서 어도비와 구글이 만든 본 고딕Source Han Sans과 본 명조Source Han Serif는 한글 글꼴 중에서 제일 마음에 들었다. 구글은 각각 Noto Sans CJK와 Noto Serif CJK로 부르며 Noto 시리즈의 한중일CJK 글꼴을 맡고 있다. 각 회사가 브랜딩을 다르게 하지만 글꼴은 같은 것이다. 본 고딕체와 같은 글자를 포개어 비교해 보면 애플 산돌 고딕 네오 보다 보기 좋게 균형이 잘 잡혀있는 모양새다.

나눔 명조체 스크린샷
나눔명조체
본 명조체 스크린샷
본 명조체

웹 폰트 표준

웹 폰트의 사용은 CSS의 @font-face 룰을 이용해 지정할 수 있는데, 이것은 CSS 폰트 모듈 명세에서 기술하고 있다.1 정확한 내용은 명세를 읽는 것이 좋지만, 명세에서 제시하는 예제를 통해 여기서 간략히 설명한다.

@font-face는 새로운 폰트 패밀리를 정의하는 것으로, 그 글꼴의 리소스URI는 로컬에 있을 수도, 다른 웹 서버에 있을 수도 있다. 폰트 스타일에 따라, 유니코드 영역에 따라 다른 리소스를 가리킬 수도 있어 유연하게 하나의 폰트 패밀리를 정의하게 된다.

@font-face {
  font-family: Gentium;
  src: url(http://example.com/fonts/Gentium.woff);
}

p { font-family: Gentium, serif; }

위의 CSS가 적용된 p 태그를 웹 브라우저가 렌더링할 때, Gentium 폰트를 다운로드하여 출력하고, 만약 실패하면 시스템의 기본 세리프 글꼴을 사용해 출력될 것이다.

10여 년 동안 웹 폰트 기술이 발전하면서 웹 브라우저마다 지원하는 파일 포맷이 파편화되어 있어서 포맷별로 선언하여 웹 브라우저(표준 명세에서는 유저 에이전트라 부른다)가 최선인 리소스를 결정하도록 한다.

@font-face {
  font-family: bodytext;
  src: url(ideal-sans-serif.woff2) format("woff2"),
       url(good-sans-serif.woff) format("woff"),
       url(basic-sans-serif.ttf) format("truetype");
}

표준에 정의된 지원 포맷으로는 woff, woff2, truetype, opentype, embedded-opentype, svg가 있다. WOFF 1.0, WOFF 2.0 포맷으로 주요 웹브라우저의 최신 버전에서 모두 커버할 수 있다. 구형 웹 브라우저를 위해 TrueType이나 OpenType 중 하나를 준비하는 정도면 충분해 보인다.

웹 폰트로 사용하려는 글꼴이 이미 설치된 시스템이거나, 사용자에 의해 설치되었을 수 있다. 이런 경우 로컬 폰트가 우선 사용되도록 local 소스를 먼저 선언한다.

/* Gentium 일반 글꼴 */
@font-face {
  font-family: MyGentium;
  src: local(Gentium),    /* 로컬에 있는 Gentium 폰트를 사용한다 */
       url(Gentium.woff); /* 없으면, 다운로드 한다 */
}

플랫폼과 글꼴에 따라서는 로컬 폰트를 찾을 때 완전한 폰트 이름full font name을 사용하거나, 또는 포스트스크립트 이름Postscript name을 사용하여 비교해 찾게 된다. 따라서, 로컬 폰트를 찾을 때는 아래와 같이 두 가지를 모두 기술해주는 것이 좋다.

/* Gentium 볼드 글꼴 */
@font-face {
  font-family: MyGentium;
  src: local(Gentium Bold),   /* 완전한 폰트 이름 */
       local(Gentium-Bold),   /* 포스트스크립트 이름 */
       url(GentiumBold.woff); /* 없으면, 다운로드 한다 */
  font-weight: bold;
}

문제 1: FOUT (Flash of Unstyled Text)

위와 같이 로컬에 설치되지 않은 폰트를 다운로드해 렌더링하는 것은 출력에 지연이 발생한다는 의미다. 웹 브라우저는 이 다운로드가 완료될 때까지 스타일에 지정된 폰트 스택의 다음 후보fallback font를 사용해 먼저 화면에 글자를 출력한다. 다운로드가 완료되면 해당 글자들을 다시 그리게 되는데, 이때 발생하는 깜빡임을 FOUT 또는 Flash of Unstyled Text라 한다.2

FOUT는 단순히 글자 깜빡임 만의 문제가 아니라, 글꼴마다 자간 같은 수치들이 다르기 때문에 같은 글꼴 크기의 문장도 길이가 달라지고, 이것이 컨텐츠의 길이나 크기에 의존하는 레이아웃에도 튐을 일으킨다.

문제 2: FOIT (Flash of Invisible Text)

2009년 Paul Irish의 글에서 제기된 FOUT 문제는 이후로 웹 브라우저들이 폰트를 다운로드 중일 때는 글자를 출력하지 않는 방법으로 해결(?)하였다. 이때 글자가 갑자기 나타나는 깜빡임을 FOIT 또는 Flash of Invisible Text라 한다. 레이아웃의 튐은 여전히 일어날 수 있다. 일정 시간 렌더링의 유예를 하는 것인데, 웹 브라우저마다 지정된 매우 짧은 시간 이내에 폰트의 다운로드가 완료되지 않으면 FOUT가 발생하더라도 먼저 폴백 폰트로 글자를 출력한다.

두 가지 문제 모두 현상을 말하고 그것을 줄이려는 것이지, 원격 리소스에 접근하는 지연이 발생하는 이상 해결책이 있는 것은 아니다. 폰트 파일은 다른 리소스와 마찬가지로 웹 브라우저가 캐쉬하기 때문에 유효한 기간 내에는 처음 한 번만 경험하게 되므로 아주 중요한 문제는 아니라고 생각한다.

폰트 표시 제어

현재 워킹 드래프트Working Draft 상태인 CSS 폰트 모듈 레벨 4 명세에서는 font-display 기술자descriptor를 통해 위와 같은 문제들을 각 폰트 페이스마다 웹 브라우저가 어떻게 동작할지 설정할 수 있다.3

auto, block, swap, fallback, optional 값 중 하나를 지정해 먼저 폴백 폰트를 출력할지, 다운로드가 완료될 때까지 블럭invisible할지 등 직접 설정할 수 있게 된다. 언어나 네트웍 상황, 디자인 철학 등에 따라 다른 표시 정책을 사용할 수 있어 좋은 개선사항이다.

현재는 CSS 폰트 모듈 레벨 3 명세까지가 W3C 권고Recommendation 상태이다. W3C 권고는 웹 표준으로 간주한다.4

어도비 폰트와 다이나믹 서브셋팅

웹 폰트는 타입킷Typekit에서 최근에 이름을 바꾼 어도비 폰트에서 호스팅을 받고 있었다. 어도비 크리에이티브 클라우드에 비용을 이미 내고 있고, 아직 한국에 데이터센터가 없는 구글 폰트보다 국내에 CDN이 있기 때문에 빠르기도 하다.

한중일에서 사용하는 CJK 폰트는 용량이 매우 크기 때문에 근래에는 다이나믹 서브셋팅dynamic subsetting이라는 기술을 사용한다. 폰트 파일을 통째 받는 것이 아니라, 페이지를 로드하는 시점에 페이지에 사용된 글자들만 자바스크립트로 요청하여 받는 것이다. 어도비 폰트에서 CJK 폰트는 다이나믹 서브셋팅을 사용하도록 설정되고, 현재 이것을 취사 선택할 수 있는 옵션은 제공하지 않는다.

이런 이유로 어도비 폰트에서 CJK 폰트를 사용하게 되면 CSS 파일을 링크하는 것이 아니라 자바스크립트 코드를 head 태그에 추가해야 한다.

다이나믹 서브셋팅을 사용하게 되면 페이지마다 동적으로 폰트를 요청하는 지연이 발생하고, 이 결과물을 캐쉬할 수 있는 방법이 없기 때문에 같은 페이지에서도 FOUT을 매 순간 겪게 되어 사용할 수 없다는 결론이다.

구글 폰트

구글 폰트는 한글 폰트에 대하여 머신 러닝으로 최적화해 동적으로 분할된 폰트를 제공한다.5 설명에 따르면, 방대한 한국어 문서를 분석해, 글리프glyph를 100여 가지의 그룹으로 나누고 필요한 그룹의 폰트만 다운로드하는 것이다.

실제로 구글 폰트에서 Noto Serif KR 폰트를 사용하기 위해 제공하는 CSS파일의 내용을 직접 확인해보면, 아래와 같이 동일한 폰트 패밀리에 대하여 유니코드 영역unicode-range을 100여개 조각으로 나누어 실제로 사용된 글자의 그룹에 해당하는 폰트 파일만 다운로드받도록 하고 있다.

/* [0] */
@font-face {
  font-family: 'Noto Serif KR';
  font-style: normal;
  font-weight: 400;
  src: local('Noto Serif KR'), local('NotoSerifKR-Regular'), url(https://fonts.gstatic.com/s/notoserifkr/v5/3Jn7SDn90Gmq2mr3blnHaTZXduVp0uNzcmdRk6NBSYsXpcC_HIoOgGv0PTY.0.woff2) format('woff2');
  unicode-range: U+f9ca-fa0b, U+ff03-ff05, U+ff07, U+ff0a-ff0b, U+ff0d-ff19, U+ff1b, U+ff1d, U+ff20-ff5b, U+ff5d, U+ffe0-ffe3, U+ffe5-ffe6;
}
/* [1] */
@font-face {
  font-family: 'Noto Serif KR';
  font-style: normal;
  font-weight: 400;
  src: local('Noto Serif KR'), local('NotoSerifKR-Regular'), url(https://fonts.gstatic.com/s/notoserifkr/v5/3Jn7SDn90Gmq2mr3blnHaTZXduVp0uNzcmdRk6NBSYsXpcC_HIoOgGv0PTY.1.woff2) format('woff2');
  unicode-range: U+f92f-f980, U+f982-f9c9;
}
...

동일한 이름의 로컬 폰트를 우선 사용하도록 하는 것과 요청한 웹 브라우저user agent를 확인해, 지원하는 최선의 포맷(이 경우 WOFF2) 정보만 담겨 있는 것도 알 수 있다.

이 글을 로드할 때 다운로드되는 폰트 파일을 테스트해 보면 25개의 WOFF2 파일을 받고 있다. 총 용량은 약 269KB인데, 한글 글꼴의 레귤러와 볼드 폰트를 모두 사용한 것을 생각하면 매우 작은 크기이다. 이 파일들은 사이트 간 캐싱cross-site caching으로 여러 사이트에서 이 폰트를 사용할수록 속도의 효과를 볼 수 있다.6

아래는 구글 폰트에서 Noto Sans를 사용하기 위해 제공하는 표준 링크인데, display 파라미터를 통해 위에서 설명한 폰트 표시 제어를 할 수 있다.

<link href="https://fonts.googleapis.com/css?family=Noto+Sans+KR&display=swap" rel="stylesheet">

기본적으로 swap이 지정되어 있는데 이것은 폴백 폰트를 먼저 표시하고, 다운로드가 완료되면 다시 해당 폰트로 표시하게 된다. 즉, FOUT 현상이 발생한다는 의미이다. display 파라미터를 삭제해보니 기본값 auto가 사용되어 대부분의 웹 브라우저에서는 FOIT 현상을 보게 된다는 것을 알 수 있다.

실제로 적용할 방법

구글 폰트는 오픈 소스 폰트 위주의 호스팅을 하기 때문에 내가 사용하는 영문 상용 폰트는 계속 어도비 폰트를 사용할 예정이다. 어도비 폰트의 다이나믹 서브셋팅은 상식적인 작동 방식이 아닌 것으로 보이고, 구글 폰트와 같은 방식으로 가게 될 것이라 예상한다.

구글 폰트가 어도비 폰트보다 한글 폰트를 더 최선인 방법으로 다루지만, 유니코드 영역에 따라 조각난 폰트 파일들이 서로 다른 속도로 로딩이 완료되면서, 화면의 글자들이 순차적으로 렌더링 되는 현상을 보게 된다. 기술적으로 어쩔 수 없는 지연은 감수하더라도 이러한 현상을 없애고, 파일의 용량을 더 줄여보기 위해 나는 조금 더 사서 고생하는 방법으로 최척화를 시도해 보기로 했다.

다음 글 서브셋팅으로 웹 폰트 최적화에서 계속된다.


  1. The @font-face rule (CSS Fonts Module Level 3) ↩︎

  2. Fighting the @font-face FOUT (Paul Irish, 2009) ↩︎

  3. Controlling Font Display Per Font-Face: ‘font-display’ descriptor (CSS Fonts Module Level 4) ↩︎

  4. What does “Web Standard” mean? What is a “Recommendation”? (Standards FAQ) ↩︎

  5. Google Fonts + 한국어 ↩︎

  6. 사이트 간 캐싱이 사용자를 추적하기 위한 목적으로 악용되면서, 웹 브라우저들이 캐싱을 요청한 사이트별로 캐시를 격리하는 정책으로 바뀌어 가고 있다. 따라서 사이트 간 캐싱의 장점을 더 이상 얻을 수 없다. 참고: Gaining security and privacy by partitioning the cache (Eiji Kitamura) ↩︎