한글 웹 타이포그래피 (4) 글 길이와 줄끝 처리

한글 웹 타이포그래피 (4) 글 길이와 줄끝 처리

앞 세 편에서 서체와 글자 크기, 굵기, 대비까지 정했다. 본문은 고딕 17px, 대비는 4.5:1 이상. 이제 그 글자를 한 줄에 몇 개까지 늘어놓을지, 그리고 그 줄을 어디서 끊을지를 정할 차례다.

글 길이와 줄끝은 따로 노는 문제 같지만 한 몸이다. 한 줄이 너무 길면 눈이 다음 줄 첫 글자를 못 찾고, 줄을 아무 데서나 끊으면 한 어절이 두 줄에 걸쳐 읽는 리듬이 끊긴다. 둘 다 결국 다음 글자를 얼마나 쉽게 따라가느냐의 문제다.

한 줄에 몇 글자가 적당한가

서구 타이포그래피에는 오래된 상수가 있다. 한 줄 45~75자, 이상적으로는 66자. 브링허스트가 정리한 이 값은 지금도 웹에서 라틴 본문의 기준으로 통한다. 줄이 이보다 길어지면, 특히 80자를 넘기면 한 줄을 다 읽고 다음 줄 맨 앞으로 시선을 되돌릴 때 엉뚱한 줄로 튀는 일이 잦아진다.

문제는 이 66자를 한글에 그대로 못 쓴다는 거다. 라틴 알파벳은 대체로 폭이 좁고 글자마다 폭이 다르다. 반면 한글은 네모틀(square frame) 안에 꽉 찬 글자라 한 자의 폭이 라틴 한 자의 대략 두 배다. 같은 66자라도 한글로 늘어놓으면 물리적인 줄 길이가 두 배 가까이 길어진다. 그러니 글자 수 기준도 절반쯤으로 내려야 한다.

이 감을 표준으로 못박아 주는 근거가 접근성 기준에 있다. WCAG 1.4.8은 본문 폭을 라틴 계열 80자 이하로 권하는데, 한중일(CJK) 문자는 그 절반인 40자 이하로 명시한다. 저시력이나 인지적 부담이 있는 독자를 위한 상한이다. 실사용 권장으로 좁히면 한글 본문 한 줄은 대략 35~45자, 목표값은 40자 언저리다.

예를 들어 지금 읽는 이 문단이 한 줄에 60자씩 담긴다고 해보자. 문장 하나가 끝나기도 전에 눈이 화면 오른쪽 끝까지 갔다가 다시 왼쪽 끝으로 크게 되돌아와야 한다. 한 번은 괜찮지만 문단 열 개를 그렇게 읽으면 눈이 먼저 지친다. 40자면 시선이 되돌아오는 거리가 짧아서 줄을 잃어버리지 않는다.

글자 수를 픽셀로 옮기기 (measure)

한 줄의 글자 수, 이걸 조판에서는 줄 길이(measure)라고 부른다. 그런데 CSS에는 “한 줄 40자"라고 적는 속성이 없다. 우리가 정할 수 있는 건 픽셀 폭이다. 그러니 40자를 폭으로 환산해야 한다.

이 블로그의 실제 토큰으로 계산해보자. 본문 컨테이너는 720px, 좌우 패딩이 각각 20px이니 글자가 실제로 놓이는 폭은 680px다. 본문 크기 17px 고딕에서 한글 한 자의 평균 폭은 대략 17px에 가깝다. 680을 17로 나누면 40. 즉 680px 폭이 곧 한글 약 40자이고, 이게 WCAG의 CJK 40자와 딱 맞아떨어진다.

그래서 measure는 본문 폭을 680px로 잡는 문제로 바뀐다. .markdown 래퍼에 max-width를 걸면 된다.

.markdown {
  max-width: 680px;   /* 한글 약 40자, WCAG CJK 40자 */
  margin-inline: auto;
}

max-width이지 width가 아니라는 게 중요하다. 화면이 680px보다 좁은 모바일에서는 폭이 화면에 맞춰 줄어들고, 넓은 데스크톱에서는 680px에서 멈춘다. 어느 화면에서도 한 줄이 40자를 크게 넘지 않는다.

여기엔 예외가 있다. 코드 블록과 표는 measure에서 빼야 한다. 코드는 한 줄이 길어도 중간에서 꺾이면 안 되고, 표는 열이 많으면 680px 안에 다 못 들어간다. 이 둘은 본문 폭을 넘겨서 넓게 두고, 넘치는 만큼 가로 스크롤을 허용한다.

.markdown pre,
.markdown table {
  max-width: none;      /* 본문 measure 제약에서 제외 */
  overflow-x: auto;     /* 넘치면 가로 스크롤 */
}

