
Compose 한글 줄바꿈은 음절에서 끊기고, 'R/u/l/e' 세로 깨짐은 Row 측정 순서 탓이었다
'R/u/l/e'는 Row 측정 순서를, 한글 음절 줄바꿈은 WordBreak.Phrase와 단어 폭 측정을 바꿔 공용 디자인 시스템에서 따로 고쳤습니다.
라벨과 값이 나란히 놓이는 행에서 'Rule'이라는 단어가 R, u, l, e 한 글자씩 네 줄로 쌓여 있었습니다. 2026년 9월 25일 Galaxy S21(Android 15)에서 사진 앱 세 개를 실기기로 점검하던 중이었고, 'Versio/n', '1.0.1-debu/g', '오른쪽으/로'도 같은 모양으로 갈라졌습니다. 겉모습은 같아도 원인은 둘이었습니다. 앞의 세 개는 값을 먼저 재는 Row가 라벨 폭을 빼앗아서 생겼고, '오른쪽으/로'는 Compose가 한글 음절 사이를 줄바꿈 지점으로 보는 탓이었습니다. 두 원인 모두 세 앱이 함께 쓰는 디자인 시스템 코드에서 따로 고쳤습니다(커밋 633c3dd).
근거는 SugarMount 앱 저장소(sugar-utils)의 커밋 633c3dd와 같은 날짜의 CHANGELOG·백로그 항목, 한글뷰어 슈가독 기기 테스트 문서(2026-10-06), Android Developers 참조 문서입니다. 기준일은 2026년 10월 21일입니다.
목차 (8)
깨진 화면 다섯 곳과 고친 공용 컴포넌트
깨진 곳은 모두 공용 컴포넌트 안에 있었습니다. 9월 25일 QA는 AI 코딩 에이전트 네 명이 차례로 맡았습니다. 앱마다 한 명이 돌았고, 마지막 한 명은 앞선 발견을 모두 자기 화면 캡처로 다시 재현한 뒤 원인 코드를 읽었죠. 아래 표는 그날 CHANGELOG와 코드 주석에 남은 증상을 옮긴 것입니다.
| 증상 | 화면 요소 | 원인 | 고친 방법 |
|---|---|---|---|
| 'R/u/l/e', 'Versio/n', '1.0.1-debu/g' | 라벨·값 행, 설정 행 | Row가 값을 먼저 재고 라벨에는 남은 폭만 줌 | PairLayout: 둘 다 들어가면 나란히, 아니면 값을 아래 줄로 |
| '오른쪽으/로', 'Transparen/t PNG' | 옵션 타일 그리드, 동작 버튼 | 칸보다 넓은 단어. Compose가 한글의 최소 폭을 음절 하나로 보고함 | 가장 긴 단어 폭을 재서 1열로 전환 |
| 'Quick compress'가 세그먼트 3분의 1 폭에 안 들어감 | 세그먼트 컨트롤 | 큰 글꼴 | 150% 글꼴부터 세로 타일 |
| 'Web', 'Custom' 선택지가 화면 밖에 숨음 | 선택 칩 줄 | 가로 스크롤 | FlowRow로 줄바꿈 |
| 200% 글꼴에서 '메일', '왼쪽으로' 같은 어절이 갈라질 수 있음 | 모든 텍스트 스타일 | 음절 사이도 줄바꿈 지점 | WordBreak.Phrase |
세 앱은 사진 용량 줄이기, 사진 반전·회전, 이미지 변환 JPG·PNG·WebP입니다. 모두 같은 core/designsystem 코드를 가져다 사용합니다. 커밋 633c3dd는 파일 57개를 고쳤지만, 줄바꿈 수정의 중심은 이 디자인 시스템의 파일 몇 개입니다.

