Skip to content

fix(page): write a column definition ahead of its paragraph's text - #420

Merged
airmang merged 31 commits into
mainfrom
fix/column-definition-before-text
Oct 7, 2026
Merged

airmang merged 31 commits into
mainfrom
fix/column-definition-before-text

Conversation

@airmang

@airmang airmang commented Oct 6, 2026 •

Copy link
Copy Markdown
Owner

바뀐 점

문단에서 새 단을 시작하는 단 정의(doc.page.set_columns(..., paragraph=...), HwpxOxmlParagraph.add_column_definition)를 문단의 글보다 앞에 쓴다. 전에는 글 뒤 런에 써서, 글이 있는 문단에서는 한/글이 새 단을 적용하지 않았다.

구역의 첫 문단이면 단 정의를 더하지 않고, 구역 설정 런에 있는 단 정의를 그 자리에서 고쳐 쓴다. 첫 문단에 단 정의가 하나 더 있으면 한/글이 문서를 열지 못한다.

한/글의 동작

  • 한/글이 만든 문서는 문단의 단 정의를 그 문단의 첫 런에 둔다. 구역의 첫 문단이면 구역 설정 다음이다.
  • 단 정의가 문단의 글 뒤에 있으면 새 영역만 열고 단은 적용하지 않는다. 그 문단과 뒤 글이 본문 폭으로 놓인다.
  • 글 없는 문단이면 한/글이 단 정의를 글 런 앞으로 옮겨 저장하고 새 단을 적용한다.
  • 구역의 첫 문단에 구역 설정 옆 단 정의 말고 단 정의가 하나 더 있으면, 1단 정의라도 한/글이 문서를 열지 못한다.

고치기 전

p = doc.add_paragraph("단 시작") 뒤에 doc.page.set_columns(2, paragraph=p)를 부르면 [t "단 시작"][ctrl colPr]로 썼다. 한/글은 이를 [t, ctrl, t]로 저장하고, 그 문단과 뒤 글을 1단(본문 폭)으로 놓았다.

구역의 첫 문단에 부르면 구역 설정 옆 단 정의는 그대로 두고 단 정의를 하나 더 써서, 한/글이 문서를 열지 못했다.

코드

  • oxml/paragraph.py의 add_column_definition
    • 단 정의의 런을 만든 뒤, 구역 설정 런이 아닌 첫 런 앞으로 옮긴다.
    • 구역의 첫 문단이면 구역 설정 런의 단 정의를 고쳐 쓴다. 문단을 주지 않은 set_columns와 같은 길이고, 돌려주는 개체는 그 단 정의의 조판 부호다.
  • 구역 설정 런의 글을 따로 떼어 내던 도우미(_paragraph_text_edit.split_section_carrier_content)는 이 경우에만 쓰였으므로 지운다.
  • _document/layout.py의 set_columns 설명에 첫 문단 경우를 적는다.
  • 변경 로그 항목을 더한다.

테스트

tests/test_section_columns.py에 시험을 더하고, 한/글 저장본 셋을 함께 넣는다.

  • 저장본: 다섯째 문단에서 2단을 시작한 문서
    • 단 정의를 글 뒤에 쓴 것(고치기 전): 한/글은 정의를 글 뒤에 둔 채 저장했고, 뒤 줄들이 본문 폭(42520)이다.
    • 빈 문단에 쓴 것: 한/글은 정의를 글 런 앞으로 옮겨 저장했고, 뒤 줄들이 2단 폭(20660)이다.
    • 이제 쓰는 꼴(정의가 글 "단 시작" 앞): 한/글이 그대로 저장했고, 그 문단과 뒤 줄들이 2단 폭이다.
  • 단 정의가 글이 있는 문단과 빈 문단 모두에서 첫 런에 쓰이는지 시험한다.
  • 구역의 첫 문단이면 단 정의가 구역 설정 런의 것 하나뿐이고 새 단 수로 바뀌는지, 저장 뒤 다시 열어도 그런지 시험한다. 구역 설정 런에 글이 함께 있는 첫 문단도 시험한다.

고친 뒤 만든 문서는 한/글에서 열리고 단이 의도대로 그려지는 것을 확인했다. 구역 설정 런에 글이 있는 새 문서, 한/글이 저장한 문서, 빈 첫 문단은 첫 문단부터 2단이고, 둘째 문단에서 시작한 단은 둘째 문단부터 2단이다.

