fix: 얇은 여백 띠 · 쪽 경계의 표 선 + feat: 여백 mm 통일 · 도구 모음 선택 - #52
Merged
Merged
Conversation
실기(데모, 사용자)에서 나왔다. 좁은 여백 16/12에서 바닥글과 쪽번호가 본문 줄에 붙고 다음 쪽
머리글이 종이 위쪽 경계에 걸쳤다.
기전: DrawPageMarginChrome은 글자를 띠 한가운데에 놓는다(bandCenter ± 글자높이/2). 띠가 상수 40이던
동안은 11pt 줄(≈14~15px)이 늘 들어갔는데, 여백이 문서 설정이 되면서 12짜리 띠가 가능해졌다.
12 띠에서는 -1..13 → 위로는 종이 밖 데스크, 아래로는 본문 시작(12)과 겹친다. 인쇄·PDF에서는
종이 밖이 잘려 나간다.
수정(사용자 결정 2026-09-20): 띠가 줄을 담을 수 없으면 그 항목을 그리지 않는다. 여백은 요청한 값
그대로 지켜진다 — 워드식으로 본문을 밀어내면 PageMargin이 정확한 값이 아니라 권고가 되고 쪽 나눔도
바뀐다. 들어가는 띠에 가운데 정렬하면 줄이 종이를 벗어날 수 없다는 것도 구성상 따라온다.
테스트: 렌더 +2(PageChromeBandRenderTests, 실제 Skia — 메인의 no-op 백엔드는 글자를 안 그려 띠를
잴 수 없다). 41 → 43, 유닛 1151 그대로, 클린 빌드 0 warn. 반증: 가드를 끄면 12 띠에 126픽셀이
그려져 빨강.
⚠ 처음 쓴 셋째 테스트("종이 위 데스크에 아무것도 없다")는 교란에도 초록이라 지웠다 — 데스크 띠는
3px이고 삐져나간 1px은 잉크가 아니라 줄 상자였다. 못 죽는 테스트는 없느니만 못하다. 그 주장은
주석으로 남겼다(들어가는 띠 + 가운데 정렬 = 종이 밖 불가).
데모에 여백 프리셋 콤보를 넣었다(데모 전용). 뷰 도구 모음에는 여백이 없어 실기로 확인할 길이
없었다. ⚠ XAML에서 SelectedIndex를 ItemsSource보다 먼저 주면 선택이 풀려 빈 칸으로 뜬다 —
코드에서 ItemsSource 다음에 준다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
사용자 지적: #50은 호스트 API만 냈다. 도구 모음에는 용지·방향뿐이라 RichEditorView를 그대로 쓰는 앱의 최종 사용자는 여백을 바꿀 길이 없었다 — 방금 고친 "얇은 띠" 동작도 실제로는 닿지 않는다. 프리셋만 넣는다(사용자 결정 2026-09-20). Word·HWP도 프리셋이 앞에 있고, 띠는 숫자로 치기보다 고르는 쪽이 맞는 쪽 속성이다. 사용자 지정 대화상자는 다음에(내부 InputDialog가 한 칸짜리라 새로 만들어야 하고 단위도 정해야 한다 — 모델은 DIP인데 사람은 mm로 생각한다). - 용지·방향 옆 콤보. 보통 48/40(편집기 기본) · 좁게 24/20 · 넓게 96/80. - 프리셋에 없는 여백(호스트가 정했거나 문서가 들고 온 것)이면 아무것도 선택하지 않는다 — 배율 콤보가 눈금 밖 배율에 하는 것과 같은 규칙. 페이지가 아닌 값을 보여주는 건 거짓말이다. - Continuous에서는 비활성(방향과 같다). 현지화 ko/en 4키. 테스트 ToolbarMarginPickerTests 5개(1151 → 1156), 렌더 43 그대로, 클린 빌드 0 warn. 반증 3종: 프리셋 없어도 첫 항목 선택 / 항상 활성 / 문서 편집이 아닌 호스트 기본값으로 설정. ⚠ 세 번째 교란이 처음엔 살아남았다 — "문서를 편집하지 호스트 기본값이 아니다"를 아무도 보고 있지 않았다(CapturePageSetupToDocument는 어느 쪽이든 돌아 문서에는 값이 남는다). 차이는 다음 문서에서만 관측된다: 페이지 설정이 없는 새 문서는 호스트 기본값에서 시작한다. 그 테스트를 더하니 교란이 죽었다. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
실기에서 사용자가 잡은 둘. 셋 다 이번 커밋이다.
1) 표 테두리 잘림(실기 확대 사진에서 확정) — 1px 펜은 선 중앙이 사각형 변에 놓여서, 표 가장자리
셀은 자기 선의 절반을 표 상자 **밖**에 그린다. 쪽 나눔은 그 상자에 떨어지므로 쪽 클립이 선을
반으로 갈랐다(한 쪽 바닥에 일부, 다음 쪽 머리에 나머지). 프로브로 재현: 채움 문단 48개에서
쪽 2 첫 선이 온전한 경우의 67% 무게(13086 vs 19442).
- ⚠ 처음 추천했던 "클립 반 픽셀 조정"은 **실측에서 기각**. 클립은 픽셀 단위로 스냅돼 0.5는
반올림돼 사라지고(4px로 키워야 반대쪽 절반이 나타남), 1px로 키우면 이전 쪽 마지막 줄이 다음
쪽 머리에 겹쳐 그려졌다. 되돌리고 사용자와 다시 정해 표 쪽을 고쳤다.
- 수정: 표의 **바깥 변**인 셀 모서리만 반 픽셀 안쪽으로(InsetTableEdges). 안쪽 경계는 두 셀이
공유하므로 그대로 — 안쪽까지 밀면 공유 선이 1px 간격으로 두 번 그려진다. 덤으로 선이 픽셀
격자에 맞아 더 또렷하다.
2) 여백 단위를 mm로 통일(사용자 결정) — 공개 API·UI·저장 전부. 새 타입 PageMargins(mm 네 변,
record struct)를 Avalonia Thickness 대신 쓴다: UI 프레임워크에서 Thickness는 어디서나 DIP라
같은 네 숫자가 다른 단위인 건 실수 만들기 딱 좋다. 기본값은 지금 그리던 크기 그대로
12.7 x 10.6mm(=48 x 40 DIP)라 기존 문서의 모양은 안 바뀐다. 레이아웃은 계속 DIP이고 변환은
PagePad*/PageSetup.DipsPerMm 한 곳에서. JSON/.flow도 mm, RTF는 원래 twips라 변환만 바뀐다.
- ⚠ RTF는 정수 twips라 mm가 정확히 왕복되지 않는다(25mm → 1417 → 24.994). 왕복 테스트를
1 twip(0.018mm) 허용 오차로 바꾸고 문서에 적었다.
3) 도구 모음 여백 콤보(사용자 요청) — "여백" 글자 대신 아이콘(RichEditorIcon.PageMargin, 종이 +
안쪽 본문 상자), 항목은 5단계 mm: 없음 · 좁게 10 · 보통 12.7 · 넓게 20 · 아주 넓게 30.
테스트: 유닛 1156, 렌더 44(표 선 회귀 1 신설), 클린 빌드 0 warn. 반증: 안쪽 밀기 제거 →
표 선 테스트 빨강(13086 vs 19442). 데모 프리셋도 mm로(아주 좁게 4/3mm는 머리글이 사라지는 띠,
비대칭은 네 변 확인용).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
사용자 지적 둘. (1) 아이콘이 콤보 밖에 따로 서 있어 다른 픽커와 달라 보였다 — 항목마다 아이콘 + 이름으로 넣어 닫힌 상자에도 아이콘이 보인다(컨트롤은 부모가 하나라 항목마다 새로 만든다). (2) 단계 이름이 합의와 달랐다: 없음/좁게/보통/넓게/아주 넓게 → 아주 좁게 5 · 좁게 10 · 보통 12.7 · 넓게 20 · 아주 넓게 30mm. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
사용자 지적 둘. 1) "아이콘을 콤보 안으로"는 줄 간격 컨트롤 모양을 뜻한 것이었다 — 테두리 상자 하나에 아이콘이 한 번, 현재 단계, 그리고 목록을 여는 chevron. 항목은 글자만. 앞 커밋의 "항목마다 아이콘"은 오해였다. ComboBox 대신 BuildMarginControl()이 Border를 만든다(BuildLineSpacingControl과 같은 틀). 프리셋에 없는 여백이면 빈 칸 대신 밀리미터를 적는다(네 변이 같으면 한 값, 좌우/상하가 짝을 이루면 두 값, 아니면 넷). 2) 기본 여백 12.7 x 10.6mm → 15mm 사방. mm가 단위인데 기본값이 옛 픽셀 상수(48 x 40 DIP)의 환산값이라 눈금이 지저분했다. 픽커의 가운데 단계와 같아져 새 문서는 "보통"으로 뜬다. 주의: 여백을 저장하지 않은 기존 문서는 본문 폭이 조금 좁아진다(A4 698 → 681 DIP). 아직 미출시라 호환 부담은 없다. 테스트: 유닛 1156, 렌더 44, 클린 빌드 0 warn. - 기본값이 바뀌어 PaginationTests 2건 · PageMarginTests 1건 · RtfPageChromeTests 1건의 기대값을 DipsPerMm 식으로 고쳤다(RTF 바닥글 탭 위치 10470 → 10210 twips). - 픽커 테스트는 새 구조로 다시 썼다(항목 클릭 · 라벨 읽기 · 연속에서 비활성). - 주의: VectorPdfTests의 벡터/래스터 대조 허용 오차를 3 → 5px로 넓혔다. 여백이 mm가 되면서 본문 폭이 794 - 2 x 56.7로 소수가 되어 두 경로가 그 끝을 다르게 반올림한다. 단정의 뜻(같은 자리에 그린다)은 그대로다. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
사용자 지적 둘.
1) 문단과 표가 딱 붙는다 — 양쪽 다 0이었다. 문단은 아래 여백이 0이고(라운드24, HWP식) 표는 위
여백이 0이었다. 표의 MarginTop 기본값을 NaN(TableBlock.AutoMarginTop, "편집기가 정함")으로
두고, 모든 블록 워커가 그것을 **본문 한 줄 간격**으로 푼다(글자 크기 × 줄 간격 − 글자 자체,
기본 10pt/160%면 8px).
- 0이 아니라 NaN인 이유: 0은 "간격 없음"이라는 뜻으로 남아야 한다. 실제로 쪽 나눔 테스트 3개가
반듯한 숫자를 얻으려고 MarginTop=0을 명시하는데, 0을 auto로 읽었다면 그 셋이 조용히 8px씩
밀렸다(처음 그렇게 만들었다가 잡혔다). NaN=미설정은 Paragraph.LineSpacing의 관례와 같다.
- 간격을 **직전 블록이 아니라 문서 기본 타이포그래피**에서 뽑는다: 워커 11곳이 앞 블록을 들고
다니지 않고 일부는 컬링에서 continue하므로, "직전 블록"은 건너뛰는 워커에서만 낡는다 —
G1/G2가 정리한 드리프트 부류다. 대가: 큰 제목 아래 표도 본문 기준 간격을 받는다.
- 워커 11곳(yOffset += block.MarginTop)을 전부 TopGapOf(block) 한 곳으로 모았다.
- JSON: NaN은 필드를 쓰지 않고, 없으면 다시 NaN으로 읽는다. 명시된 0은 0 그대로.
2) 픽커의 글자·아이콘을 옆 컨트롤과 같게: 라벨과 목록 항목 FontSize 12(PageCombo·줄 간격 상자와
동일), 여백 아이콘 사각형을 인쇄 아이콘과 같은 비율로.
테스트 TableTopGapTests 6개(1156 → 1162), 렌더 44, 클린 빌드 0 warn. 반증(auto를 0으로 풀기):
처음엔 1개만 죽었다 — 높이 비교 테스트가 "둘 다 0"에서도 통과해서, 간격이 실제로 커졌다는 단정을
더했다. 이제 2개가 죽는다. 공개 API 추가 1(TableBlock.AutoMarginTop).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1) 사용자 요청: 표에만 넣었던 자동 위 여백을 그림·구분선까지. 센티넬을 TableBlock에서 Block으로
올렸다(Block.AutoTopMargin) — 셋 다 "쪽 위의 객체"라 같은 규칙을 쓴다. 문단은 종전대로 0.
JSON도 셋 다 NaN이면 필드를 안 쓰고, 없으면 자동으로 읽는다.
- 레거시 문서 테스트 하나를 뒤집었다(LegacyJson_WithoutMarginFields_GetsHistoricalDefaults):
위 여백을 적지 않은 파일은 이제 0이 아니라 자동을 받는다. 의도한 변화다 — 의견을 밝힌 적 없는
파일(필드가 생기기 전 모든 파일)만 해당하고, 명시된 0은 그대로 0이다. 아래 여백 규칙 무변경.
2) Ubuntu CI 빨강: VectorPdfTests의 벡터/래스터 잉크 끝 차이가 6px(Windows 4px)이라 앞 커밋의
고정 5px 허용치를 넘었다. 여백이 mm가 되며 본문 폭이 소수가 되어 두 경로가 그 끝을 다르게
반올림하는데, 그 크기는 플랫폼 글리프 래스터화에도 달렸다. 허용치를 본문 폭의 2%(최소 4px)로
바꿨다 — "같은 자리에 그린다"는 뜻은 그대로고, 종이를 벗어나는 잉크는 위의 여백 단정이 잡는다.
테스트 1164 + 렌더 44, 클린 빌드 0 warn.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
데모에서 높은 배율로 보니 그림이 1~2픽셀 잘려 보인다는 보고. 자르는 것은 없었다 — 그림에는 객체 표시용 옅은 외곽선이 늘 그려지는데, 펜은 자기가 그리는 사각형 위에 중심이 놓이므로 선의 절반이 그림 안쪽에 얹혀 바깥 반 펜만큼의 화소를 덮고 있었다. 선택했을 때의 굵은 테두리(2px)는 그 두 배를, 셀 안 그림과 선택된 인라인 아이콘도 같은 식으로 덮었다. 네 곳 모두 반 펜 바깥으로 옮겼다(Around 헬퍼). 표는 반대 방향인데(InsetTableEdges), 거기선 선이 표 자신의 잉크라 쪽나눔이 아는 상자 안에 있어야 하기 때문이다. 그림의 외곽선은 내용을 둘러싼 표시일 뿐이므로 내용이 온전해야 한다. 측정(선택된 그림, 그림 색이 남은 줄 수 / 120): 테두리를 그림 위에 그릴 때 117.4 → 바깥에 그릴 때 119.1. (남은 0.9는 그림 자신의 경계가 화소 사이에 떨어져 생기는 앤티앨리어싱 가장자리로, 바로 옆에 불투명한 선을 그으면 어쩔 수 없이 어두워진다.) 옅은 외곽선(1px, 47% 불투명)은 같은 결함이 0.4줄밖에 움직이지 않아 단정을 걸면 threshold가 취약해진다 — 같은 사각형을 같은 헬퍼로 네 줄 옆에서 그리므로 테스트는 선택 테두리로 대표한다. 테스트 1164 + 렌더 45, 클린 빌드 0 warn. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
앞 커밋(외곽선을 그림 바깥으로)을 데모에서 확인하니, 2쪽 맨 위의 그림을 선택하면 테두리가 세 변에만 있었다. 쪽마다 본문 영역으로 클립해 다시 그리는데 그 경계가 그림 윗변과 정확히 겹쳐, 바깥에 그린 윗선이 잘려 나갔다. 옅은 상시 외곽선도 원래 절반이 바깥이라 같은 자리에서 윗선만 약했다 — 처음 보고된 "그림이 1~2픽셀 잘려 보인다"의 실제 모습이 이것이었다. 외곽선·선택 테두리·핸들은 내용이 아니라 편집용 표시이므로, 블록 순회 중엔 큐에 담고 (_pictureChrome) 쪽 내용을 다 그린 뒤 종이 경계로 클립해 그린다(쪽 경계 없는 보기는 제 열에 쪽 사이 간격 절반씩, 연속 보기는 클립 없이). 선택된 그림은 모든 쪽 재생에서 그려지므로 그림이 실제로 걸친 쪽에서만 큐에 넣는다. 테스트: 선택된 그림이 2쪽 맨 위에 오는 경우 — 정말 쪽 맨 위인지 먼저 확인한 뒤 그 위에 테두리가 있는지 본다. 본문 클립으로 되돌리면 빨개짐(반증). 사용자 실기 확인 완료. 테스트 1164 + 렌더 46, 클린 빌드 0 warn. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Sep 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
실기(데모)에서 사용자가 잡은 것 둘. 커밋 2개다.
1. fix — 얇은 띠에서 머리글·바닥글이 종이 밖·본문 위에 그려졌다
#50이 만든 결함. 좁은 여백(16/12)에서 바닥글과 쪽번호가 본문 줄에 붙고, 다음 쪽 머리글이 종이 위쪽 경계에 걸쳤다.
기전:
DrawPageMarginChrome은 글자를 띠 한가운데에 놓는다(bandCenter ± 글자높이/2). 띠가 상수 40이던 동안은 11pt 줄(약 14~15px)이 늘 들어갔는데, 여백이 문서 설정이 되면서 12짜리 띠가 가능해졌다. 12 띠에서는-1..13→ 위로는 종이 밖 데스크, 아래로는 본문 시작(12)과 겹침. 인쇄·PDF에서는 종이 밖이 잘려 나간다.수정(사용자 결정): 띠가 줄을 담을 수 없으면 그 항목을 그리지 않는다. 여백은 요청한 값 그대로 지켜진다 — 워드식으로 본문을 밀어내면
PageMargin이 정확한 값이 아니라 권고가 되고 쪽 나눔도 함께 바뀐다.2. feat — 도구 모음 여백 선택
사용자 지적: #50은 호스트 API만 냈다. 도구 모음에는 용지·방향뿐이라
RichEditorView를 그대로 쓰는 앱의 최종 사용자는 여백을 바꿀 길이 없었다 — 위에서 고친 동작도 실제로는 닿지 않는다.프리셋만 넣는다(사용자 결정). 용지·방향 옆 콤보: 보통 48/40 · 좁게 24/20 · 넓게 96/80.
InputDialog가 한 칸짜리라 새로 만들어야 하고, 단위도 정해야 한다(모델은 DIP인데 사람은 mm로 생각한다).⚠ 반증 하나가 처음엔 살아남았다. "픽커는 문서를 편집하지 호스트 기본값이 아니다"를 아무도 보고 있지 않았다 —
CapturePageSetupToDocument는 어느 쪽이든 돌아서 문서에는 값이 남는다. 차이는 다음 문서에서만 관측된다(페이지 설정이 없는 새 문서는 호스트 기본값에서 시작). 그 테스트를 더하니 교란이 죽었다. 이건 WinUI 포트가 용지에서 실제로 냈던 결함과 같은 자리다.검증
SelectedIndex를ItemsSource보다 먼저 주면 선택이 풀려 빈 칸으로 뜬다 — 코드에서 뒤에 준다.남은 실기 확인: 인쇄·PDF 여백이 화면과 같은지, 저장·열기로 여백이 살아남는지, 좁게에서 머리글·바닥글이 사라지는지(의도한 동작).
🤖 Generated with Claude Code