장용석 블로그
8 min read
text-wrap balance 그 뿌리를 찾아서 [1]

인트로

CSS text-wrap: balance  |  CSS and UI  |  Chrome for Developers

A classic typography technique of hand-authoring line breaks for balanced text blocks, comes to CSS.

https://developer.chrome.com/docs/css-ui/css-text-wrap-balance?hl=en

text-wrap: balance 아직 들어보지 못한 사람들도 있겠지만, 위의 구글 문서에서 살펴볼 수 있는 것 처럼 크롬 114버전 부터 지원된 CSS Text Level4에 속하는 CSS 속성이다.

어떤 기능인지 먼저 살펴보고 가자

text-wrap: balance 란?

See the Pen Animated comparison of balanced and unbalanced headlines by web.dev (@web-dot-dev) on CodePen.

출처: https://developer.chrome.com/docs/css-ui/css-text-wrap-balance

2024년 3월 기준 safari가 아니고, 크롬 114버전 이상이면 제대로 보일 것 이다. 여기 까지 보았을 때는, 단어의 뜻과 예제를 참고했을때, text-wrap: balance는 텍스트의 줄바꿈을 균형있게 해주는 기능이라고는 예상 할 수 있을 것이다.

그런데 뭔가 이질감이 드는 부분이 있다.

뭔가 다른 명확한 속성들과 별개로 ‘균형’ 이라는 단어가 컴퓨터와 대화하는 과정에서는 어색하게 느껴진다. \

chrome 페이지의 소개 문구 또한 아래와 같이만 설명되고 있다.

you can request the browser to figure out the best balanced line wrapping solution for the text

어떤 기준을 가지고 균형을 맞추는 것인지, 그것이 궁금해졌다.

다이빙전 준비 체조로 이 기능의 명세와, 기원에 대해 알아보자.

w3c wg에 적힌 text-wrapping의 명세를 살펴보자.
https://www.w3.org/TR/css-text-4/#text-wrapping

중요한 부분을 살펴보면 아래와 같다.

아하! 어느정도 구체적인 결정 방식이 설명되어 있다.
남은 공간의 크기가 평균에 가까워지도록 줄바꿈을 결정하는 것이다. 쉽게 말하면, 줄바꿈을 결정할 때, 줄바꿈 후 남은 공간의 크기가 비슷하도록 하는 것이다.

그럼 더 깊이 가보기 전 남은 궁금증이 있다.
이 기능은 어디서 나왔을까?

기원

이라고 하기엔 그렇게 근본이 있는 것은 아니지만…

이전에는 가독성을 위해서 <wbr> 태그나, &shy;를 사용하여 사람이 직접 줄바꿈을 조절했다.
혹은 javascript로 줄바꿈을 조절하는 라이브러리를 사용했다.

Headline Balancing Act. Text-balancer is a javascript module… | by The NYT Open Team | NYT Open

Text-balancer is a javascript module that seeks to eliminate typographic widows from text.

https://open.nytimes.com/headline-balancing-act-6e92d3d6119
Headline Balancing Act. Text-balancer is a javascript module… | by The NYT Open Team | NYT Open