줄을 어절 단위로 끊기

폭을 정했으니 이제 그 폭에서 줄이 어떻게 끊기는지를 봐야 한다. 여기서 한글이 라틴과 또 다르게 논다.

브라우저의 기본 줄바꿈 규칙은 이렇다. 띄어쓰기가 있는 언어는 공백에서 줄을 끊고, 한중일 문자는 음절, 즉 글자 단위로 끊는다. 이게 CSS 스펙에 적힌 word-break의 기본 동작이다. 문제는 한글이 띄어쓰기를 쓰는데도 CJK로 분류돼서, 폭이 부족하면 한 어절 중간에서 글자 단위로 뚝 끊긴다는 거다.

예를 들어 “타이포그래피"라는 한 단어가 줄 끝에 걸리면 기본값에서는 “타이포그"까지 한 줄에 남고 “래피"가 다음 줄로 넘어갈 수 있다. 읽는 사람 입장에서는 한 단어가 두 동강 난 셈이라 순간 멈칫한다. 이걸 막는 게 word-break: keep-all이다.

.markdown {
  word-break: keep-all;   /* 어절 중간에서 끊지 않음 */
}

MDN 기준으로 keep-all은 CJK 텍스트가 단어 중간에서 끊기는 것을 막는다. 한글에서는 줄바꿈이 오직 어절 사이 공백에서만 일어나서, “타이포그래피"가 통째로 다음 줄로 넘어간다. 한 어절이 온전히 붙어 있으니 읽는 리듬이 끊기지 않는다.

keep-all에는 부작용이 하나 있다. 공백 없이 아주 긴 문자열, 예를 들어 긴 URL이나 좁은 표 셀 안의 긴 영단어는 끊을 자리가 없어서 박스를 뚫고 넘친다. 이럴 때만 overflow-wrap을 같이 걸어 빠져나갈 구멍을 만든다.

.markdown {
  word-break: keep-all;
  overflow-wrap: anywhere;   /* 끊을 자리 없는 긴 문자열만 강제로 끊음 */
}

여기서 스펙상의 역할을 구분해두면 헷갈리지 않는다. word-break는 단어 안쪽에서의 줄바꿈을 다루고, overflow-wrap은 단어가 박스를 넘칠 때 단어 안팎 모두에서 마지막 수단으로 끊는다. 그래서 둘을 같이 쓰면 평소엔 어절 단위로 깔끔하게 끊다가, 도저히 안 될 때만 긴 문자열을 강제로 자른다.

양끝맞춤(justify)을 피하는 이유

줄끝을 얘기하면 자연스럽게 나오는 질문이 있다. 오른쪽 끝을 가지런히 맞추면 더 깔끔하지 않냐는 것. 즉 양끝맞춤(justify)이다. 결론부터 말하면 웹 한글 본문에는 웬만하면 쓰지 않는다.

여기서 정직하게 구분해야 할 게 있다. 인쇄 조판에서 한중일 텍스트는 오히려 양끝맞춤이 기본이다. 글자가 다 같은 네모틀이라 글자 사이 간격을 균일하게 벌리면 깔끔한 블록이 만들어진다. 라틴 문자보다 오히려 양끝맞춤이 자연스러운 문자 체계다. 그래서 “CJK는 양끝맞춤이 관습"이라는 말 자체는 틀린 말이 아니다.

문제는 웹 브라우저가 그 수준의 조판을 안 한다는 데 있다. text-align: justify로 한글을 맞추면 브라우저는 CSS 스펙대로 처리하는데, 한글은 띄어쓰기가 있는 언어라 글자 사이가 아니라 어절 사이 공백을 늘려서 폭을 맞춘다. 그 결과 어떤 줄은 어절 간격이 좁고 어떤 줄은 넓어져 들쭉날쭉해지고, 세로로 성긴 공백이 이어지면 흰 강(rivers) 같은 얼룩이 본문을 가로지른다.

한 줄에 어절이 서너 개밖에 안 들어가는 좁은 measure에서는 이게 더 심하다. 680px에 40자면 어절 몇 개 안 들어가는데, 그 사이를 억지로 벌려 오른쪽 끝을 맞추니 간격 차이가 눈에 확 띈다. 그러니 웹 한글 본문은 왼쪽 정렬로 두고 오른쪽은 자연스럽게 울퉁불퉁하게 둔다.

.markdown {
  text-align: left;   /* justify 대신 왼쪽 정렬, 오른쪽은 자연스럽게 */
}

