Skip to content

fix(shapes): refuse sizes and corner ratios Hancom reads as 0 - #416

Merged
airmang merged 6 commits into
mainfrom
fix/shape-size-values
Oct 7, 2026
Merged

airmang merged 6 commits into
mainfrom
fix/shape-size-values

Conversation

@airmang

@airmang airmang commented Oct 6, 2026

Copy link
Copy Markdown
Owner

바뀐 점

도형·그림·수식을 넣을 때 음수 크기, 사각형의 음수 모서리 곡률, 수식의 0 이하 기준 크기를 무엇도 더하기 전에 거부한다. 전에는 그대로 썼다.

한/글의 동작

  • 음수 크기를 0으로 읽고 0으로 저장한다.
    • 그 크기에서 쓴 원래 크기(orgSz), 회전 중심, 그림의 이미지 크기(imgDim)도 0이 된다.
  • 사각형의 음수 모서리 곡률(ratio)도 0으로 읽는다.
  • 수식 기준 크기(baseUnit)는 2³¹−1까지 그대로 두고, 저장된 상자 크기로 쪽을 짠다.
    • 아주 큰 기준 크기로 잰 상자는 32비트를 넘는다. 한/글은 이를 2³²−1로 줄여 저장한다.

고치기 전

  • doc.shapes.add_rectangle(width=-1, height=-1)처럼 음수 크기를 그대로 썼다. 한/글에서는 크기 0인 도형이 된다.
  • 수식의 base_unit=0은 ValueError였다. 다만 퍼사드가 문단을 먼저 더한 뒤라 빈 문단이 남았다.
  • HwpxOxmlParagraph.add_picture는 런을 먼저 더하고 그림 요소를 만들었다. 그래서 그림 요소에서 값이 거부되면 빈 런이 남았다.

코드

  • oxml/shape_position.py에 validate_shape_size, validate_rect_ratio, validate_equation_base_unit을 더한다.
    • 크기는 0 이상 2³¹ 미만의 int여야 한다. bool은 거부한다.
    • 곡률도 같은 범위여야 한다.
    • 기준 크기는 1 이상 2³¹ 미만의 int여야 한다.
    • 새 오류 코드는 shape-size-value, shape-rect-ratio-value, shape-equation-base-unit-value다. 셋 다 HwpxValueError라서 기준 크기 오류는 전처럼 ValueError로도 잡힌다.
  • oxml/objects.py
    • 모든 도형이 거치는 _build_shape_common_children가 크기를 검사하고, 사각형 요소는 곡률을 검사한다.
    • HwpxOxmlShape.resize도 크기를 검사한다.
    • 파일 줄 수 상한(1600)에 맞춰, 검사 함수가 값을 돌려주게 해 한 줄로 쓴다.
  • build_at_original_size는 그리는 크기를 검사한다.
  • 퍼사드(_document/shapes.py, _document/media.py)는 문단이나 그림을 더하기 전에 검사한다. 그림은 크기를 정하고 검사한 뒤에 그림 데이터를 저장한다.
  • HwpxOxmlParagraph
    • add_picture는 그림 요소를 만든 뒤에 런을 더한다.
    • add_equation은 런을 더하기 전에 기준 크기와 상자를 검사한다.
  • errors.py와 docs/error-codes.md에 코드 셋을 등록하고, 변경 로그 항목을 더한다.

테스트

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

  • 저장본
    • −1×−1로 쓴 사각형·그림·수식
    • 곡률 −1인 사각형
    • 기준 크기 2³¹−1로 상자를 잰 수식, 기준 크기 2³¹−1에 상자를 준 수식
  • 시험하는 것
    • 한/글이 저장한 값: 0, 2³²−1, 그대로 둔 기준 크기
    • 사각형·타원·호·수식·그림의 크기, 곡률, 기준 크기에서 범위 밖 값이 오류 코드와 함께 거부되는지. 거부된 뒤 문단 수, 그림 수, 구역 XML이 그대로인지도 본다.
    • 0과 2³¹−1이 그대로 쓰이는지
    • 문단 수준 쓰기에서 거부될 때 런이 남지 않는지
    • resize가 음수 크기를 거부하는지

이 PR은 #415 위에 쌓았다. #415가 병합되면 그 커밋 둘은 이 PR의 diff에서 빠진다.

🤖 Generated with Claude Code

airmang and others added 4 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>
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>
@airmang
airmang merged commit 0252ad2 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