뉴욕 타임즈에서 보면, 이 기능은 신문의 헤드라인을 균형있게 만들기 위해 사용되었다고 한다. (관련 JS 코드 https://github.com/nytimes/text-balancer)\ 다른 예로는 https://github.com/adobe/balance-text, https://github.com/shuding/react-wrap-balancer 등이 있다.

안드로이드 쪽에는 이미 적용되어 있는 부분도 있다. (https://cs.android.com/android/platform/superproject/+/master:frameworks/minikin/include/minikin/LineBreaker.h;l=38;drc=5ca657189aac546af0aafaba11bbc9c5d889eab3?hl=ja)

종합적으로 광고나, 신문, 웹페이지 등에서 텍스트의 가독성을 위해 사용되었던 기능이라고 볼 수 있다.

다이빙

대충 어떤 기능인지, 어디서 나왔는지 알았으니, 이제 물러나도 좋다.
지금부터는 이 기능의 시작부터 끝까지 살펴보는 것이다.

관련된 문서 자료부터 살펴보자

Intent to Ship: CSS healine balancing

’Koji Ishii’가 blink-dev 그룹에 올린 이메일이다. 아래로 죽 그룹간의 토론이 이어진다. 우선 Koji의 이메일 부터 살펴보자.

Specification

https://w3c.github.io/csswg-drafts/css-text-4/#valdef-text-wrap-balance\ csswg-drafts에 올라가있는 명세이다.

Design doc

https://docs.google.com/document/d/16-T9gqCagJxcST6hcnneSb7qGunxXa37_UHYqMqhPL0/edit?usp=sharing ‘Koji Ishii’가 작성한 기술의 디자인에대한 문서이다. 그리 길지는 않고, 구현에 대한 설명이 주를 이루고 있다. 나중에 살펴보도록 하고 이어서 이메일을 살펴보자.

TAG review

https://github.com/w3ctag/design-reviews/issues/822 여기서 TAGTechnical Architecture Group의 약자로, w3c의 기술적인 문제에 대한 리뷰를 하는 그룹이다. 해당 이슈를 통해서 text-wrap: balance에 대한 리뷰가 이루어졌다.

기본적인 내용은 이전에 살펴본 명세나 디자인 문서와 동일하고, 링크 또한 동일하다.
추가적인 것은 test에 관한 내용이 있다.

https://wpt.fyi/results/css/css-text/white-space?label=master&label=experimental&aligned&view=subtest&q=text-wrap

테스트는 w3c에서 관리하는 wpt.fyi(Web Platform Tests Dashboard)에서 이뤄지고 있다. wpt는 web platform test의 약자로, 웹 플랫폼의 표준을 테스트하는 테스트 스위트이다.

각 테스트 케이스는 html 파일로 나눠져 있고 이런식으로 들어가 있다.

https://github.com/web-platform-tests/wpt/blob/695af8663f/css/css-text/white-space/text-wrap-balance-overflow-001.html

<!DOCTYPE html>
<link rel="help" href="https://drafts.csswg.org/css-text-4/#valdef-text-wrap-balance">
<link rel="match" href="reference/text-wrap-balance-overflow-001-ref.html">
<style>
.container {
  font-family: monospace;
  font-size: 20px;
  inline-size: 15ch;
  border: 1px solid;
  overflow-wrap: break-word;
  text-wrap: balance;
}
</style>
<div class="container">CONTROLLING YOUR BU</div>
<div class="container">CONTROLLING YOUR BU BU</div>

물론 라이브로 확인해볼 수도 있다 ㅎㅎ https://wpt.live/css/css-text/white-space/text-wrap-balance-overflow-001.html

관련 PR https://github.com/web-platform-tests/interop/issues/561

뭔가 너무 많이 돌아온 것 같은데 일단 다시 이메일로 돌아가보자.

Interoperability and Compatibility

상호운용성과 호환성에 대한 이야기가 나온다. 아무래도 생태계를 위해서는 다른 브라우저들과의 호환성이 중요하다. koji가 각 브라우저 프로젝트들에 해당 기능에 대한 입장 요청을 보낸 이슈가 첨부되어 있다. 실제 엔진의 프로젝트는 아니고 standards-positions 이슈를 통해서 해당 기능에 대한 입장을 요청한 것이다.

모질라(https://github.com/mozilla/standards-positions/issues/755)

해당 리뷰의 댓글로 모질라 측에서 명세를 보고 작성한 이슈와 PR은 각각 아래서 살펴볼 수 있다.

웹킷(https://github.com/WebKit/standards-positions/issues/143)

애플의 웹킷 쪽은 여기서 버그를 트래킹하고 있다. https://bugs.webkit.org/show_bug.cgi?id=249840

관련 PR은 이쪽에서 볼 수 있다. \

Provide an initial implementation of `text-wrap: balance` by RWDavid · Pull Request #16723 · WebKit/WebKit · GitHub

2d299a2 Provide an initial implementation of `text-wrap: balance` https://bugs.webkit.org/show_bug.cgi?id=249840 rdar://problem/103663513 Reviewed by Alan Baradlay. This patch implements `text-wrap: balance` in IFC. It uses a Knuth-Plass style dynamic programming algorithm to minimize the variance of all the line lengths. There is no line limit imposed on this implementation. Ideally, the act of balancing inline content will use the same number of lines as if the inline content was laid out via `text-wrap: wrap`. However, adhering to this ideal is expensive, and not caring about this ideal will allow us to use a more efficient algorithm. Since inline content spanning many lines is less likely to care about the exact preservation of vertical space (i.e. number of lines used), we ignore this ideal number of lines requirement beyond a certain line count (tentatively set to 12 lines for now). There are two implementations of the Knuth-Plass style algorithm: (1) one which attempts to preserve the number of lines used, and (2) one that ignores that requirement. If we let N denote the number of break opportunities, let L denote the number of lines used, and let K denote the maximum number of inline items that can fit in a single line, then algorithm (1) has a time complexity of O(N * L * K), while algorithm (2) has a time complexity of O(N * K). We use algorithm (1) when we want to preserve the number of lines used, and we use algorithm (2) when we want to prioritize performance (i.e. when inline content spans many lines). It is worth noting that algorithm (2) will often also preserve the number of lines used. In the case that it does not, the number of lines will differ only by a small amount. This is an initial implementation, and not all features are supported. Unsupported features include (and are not limited to): - first-line styling - indentation - white-space-collapse - tabs - hyphens - floats (including initial-letter) * LayoutTests/TestExpectations: * LayoutTests/platform/mac-wk2/TestExpectations: * Source/WebCore/Sources.txt: * Source/WebCore/WebCore.xcodeproj/project.pbxproj: * Source/WebCore/layout/formattingContexts/inline/InlineContentBalancer.cpp: Added. (WebCore::Layout::computeBreakOpportunities): (WebCore::Layout::computeCost): (WebCore::Layout::InlineContentBalancer::InlineContentBalancer): (WebCore::Layout::InlineContentBalancer::initialize): (WebCore::Layout::InlineContentBalancer::computeBalanceConstraints): (WebCore::Layout::InlineContentBalancer::balanceRangeWithLineRequirement): (WebCore::Layout::InlineContentBalancer::balanceRangeWithNoLineRequirement): (WebCore::Layout::InlineContentBalancer::inlineItemWidth const): (WebCore::Layout::InlineContentBalancer::shouldTrimLeading const): (WebCore::Layout::InlineContentBalancer::shouldTrimTrailing const): (WebCore::Layout::InlineContentBalancer::SlidingWidth::SlidingWidth): (WebCore::Layout::InlineContentBalancer::SlidingWidth::width): (WebCore::Layout::InlineContentBalancer::SlidingWidth::advanceStart): (WebCore::Layout::InlineContentBalancer::SlidingWidth::advanceStartTo): (WebCore::Layout::InlineContentBalancer::SlidingWidth::advanceEnd): (WebCore::Layout::InlineContentBalancer::SlidingWidth::advanceEndTo): * Source/WebCore/layout/formattingContexts/inline/InlineContentBalancer.h: Added. * Source/WebCore/layout/formattingContexts/inline/InlineFormattingContext.cpp: (WebCore::Layout::InlineFormattingContext::layoutInFlowAndFloatContent): * Source/WebCore/layout/formattingContexts/inline/TextOnlySimpleLineBuilder.cpp: (WebCore::Layout::TextOnlySimpleLineBuilder::isEligibleForSimplifiedTextOnlyInlineLayout): * Source/WebCore/layout/integration/LayoutIntegrationCoverage.cpp: (WebCore::LayoutIntegration::shouldInvalidateLineLayoutPathAfterChangeFor): Canonical link: https://commits.webkit.org/266980@main 26a467c Misc iOS, tvOS & watchOS macOS Linux Windows ✅ 🧪 style ✅ 🛠 ios ✅ 🛠 mac ✅ 🛠 wpe ✅ 🛠 wincairo ✅ 🧪 bindings ✅ 🛠 ios-sim ✅ 🛠 mac-AS-debug   🧪 wpe-wk2 ✅ 🧪 webkitperl ✅ 🧪 ios-wk2 ✅ 🧪 api-mac ✅ 🛠 gtk ✅ 🧪 ios-wk2-wpt ✅ 🧪 mac-wk1 ✅ 🧪 gtk-wk2 ✅ 🧪 api-ios ✅ 🧪 mac-wk2 ✅ 🧪 api-gtk ✅ 🛠 tv   🧪 mac-AS-debug-wk2 ✅ 🛠 tv-sim ✅ 🛠 🧪 merge ✅ 🛠 watch ✅ 🛠 watch-sim

https://github.com/WebKit/WebKit/pull/16723
Provide an initial implementation of `text-wrap: balance` by RWDavid · Pull Request #16723 · WebKit/WebKit · GitHub

애플의 경우 EWS(early warning system)을 통해서 병합전 각 환경(OS별)별 빌드 결과를 확인할 수 있다.
여기서 다룰 내용은 아니므로 각자 들어가서 구경해보자.
EWS 링크(https://ews-build.webkit.org/#/builders/26/builds/15587)
EWS(https://github.com/webkit-early-warning-system)

우리가 어디 까지 왔는지 다시 정리해보자.

우리는 koji의 이메일 본문을 통해서 해당 기능의 명세, 디자인 문서, TAG 리뷰, 테스트, 각 브라우저별 호환성에 대한 이슈를 살펴보았다. 또한 각 브라우저별로 해당 기능에 대한 입장을 요청한 이슈와 PR을 살펴보았다.

본문 자체는 이정도로 살펴보면 좋을 것 같고… 해당 이메일을 수신 받은 blink-dev 그룹이 주고 받은 내용을 살펴보자.

라고 하려고 했는데, 내용이 상당히 길기도 하고. 굳이 살펴볼 필요가 있을까 싶다.
주로 성능적 이슈에 대한 내용이라던지 다른 속성과의 관계(white-space), 구현에 대한 명확성, 기능에 대한 제어(마지막 줄 길이를 4em으로 할건지 전체 줄의 최소 15%로 할건지 등) 등에 대한 토론이 이루어졌다.

크게 다뤄진 이슈만 요약해보자.

text-wrap의 경우, 줄바꿈을 제어하는 속성인데, 이는 기존의 white-space와 충돌하는 기능이다. 그에 따라 이 둘 사이의 정리가 필요하다는 것이다.
white-space 속성은 공백 처리와 줄바꿈 방식을 제어하지만, text-wrap: balance와 같은 값을 사용할 때는 줄바꿈의 균형을 맞추는 추가적인 동작이 요구된다.
해결 방안으로는 white-space 속성이 text-wrap의 값도 내포할 수 있도록, 즉 shorthand로서 기능하도록 하는 방법이 제안되었다.

비슷한 이전 사례로는 baseline-source 이 있는데, 이때의 경우도 vertical-align과 충돌하는 문제가 있었고, 이를 해결하기 위해 vertical-align의 값으로 baseline-source를 사용할 수 있도록 하는 방법이 제안되었다. [baseline-source] Set baseline-source to auto when vertical-align set.

잠시… 멈추고…

이렇게 쭉 살펴보다보니, 하나의 기능이 얼마나 많은 과정을 거쳐서 브라우저에 적용되는지 알 수 있었다.
하지만 우리의 본래의 여정은 text-wrap: balance의 명세와 구현에 대한 것이었다.
여태 관련 명세 및 작업에 대한 것만 쭉 살펴보다가 이만큼 와버렸기에. 더이상 구현에 대한 것을 마저 정리할 힘이 바닥나버렸다.

아무래도 명세를 읽다보니 수많은 하이퍼텍스트의 늪에 빠져버린 것이다.
그래도 각 엔진들의 이슈와 PR이 어디서 관리되는지 Test는 어디서 진행되는지. 궁금한 이에게 이정표가 되었길 바라며.

그래서 이번 포스트는 여기까지만 하고, 다음 포스트에서는 실제 구현에 대한 것을 살펴보도록 하겠다. (자료조사는 해두었다. 걱정 말기를)

이만

RSS 구독