Skip to content

feat(shapes): group pictures, text boxes, lines and groups inside groups - #438

Merged
airmang merged 6 commits into
mainfrom
feat/group-members-pictures-text-lines-groups
Oct 8, 2026
Merged

airmang merged 6 commits into
mainfrom
feat/group-members-pictures-text-lines-groups

Conversation

@airmang

@airmang airmang commented Oct 8, 2026

Copy link
Copy Markdown
Owner

바뀐 점

묶음(doc.shapes.add_container)에 선·글상자·그림·묶음 안 묶음 부재를 더한다. 지금까지 부재는 사각형·타원·다각형뿐이었지만, 한/글 문서의 묶음은 대부분 그림과 글상자를 함께 담고, 묶음 안에 묶음을 둔 것도 흔하다.

from hwpx.oxml import ContainerMember as M

image = doc.media.add_image(png_bytes, "png")
doc.shapes.add_container([
    M.picture(0, 0, 8000, 6000, image),
    M.text_box(8500, 0, 9000, 3000, "제목", fill_color="#DDEEFF"),
    M.line(8500, 3500, 17500, 3500),
    M.group(8500, 4000, [M.ellipse(0, 0, 2000, 2000), M.rect(2500, 0, 2000, 2000)]),
])

한/글의 동작

  • 묶음의 부재는 꼬리(sz·pos·outMargin·shapeComment)가 없다. id·zOrder는 0, numberingType은 NONE, textWrap은 TOP_AND_BOTTOM이고, groupLevel은 깊이(1, 2, 3…)다.
  • 묶음 안 묶음도 꼬리 없는 부재다. 가장 바깥 묶음만 꼬리와 numberingType="PICTURE"를 가진다.
  • 한/글은 묶음의 모든 도형을 **가장 바깥 묶음의 좌표에 있는 자기 transMatrix**에 그린다. 그래서 묶음 안 묶음의 부재도 바깥 묶음 좌표로 저장된다. 안쪽 묶음 좌표에 두면 한/글은 그 도형을 바깥 묶음의 왼쪽 위에 겹쳐 그린다.
  • 그림 부재는 img·imgRect·imgClip·inMargin·imgDim·effects를 지니고, 글상자 부재는 모서리 점 앞에 drawText를 둔다.

코드

  • oxml/container.py 새 모듈. objects.py가 1,600줄 상한에 닿아, ContainerMember와 묶음 조립을 옮겼다. objects는 ContainerMember·_create_container_element를 다시 내보내고, 도형 빌더는 쓰는 곳에서 불러와 어느 모듈을 먼저 import해도 된다.
    • ContainerMember.line(start_x, start_y, end_x, end_y): 두 끝 중 왼쪽 위에 놓이고, 두 끝을 그 모서리 기준으로 지닌다.
    • ContainerMember.text_box(x, y, width, height, text, ...): 사각형에 set_draw_text와 같은 글을 담는다(이름·편집 가능·여백·글자 모양·문단 모양·세로 정렬). 글은 묶음이 구역에 들어간 뒤 쓰여 문단 id가 구역에서 겹치지 않는다. 잘못된 세로 정렬은 문서를 바꾸기 전에 거부한다.
    • ContainerMember.picture(x, y, width, height, image): doc.media.add_image가 돌려준 이진 항목(또는 그 id)을 받는다. 비면 shape-container-picture-image로 거부한다.
    • ContainerMember.group(x, y, members): 묶음 안 묶음. 놓이는 자리만큼 모든 하위 도형의 offset과 transMatrix를 옮겨, 가장 바깥 묶음 좌표에 둔다.
    • 모든 부재에 zOrder="0"·numberingType="NONE"을 쓰고, 묶음을 다 만든 뒤 groupLevel을 깊이로 매긴다.
  • objects.py: 문단 add_container가 묶음을 넣은 뒤 글상자 부재의 글을 쓴다. 넣을 때 요소가 복사되므로, 넣은 묶음의 부재를 순서대로 짝짓는다.
  • 오류 코드 등록, 지원 표(두 벌), README 두 벌의 알려진 제약, 변경 로그, module-ownership.json, 문서 해시 원장을 고쳤다.
  • 잠금 둘을 올렸다. import hwpx가 읽는 모듈 수 상한은 98에서 99로, 분류 모듈 수는 181에서 182로 바뀐다. 새 모듈이 objects와 함께 읽히기 때문이다.

