Skip to content

feat(layout): page estimate for objects stacked top and bottom in an empty paragraph - #367

Open
airmang wants to merge 6 commits into
mainfrom
feat/page-estimate-objects-stacked-in-a-paragraph
Open

airmang wants to merge 6 commits into
mainfrom
feat/page-estimate-objects-stacked-in-a-paragraph

Conversation

@airmang

@airmang airmang commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

바뀐 점

쪽 수 추정(실험, estimate_pages)이 빈 문단에 문단 위 기준으로 둔 위아래 개체 여럿(표·그림)을 따른다.

  • 전에는 지원 밖(an object with text or other objects in its paragraph)이었다.
  • 한/글은 개체를 차례로 둔다.
    • 문단의 쪽부터 따져, 그 쪽에서 가로로 겹치는 앞 개체 아래에 들어가는 첫 쪽에 둔다. 문단의 쪽에서는 띄움과 앞 개체 아래 가운데 낮은 곳이고, 뒤 쪽에서는 맨 위부터다. 그래서 앞 개체가 다음 쪽으로 가도 뒤 개체가 문단의 쪽에 들어가기도 한다.
    • 가로로 겹치지 않는 개체는 각자 제 띄움에 선다.
    • 어느 쪽에도 들어가지 않는 개체는 마지막으로 쓴 쪽의 다음 쪽 맨 위에서 시작한다. 다만 첫 개체는 문단 자리에서 시작해 흐른다.
    • 쪽보다 긴 표는 그 뒤 쪽들로 나뉜다. CELL은 줄 사이에서, TABLE은 행째 나뉜다. 표가 거쳐 간 쪽은 찬 것으로 본다.
    • 이런 문단이 이어지면 뒤 문단의 개체도 앞 문단 개체를 비켜, 들어가는 첫 쪽에 놓인다. 다만 쪽을 넘어 이어지는 표의 나머지는 다음 쪽 맨 위에서 이어지고, 그 쪽의 다른 개체와 겹쳐도 비키지 않는다.
  • 문단의 줄과 뒤 줄은 개체를 비킨 첫 자리에 놓인다. 그 쪽에 자리가 없으면 다음 쪽 맨 위로 간다.
    • 앞 문단의 개체가 혼자 차지한 쪽으로 넘어간 줄(글자처럼 둔 표가 든 줄도)은 개체 아래 남은 자리에 들어가지 않으면 그 쪽을 건너뛴다.
  • 코드
    • _stack이 개체마다 _Stacked를 만든다. 띄움, 단 안의 가로 자리, 바깥 여백을 넣은 높이(표는 행으로 잰 높이), 표의 행을 담는다.
    • _Paginator._stacked가 개체를 쪽에 놓고(_stack_spot) 그 쪽의 띠(bands)에 넣는다. 그래서 뒤 줄은 _lay의 _band_hit가 개체 밖으로 옮긴다. 쪽보다 긴 표는 _flow_table로 나눈다.
    • 쪽 배치기가 쌓인 개체의 가로 자리를 기억해(_Paginator.stacked), 뒤 문단의 개체를 놓을 때 쓴다.
    • 쪽 배치기는 받은 띠를 복사해 쓴다. _lay_section이 종이 개체의 띠가 안정됐는지 견주는 사전을 건드리지 않기 위해서다.
    • 문단의 빈 줄은 _stacked가 직접 놓는다. _lay는 새 쪽에서는 넘치는 줄도 그 쪽에 두는데, 개체가 자리를 다 쓴 쪽이면 한/글은 줄을 다음 쪽으로 보낸다.
    • _lay도 띠 아래로 옮긴 줄이 들어가지 않으면 새 쪽이라도 다음 쪽으로 보낸다(below). 쪽보다 높아도 그 쪽에 두는 것은 쪽 맨 위 줄뿐이다.
  • 여러 단, 그리고 쪽 바닥을 넘는 그림·도형은 전처럼 지원 밖이다.
  • 변경 로그를 고친다.

