Skip to content

fix(page,styles): refuse page, section and list values Hancom does not keep - #428

Open
airmang wants to merge 20 commits into
mainfrom
fix/page-and-list-values
Open

airmang wants to merge 20 commits into
mainfrom
fix/page-and-list-values

Conversation

@airmang

@airmang airmang commented Oct 6, 2026

Copy link
Copy Markdown
Owner

바뀐 점

쪽·구역·목록 설정에서 한/글이 그대로 두지 않는 값과, python-hwpx가 음수를 조용히 0으로 쓰던 값을 무엇도 바꾸기 전에 형식 있는 오류로 거부한다. 구역에 없던 설정을 더할 때 나던 TypeError도 고친다.

한/글의 동작

  • 부호 없는 16비트 수로 두는 값. 그 밖의 값은 감싸 읽는다(65536은 0, 2³¹은 0이나 65535).
    • 줄 번호 시작(lineNumberShape@startNumber)과 간격(@countBy)
    • 구역 시작 번호(startNum@page·@pic·@tbl·@equation)
    • 목록 시작 번호(hh:numbering@start)
  • 줄 번호 거리(@distance)는 32비트로 그대로 둔다.
  • 쪽 테두리 오프셋(pageBorderFill/offset)은 부호 있는 16비트 수로 읽는다.
    • 0~32767은 그대로 둔다.
    • 65535는 −1로 읽고, 65536과 2³¹은 0이 된다.
  • 모르는 값은 기본값으로 읽는다.
    • 제본 방식(pagePr@gutterType)은 LEFT_ONLY
    • 쪽 테두리 채울 영역(pageBorderFill@fillArea)은 PAPER
  • 0은 모두 그대로 둔다.
    • 목록 시작 0은 1처럼 그린다.
    • 구역 시작 쪽 0은 앞 구역에서 이어 센다는 뜻이다.

고치기 전

  • 위 범위 밖 값을 그대로 썼다.
  • 음수 쪽 크기·여백·줄 번호·시작 번호·쪽 테두리 오프셋·글자 크기를 0으로 바꿔 썼다.
  • 목록 시작 번호 0을 1로 썼다.
  • 구역에 없던 쪽 테두리(예: page_type="SOMETIMES")나 시작 번호·줄 번호·격자·보이기 설정을 더하면 내부 TypeError로 끝났다. 표준 라이브러리 SubElement를 lxml 요소에 썼기 때문이다.

코드

  • oxml/section_format.py
    • _checked, _choice, _check_page_size가 범위와 어휘를 검사한다.
    • set_page_size: 크기는 0 이상 2³¹ 미만, gutter_type은 LEFT_ONLY·LEFT_RIGHT·TOP_BOTTOM
    • set_page_margins: 0 이상 2³¹ 미만
    • set_start_numbering: 0~65535
    • set_line_number_shape: 시작·간격은 0~65535, 거리·재시작은 0 이상 2³¹ 미만
    • set_page_border_fill: page_type은 BOTH·EVEN·ODD, fill_area는 PAPER·PAGE·BORDER, 오프셋은 0~32767
    • 새 요소는 모두 _append_child로 만든다(ET.SubElement 10곳을 바꿈).
  • _document/layout.py
    • apply_list_format의 start는 0~65535이고, 받은 값을 그대로 쓴다.
    • set_page_setup(doc.page.setup)은 쪽을 바꾸기 전에 크기와 여백을 검사한다.
  • oxml/document_parts.py: ensure_run_style의 글자 크기는 0 이상이어야 한다(음수 거부).
  • 새 오류 코드: page-number-value, page-border-fill-invalid, page-gutter-type-invalid, page-size-value, style-list-start-value, style-run-size-value
  • errors.py, docs/error-codes.md, 변경 로그를 고친다.

테스트

tests/test_page_and_list_values.py를 더하고, 한/글 저장본 열다섯을 함께 넣는다.

  • 경계를 넘은 값의 저장본 아홉
    • 줄 번호 시작 65536, 목록 시작 65536·2³¹
    • 구역 시작 쪽 65536·2³¹, 구역 시작 표 65536
    • 쪽 테두리 오프셋 2³¹, 채울 영역 EVERYWHERE, 제본 방식 MIDDLE
  • 경계와 0의 저장본 여섯
    • 오프셋 32767·65535(−1로 읽음), 줄 번호 간격 65536
    • 줄 번호 시작 0, 구역 시작 쪽 0, 목록 시작 0
  • 시험하는 것
    • 한/글이 저장한 값
    • 범위·어휘 밖 값과 음수 20가지가 오류 코드와 함께 거부되고, 머리말과 구역 XML이 그대로인지(page.setup의 음수 여백 포함)
    • 경계 값과 0이 그대로 쓰이는지(목록 시작 0 포함)
    • 구역에 없던 시작 번호·줄 번호·쪽 테두리·격자·보이기 설정이 TypeError 없이 더해지는지

고치기 전에는 27판 가운데 25판이 틀렸다.

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

🤖 Generated with Claude Code

airmang and others added 20 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>
…t keep

Hancom keeps a line number's start and step, a section's start numbers
and a list's start as 16 bits unsigned and a page border's offsets as 16
bits signed, wrapping anything past them, and reads a gutter type or a
page border fill area it does not know as its default. Such values, and
the negative sizes, margins, numbers and offsets python-hwpx used to
write as 0, are now refused with typed errors before anything changes
(page-number-value, page-border-fill-invalid, page-gutter-type-invalid,
page-size-value, style-list-start-value, style-run-size-value). A list
start of 0 is written as given (Hancom keeps it). Page setup checks its
size and margins before changing the page. Settings added to a section
that lacked them are created as its own kind of element: a page border
fill of a new page type failed with TypeError.

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