인쇄에서 되는 게 웹에서 안 되는 건 원리가 틀려서가 아니라 브라우저가 CJK 양끝맞춤을 제대로 구현하지 않아서다. 이건 관습의 문제가 아니라 구현의 문제라는 걸 기억해두면, 나중에 브라우저가 글자 단위 양끝맞춤을 지원하면 이 판단을 다시 열어볼 수 있다.

외톨이 줄(orphan/widow) 관리

줄끝 처리의 마지막 골칫거리는 문단이나 제목의 마지막 줄에 단어가 딱 하나만 남는 경우다. 이런 외톨이 줄(orphan/widow)은 문단 아래에 짧은 꼬리처럼 붙어서 보기에 어수선하다.

용어를 잠깐 정리하면, 인쇄에서 orphan은 문단 첫 줄이 페이지나 단 맨 아래에 혼자 떨어진 것, widow는 문단 마지막 줄이 다음 페이지 맨 위에 혼자 올라간 것을 가리킨다. 웹에서 우리가 실제로 자주 만지는 건 이 엄밀한 정의보다, 문단이나 제목의 마지막 줄에 단어 하나만 덩그러니 남는 상황이다.

본문 문단에는 text-wrap: pretty가 잘 맞는다. 이 값은 문단 끝을 미리 살펴서 마지막 줄에 단어가 하나만 남을 것 같으면, 바로 윗줄에서 단어 하나를 끌어내려 짝을 맞춘다.

.markdown p {
  text-wrap: pretty;   /* 마지막 줄에 단어 하나만 남는 것 방지 */
}

제목은 성격이 달라서 다른 값을 쓴다. 제목은 두세 줄로 짧게 꺾이는 경우가 많은데, 이때는 줄 길이를 고르게 나누는 text-wrap: balance가 낫다. 제목이 “아주 긴 제목 한 줄과 / 단어 하나” 꼴로 깨지는 걸 막고 줄들을 비슷한 길이로 맞춰준다.

.markdown h1,
.markdown h2,
.markdown h3 {
  text-wrap: balance;   /* 제목 줄 길이를 고르게 */
}

text-wrap: balancepretty는 비교적 최근 문법이라 아주 오래된 브라우저에서는 무시된다. 다만 무시돼도 그냥 평범한 줄바꿈으로 떨어질 뿐 깨지지는 않으니, 점진적 향상(progressive enhancement)으로 얹어두면 된다. 특정 두 단어만은 반드시 붙여두고 싶을 때는 그 사이 공백을 줄바꿈 없는 공백( )으로 바꾸는 옛날 방식도 여전히 유효하다. 예를 들어 “React 18” 같은 조합이 갈라지면 곤란할 때 쓴다.

정리: measure와 줄끝 값

이번 편에서 정한 값을 한자리에 모은다. 본문 폭은 720px 컨테이너 안 680px, 한글 약 40자로 WCAG의 CJK 40자에 맞췄다. 줄끝은 어절 단위로 끊고, 양끝맞춤은 피하고, 외톨이 줄을 다듬는다.

.markdown {
  max-width: 680px;          /* measure: 한글 약 40자 (컨테이너 720px) */
  margin-inline: auto;
  text-align: left;          /* 양끝맞춤(justify) 회피 */
  word-break: keep-all;      /* 어절 단위 줄바꿈 */
  overflow-wrap: anywhere;   /* 끊을 자리 없는 긴 문자열만 강제 분리 */
}

.markdown p {
  text-wrap: pretty;         /* 문단 마지막 줄 외톨이 방지 */
}

.markdown h1,
.markdown h2,
.markdown h3 {
  text-wrap: balance;        /* 제목 줄 길이 균형 */
}

.markdown pre,
.markdown table {
  max-width: none;           /* 코드·표는 measure 예외, 넓게 */
  overflow-x: auto;
}

이 값들이 잘 먹었는지는 간단히 확인할 수 있다. 데스크톱에서 본문 한 줄을 세어보면 대략 이렇게 나온다.

본문 컨테이너 720px − 좌우 패딩 20px × 2 = 680px
680px ÷ 17px ≈ 한글 40자/줄
→ WCAG 1.4.8 CJK 40자 이하 충족

폭과 줄끝까지 정했으니 이제 한 줄 안에서 글자와 글자, 줄과 줄 사이를 얼마나 띄울지가 남았다. 다음 편에서 자간과 행간, 그리고 문단 사이 간격을 다룬다. 행간 값은 이번에 정한 measure에 직접 묶여 있다. 줄이 길수록 다음 줄 첫 글자를 찾기 어려워서 행간을 더 벌려야 하는데, 우리는 이미 줄을 40자로 짧게 잡아뒀으니 그만큼 행간 판단도 달라진다.