테스트

  • tests/test_layout_page_estimate.py에 한/글 저장본 서른을 더한다(STACKED_PAGES). 모두 빈 문단에 문단 위 기준으로 둔 위아래 개체다.
    • 한 쪽에 드는 것 아홉
      • 표 둘·셋, 둘째를 1230·5000 띄운 표, 첫째만 2000 띄운 표, 바깥 여백 140인 표 넷
      • 그림 둘, 나란한 그림, 그림과 표
    • 쪽 바닥을 넘는 것 아홉
      • 마지막 그림이나 표가 다음 쪽으로 가는 것
      • 가운데 표가 다음 쪽으로 가고 셋째가 첫 쪽에 남는 것(CELL·TABLE)
      • 쪽보다 긴 가운데 표(CELL·TABLE)
      • 표 넷이 네 쪽에 걸치는 것, 셋째가 둘째 쪽에 놓이는 것, 바깥 여백 141인 둘째가 다음 쪽 맨 위에 놓이는 것
    • 문단이 쪽 아래쪽에서 시작해 첫 표가 들어가지 않는 것 넷
      • CELL 표 둘, TABLE 표 둘, 작은 둘째
      • 쪽보다 긴 첫 표: 빈 줄이 셋째 쪽 맨 위로 간다.
    • 둘째 표가 다음 쪽으로 간 문단 뒤에 오는 것 넷
      • 같은 문단이 하나 더: 그 표들은 첫 문단 빈 줄 아래에 놓인다.
      • 네 쪽에 걸치는 두 문단: 뒤 문단의 첫 표가 다음 쪽 맨 위에서 앞 문단 표와 겹쳐 이어지고, 둘째 표는 앞 문단 표 아래에, 빈 줄은 넷째 쪽에 놓인다.
      • 흐르는 표 하나가 든 문단, 두 쪽에 걸친 글
    • 둘째 표가 다음 쪽 맨 위에 혼자 놓여 그 쪽을 거의 다 쓴 문단 뒤에 오는 것 넷
      • 글 60줄: 남은 1500에 한 줄이 들어가고 나머지는 그다음 쪽으로 간다.
      • 남은 자리가 900이면 줄이 그 쪽을 건너뛴다. 55000 높이의 글자처럼 둔 표도 건너뛴다.
      • 둘째 표가 5762로 낮으면 글자처럼 둔 표가 그 아래에 놓인다.
    • 캐시가 있을 때와 없을 때 모두 쪽 수와 줄 위치가 한/글과 같다.
    • 마지막 쪽에 줄이 없는 문서가 있어서 HANCOM_PAGES가 아니라 따로 된 테스트로 둔다.
  • 쪽 바닥을 넘는 그림(첫 개체, 뒤 개체)이 지원 밖인지 확인한다.
  • 고치기 전 코드에서는 새 테스트 서른둘이 모두 실패한다. 그 가운데 그 쪽을 건너뛰는 둘은 below 없이도 실패한다.

🤖 Generated with Claude Code

airmang and others added 4 commits October 2, 2026 02:31
…empty paragraph

Several objects placed top and bottom from the top of a paragraph holding nothing else were refused as "an object with text or other objects in its paragraph". Hancom places them one by one on the first page, from the paragraph's on, where they fit below the earlier ones they overlap across (on the paragraph's page at their offset or below them, whichever is lower); one fitting on no page starts at the top of the page after the last one used, a table taller than a page split over the pages after (between lines when it splits by cell, row by row otherwise). The paragraph's line and the lines after take the first places clear of them.

