일본어 문구에서 한국 한자가 나오는 문제 해결(2) - 웹 폰트 적용하기

부루베릐·2023년 6월 18일
1

TIL

목록 보기
12/23
post-custom-banner

지금까지는 Pretendard 폰트를 사용한다고만 했지, 이 폰트를 어떻게 외부로부터 가져올지는 구현하지 않았다. Pretendard와 같은 외부 웹 폰트를 클라이언트로 가져오는 방식을 살펴보자.

@font-face

여러 유형의 웹 폰트 파일 사용하기

실제로 외부의 폰트를 다운로드 받아 사용하기 위해서는 @font-face CSS 속성을 사용하여야 한다. font-face 안에서 사용할 폰트 파일 형식을 순서에 맞게 배정해주는 것이 무엇보다 중요하다.

@font-face {
  font-family: <a-remote-font-name>;
  src: <source> [,<source>]*;
  [font-weight: <weight>];
  [font-style: <style>];
}

@font-face {
	font-family: 'Pretendard';
	src: url('fonts/Pretendard.eot') format('eot')
	src: local('Pretendard')
       url('fonts/Pretendard.woff2') format('woff2')
       url('fonts/Pretendard.woff') format('woff')
       url('fonts/Pretendard.ttf') format('ttf')
       url('fonts/Pretendard.otf') format('otf')
}

font-face에 한꺼번에 여러 유형의 파일들을 같이 표기한 것을 볼 수 있다. 이렇다고 모든 파일을 한꺼번에 받아오지는 않고, 브라우저가 여기서 자기가 지원하는 형식을 위에서부터 차례대로 찾아 하나를 다운로드한다. 만약 먼저 순서의 폰트 형식을 브라우저가 지원하지 않으면 다음 형식으로 넘어가는 방식이다. 따라서 IE에서 사용하는 EOT 형식 파일을 따로 떼어 맨 먼저 앞에 둔다. 그 뒤로는 로컬에 설치된 폰트를 먼저 찾아보고, 만약 없다면 그 때부터 woff2, woff, tff, oft 순으로 웹 폰트를 찾는다. woff2와 woff 모두 웹 폰트 사용에 최적화 되어있는 압축 형식의 폰트이고, woff2의 압축률이 woff보다 30~50% 더 압축률이 좋다고 하니, woff2를 맨 먼저 넣는 것이 좋다.


하나의 폰트 내에서 굵기와 스타일 서체 추가하기

폰트를 사용할 때, 하나의 폰트 파일만 다운로드하여 사용하는 경우는 많이 없다. 같은 종류의 폰트라 하더라도 폰트의 굵기에 따라 여러 폰트 파일을 사용하는 것이 일반적이기 때문이다.

따라서 폰트의 굵기를 다양하게 사용하고 싶다면 다음과 같이 굵기에 따른 폰트 파일을 모두 다운받을 수 있도록 설정하는 것이 좋다!

fonts
 ├─ Pretendard-Black.otf
 ├─ Pretendard-Bold.otf
 ├─ Pretendard-ExtraBold.otf
 ├─ Pretendard-ExtraLight.otf
 ├─ Pretendard-Light.otf
 ├─ Pretendard-Medium.otf
 ├─ Pretendard-Regular.otf
 ├─ Pretendard-SemiBold.otf
 └─ Pretendard-Thin.otf

각 굵기에 따른 폰트를 각각의 font-face로 설정하여 사용하면 된다.

@font-face {
	font-family: 'Pretendard';
	src: local('PretendardRegular')
       url('fonts/PretendardRegular.woff2') format('woff2')
       url('fonts/PretendardRegular.woff') format('woff')
       url('fonts/PretendardRegular.ttf') format('ttf')
	font-weight: normal;
}

@font-face {
	font-family: 'Pretendard';
	src: local('PretendardBold')
       url('fonts/PretendardBold.woff2') format('woff2')
       url('fonts/PretendardBold.woff') format('woff')
       url('fonts/PretendardBold.ttf') format('ttf')
	font-weight: bold;
}

@font-face {
	font-family: 'Pretendard';
	src: local('PretendardLight')
       url('fonts/PretendardLight.woff2') format('woff2')
       url('fonts/PretendardLight.woff') format('woff')
       url('fonts/PretendardLight.ttf') format('ttf')
	font-weight: light;
}

font-weight에 따라 font-face를 설정하고 그에 맞는 파일을 불러오면 된다. 이 때 주의해야 할 점은 font-family에 들어가는 폰트 이름은 하나로 통일해야 한다는 것. 위의 예시에서도 Pretendard 하나로만 되어 있는 것을 볼 수 있다.


@import

@import는 외부 CSS 소스를 내부에서 사용할 수 있도록 해 주는 속성이다.