테스트

  • tests/test_container_members.py(새 파일)
    • 선 부재: 끝점·오프셋·크기
    • 글상자 부재 둘: 글, drawText 위치, lastWidth, 세로 정렬·여백, 구역 안 문단 id 중복 없음
    • 잘못된 세로 정렬은 문서를 바꾸기 전에 거부
    • 그림 부재: 이진 항목 참조, 하위 요소, 꼬리 없음. 빈 이미지 거부
    • 묶음 안 묶음 세 단계: groupLevel, 부재 속성, 크기, 그리고 하위 도형이 바깥 묶음 좌표로 옮겨지는지
    • 섞인 묶음의 저장·다시 열기
  • 기존 tests/test_container_authoring.py는 그대로 통과한다.
  • 한/글에서 열어 확인했다. 선·글상자·그림·묶음 안 묶음(두 단계)을 섞은 묶음 여덟 가지를 글자처럼·떠 있게 두고 한/글로 열면 모두 열린다. 다시 저장해도 요소 수가 그대로고, 쪽 글에 글상자 글이 나온다. 묶음 안 묶음은 놓은 자리에 그려진다.

🤖 Generated with Claude Code

airmang and others added 4 commits October 8, 2026 10:52
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ix and module ownership

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, refresh the manual hashes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@airmang

airmang commented Oct 8, 2026

Copy link
Copy Markdown
Owner Author

리뷰 결과: 머지 전에 고칠 것 하나, 나머지는 좋습니다.

고칠 것: 같은 부재를 다시 쓰면 묶음 안 묶음의 도형이 두 번 옮겨진다

_shift_descendants가 하위 도형의 offset·transMatrix를 제자리에서 더하기 때문에, ContainerMember(frozen dataclass라 값처럼 쓰기 쉬운)를 두 번째 묶음에 다시 넘기면 이미 옮긴 좌표에 또 더해집니다.

inner = M.group(1000, 1000, [M.rect(0, 0, 500, 500)])
members = [M.rect(0, 0, 3000, 3000), inner]
doc.shapes.add_container(members)   # 안쪽 사각형 offset (1000, 1000)
doc.shapes.add_container(members)   # 안쪽 사각형 offset (2000, 2000) — 묶음 상자 밖

같은 안쪽 묶음을 한 묶음에 두 번 넣어도([inner, inner]) 같은 이유로 어긋납니다. 이 PR 전의 사각형·타원·다각형 부재는 _place가 값을 대입만 해서 다시 써도 같은 결과였으므로, 이번에 생긴 회귀입니다.

제안: 묶음을 조립할 때 부재 요소를 복사해서(안쪽 묶음은 members로부터 다시 조립) 옮기고, ContainerMember 자신의 요소는 건드리지 않게 해 주세요. 시험에 위 두 경우(두 번 호출, 같은 안쪽 묶음 두 번)를 더해 주세요.

확인한 것

  • 묶음 안 묶음의 부재를 가장 바깥 묶음 좌표에 두는 규칙: 한/글이 저장한 문서의 중첩 묶음에서도 안쪽 묶음이 원점에서 떨어져 있어 구별되는 부재는 모두 바깥 묶음 좌표에 있었고, 안쪽 묶음 기준 좌표인 것은 없었습니다. 세 단계에서 이동이 누적되는 계산(C → B → A)도 맞습니다.
  • 한/글에서 연 결과 그림·글상자·선·안쪽 묶음이 놓은 자리에 각각 그려지는 것을 확인했습니다.
  • 글상자 글을 구역에 들어간 뒤 쓰는 것, _MEMBER_TAGS로 부재만 골라 순서대로 짝짓는 것, groupLevel을 마지막에 깊이로 매기는 것 모두 맞습니다.

막지 않는 것(원하면 같이)

  • ContainerMember.picture는 str(image)가 비지 않으면 무엇이든 받습니다. bytes를 그대로 넘기면 "b'\x89PNG...'"가 이진 항목 id로 들어갑니다. BinaryItem이거나 문서에 있는 id인지까지 보면 좋겠습니다.

airmang and others added 2 commits October 8, 2026 11:17
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@airmang

airmang commented Oct 8, 2026

Copy link
Copy Markdown
Owner Author

다시 리뷰: 병합합니다.

  • 1ca08f1: 묶음을 조립할 때 부재를 복사하고(안쪽 묶음은 members로 다시 조립) 넘겨받은 ContainerMember는 건드리지 않습니다. 같은 부재로 묶음을 두 번 만들어도, 같은 안쪽 묶음을 한 묶음에 두 번 넣어도 하위 도형이 제자리에 놓이는 것을 확인했고, 새 시험이 두 경우를 덮습니다.
  • 다시 쓴 부재는 instid도 그대로 복사됩니다(이 PR 전 사각형 부재와 같은 동작). 그런 문서도 한/글에서 열리고, 다시 저장해도 좌표가 그대로입니다.
  • a43de0c: 문서 예제 원장의 README.md 해시를 커밋된 내용으로 고쳤습니다(CI의 test_current_core_manual_ledger_is_complete_and_mechanically_honest).

@airmang
airmang merged commit 0db142d into main Oct 8, 2026
13 checks passed
@airmang
airmang deleted the feat/group-members-pictures-text-lines-groups branch October 8, 2026 03:05
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