이건 뭐 별 거 아니지! 하다가 하루 종일 매달리게 되어 감추지 못할 분노를 포스팅에 담아본다....
웹 개발 중 디자인적으로 제일 중요한 것을 꼽자면, 나는 폰트를 꼽겠다. 그만큼 폰트는 대상에 대한 이미지를 결정하는 데에 큰 역할을 하기 때문. 짜치는 폰트를 보면 인상부터 찌푸려지는 나에겐,,,
그렇기 때문에 웹 페이지 전반에 쓰일 폰트를 결정하는 것은 그만큼 중요한 일인데, 예전엔 폰트를 어느 정도 포기해야 했다. 인터넷 속도도 안 나오고, 디자인 하나 보여주자고 서버 트래픽을 nMB나 쓰게 하는 건 있을 수 없는(...) 일이었기 때문에, 과거의 웹 디자이너들은 웹 안전 폰트(Web Safe Font)를 사용했다.
Web Safe Font
시스템에 기본적으로 깔려있는 폰트들. Arial, Courier New, ... 등등
Arial, Courier New,, 어디서 많이 보던 폰트 목록들이 바로 이 웹 안전 폰트인 것이다. 따로 폰트를 설정하더라도, 폴백으로 안전 폰트들을 지정하는 경우가 많다.
폴백이라곤 하지만,, Serif체를 쓰고 있는데 Sans가 폴백으로 나오면 느낌이 달라지기 때문에 어느 정도 최소한의 예의 느낌을 맞춰주는 것이 좋다. 이를 위해 CSS에서는 기본적으로 serif
, sans-serif
, monospace
, cursive
, fantasy
를 지정해주고 있다! 그래서 해당 폰트를 써주면 그 서체 느낌들 중 설치된 기본 폰트를 가져온다.
Web Safe Font로 Fallback
p { font-family: "Trebuchet MS", Verdana, sans-serif; }
그러면 최소한의 느낌을 맞출 수 있다! CSS 기본 폰트들은 여기 사이트를 참고
그러다가 웹 기술의 폭발적인 성장으로 이젠 폰트를 웹에서 받을 수 있게 되었다. 그러므로써 디자이너들은 본인들이 원하는 서체로 표현할 수 있게 되었다구!
...물론 디자이너들에겐 좋은 일이지만,, 서버와 프론트에겐 신경쓸 게 늘어버린 것^~^,,
기존의 폰트의 종류에는 TTF와 OTF가 존재했다. 그보다 더 앞선 비트맵 폰트는 언급하지 않겠다... 여튼 TTF와 OTF 둘 다 베지어 곡선을 사용한 벡터 형식의 폰트인데, 차이점은 곡점 개수의 차이이다. TTF는 2개를 사용하고, OTF는 3개를 사용한다. 그러므로 OTF는 TTF보다 더 부드러운 곡선을 표현할 수 있다.
그런데~ 더 부드러운 곡선을 표현할 수 있으므로, 그만큼 용량이 커지게 된다. 이 용량이 커지는 게.. 생각보다 크다. 거의 두 배차이인데, 한글 폰트는 거기에 포함되는 심볼들이 더 많기 때문에 용량이 배로 늘어버린다.
한글은,, 특유의 생김새 덕분에 표현하는 종류가 많다. 총 하면 11,172자가 나온다!
당연히 이 글자를 다 표현하려면 차지하는 용량이 엄청 크기 때문에,, 보통 굵기 폰트 하나가 7MB 쯤(!) 하는 불상사가 일어난다. 서버에선 여전히 7MB를 폰트에 내어줄 생각은 전혀 없으므로... 방법을 고안해낸 게 '필요한 글자만 쓰자' 이다. 요게 Subset Character이다.
사실 저 11,172자 안에는 현대엔 쓰지 않는 글자들이 엄청 많다. '꽶'이라든지 '뛃'이라든지.. 그래서 이런 저런 글자들을 다 빼고, 현대 언어에서 쓰이는 글자를 모아둔 목록이 있다.
이를 KS X 1001 완성형 이라고 한다. 이 목록에는 2,350자의 글자가 포함되어 있다. 약 80%나 줄인 것! 덕분에 현재 용량에 목숨거는 웹폰트들은 해당 완성형 목록을 사용하고 있다.
그러나 당연하게도 요 2,350자에는 표현하지 못하는 일상 글자들이 존재한다. 여깄다, 저깄다, 설렜다 등등.. 의외로 쓸 법한 단어임에도 불구하고 표현이 안 되는 경우가 더러 있다.
이를 보완하기 위해 어느 한 타이포그래피 연구소에서 낸 논문에서 제시한 224자를 추가하는 경우도 있고, Adobe에서 만든 Adobe-KR-9 보충 0 목록을 사용하기도 한다. 전자의 경우 총 2,574자를 가지게 되고, 후자는 2,780자를 가지게 된다.
그래서, 여전히 용량이 중요하다! 싶은 서비스에선 2,350자, 어느 정도 보장을 해주고 싶은 서비스에선 2,780자를 선택하는 추세인 것 같다.
+) 참고로 위에서 언급한 KS X 1001 완성형 목록이나, Adobe-KR-9 보충 0 목록은 한글만 존재한다. 따로 특수문자 역시 Subset Character에 포함시켜줘야 한다! 그렇게 되면 대충 3500~4000자가 나온다.
내가 만든 Subset List
따로 모아둔 Subset List들이 있을 법도 한데 없어서 만들었다: 노션
그리고 저렇게 필요한 글자만 골라내는 것 말고도, 압축 기술이 좋아진 것도 한 몫을 했다. zlib이라는 압축, 더 구체적으론 compress2이라는 압축으로 만든 폰트 확장자가 바로 WOFF이고 따로 웹폰트 확장자라고 불린다. 이름부터 Web Open Font Format.
기존의 TTF나 OTF의 저작권 문제도 해결했다고 한다. 예전 폰트 파일의 문제는 복제가 가능하고 추적이 불가능하다는 것이었는데,, 이젠 파일 안에 메타 정보를 넣어서 해결하는 듯하다.
여기에 WOFF2는 WOFF 보다 약 50% 이상 압축을 더 할 수 있는데, Brotli 라는 압축 기술을 사용했다고 한다.
그러면 WOFF2를 사용해야 하는 거 아닌가! 싶지만 언제나 다양한 환경을 생각하고 Backward compatibility를 생각할 것.
기본 이론은 이만하면 됐고, 이번엔 폰트를 직접 만들어보자. 사실 이에 대한 건 여러 군데 잘 나와있어서 나는 가볍게만 다뤄보겠다.
일본에서 만든 서브셋 폰트 메이커이다.
대충 머 이렇게 생겼는데.. 느낌 오는 대로 눌러서 해보자. 는 아니고...
이렇게 해주면 된다. 중간에 있는 건 서체 이름을 바꾸는 기능이라, 필요 없으면 해제하면 된다.
그 아래 있는건 변환이 끝나고 나서 WOFF 변환기를 실행시킬 거냐는 옵션인데, 여기에서 말하는 WOFF 변환기는 자사 프로그램이다. 보통 Font Weight 별로 한 7개 정도는 해야하니까 해제하는 게 낫다.
그리고 이게 제일 좋았다. 여타 다른 온라인에서 제공하는 Converter는 제대로 동작을 안 한다! 내가 이거 때문에 삽질을 엄청 했다. 코드 문제인 줄 알았는데 파일 문제였다니... 다른 데에서 압축하는 건 subset 목록을 영문으로만 한정해서 하는 듯 하다. 그래서 한글 폰트는 제대로 나오질 않는다. 어쩐지 용량이 말도 안 되게 8KB 이러더라
하지만 요놈은 진짜로 작동한다. 제대로 됨! 광명을 찾았다. 이것도 대충 눈치껏 하면 되는데,,,
순서는 위에서 만든 Subset 폰트 파일을 넣고, 도착지를 설정하지 않으면 같은 폴더에 자동으로 WOFF 파일이 생기고, WOFF2도 체크하면 같이 생긴다. 그 후 버튼 세 개 있는 거에서 맨 왼쪽 꺼를 누르면 된다.
사진 만들긴 귀찮으니 대충 번역기 돌려서 정리하면 이렇다.
1.변환 전 파일(S)
변환 전 파일이 폰트인 경우 WOFF로 변환, WOFF인 경우 폰트로 변환합니다.
2. 변환 후 파일(D)
설정되어 있지 않은 경우, 파일과 같은 이름으로 확장자만 바꿔서 자동 생성합니다.
3.폰트를 WOFF로 변환할 때
WOFF2 생성하기(M)
EOT 파일 생성하기(E)
메타데이터 추가하기(M)
샘플 HTML 파일 만들기(H)
샘블 문자열(T):
변환 전 폰트를 Src에 추가하기 (A)
4. 무사시 시스템(만든 회사)
5. 변환하기, 종료, 도움말(H)
그리고 변환하기를 누르면 빨간색 글씨로 뭐라하는데 대충 변환 중이라는 내용이고, 완료 되면 열받게 Warning으로 알려준다. 노란색 느낌표가 떠도 당황하지 말자. 완료됐다는 소리다.
그러면~ 다음과 같이 아주 예쁜 목록이 나온다.
내가 이걸 얻기 위해서 얼마나 개고생을 했는지...
이제 요걸로 잘 가져다가 쓰면 된다.
나는 다음과 같이 font-face를 설정했다.
Noto Serif Font Face
/** 100 Thin 200 Extra Light(Ultra Light) 300 Light 400 Normal(Regular) 500 Medium 600 Semi Bold(Demi Bold) 700 Bold 800 Extra Bold(Ultra Bold) 900 Black(Heavy) \*/ @font-face { font-family: 'Noto Serif KR'; font-style: normal; font-display: swap; font-weight: 100; src: url('../Assets/Fonts/NotoSerifKR-ExtraLight.woff2') format('woff2'), url('../Assets/Fonts/NotoSerifKR-ExtraLight.woff') format('woff'), url('../Assets/Fonts/NotoSerifKR-ExtraLight.otf') format('opentype'); } @font-face { font-family: 'Noto Serif KR'; font-style: normal; font-display: swap; font-weight: 200; src: url('../Assets/Fonts/NotoSerifKR-ExtraLight.woff2') format('woff2'), url('../Assets/Fonts/NotoSerifKR-ExtraLight.woff') format('woff'), url('../Assets/Fonts/NotoSerifKR-ExtraLight.otf') format('opentype'); } @font-face { font-family: 'Noto Serif KR'; font-style: normal; font-display: swap; font-weight: 300; src: url('../Assets/Fonts/NotoSerifKR-Light.woff2') format('woff2'), url('../Assets/Fonts/NotoSerifKR-Light.woff') format('woff'), url('../Assets/Fonts/NotoSerifKR-Light.otf') format('opentype'); } @font-face { font-family: 'Noto Serif KR'; font-style: normal; font-display: swap; font-weight: 400; src: url('../Assets/Fonts/NotoSerifKR-Regular.woff2') format('woff2'), url('../Assets/Fonts/NotoSerifKR-Regular.woff') format('woff'), url('../Assets/Fonts/NotoSerifKR-Regular.otf') format('opentype'); } @font-face { font-family: 'Noto Serif KR'; font-style: normal; font-display: swap; font-weight: 500; src: url('../Assets/Fonts/NotoSerifKR-Medium.woff2') format('woff2'), url('../Assets/Fonts/NotoSerifKR-Medium.woff') format('woff'), url('../Assets/Fonts/NotoSerifKR-Medium.otf') format('opentype'); } @font-face { font-family: 'Noto Serif KR'; font-style: normal; font-display: swap; font-weight: 600; src: url('../Assets/Fonts/NotoSerifKR-SemiBold.woff2') format('woff2'), url('../Assets/Fonts/NotoSerifKR-SemiBold.woff') format('woff'), url('../Assets/Fonts/NotoSerifKR-SemiBold.otf') format('opentype'); } @font-face { font-family: 'Noto Serif KR'; font-style: normal; font-display: swap; font-weight: 700; src: url('../Assets/Fonts/NotoSerifKR-Bold.woff2') format('woff2'), url('../Assets/Fonts/NotoSerifKR-Bold.woff') format('woff'), url('../Assets/Fonts/NotoSerifKR-Bold.otf') format('opentype'); } @font-face { font-family: 'Noto Serif KR'; font-style: normal; font-display: swap; font-weight: 800; src: url('../Assets/Fonts/NotoSerifKR-Bold.woff2') format('woff2'), url('../Assets/Fonts/NotoSerifKR-Bold.woff') format('woff'), url('../Assets/Fonts/NotoSerifKR-Bold.otf') format('opentype'); } @font-face { font-family: 'Noto Serif KR'; font-style: normal; font-display: swap; font-weight: 900; src: url('../Assets/Fonts/NotoSerifKR-Black.woff2') format('woff2'), url('../Assets/Fonts/NotoSerifKR-Black.woff') format('woff'), url('../Assets/Fonts/NotoSerifKR-Black.otf') format('opentype'); }
src에 local을 넣어줘야 불필요한 네트워크 트래픽이 발생하지 않을 것 같아 넣어줬었는데, 이상하게 local로 받아오면 font-weight 쪽에서 설정이 안 먹혀서 빼줬다. 버근가?
그리고~ 나는 Vite 환경이라서 저렇게 리소스를 임포트해줬다. 바뀔 리 없는 파일이라 Public에 놓고 써도 되겠지만,, 일단 저렇게 해놨다.
폰트를 적용할 땐 다음과 같이 하면 된다.
폰트 적용
html { font-family: 'Noto Serif KR', serif; }
또~ 만약 폰트를 중요하게 여기는 서비스라면 preload로 가장 많이 쓰이는 폰트 하나를 가져와도 될 것 같다.
폰트Preload
<link rel="preload" href="../Assets/Fonts/NotoSerifKR-Regular.woff2" as="font" type="font/woff2" crossorigin />
나참! 이렇게 예쁠 일인가!