Compose는 한글 음절 사이도 줄바꿈 지점으로 봅니다
Compose 텍스트에서 단어를 어디서 나눌지는 LineBreak.WordBreak가 정합니다. Android Developers의 LineBreak.WordBreak 참조 문서는 Default를 "로캘의 기본 단어 나누기 규칙"이라고 설명합니다. 라틴 문자는 단어 사이에서 줄을 바꾸고, 공백을 쓰지 않는 언어(문서의 예는 일본어)는 글자 사이에서도 바꿀 수 있다고 적었습니다. Phrase는 같은 구 단위에 속한 글자 사이에는 줄을 바꾸지 않는 규칙이고, 제목과 UI 라벨 같은 짧은 텍스트에 알맞다고 합니다. 두 값 모두 Compose 1.3.0에 들어왔습니다.
한국어는 띄어쓰기를 합니다. 그런데도 앱에서는 음절 사이가 갈라졌습니다. 저장소의 WordWidth.kt 주석은 원인을 이렇게 적어 두었습니다. Compose가 계산하는 최소 고유 폭(min intrinsic width)이 한글 음절 하나하나를 줄바꿈 지점으로 본다는 겁니다. 그래서 '오른쪽으로'가 음절 한 개 폭이면 들어간다고 계산됐고, 그리드는 두 열을 유지한 채 단어를 중간에서 잘랐습니다.
플랫폼 쪽 사정도 있습니다. LineBreakConfig 문서를 보면 구 단위 줄바꿈 LINE_BREAK_WORD_STYLE_PHRASE는 API 33(Android 13)에 추가됐고, 지원 여부가 로캘에 따라 다르며 지원하지 않는 로캘에서는 효과가 없습니다. 이 앱들의 minSdk는 29입니다. Android 10~12 기기에서는 Phrase를 넣어도 아무 일도 일어나지 않죠.
WordBreak.Phrase는 공용 TextStyle 한 곳에 넣었습니다
타이포그래피를 만드는 SugarType.kt의 style() 함수 하나가 모든 글꼴 단계에 같은 lineBreak를 붙입니다. 화면 코드는 한 줄도 바꾸지 않았습니다.
private val WordAwareBreak =
LineBreak.Simple.copy(wordBreak = LineBreak.WordBreak.Phrase)
private fun style(weight: FontWeight, size: Int, line: Int, spacing: Boolean = false) = TextStyle(
// fontFamily, fontSize, lineHeight, letterSpacing ...
lineBreak = WordAwareBreak,
)
한국어 화면이 있는 Compose 앱이라면 Phrase를 Text마다 붙이지 말고 공용 TextStyle에 한 번만 넣으십시오. 화면마다 붙이면 새 화면을 만들 때마다 빠뜨립니다. 다만 같은 주석에 "줄보다 넓은 단어는 여전히 잘린다"고 적어 두었습니다. (한국어 첫 화면의 큰 제목은 따로 "·" 뒤에 폭이 0인 줄바꿈 지점을 넣었습니다.)
가장 긴 단어의 폭으로 칸 수를 정합니다
옵션 타일 그리드는 타일이 보고하는 최소 고유 폭을 보고 열 수를 정합니다. 한글은 이 값이 음절 하나 폭으로 나오니, 디자인 시스템에 단어 폭을 직접 재는 함수를 새로 넣었습니다. 공백으로 나눈 단어를 한 줄로 재고, 가장 넓은 값을 돌려줍니다.
text.split(Regex("\\s+")).filter { it.isNotEmpty() }.maxOfOrNull { word ->
measurer.measure(word, style, softWrap = false, maxLines = 1).size.width
} ?: 0
타일의 제목과 부제는 이 값을 최소 고유 폭으로 보고합니다(minIntrinsicWidthAtLeast, LayoutModifier로 구현). 그리드는 그 값을 칸 폭과 비교합니다.
val cols = when {
requested == 1 -> 1
largeFont -> 1
measurables.any { it.minIntrinsicWidth(Constraints.Infinity) > cellWidthFor(requested) } -> 1
else -> requested
}
앱 코드 중에서는 사진 반전·회전의 동작 버튼 네 개가 같은 함수를 씁니다. 라벨의 가장 긴 단어가 화면 절반에서 버튼 좌우 여백과 아이콘을 제외한 폭보다 넓으면, 한 줄에 버튼 두 개 대신 하나만 놓습니다.
150% 글꼴부터는 세로로, 칩 줄은 FlowRow로
큰 글꼴 기준은 상수 하나입니다. Chips.kt의 LARGE_FONT_SCALE = 1.5f 이상이면 세 군데가 세로 배치로 전환됩니다.
- 세그먼트 컨트롤: 한 줄 세그먼트 대신 옵션 타일을 위아래로 쌓습니다. 체크 아이콘도 지웠습니다. 아이콘이 세그먼트 폭의 3분의 1을 먹고 있었기 때문입니다.
- 옵션 타일 그리드: 요청한 열 수와 상관없이 1열입니다.
- 사진 반전·회전의 동작 버튼: 한 줄에 하나씩 놓습니다.
선택 칩 줄은 원래 Row에 horizontalScroll을 건 구조였습니다. 화면을 넘는 칩은 오른쪽 밖에 숨었고, 사용자는 'Web'이나 'Custom'이 있는지 알 수 없었죠. FlowRow로 바꾸자 칩이 다음 줄로 내려옵니다.
// 전: Row(modifier = modifier.horizontalScroll(rememberScrollState()), ...)
FlowRow(
modifier = modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(SugarSpacing.sm),
) { /* FilterChip ... */ }
4일 전 커밋의 Row와 2px 차이로 빗나간 버튼 폭
'R/u/l/e'는 한글 문제가 아니었습니다. 라벨·값 행은 2026-09-21 디자인 v2 커밋(33e6cc0)에서 AI 코딩 에이전트와 함께 만든 코드입니다. 라벨에는 Modifier.weight(1f, fill = false)를 주고 값에는 가중치를 주지 않은 Row였습니다. Row는 가중치 없는 자식을 먼저 측정합니다. 긴 값이 폭을 다 가져가고, 라벨은 남은 몇 픽셀에 한 글자씩 접혔습니다.
이 구조는 4일 뒤 실기기 QA에서 교차 검증 에이전트가 원인 코드를 읽으며 찾아냈습니다. 새 PairLayout은 두 칸의 자연 폭(maxIntrinsicWidth)을 먼저 재고, 합이 줄 폭 안이면 나란히, 넘으면 값을 라벨 아래로 내립니다.
두 번째 실수는 숫자였습니다. 동작 버튼에서 글자 외의 폭은 OutlinedButton 좌우 여백 24dp씩, 아이콘 20dp, 아이콘 간격 8dp를 더하면 정확히 76dp입니다. 코드 주석에 따르면 이 정확한 폭으로 계산했을 때 130% 글꼴의 '오른쪽으로'는 남는 공간보다 2px 작았는데도 줄이 바뀌었습니다. 여유 8dp를 더해 BUTTON_CHROME = 84.dp로 고정했습니다.
같은 결함은 다른 경로로도 나왔습니다. 2026-10-06 한글뷰어 슈가독 기기 테스트에서 200% 글꼴의 형식 자리 표시가 'HW/PX'(S21), 'HW/P'(S20)로 갈라졌습니다. 이 앱도 같은 디자인 시스템을 사용하니 Phrase는 이미 켜져 있었습니다. 'HWPX'는 단어 하나라서 칸보다 넓으면 잘립니다. 자리 표시를 한 줄로 고정하고 칸에 맞춰 글자를 줄이게 하자 이번에는 'HWP 3.0'이 가장 작은 크기로도 들어가지 않아 'HWP 3.('으로 잘렸습니다. 최종 수정은 자리 표시 이름을 4자 이하('HWP3')로 줄이는 것이었습니다. 배지와 TalkBack 설명은 'HWP 3.0' 그대로입니다.

