Skip to content

fix(layout): follow columns that change inside a section - #421

Open
airmang wants to merge 25 commits into
mainfrom
fix/page-estimate-column-areas
Open

airmang wants to merge 25 commits into
mainfrom
fix/page-estimate-column-areas

Conversation

@airmang

@airmang airmang commented Oct 6, 2026

Copy link
Copy Markdown
Owner

바뀐 점

쪽 수 추정(실험, estimate_pages)이 구역 안에서 단이 바뀌는 문서를 지원 밖(the columns change inside the section)으로 두던 것을 고친다. 이제 한/글처럼 단 영역별로 놓는다.

한/글의 동작

  • 구역 첫 문단 뒤에서 단 정의를 가진 문단은 새 단 영역을 시작한다.
  • 새 영역은 앞 영역이 끝난 쪽에서, 앞 영역의 가장 낮은 줄 아래끝(줄 위치 + 줄 높이)보다 1134(4mm) 아래에서 시작한다.
    • 줄 간격(100·160·200%), 글자 크기(10·20pt), 단 간격(1200·2268)과 상관없다.
    • 영역 끝 문단의 아래 간격은 더하지 않는다. 영역 첫 문단의 위 간격은 영역 안에서 쓴다.
  • 영역의 줄 위치(hp:lineseg@vertpos)는 그 쪽에서 영역 위쪽부터 센다. 영역이 다음 쪽으로 넘어가면 그 쪽 본문 위부터 센다.
  • 다음 영역이 오는 영역은 마지막 쪽의 단을 높이로 균형 맞춘다. 일반 다단(NEWSPAPER)도 그렇다.
    • 줄 수가 아니라 높이로 맞춘다. 첫 줄이 20pt면 3줄과 4줄이 된다.
    • 쪽을 넘는 영역은 꽉 찬 쪽을 단 순서대로 채우고, 마지막 쪽만 균형 맞춘다.
  • 일반 다단의 단 나누기는 다음 단으로 넘어간다. 균형 다단(BALANCED_NEWSPAPER)의 단 나누기는 영역 안에 새 균형 블록을 연다. 새 블록은 앞 블록 아래 1134에서 시작하고 줄 위치를 0부터 센다.
  • 단 정의가 문단의 글 뒤에 있으면 새 영역은 열지만 단은 앞 영역 그대로다.

코드

layout/pages.py

  • _column_areas: 구역 최상위 문단에서 단 정의(구역 설정 런의 것 빼고)와 균형 다단의 단 나누기를 찾는다. 글 뒤의 단 정의는 단을 바꾸지 않는다.
  • _lay_areas: 영역마다 그 단 설정으로 _Page를 바꿔 문단을 잰다(캐시 없는 문단은 그 단 폭에서 줄을 나눈다).
    • 영역은 앞 영역이 끝난 쪽에서 앞 영역 가장 낮은 줄 아래끝 + 1134에 시작한다.
    • 자리가 없거나 영역 첫 문단이 쪽 나누기를 가지면 다음 쪽 위에서 시작한다.
  • _lay_area
    • 영역의 첫 쪽은 아래에 (본문 − 영역 위쪽, 본문) 띠를 두어 짧게 한다. 줄은 영역 위쪽부터 놓인다.
    • 다음 영역이 오면 마지막 쪽 단들의 아래에 (H, 본문) 띠를 두고, 쪽이 늘지 않는 가장 작은 H를 이분 탐색으로 찾는다.
  • _columns는 단 설정 하나를 읽는 _column_layout으로 나누고, 구역의 첫 단 설정을 쓴다.
  • _SectionLayout에 줄마다의 쪽·단(places)과 구역의 쪽 수를 더하고, _assemble이 이를 쓴다.
  • 단이 바뀌는 구역의 각주와 쪽·종이 기준 개체는 지원 밖으로 남긴다.
  • 모듈 설명과 변경 로그를 고친다.

테스트

tests/test_layout_page_estimate.py의 HANCOM_PAGES에 한/글 저장본 아홉을 더한다. 캐시가 있을 때와 없을 때 모든 줄 위치와 쪽 수를 시험한다.

  • 공통 꼴: 10pt 160% 글 3줄, 2단(간격 1200)을 시작하는 문단과 그 뒤 글, 1단을 시작하는 문단과 글 3줄
  • 일반 다단: 2단 글 6줄(4/3), 30줄(16/15), 90줄(쪽을 넘음), 90줄 + 단 나누기
  • 균형 다단 + 단 나누기: 두 균형 블록
  • 2단을 시작하는 빈 줄이 20pt: 높이로 3/4
  • 단 정의가 글 뒤에 있는 문서(영역만 열리고 단은 그대로)
  • 단 정의를 글 앞에 쓴 문서: 6줄, 30줄 + 단 나누기

고치기 전에는 아홉 모두 두 모드에서 지원 밖이었다.

_column_areas 단위 시험도 더한다. 문단의 첫 단 정의만 영역을 열고, 글 뒤의 정의는 단을 바꾸지 않으며, 균형 다단의 단 나누기도 영역을 여는지 본다.

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

🤖 Generated with Claude Code

airmang and others added 25 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>
A paragraph holding a column definition after the section's first starts
a new area of columns in Hancom: on the page where the one before ends,
1134 (4 mm) below its lowest line whatever the spacing, or at the next
page's top; its lines are counted from the area's top there. An area
followed by another has the columns of its last page balanced by height,
as short as they can be with every line still on that page, and a column
break in balanced columns starts such an area too. A definition behind
its paragraph's text starts the area but not its columns. The page
estimate reported such sections as unsupported; it now lays them out
area by area (footnotes and objects placed on the page or the paper in
them stay unsupported).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A paragraph holding two column definitions started two areas, one of them empty; the first definition now starts the paragraph's area.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>

This branch has not been deployed

No deployments
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