@font-face는 특정한 외부 폰트 리소스를 다운로드하는 방법이고, @import는 엄밀히 말하면 CSS 파일 자체를 다운로드하기 위해 사용하는 방식이다. 따라서 @import를 사용하여 웹 폰트를 다운로드한다면 @font-face가 포함되어 있는 CSS 파일을 다운로드한다는 뜻이다.

Pretendard 폰트를 제대로 사용하기 위해서는 @import 구문을 사용해야 한다. Pretendard는 웹 폰트 다운로드로 인한 성능 하락을 막기 위하여 CDN을 제공하고 있고, CDN을 통해 폰트 CSS 시트를 받아올 수 있기 때문이다. 실제로 Pretendard 공식 문서를 살펴보면 다음과 같이 폰트 CSS 문서 자체를 받아올 수 있도록 코드와 URL을 제공해준다.

@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.7/dist/web/static/pretendard.css");

따라서 위의 코드를 우리 글로벌 CSS 파일에 등록해서 사용하도록 하자.

@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.7/dist/web/static/pretendard-dynamic-subset.css");

html {
	font-family: -apple-system, BlinkMacSystemFont, "Apple SD Gothic Neo", Pretendard, ...;
	word-wrap: break-word;
}

html[lang="ja"] {
	@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.7/dist/web/static/pretendard-jp-dynamic-subset.css");
	font-family: "Pretendard JP", Pretendard, -apple-system, BlinkMacSystemFont, ...;
}

여기서 html font-family의 폰트 구성에서 Pretendard가 맨 처음 나오지 않는 이유는 각 사용자들의 환경에 맞는 폰트를 우선적으로 가져오도록 하기 위해서이다.

이렇게 하면 사이트가 Pretendard 폰트에 대한 @font-face 설정이 담긴 CSS 스타일 시트를 CDN을 통해 다운로드하는 것을 직접 체킹할 수 있다.

만약 사이트에서 Pretendard JP Bold 폰트를 필요로 하면, 다음과 같이 해당 @font-face만을 요청하여 받아온다.

이제 안내 모달을 다시 살펴보면 일본 언어 설정 환경에서 한국 한자가 아닌 일본 한자가 잘 나오는 것을 확인할 수 있다!


웹 폰트 개선

위에서 CDN을 사용하여 웹 폰트를 다운로드할 수 있었다. 따라서 맨 처음 사용자가 폰트를 요청할 때에는 다소 시간이 걸리겠지만, 두 번째부터는 CDN 서버에 캐싱이 되어 훨씬 빠르게 폰트를 가져올 수 있을 것이다.

여기서 더 웹 폰트 다운로드 성능을 개선할 수 있는 방법이 두 가지가 있다. 첫 번째는 @import 구문 사용을 지양하는 것, 두 번째는 다이내믹 서브셋 폰트를 사용하는 것이다.


@import ⇒ link(참고)

내가 맨 처음 @import 구문을 사용하여 CSS 리소스를 다운받았던 이유는, 웹 폰트 스타일 시트를 다운받는 로직과 해당 폰트를 적용하는 로직이 모두 하나의 CSS 파일 안에서 해결할 수 있었기 때문이었다.

html[lang="ja"] {
	@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.7/dist/web/static/pretendard-jp-dynamic-subset.css");
	font-family: "Pretendard JP", Pretendard, -apple-system, BlinkMacSystemFont, ...;
}

즉 웹 폰트를 다운로드받고 이를 사용하는 것 모두 한 눈에 들어올 수 있으므로, @import를 사용하는 방식은 간편하다는 장점이 있겠다.

하지만 웹 폰트를 제공하는 서비스(Google Font나 Pretendard 등)들을 보면, @import보다는 다른 방법을 좀 더 추천한다. 바로 link 태그를 html의 부분에 넣어 리소스를 다운받는 것이다.

<head>
	<link rel="stylesheet" as="style" crossorigin href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.7/dist/web/static/pretendard.css" />
</head>

link 태그와 비교하였을 때, @import CSS 구문은 소스를 순차적으로 다운로드한다는 단점이 있다(참고). 따라서 해당 리소스의 다운로드가 진행되는 동안 브라우저는 화면 렌더링을 잠시 멈추게 된다. 이런 이유로 인해 @import를 사용하여 리소스를 다운로드하는 것은 렌더링 성능에 악영향을 미칠 수 있어 좋은 해결 방법은 아니다.

이에 반해 link 태그를 사용하면 병렬적으로 리소스를 다운받을 수 있으므로, 사용자 경험을 기준으로 한다면 link 태그를 사용하는 것이 더 좋을 것이다.

때문에 사용자의 언어 설정을 파악한 후, 이에 맞춰 html 태그의 lang 속성을 바꿔줌과 동시에 useMeta()를 사용하여 link 태그를 통해 언어에 맞는 Pretendard CSS 스타일 시트를 받아오게 하였다.