_stack measures each object (_Stacked: offset, span across the column, height with outer margins, a table's rows) and _Paginator._stacked places them, adding them to the frame's bands so that _lay keeps every line clear of them. The paginator keeps its own copy of the bands it is given. Columns, and a first object not fitting on the paragraph's page, are still refused.

Eighteen Hancom-saved documents: tables and pictures one below another, offset down, side by side, with outer margins, past the page's foot, a middle table going on to the next page while a later one fits on the first, tables taller than a page by cell and by row, and four tables over four pages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…flows from there

The first of the objects stacked in an empty paragraph, fitting on no page below the paragraph's top, was refused. Hancom starts it where it stands and splits it at the page's foot as a flowing table (between lines when it splits by cell, row by row otherwise); the later ones follow on the pages it goes on to, which it fills, and the paragraph's line goes to the first place clear of them, on the next page's top when its own page has no room left. _Paginator._stacked now places the line itself (_lay would keep it on a fresh page even when the objects leave it no room), and _stack_spot finds an object's page. A picture or drawing past the page's foot is still refused.

Four more Hancom-saved documents: the paragraph low on its page with two tables split by cell or by row, a small second table, and a first table taller than a page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The refusals for objects stacked in an empty paragraph now name the case: in columns, beside notes or a table's band, or on a page holding other objects. A flowing table reaching such objects on a page is no longer reported as reaching an object placed on the paper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…f the earlier ones

Objects stacked in an empty paragraph on a page holding an earlier such paragraph's objects were refused. Hancom places them, and the paragraph's line, clear of those too: each goes to the first page where it fits below the objects there it overlaps across, the earlier paragraphs' included, and the pages holding such objects count as used. A table going on over a page end still goes on at the next page's top, across whatever is there. The paginator now keeps each stacked object's span (_Paginator.stacked) to place the next ones by.

Four more Hancom-saved documents: two such paragraphs one after the other (one over four pages, as in a form), and such a paragraph followed by a flowing table and by text going on over two pages.

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

airmang commented Oct 1, 2026

Copy link
Copy Markdown
Owner Author

커밋 2c1b85d8을 더했습니다. 이런 문단이 이어지는 경우입니다.

  • 앞 문단의 쌓인 개체가 있는 쪽은 전에는 지원 밖이었습니다.
  • 이제 뒤 문단의 개체와 빈 줄도 앞 문단 개체를 비켜, 들어가는 첫 쪽에 놓입니다.
  • 쪽을 넘어 이어지는 표의 나머지는 전처럼 다음 쪽 맨 위에서 이어지고, 그 쪽의 다른 개체와 겹쳐도 비키지 않습니다. 한/글이 이렇게 그립니다.
  • 한/글 저장본 넷을 더했습니다(본문의 테스트 절).
  • 전체 테스트와 게이트를 다시 통과했습니다.

…jects-stacked-in-a-paragraph

# Conflicts:
#	src/hwpx/layout/pages.py
@airmang

airmang commented Oct 1, 2026

Copy link
Copy Markdown
Owner Author

main(1c17ad7b, #362·#363·#364 병합)을 합쳤습니다(5b2b0d86). 충돌은 pages.py 두 곳이었습니다. 둘 다 #363과 이 PR이 같은 자리에 더한 것이라 함께 두었습니다.

  • _Para: #363의 kept와 이 PR의 stack
  • _Paginator.__init__: 이 PR의 띠 복사와 stacked, #363의 곁 띠 설명

동작은 바뀌지 않았습니다. 쪽 수 추정 테스트와 게이트를 다시 통과했고, 한/글 저장본 대조도 그대로입니다.

…t page

A line moved below an object's band on a fresh page took that page however
tall it was, as a line at a page's top does, and overflowed it. Hancom skips
the page instead: only a line at the top takes a page it is taller than.

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

airmang commented Oct 2, 2026

Copy link
Copy Markdown
Owner Author

커밋 38a83e63을 더했습니다. 앞 문단의 쌓인 개체가 혼자 차지한 쪽으로 넘어간 줄이 개체 아래 남은 자리에 들어가지 않을 때의 처리입니다.

  • 한/글은 그 줄을 다음 쪽으로 보냅니다. 글자처럼 둔 표가 든 줄도 같습니다.
  • 전에는 새 쪽의 첫 줄이라는 이유로, 쪽 끝을 넘겨서라도 그 쪽에 두었습니다.
  • 이제 _lay는 띠 아래로 옮긴 줄이 들어가지 않으면 다음 쪽으로 보냅니다(below). 쪽보다 높아도 그 쪽에 두는 것은 쪽 맨 위 줄뿐입니다.

한/글 저장본 넷을 더했고, 그 가운데 둘은 이 고침이 없으면 실패합니다. 본문과 변경 로그를 고쳤습니다. 전체 테스트와 게이트를 다시 통과했습니다.

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