(번역) CSS에 대한 깊은 이해: 폰트 매트릭스, line-height와 vertical-align

Posted by in Research, Translation

원문: http://iamvdo.me/en/blog/css-font-metrics-line-height-and-vertical-align
본 번역문은 원작자인 Vincent De Oliveira의 동의하에 번역 및 게시되었습니다.

 

line-height와 vertical-align아주 간단한 CSS 속성들입니다매우 간단하기 때문에 대분의 사람들은 이것들이 어떻게 작동하고 어떻게 사용하는지 완전히 이해하고 있다고 확신합니다하지만 실제로는 그렇지 않습니다이것들은 매우 복잡합니다CSS에서  알려진 특징  하나인 inline formatting context 형성하는 주요한 역할 하기 때문에 아마도 가장 어려운   하나가  수도 있습니다.

예를 들어, line-height는 길이 혹 단위가 없는 값으 설정할  있지만 1기본값은 normal니다좋습니다하지만 ‘normal’은 무엇일까요우리는 종종  값은 1이나 1.2라고(혹은 그래야 한다고읽곤 했습니다심지어는 CSS 스펙에서 조차  부분에 대해 불분명합니다 우리는 단위가 없는 line-heightfont-size 상대적이라는 것을 알고 있습니다하지만문제는 font-size: 100pxfont-family에 따 다르게 동작한다는 것입니다그렇다면 line-height 항상 같을까요다를까요이것은 정말 1에서 1.2 사이의 값일까요그리고 vertical-alignline-height 어떤 영향을 받을까요?

절대 단순하지 않은 CSS 메커니즘에 대해 깊게 알아봅시다

font-size 대해 먼저 이야기해봅시다

간단한 HTML 코드를 살펴봅시다. <p> 태그 안에 서로 다른 font-family가 선언된 3개의 <span> 태그가 있습니다:

다른 font-family에 동일한 font-size를 적용할 경우 각 요소들은 서로 다른 높이값을 가집니다:


그림 1. 다른 font-family에 동일한 font-size을 적용한 경우 각각 다른 높이값을 가집니다.

우리가 그러한 작동 방식에 대해 알고 있다 하더라도 font-size: 100px; 이 각 요소의 높이값을 100px로 만들지 않는 이유는 무엇입니까? 저는 각 값을 측정해 보았습니다: Helvetica: 115px, Gruppo: 97px and Catamaran: 164px


그림 2. font-size:100px 요소의 높이값은 97px에서 164px까지 다르게 나타납니다.

이러한 현상이 처음에는 이상하게 보일지도 모르겠습니다만, 사실은 이것은 완전히 예상 가능한 결과입니다 이유는 폰트  자체 안에 있습니다이것이 어떻게 동작하는지 알아봅시다:

  • 폰트는 각 글자가 그려질 컨테이너의 일종인 em-square(혹은 UPM, units per em)을 정의합니다. 이 square는 상대적인 단위를 사용합니다. 그리고 일반적으로 1000 단위로 설정됩니다. 하지만 1024, 2048 혹은 다른 어떤 것도 될 수 있습니다.
  • 상대적인 단위를 기반으로 폰트의 매트릭스가 설정됩니다.(ascender, descender, capital height, x-height  등). 일부 값들은 em-square의 바깥으로 흘러 넘 칠 수 있습니다.
  • 브라우저에서 상대 단위는 원하는 font-size에 맞게 조정됩니다.

역주(Translation with notes) – 용어 참고

EM-square : http://designwithfontforge.com/en-US/The_EM_Square.html
폰트 매트릭스 관련 용어 : http://typostrate.com/resources/typography-lexicon/


Catamaran 폰트를FontForge에서 열어 매트릭스를 살펴봅시다:

  • em-square 1000입니다.
  • ascender 1100이고, descender 540입니다몇 가지 테스트를 진행해 보니브라우저가 OSX에서는 HHead Ascent/Descent 값을 사용하며, Windows에서는 Win Ascent/Descent 값을 사용하는 것으로 보입니다.( 값들은 서로 다릅니다.) 또한, Capital Height 680이고, X height 값은 485입니다.


그림 3. FontForge 사용한 폰트 매트릭스 

, Catamaran 폰트는 1000단위 em-square에서 1100+540단위를 사용하며, font-size: 100px;을 설정하면 높이값은 164px 됩니다 계산된 높이는 요소의 content-area 정의하며 아티클의 나머지 부분에서  용어를 참조할 것입니다. content-area는 background 값 적용되는 영역이라고 생각할  있습니다. 2

또한 대문자는 68px(680단위), 소문자(x-height) 49px(485단위) 것이라는 것을 예상할  있습니다결과적으로, 1ex = 49px 이고, 1em  164px 아닌 100px입니다. (감사하게도, em은 계산된 높이값이 아닌 font-size 기반으로 합니다.)


그림 4. Catamaran 폰트: font-size:100px;을 설정했을 때 UPM(Units Per Em)과 픽셀 사이즈.

 자세히 살펴보기에 앞서앞으로 살펴볼 내용과 관련된 사항을 간단히 정리하도록 하겠습니다. <p>요소가 스크린에 렌더링  너비에 따라서 여러 줄로 구성될  있습니다각각의 라인은 한개 혹은 여러개의 인라인 요소들(HTML 태그 혹은 텍스트 컨텐츠를 위한 익명의 인라인 요소)들로 구성될  있으며 라인은 line-box라고 불립니다. line-box 높이는 자식 요소의 높이값을 기반으로 합니다. 결국 브라우저는  인라인 요소들의 높이를 계산합니다그리고 자식의 가장 높은 곳부터 가장 낮은 곳까지의 길이가 line-box의 높이가 됩니다. 결과적으로, line-box는 기본적으로 그들의 자식을 모두 포함하기 충분한 높이값을 가지게 됩니다.

 HTML 요소는 사실 line-box의 스택입니다. 만약 각 line-box 높이값을 알고 있다면여러분은  요소의 높이값을 알고 있을 것입니다.

이전의 HTML 코드를 아래와 같이 업데이트 한다면:

3개의 line-box 생성될 것입니다.

  • 첫 번째와 마지막 line-box는 각각 단일 익명 인라인 요소(text context)를 포함하고 있습니다.
  • 두 번째 line-box는 두 개의 익명 인라인 요소와, 3개의 <span> 포함하고 있습니다.


그림 5. <p>(검은 ) line-box( )들로 만들어져 있습니다 line-box 인라인 요소들(실선)  익명 인라인 요소들(점선) 포함하고 있습니다.

두 번째 line-box의 높이가 그것의 자식  자세히 말하면 Catamaran 폰트를 사용한  때문에 계산된 content-area로 인해 다른 line-box들에 비해 확연이 높은 것을   있습니다.

line-box의 형성에서 가장 어려운 부분은 우리가 정확하게   없을 뿐만 아니라 CSS 이것을 제어   없다는 것입니다. ::first-line을 이용해 background를 적용하더라도 line-box의 높이 대한 어떠한 시각적인 단서도 제공하지 않을 것입니다.

line-height : 문제를 뛰어 넘어서

지금까지 두 가지 개념에 대해 설명했습니다 : content-area와 line-box. 만약 앞의 내용을 잘 읽었다면, line-box의 높이는 자식 요소의 높이에 따라 계산된다고 했다는 것을 알 수 있을 것입니다. 분명히 말하지만, line-box의 높이는 자식 요소의 content-area 높이가 아닙니다. 이것은 매우 큰 차이를 만듭니다.

이 말이 이상하게 들리겠지만, inline 요소는 두 개의 다른 높이를 가집니다 : content-area 높이와 virtual-area 높이.(우리 눈에 보이지 않는 높이라는 의미로 virtual-area라는 용어를 지어냈기 때문에, 어떤 스펙 문서에서도 이를 찾아볼 수 없을 것입니다.)

  • content-area높이는 앞에서 보았듯이 폰트 매트릭스에 의해 정의됩니다.
  • virtual-area높이는 line-height이고, 이 높이는 line-box의 높이 계산에 사용됩니다.


그림 6. 인라인 요소는 두 가지 높이 값을 가집니다.

즉, line-heightbaseline간의 간격이라는 일반적인 믿음이 무너졌습니다. 적어도 CSS에서는 말입니다. 3


그림 7. CSS에서 line-height는 baseline 간의 간격이 아닙니다.

virtual-area와 content-area 높이의 차이를 행간이라고 합니다. 이 행간의 절반은 content-area의 상단에 더하고, 나머지 절반은 하단에 더합니다. 때문에 content-area는 항상 virtual-area의 중앙에 위치합니다.

계산된 line-height(virtual-area)는 content-area보다 더 클뿐만 아니라 작거나 같을 수도 있습니다. content-area 보다 virtual-area가 작은 경우, 행간은 음수가 되며 line-box는 자식보다 더 작아 보입니다.

다른 종류의 inline 요소가 있습니다 :

  • 대체된 inline 요소(<img>, <input>, <svg> 등)
  • inline-block과 모든 inline-* 요소
  • 특정한 formatting context가 있는 인라인 요소(예. flexbox안의 모든 flex아이템들은 블록화됩니다.)

이러한 특정 인라인 요소들 때문에, 높이는 height, margin 그리고 border 속성을 기반으로 계산됩니다. 만약 height 값이 auto이면 lihe-height가 사용되고, content-arealine-height와 정확히 동일해집니다.


그림 8. 대체된 인라인 요소(repaced inline elements), inline-block / inline-*, 블록화 된 인라인 요소는 height나 line-height와 동일한 content-area를 가집니다.

어쨌든, 여전히 우리가 직면하고 있는 문제는 ‘line-height 속성의 normal 값은 얼마일까?’ 입니다. 그것은 content-area의 높이 계산과 관련이 있는데, 폰트 메트릭스 안에 해답이  있습니다.

FontForge로 돌아가 봅시다. Catamaran의 em-square는 1000입니다. 하지만 우리는 여러개의 ascender / descender 값들을 볼 수 있습니다 :

  • generals Ascent/Desent : ascender는 770이고 descender는 230. 문자를 그리는 데 사용됩니다. (table “OS/2”)
  • metrics Ascent/Desent : ascender는 1100이고 descender는 540. content-area의 높이에 사용됩니다. (table “hhea” and table “OS/2”)
  • metric Line Gap(두 줄의 텍스트 사이에 있어야 하는 거리, 줄 간격)의 값을 Ascent/Descent 매트릭스에 추가하여 line-height:normal의 값을 구합니다.

Catamaran 폰트의 line-gap은 0이므로 line-height : normalcontent-area와 동일한 값을 가집니다. 이 값은 1640 단위 또는 1.64입니다.

이와 비교하여 Arial폰트의 em-square는 2048 단위이며, ascender는 1854, descender는 434, line gap은 67입니다. font-size: 100px을 설정했을 때, content-area는 112px(1117 단위)이며, line-height: normal은 115px(1150 단위 혹은 1.15)임을 의미합니다. 매트릭스의 이러한 값은 폰트에 따라 다르며 각 값은 폰트 디자이너가 설정합니다.

이제 line-height : 1을 설정하는 것은 나쁜 습관이라는 사실이 명백해졌습니다. 단위 없는 값은 content-area가 기준이 아닌 font-size의 상대 값이며 content-area 보다 작은 virtual-area을 다루는 것이 많은 문제의 원인이 된다는 것을 기억하길 바랍니다.


그림 9. line-height: 1을 사용하면 line-box가 content-area보다 작을 수 있습니다.

하지만 line-height:1만 문제가 되는 것은 아닙니다. 도움이 될진 모르겠지만, 제 컴퓨터에 설치되어있는 1117개의 폰트(네, 저는 Google Web Font의 모든 폰트를 설치했습니다.) 중 약 95%에 달하는 1059개의 폰트가 1보다 큰 계산된 line-height를 가집니다. 폰트의 계산된 line-height는 0.618~3.378의 값을 가집니다. 여러분은 분명히 봤습니다. 3.378이라는 숫자를요!

line-box 계산에 대한 세부 사항:

  • 인라인 요소의 경우 padding 및 border가 배경 영역은 증가시키지만 content-area의 높이는 증가시키지 않으며 line-box의 높이도 증가시키지 않습니다. 따라서 content-area는 항상 화면에서 볼 수 있는 것은 아닙니다. margin-topmargin-bottom은 아무런 영향을 미치지 않습니다.
  • 대체된 인라인 요소(replaced inline elements), inline-block 및 블록화 된 인라인 요소의 경우 : padding, margin, border가 높이를 증가시키므로 content-area의 높이가 line-box의 높이가 됩니다.

vertical-align: 모든 것을 지배하는 하나의 속성

vertical-align 속성이 line-box의 높이를 계산하는데 필수적인 요소임에도 불구하고 아직 이에 대해 언급하지 않았습니다. 심지어는 vertical-align이 inline formatting context에 주도적인 역할을 할 수 있다 말할 수도 있습니다.

vertical-align의 기본 값은 baseline입니다. 폰트 매트릭스의 ascender와 descender를 기억하시나요? 이 값의 비율에 따라 baseline의 위치를 결정합니다. ascenders와 descenders 사이 비율이 거의 50/50이 아니기 때문에 예기치 않은 결과가 발생할 수 있습니다. 형제 요소로 예를 들어봅시다.

아래 코드로 시작해봅시다:

font-family, font-size 고정된 line-height를 상속한 2개의 형제 <span> 요소를 갖고 있는 <p> 태그가 있습니다. Baseline은 일치하고 있으며, line-box의 높이와 line-height값은 같습니다.


그림 10. 동일한 폰트 값, 동일한 baseline 모든 것이 괜찮아 보입니다.

두 번째 요소의 폰트 크기가 더 작은 경우 어떻게 될까요?

이상하게 들리겠지만, 기본 baselines 정렬은 아래 이미지에서 볼 수 있듯이 더 높은(!) line-box를 만들 수 있습니다.

line-box의 높이는 자식 요소의 가장 높은 지점에서 가장 낮은 지점까지를 계산한 값이라는 것을 기억합시다.


그림 11. 더 작은 자식 요소는 line-box의 높이를 더 높일 수 있습니다.

line-height에 단위 없는 값을 사용하는 것이 좋다라는 주장이 있을 수 있습니다. 하지만, 완벽한 수직 리듬을 만들기 위해서는 고정된 값이 필요합니다. 솔직히 말하면, 당신이 무엇을 선택하든 항상 인라인 정렬에 문제를 겪을 것입니다.

또 다른 예를 봅시다. line-height: 200px<p> 태그는 line-height값이 상속된 한 개의 <span>을 포함하고 있습니다.

line-box의 높이는 얼마입니까?  우리는 200px을 기대 하지만, 실제 값은 그렇지 않습니다. 여기서 문제는 <p> 자체에 다른 font-family (기본값으로 serif)가 있다는 것입니다. <p> 태그와 <span> 사이의 baseline이 다를 수 있기 때문에, line-box의 높이가 예상보다 높게 나타납니다. 이러한 현상이 발생하는 이유는 너비가 0인 문자(strut)에서부터 line-box가 시작되는 것처럼 브라우저가 계산을 하기 때문입니다.

보이지 않는 문자, 눈에 보이는 영향.

이야기를 계속하자면, 우리는 여전히 형제 요소에 관하여 이전과 동일한 문제에 직면해 있습니다.


그림 12. 각 자식은 line-box가 보이지 않는 너비 0인 문자로 시작하는 것처럼 정렬됩니다.

baseline 정렬에서는 틀어져 보입니다. 그렇다면, vertical-align: middle을 사용해보면 어떨까요? 스펙에서 읽을 수 있듯이 middle값은 “부모 박스의 baseline에 부모의 x-height의 절반을 더한 지점에 박스의 수직 중간 점을 정렬시킵니다”. x-height 의 비율이 다른 것처럼 baselines의 비율도 다르기 때문에 middle 정렬 또한 신뢰할 수 없습니다. 최악의 경우 대부분의 시나리오에 middle은 절대로 “중간에” 있지 않습니다. 너무 많은 요인(x-height, ascender/ descender ratio 등)들이 관련되어 있으며 CSS를 통해 설정할 수 없습니다.

 

부가적으로, 다음과 같은 4가지 값이 있으며 경우에 따라 유용합니다.

  • vertical-align: top / bottom을 이용한 line-box의 상단 또는 하단 정렬
  • vertical-align: text-top / text-bottom을 이용하여 content-area의 상단 또는 하단에 정렬


그림 13. Vertical-align: top, bottom, text-top, text-bottom

하지만 이 역시 조심해야 합니다. 모든 경우에 virtual-area을 기준으로 정렬하므로 보이지 않는 높이가 생깁니다. vertical-align: top을 사용한 간단한 예제를 확인해 봅시다. 보이지 않는 line-height는 이상하긴 해도 그다지 놀랍지 않은 결과를 낳을 것입니다.


그림 14.vertical-align은 처음에는 이상한 결과를 만드는 듯 하지만, line-height를 시각화 한다면 충분히 예상가능합니다.

마지막으로 vertical-align은 기준선과 관련하여 박스를 올리거나 내리는 숫자 값도 허용합니다. 이 옵션이 유용할 수 있을 것입니다.

CSS는 놀랍습니다

line-heightvertical-align이 어떻게 함께 동작하는지에 대해서 알아보았습니다. 하지만 이제 ‘폰트 매트릭스는 CSS로 제어 가능한가?’라는 의문이 듭니다. 저도 정말로 가능하길 바라지만 대답은 ‘아니요!’입니다. 어찌 되었든, 저는 우리가 무언가 좀 더 해보아야 한다고 생각합니다. 폰트 매트릭스는 변함이 없기 때문에 우리는 무언가 할 수 있어야 합니다.

예를 들어 대문자 높이가 정확히 100px인 Catamaran 폰트의 텍스트를 사용하기를 원한다면 어떻게 해야 할까요? 할 수 있을 것 같은데…. 수학 좀 해 봅시다.

첫 번째로 CSS 커스텀 속성4을 이용해 폰트 매트릭스를 설정하고, 대문자 높이가 100px이 될 수 있도록 font-size를 계산합니다.


그림 15. 이제 대문자의 높이는 100px이 되었습니다. 

꽤 간단하지 않나요? 아닌가요? 하지만 저 텍스트가 세로 중앙 정렬되어 보이길 원한다면, 그래서 저 남은 공간을 “B” 위와 아래에 똑같이 분배하고 싶다면 어떻게 해야 할까요? 이 요구사항을 반영하기 위해서는 vertical-align을 ascender/descender 한 비율을 기반으로 계산해야 합니다.

첫째, line-height: normal과 content-area의 높이 계산 :

다음 단계에서 필요한 사항 :

  • 대문자의 하단부터 하단 선의 거리
  • 대문자의 상단부터 상단 선의 거리

다음과 같이 구합니다 :

이제 vertical-align을 계산할 수 있습니다. 이 두 간격의 차와 계산된 font-size를 곱한 값입니다.(반드시 인라인 자식 요소에 이 값을 적용해야 합니다.)


마지막으로, 원하는 line-height를 설정하고 세로 중앙 정렬을 유지하면서 그것을 계산합니다.  


그림 16. line-height 변화에 따른 결과. 텍스트는 항상 세로 중앙 정렬됩니다.

“B”와 높이가 일치하는 아이콘을 추가하는 것은 이제 쉽습니다 :


그림 17. 아이콘과 B는 같은 높이를 가집니다.

JSBin에서 결과 확인하기

주의할 점은 이 테스트는 오직 설명을 목적으로 했다는 것입니다. 이것에 의지하면 안 되는 많은 이유가 있습니다.

  • 폰트 매트릭스가 일정하지 않는 이상 , 브라우저의 계산이 항상 달라집니다. ¯⁠\⁠(ツ)⁠/⁠¯
  • 폰트가 로드되지 않는 경우, 폴백 폰트는 다른 폰트 매트릭스를 가질 것입니다. 다양한 값들을 처리하는 것을 빠르게 관리하기 어려워집니다.

얻은 것

우리가 배운 것들은..

  • inline formatting context는 매우 이해하기 힘듭니다.
  • 모든 인라인 요소는 2가지 높이가 있습니다.
    • 폰트 매트릭스 기반의 content-area
    • virtual-area(line-height)
    • 이 2가지 높이 중 어느 것도 의심할 여지없이 시각화할 수 없습니다. (만약 당신이 개발자 도구 개발자이고 이 작업을 할 수 있게 만든다면, 이것은 정말 놀라운 일이 될 것입니다.)
  • line-height: normal은 폰트 매트릭스 값을 기반으로 합입니다.
  • line-height: n은 virtual-area를 content-area 보다 더 작게 만들 수도 있습니다.
  • vertical-align은 매우 신뢰할 수 없습니다.
  • line-box의 높이는 자식들의 line-height 와 vertical-align 속성을 기반으로 계산됩니다.
  • 폰트 매트릭스를 CSS로 쉽게 읽거나 설정할 수 없습니다.

하지만 저는 여전히 CSS를 사랑합니다. 🙂

참고자료

각주

  1. 어떤 값을 선언하든 상관없습니다. 여기서 중요한 포인트는 아닙니다. 
  2. 엄밀히 말하면 그것은 사실이 아닙니다. 
  3. 다른 편집 소프트웨어에서는, 베이스라인 사이의 거리일 수 있습니다.  Word나 Photoshop이 이런 경우입니다. 주요 차이점은 CSS에서는 첫 번째 라인도 영향을 받는다는 것입니다. 
  4. 전처리기의 변수를 이용해도 괜찮습니다. 커스텀 속성이 필수는 아닙니다.