setup() {
  const pretendardCDNUrl = computed(() =>
    lang.value === 'ja'
      ? 'https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.6/dist/web/static/pretendard-jp.css'
      : 'https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.6/dist/web/static/pretendard.css'
  )

  useMeta(() => ({
    htmlAttrs: {
      lang: lang.value,
    },
    link: [
      {
        rel: 'stylesheet',
        type: 'text/css',
        href: pretendardCDNUrl.value,
      },
    ],
  }))
}
  

다이내믹 서브셋 사용

웹 폰트로 인한 사용자 경험 저하 문제가 많다보니, 더욱 웹 폰트의 크기를 줄일 수 있는 방법들이 많이 연구된 것 같다. 그 중 하나가 바로 다이내믹 서브셋이다.

다이내믹 서브셋이란 CSS의 unicode-range 속성을 사용하여 실제로 사용자들이 사용하지 않는 불필요한 문자를 삭제하여 폰트 용량을 경량화시키는 방식을 이야기한다. 일반적으로 한글의 경우 총 1만 1000개가 넘는 글자가 존재한다고 한다. 이 때문에 폰트를 다운로드할 때 실제로 아예 사용하지 않는 글자도 함께 다운로드하게 되므로 불필요한 글자들은 애초에 다운로드하지 않는 것이 좋다.

/* pretendard-dynamic-subset.css */

@font-face {
    font-family: 'Pretendard';
    font-style: normal;
    font-display: swap;
    font-weight: 100;
    src: url(../../../packages/pretendard/dist/web/static/woff2-dynamic-subset/Pretendard-Thin.subset.0.woff2) format('woff2'), url(../../../packages/pretendard/dist/web/static/woff-dynamic-subset/Pretendard-Thin.subset.0.woff) format('woff');
    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;
}

위의 코드는 실제 Pretendard의 다이내믹 서브셋 CSS 파일 내에 있는 @font-face의 예시이다. unicode-range에 저렇게 글자의 유니코드를 입력하면, 해당 유니코드가 실제로 사용될 때만 폰트를 다운로드하고 사용되지 않는다면 다운로드하지 않는다. 구글이 제공하는 웹 폰트의 경우 머신 러닝을 사용하여 사람들이 자주 사용하는 글자와 사용하지 않는 글자를 나누었다고 한다(참고). Pretendard는 구글 웹 폰트에서 다이나믹 서브셋을 나누는 기준을 참고하여 구현하였다 밝히고 있다.

실제로 다이내믹 서브셋을 사용하였을 때와 사용하지 않았을 때를 비교하여 보자.

<!-- 다이내믹 서브셋을 사용하지 않았을 때 -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
	<link rel="stylesheet" as="style" crossorigin href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.7/dist/web/static/pretendard-dynamic-subset.css" />
  
<style>
html {
  font-family: Pretendard, sans-serif;
}
</style>
</head>
<body>
  <h1>hello</h1>
  <div>i'm jack</div>
</body>
</html>

일단 사용하지 않았을 때는 Bold와 Regular 통짜 폰트를 모조리 가지고 오기 때문에 합쳐서 1.5MB 정도의 리소스를 다운로드 받는다.

<!-- 다이내믹 서브셋을 사용했을 때 -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
	<link rel="stylesheet" as="style" crossorigin href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.7/dist/web/static/pretendard-dynamic-subset.css" />
  
<style>
html {
  font-family: Pretendard, sans-serif;
}
</style>
</head>
<body>
  <h1>hello</h1>
  <div>i'm jack</div>
</body>
</html>

하지만 사용하였을 때에는 필요한 글자들만을 받아오므로 40kB로 용량이 확실하게 줄어든 것을 확인할 수 있다!

따라서 마지막으로 다이내믹 서브셋을 적용하여 구현을 마무리짓도록 하자.

setup() {
  const pretendardCDNUrl = computed(() =>
    lang.value === 'ja'
      ? 'https://cdn.jsdelivr.net/.../pretendard-jp-dynamic-subset.css'
      : 'https://cdn.jsdelivr.net/.../pretendard-dynamic-subset.css'
  )

  useMeta(() => ({
    htmlAttrs: {
      lang: lang.value,
    },
    link: [
      {
        rel: 'stylesheet',
        type: 'text/css',
        href: pretendardCDNUrl.value,
      },
    ],
  }))
}

이제 일본어 설정일 때는 일본 폰트를 다운받아 제대로 신자체가 나오게 된다. 이에 더해서 html 태그의 lang 속성도 사용자의 언어 설정에 따라 달라지니, 조금은 우리 서비스의 웹 접근성이 향상되지 않았을까 생각해본다!

참고

일본어 웹서비스를 위한 폰트와 문장 부호 설명서

https://github.com/orioncactus/pretendard

don’t use @import | High Performance Web Sites

post-custom-banner

1개의 댓글

comment-user-thumbnail
2023년 6월 23일

정말 대단하군요 멋지십니다

답글 달기