점검 목록
9월 25일 수정 뒤에는 별도 에이전트가 한 번, 사람이 손으로 한 번 더 확인했습니다. 조합은 글꼴 1.0/1.3/2.0배, ko/en, 라이트/다크, 가로 화면이었고 회귀는 없었습니다. 아래 목록은 이번 수정에서 뽑은 확인 항목입니다.
- 공용
TextStyle에lineBreak = LineBreak.Simple.copy(wordBreak = LineBreak.WordBreak.Phrase)가 있는가. Android 12 이하는 무시하므로 아래 항목이 여전히 필요합니다. - 칸 수를 정하는 레이아웃이 Compose의 최소 고유 폭 대신 가장 긴 단어 폭을 보는가.
fontScale1.5 이상에서 세그먼트, 그리드, 버튼 줄이 세로 배치로 바뀌는가.- 선택지가 가로 스크롤 뒤에 숨지 않는가(
FlowRow). - 라벨·값 행을
Row의 가중치에 맡기지 않았는가. - 계산으로 정한 여백에 기기에서 본 오차를 더했는가(이번에는 8dp).
- 단어 하나짜리 짧은 라벨('HWPX' 같은 형식 이름)이 200% 글꼴에서 칸에 들어가는가.
이미지 변환 JPG·PNG·WebP의 입력 화면에서 크기 배지가 "JPEG · 1.3 / MB"로 두 줄이 됩니다. CHANGELOG 2026-09-25 항목에 사소한 미해결로 적혀 있습니다. 출시 전에 거치는 검사 전체는 AI가 만든 Android 앱, 출시 전에 무엇을 검사하나: 릴리스 게이트와 R8 크래시에 정리했습니다.
자주 묻는 질문
Jetpack Compose에서 한글을 단어 단위로 줄바꿈하려면 어떻게 하나요?
TextStyle의 lineBreak에 LineBreak.Simple.copy(wordBreak = LineBreak.WordBreak.Phrase)를 넣습니다. 공용 타이포그래피에 한 번 넣으면 모든 Text가 따릅니다. Android 13 미만에서는 무시되므로 가장 긴 단어 폭을 재는 레이아웃 판단을 함께 넣습니다.
WordBreak.Phrase를 넣었는데도 단어가 중간에서 잘리는 이유는 뭔가요?
줄이나 칸보다 넓은 단어는 Phrase도 자르기 때문입니다. 칸을 넓히거나 열 수를 줄이거나 글자를 줄여야 합니다. 한글뷰어 슈가독은 200% 글꼴에서 'HWPX' 자리 표시가 'HW/PX'로 갈라져 한 줄 고정과 자동 축소를 넣었고, 그래도 잘린 'HWP 3.0'은 'HWP3'로 줄였습니다.
Compose 레이아웃은 글꼴 크기 몇 배까지 확인해야 하나요?
이 앱들은 1.0, 1.3, 2.0배를 ko/en, 라이트/다크, 가로 화면과 조합해 확인했습니다. 레이아웃은 1.5배부터 세로 배치로 바뀌므로, 그 앞뒤 값을 모두 봐야 전환이 맞는지 알 수 있습니다.
이 글은 SugarMount가 직접 측정한 값으로 쓰고, 초안 작성에 AI 코딩 에이전트를 썼습니다.
다른 AX 노트
- Android assets를 미리 gzip한 실측: APK 이득은 0, .pack.gz는 .pack이 되어 FileNotFoundException
- SKT 판매 삼성 폰에서 market://을 열면 스토어 선택 창이 뜬다: Play 패키지부터 지정하기
- addOnCompleteListener(this)가 onStop에서 지운 리스너 때문에 스플래시에 멈춘 버그
- "6.7" < "6.11"이 거짓이라 콘텐츠 갱신이 멈췄다: 6.2~6.9 기기만 막힌 Remote Config 버전 비교
- 화살표 퍼즐 1,100판을 만든 역구성 생성기와 G2 게이트
- 대개편 직후 DAU는 올랐고, 자연 유입 코호트 D1은 15.2%에서 11.2%로 내려갔다