From 1f8357ca45b9c0303f32b6d65a396013df2be5ed Mon Sep 17 00:00:00 2001 From: airmang <38392618+airmang@users.noreply.github.com> Date: Fri, 16 Oct 2026 14:04:29 +0900 Subject: [PATCH 1/2] fix(form_fit): let an ellipsis after a space start a line FormFit and the page estimate now start a line with an ellipsis that follows a space, as Hancom Office lays it out. An ellipsis attached to the character before it still keeps to that character. A Hancom-saved fixture holds a table cell breaking so. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 ++ src/hwpx/form_fit/measure.py | 4 ++-- .../pages_ellipsis_starts_a_cell_line.hwpx | Bin 0 -> 21552 bytes tests/test_layout_page_estimate.py | 3 +++ 4 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 tests/fixtures/hancom_saved/pages_ellipsis_starts_a_cell_line.hwpx diff --git a/CHANGELOG.md b/CHANGELOG.md index 70a51509..4a055300 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -99,6 +99,8 @@ - 탭 정의에 자기 탭 위치나 자동 탭이 있는 문단은 전처럼 잰다. - 쪽 수 추정에서 강제 줄 바꿈 다음 줄은 첫 줄이 아니다. 들여쓰기를 빼지 않고 다른 줄의 폭으로 잰다. 전에는 첫 줄 폭으로 재서, 들여쓴 문단에서 줄이 하나 더 생길 수 있었다. +- FormFit과 쪽 수 추정이 공백 뒤의 말줄임표(`…`)로 줄을 시작한다. 한/글이 그렇게 놓는다. 앞 글자에 붙은 `…`는 전처럼 + 그 글자와 함께 다음 줄로 간다. - `.hwp`를 열 때 그림의 투명도와 반전을 한/글이 읽는 자리에서 읽는다. 한/글은 그림 레코드의 투명도를 그림 원래 크기 뒤 마지막 바이트에서 읽고, 바이너리 항목 번호 뒤 바이트는 읽지 않는다(저장할 때 0으로 쓴다). 전에는 그 바이트를 투명도로 옮겨, 거기에 255가 든 문서의 그림이 HWPX에서 보이지 않았다. 반전(`hp:pic@reverse`)은 그림 개체 속성의 diff --git a/src/hwpx/form_fit/measure.py b/src/hwpx/form_fit/measure.py index 6aed1a63..d619035d 100644 --- a/src/hwpx/form_fit/measure.py +++ b/src/hwpx/form_fit/measure.py @@ -122,7 +122,7 @@ #: same: rounded, \uc7a5\ud3c9 and \uc790\uac04 applied as any other is). _HALF_EM_GLYPHS = frozenset("!") #: Closing punctuation that never starts a line; it moves down with the -#: character before it. +#: character before it; but the ellipsis starts a line after a space. _NO_LINE_START = frozenset("!%),.:;?]}¢°’”‰′″℃〉》」』】〕…·、。") #: Opening punctuation that never ends a line. _NO_LINE_END = frozenset("([{‘“〈《「『【〔") @@ -1550,7 +1550,7 @@ def hancom_line_starts( options = [ index for index in breaks if start < index <= end - and text[index] not in _NO_LINE_START + and (text[index] not in _NO_LINE_START or text[index] == "…" and text[index - 1] in _HANGING_SPACES) and text[index - 1] not in _NO_LINE_END ] start = max(options) if options else end diff --git a/tests/fixtures/hancom_saved/pages_ellipsis_starts_a_cell_line.hwpx b/tests/fixtures/hancom_saved/pages_ellipsis_starts_a_cell_line.hwpx new file mode 100644 index 0000000000000000000000000000000000000000..64d91ed337aa2bf4b55f33388a3ecfd7062b1753 GIT binary patch literal 21552 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 Date: Fri, 16 Oct 2026 22:37:28 +0900 Subject: [PATCH 2/2] Keep independent review additions at distinct changelog and test locations --- CHANGELOG.md | 4 ++-- tests/test_layout_page_estimate.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b55dab5c..6e1bd09d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -107,8 +107,6 @@ 않으면 탭은 폭이 없다. 내어쓰기 자동 탭은 둘째 줄이 시작하는 곳에 왼쪽 위치를 둔다. - 쪽 수 추정에서 강제 줄 바꿈 다음 줄은 첫 줄이 아니다. 들여쓰기를 빼지 않고 다른 줄의 폭으로 잰다. 전에는 첫 줄 폭으로 재서, 들여쓴 문단에서 줄이 하나 더 생길 수 있었다. -- FormFit과 쪽 수 추정이 공백 뒤의 말줄임표(`…`)로 줄을 시작한다. 한/글이 그렇게 놓는다. 앞 글자에 붙은 `…`는 전처럼 - 그 글자와 함께 다음 줄로 간다. - FormFit과 쪽 수 추정이 텍스트 내보내기가 쓰지 않는 번호 형식(`KOREAN_DIGIT`, `HANGUL` 등)의 문단 번호를 만나도 멈추지 않는다. 한/글이 재는 번호(`useInstWidth`)이면 그 숫자를 한글 한 글자 폭으로 잰다. 전에는 번호가 자기 글자 모양을 가진 문단에서 `KeyError`가 났다. @@ -142,6 +140,8 @@ 나누는 표는 마지막 행의 남는 높이를 잘라 그 쪽에서 끝날 때도 그렇다. 나누지 않는 표가 그 자리에 들어가지 않으면 표만 다음 쪽 맨 위로 가고, 문단 줄과 그 뒤 문단은 제자리에 남는다. 전에는 앞의 두 경우 표를 그 자리에 두어 쪽 수가 한/글과 달랐고, 나누지 않는 표는 첫 줄과 함께 넘겨 쪽이 하나 더 나왔다. +- FormFit과 쪽 수 추정이 공백 뒤의 말줄임표(`…`)로 줄을 시작한다. 한/글이 그렇게 놓는다. 앞 글자에 붙은 `…`는 전처럼 + 그 글자와 함께 다음 줄로 간다. - 쪽 수 추정이 문단 아래로 띄워 둔 위아래 배치 개체가 쪽 끝 가까이 있을 때 한/글처럼 놓는다. 개체 아래로 내려간 다음 줄이 쪽 끝을 넘으면 다음 쪽 맨 위에서 시작한다(개체 아래 간격은 그 쪽으로 가져가지 않는다). 개체의 앵커 줄이 다음 쪽으로 넘어가면 개체도 그 줄과 함께 넘어가, 그 줄 위에서 오프셋만큼 아래에 선다. 전에는 앞의 경우를 지원하지 diff --git a/tests/test_layout_page_estimate.py b/tests/test_layout_page_estimate.py index da48b8d2..3cc66301 100644 --- a/tests/test_layout_page_estimate.py +++ b/tests/test_layout_page_estimate.py @@ -23,9 +23,6 @@ HH = "{http://www.hancom.co.kr/hwpml/2011/head}" FIXTURES = Path(__file__).parent / "fixtures" / "hancom_saved" HANCOM_PAGES = { - # Tables of short sentences, one cell holding "… " among its words: Hancom starts a line with the ellipsis after - # a space (attached to the character before it, it keeps to that one). - "pages_ellipsis_starts_a_cell_line": 4, # A picture (or a rectangle) placed top and bottom alone in an empty paragraph whose spacing after is 1000, # the next paragraph's spacing before 600, the empty line 1600 tall with its spacing: unless the object # moves with the text, may overlap and starts above the line's foot, the empty line stays at the paragraph's @@ -206,6 +203,9 @@ "pages_square_band_then_a_table_margin_top": 1, # the rectangle's top outer margin 1417 in its band "pages_square_band_then_a_table_after_the_band": 1, # the paragraph after the band "pages_fixed_width_spaces": 2, # rows of a syllable and a fixed-width space: a quarter em that hangs + # Tables of short sentences, one cell holding "… " among its words: Hancom starts a line with the ellipsis after + # a space (attached to the character before it, it keeps to that one). + "pages_ellipsis_starts_a_cell_line": 4, "pages_no_break_spaces": 2, # rows of "가나" and a no-break space: each is half an em and keeps the row # one word "pages_typed_ideographic_spaces": 5, # cells of "가나다라마" and a U+3000 typed in the text, a full em