이 PR은 #415, #416, #417, #418, #419 위에 쌓았다. 앞 PR이 병합되면 그 커밋들은 이 PR의 diff에서 빠진다.

🤖 Generated with Claude Code

airmang and others added 15 commits October 6, 2026 07:25
add_picture wrote any align it was given into hp:pos@horzAlign ("BOGUS",
"  RIGHT ", "TOP"), which Hancom reads as LEFT; a non-string failed only after
the image and its paragraph were added. The alignment is now checked first,
in any case, against the schema's horizontal alignments, and anything else is
refused with shape-position-frame before anything is stored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A negative width or height of a rectangle, ellipse, arc, picture or
equation box, or a negative rectangle corner ratio, was written as given;
Hancom reads it as 0. Such values, and sizes of 2**31 or more, are now
refused with typed errors before any paragraph, run or image is added
(shape-size-value, shape-rect-ratio-value). An equation's base_unit
outside 1..2**31-1 raises shape-equation-base-unit-value (a ValueError,
as before) before its paragraph is added. HwpxOxmlShape.resize refuses a
negative size too, and HwpxOxmlParagraph.add_picture builds the picture
before adding its run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hancom reads a caption's gap and the gap between columns of the same
width as signed 16-bit numbers and a new number as an unsigned one,
wrapping anything else (a column gap of 32768 makes the columns overlap;
a negative one written as text is read as 0). set_caption's gap outside
-32768..32767, a same_gap (or page setup column_gap_mm) outside 0..32767
and a restart number outside 0..65535 are now refused before anything
changes (shape-caption-gap-value, page-column-gap-value,
page-new-num-value). The caption side check moves to shape_position
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hancom reads a column's width and gap (hp:colSz) as a share of the text
width out of 32768 and lays the column out width * text width / 32768
wide, whatever the shares add up to. column_widths were written as
given, so widths in HWP units adding up to the text width came out about
1.3 times as wide and ran off the paper. They are now taken as
proportions and written as shares adding up to 32768 (column_shares);
widths that already add up to 32768 are written unchanged. Negative,
non-int, all-zero or malformed pairs are refused before anything changes
(page-column-widths-value). The page estimate takes a column's width as
its share of 32768 of the text width.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hancom draws a table column at least its cells' left and right margins
and 283 wide (1303 in a new table), keeps the narrower width written in
the cells and saves the table as wide as it draws the columns. A new
table now gives each column at least that floor (a nested table without
a width in a narrow cell too), and set_column_widths writes a column
below its floor at the floor and widens the table by as much. A new
table width that is no int in 0 < width < 2**31 is refused before any
paragraph, run or border fill is added (table-width-value).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hancom starts new columns only from a column definition ahead of the
paragraph's text (its own documents give it the paragraph's first run,
after a section's settings); one behind the text starts a new area but
not its columns, so the text after it stays at the text width.
add_column_definition (doc.page.set_columns with a paragraph) appended
its run after the paragraph's text; it now goes ahead of it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
airmang and others added 6 commits October 6, 2026 16:27
add_equation's base unit check now raises a typed error, so
oxml/paragraph.py raises one untyped error fewer. The census lock
records 202 untyped raises (paragraph.py 18), as the ratchet's
self-test expects the lock to match the tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
new_table_width returns None only when no width is given. Overloads
now say so, so the width HwpxOxmlTable.create builds the table with is
an int for mypy and pyright.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
airmang and others added 7 commits October 7, 2026 08:48
…a definition

Hancom does not open a section whose first paragraph holds a column
definition beside the one next to its settings (hp:secPr), even a
one-column one, whatever run or text it sits in; a new document, a
Hancom-saved first paragraph and an empty one all failed to open after
set_columns(..., paragraph=first) or add_column_definition on it. On a
section's first paragraph both now rewrite the settings' own definition
in place, as set_columns without a paragraph does, and Hancom lays the
section out in those columns from its start. Moving the text that
shares the settings' run (split_section_carrier_content) served only
that case and goes.

Refs #435

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
airmang and others added 3 commits October 7, 2026 09:23
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
add_column_definition stays within the complexity limit of the touched
oxml modules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@airmang
airmang merged commit 7760bc9 into main Oct 7, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant