diff --git a/CHANGELOG.md b/CHANGELOG.md index bea13a20..e08c16d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ ### 고침 +- 쪽 수 추정(실험, `estimate_pages`)이 글 문단의 첫 줄에서 아래로 띄운 흐르는 위아래 표를 쪽 끝에서도 따른다. + - 그 줄이 쪽 바닥 위에 들어가지 않으면 줄과 표가 함께 다음 쪽으로 가고, 표는 거기서 띄운 만큼 아래에서 흐른다. + - 줄은 들어가는데 표 윗선이 바닥을 넘거나, 행 단위로 옮기는(TABLE) 표의 첫 행이 들어가지 않으면, 표만 다음 쪽 맨 + 위에서 시작한다. 문단의 글은 그 줄 아래로 이어지고, 표가 차지하는 쪽에서는 표 아래로 간다. + - 전에는 모두 지원 밖(`a top-and-bottom table offset down from a line at a page end`)이었다. 첫 줄이 아닌 줄에서 + 띄운 표는 전처럼 지원 밖이다. - 쪽 수 추정(실험, `estimate_pages`)이 표 칸 안에 위아래나 어울림으로 둔 그림·도형의 높이를 행 높이에 넣는다. 글 없는 칸 문단에서 문단 위를 기준으로 둔 것(글자처럼 두지 않은 것)이면, 칸이 그 개체의 아래 끝(바깥 여백 포함)까지 온다. 전에는 그 개체를 세지 않아 행을 한 줄 높이로 잡았다. diff --git a/src/hwpx/layout/pages.py b/src/hwpx/layout/pages.py index e33a51b0..284ac425 100644 --- a/src/hwpx/layout/pages.py +++ b/src/hwpx/layout/pages.py @@ -60,7 +60,10 @@ offset down, it stands that much lower, and the first line reaching it (in that paragraph or the ones after) and the lines after come below it -- a table flowing with the text flows from there over the page end, and that line goes below its end (so does the line of a paragraph holding only - another such table, which then flows from there); a flowing table alone in its paragraph starts + another such table, which then flows from there); offset down from a paragraph's first line that + does not fit above the page's foot, it goes on to the next page with the line, and one whose top + falls past the foot, or whose first row moved row by row does not fit under it, starts at the next + page's top while the paragraph's lines go on under its line; a flowing table alone in its paragraph starts its offset below the paragraph's top, above the paragraph's spacing before (one offset up starts there), and the next paragraph goes below the table's end or below the paragraph's line and its own spacing before, whichever is lower; @@ -2117,10 +2120,16 @@ def _banded(self, index: int, paras: list[_Para], para: _Para, start: int) -> No return if para.band is not None: band, table = para.band, para.band.table + if not band.line and self.last_vp is not None and start + para.height(0) > self.body: + start = self._next_frame(para, 0, True) # the line goes on to the next page, its table with it top = start + para.span(0, band.line) + band.offset - if start + para.span(0, band.line) + para.height(band.line) > self.body \ - or top + table.above >= self.body or _starts_later(table, top + table.above, self.body): + if start + para.span(0, band.line) + para.height(band.line) > self.body: raise _Unsupported("a top-and-bottom table offset down from a line at a page end") + if top + table.above >= self.body or _starts_later(table, top + table.above, self.body): + if band.line: + raise _Unsupported("a top-and-bottom table offset down from a line at a page end") + self._band_on_next_page(index, paras, para, table, top, start, broke) + return frame, end = _flow_table(table, self.frame, top + table.above, self.body) self._clear_of_paper(self.frame, frame) self.table_end = max(self.table_end, frame) @@ -2155,6 +2164,23 @@ def _banded(self, index: int, paras: list[_Para], para: _Para, start: int) -> No if self._lay(index, paras, tail, end, False): self.last_vp, self.last_pitch, self.pending_next = self.out[-1][1], tail.advance(tail.lines - 1), para.next + def _band_on_next_page(self, index: int, paras: list[_Para], para: _Para, table: _FlowTable, top: int, + start: int, broke: bool) -> None: + """A table offset down from a paragraph's first line whose top falls past the page's foot, or whose first + row (moved row by row) does not fit under it: it starts at the next page's top, and the paragraph's lines + go on under its line, on the pages the table takes below the table.""" + + frame, end = _flow_table(table, self.frame, top + table.above, self.body) + self._clear_of_paper(self.frame + 1, frame) + taken = range(self.frame + 1, frame + 1) + if frame == self.frame or any(page in self.reserved for page in taken): + raise _Unsupported("a top-and-bottom table offset down from a line at a page end") + self.reserved.update(dict.fromkeys(taken, self.body)) + self.reserved[frame] = end + table.below + self.table_end = max(self.table_end, frame) + if self._lay(index, paras, para, start, broke): + self.last_vp, self.last_pitch, self.pending_next = self.out[-1][1], para.advance(para.lines - 1), para.next + def _clear_of_paper(self, first: int, last: int, top: int = 0, end: int | None = None, below: bool = False) -> None: """A table from its anchor line's *top* in frame *first* to *end* in frame *last* (the whole frames diff --git a/tests/fixtures/hancom_saved/pages_table_offset_down_from_a_line_past_the_foot.hwpx b/tests/fixtures/hancom_saved/pages_table_offset_down_from_a_line_past_the_foot.hwpx new file mode 100644 index 00000000..321b6466 Binary files /dev/null and b/tests/fixtures/hancom_saved/pages_table_offset_down_from_a_line_past_the_foot.hwpx differ diff --git a/tests/fixtures/hancom_saved/pages_table_offset_down_its_first_row_past_the_foot.hwpx b/tests/fixtures/hancom_saved/pages_table_offset_down_its_first_row_past_the_foot.hwpx new file mode 100644 index 00000000..8f405223 Binary files /dev/null and b/tests/fixtures/hancom_saved/pages_table_offset_down_its_first_row_past_the_foot.hwpx differ diff --git a/tests/fixtures/hancom_saved/pages_table_offset_down_its_top_past_the_foot.hwpx b/tests/fixtures/hancom_saved/pages_table_offset_down_its_top_past_the_foot.hwpx new file mode 100644 index 00000000..68eac029 Binary files /dev/null and b/tests/fixtures/hancom_saved/pages_table_offset_down_its_top_past_the_foot.hwpx differ diff --git a/tests/test_layout_page_estimate.py b/tests/test_layout_page_estimate.py index 3a4bf0d2..42e456a5 100644 --- a/tests/test_layout_page_estimate.py +++ b/tests/test_layout_page_estimate.py @@ -86,6 +86,14 @@ "pages_table_caption_below_ends_100_above_page_foot": 2, # the last row and the caption below end 100 # above the foot: both go on "pages_table_caption_below_ends_101_above_page_foot": 2, # 101 above it: they stay + "pages_table_offset_down_from_a_line_past_the_foot": 2, # a table top and bottom 1517 down from a line + # that does not fit: both on the next page + "pages_table_offset_down_its_top_past_the_foot": 2, # 2000 down, the line fitting: the table at the + # next page's top, the text after below it + "pages_table_offset_down_its_first_row_past_the_foot": 2, # moved row by row, its first row not fitting: + # the same, a line of the text after on the page + + "pages_table_cell_holding_a_drawing_top_and_bottom": 1, # a cell paragraph holding only a rectangle 10000 # tall placed top and bottom from it: the row is # as tall as it with the cell's margins