diff --git a/CHANGELOG.md b/CHANGELOG.md index 45347ef9..8a1f50da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -129,6 +129,9 @@ - 쪽 수 추정이 빼기 부호로 쓴 개체 오프셋(`vertOffset="-1500"`처럼, 한/글이 쓰는 부호 없는 32비트 꼴이 아닌 것)을 한/글처럼 0으로 읽는다. 한/글은 그런 값을 0으로 읽어 개체를 문단(또는 종이) 기준 자리에 그대로 그리고, 저장할 때 0으로 고쳐 쓴다. 전에는 그만큼 위(왼쪽)로 놓인 것으로 읽어 뒤 줄 자리가 한/글과 달랐다. +- 쪽 수 추정이 "다음 문단과 함께"가 이어진 문단들(사슬)을 한/글처럼 놓는다. 사슬 문단이 쪽 끝에서 넘치면 다른 문단처럼 + 나뉘고 그 앞 사슬은 남는다. 사슬 문단이 통째로 다음 쪽으로 가야 하면(다음 문단 첫 줄이나 표가 들어가지 않으면) 그 앞 + 사슬 전체가 함께 간다. 사슬 문단의 각주도 자리를 차지한다. 전에는 바로 다음 문단만 보았다. - 쪽 수 추정이 글자처럼 취급한 그림을 그 효과까지 합친 크기로 놓는다. 빛남은 네 변에 반경만큼, 바깥 그림자는 그림(빛남 포함)을 방향으로 거리만큼 옮기고 반경만큼 넓힌 자리까지 차지해, 그 줄이 그만큼 높아진다. 전에는 효과를 세지 않아 줄과 쪽이 한/글보다 짧게 나왔다. 안쪽 그림자는 자리를 차지하지 않는다. 그림자의 흐림은 한/글처럼 화면 diff --git a/src/hwpx/layout/pages.py b/src/hwpx/layout/pages.py index e31c6cf0..d3101b3c 100644 --- a/src/hwpx/layout/pages.py +++ b/src/hwpx/layout/pages.py @@ -3689,7 +3689,7 @@ def _chunk(self, index: int, paras: list[_Para], para: _Para, start: int, remain if count < remaining: count = self._keep_rules(para, start, remaining, count) if count == remaining and para.keep_next and index + 1 < len(paras): - count = self._keep_with_next(para, paras[index + 1], start, remaining, count) + count = self._keep_with_next(para, paras[index + 1:], start, remaining, count) return count def _keep_rules(self, para: _Para, start: int, remaining: int, count: int) -> int: @@ -3702,11 +3702,40 @@ def _keep_rules(self, para: _Para, start: int, remaining: int, count: int) -> in return count - 1 return count - def _keep_with_next(self, para: _Para, following: _Para, start: int, remaining: int, count: int) -> int: - after = start + para.span(0, remaining) + para.next + following.prev - need = 2 if following.widow_orphan and following.lines > 1 else 1 - if self._fits(after, need, following) < need and start != para.prev: - return 0 + def _keep_with_next(self, para: _Para, following: list[_Para], start: int, remaining: int, count: int) -> int: + """*count*, or 0 when the paragraph set to stay with the next has to go on to the next frame. Its last + line stays with the next paragraph's first line (two under the widow and orphan rule); so does each + paragraph after it set so too (a chain). A paragraph of the chain that reaches the frame's foot + splits there as any other, the chain before it staying; one that has to go on to the next frame + whole takes the whole chain before it along. A paragraph at the frame's top stays.""" + + if start == para.prev: + return count + saved, carry = list(self.page_notes), self.carry + try: + return self._chain_stays(para, following, start, remaining, count) + finally: + self.page_notes, self.carry = saved, carry + + def _chain_stays(self, para: _Para, following: list[_Para], start: int, remaining: int, count: int) -> int: + for line in range(remaining): # the notes of the lines placed take room below the chain + height, many, _ = para.notes.get(line, (0, 0, 0)) + self.page_notes = [self.page_notes[0] + height, self.page_notes[1] + many] + after = start + para.span(0, remaining) + para.next + for item in following: + after += item.prev + if not item.keep_next or item.table is not None or item.page_break or item.break_before \ + or item is following[-1]: + need = 2 if item.widow_orphan and item.lines > 1 else 1 + return count if self._fits(after, need, item) >= need else 0 + fit = self._fits(after, item.lines, item) + if fit == item.lines: + after += item.span(0, item.lines) + item.next + for height, many, _ in item.notes.values(): + self.page_notes = [self.page_notes[0] + height, self.page_notes[1] + many] + continue + # it splits at the foot, its first lines staying with the paragraph before; or it goes on whole + return count if fit and not item.keep_lines else 0 return count def _fits(self, start: int, count: int, para: _Para, first: int = 0) -> int: diff --git a/tests/fixtures/hancom_saved/pages_keep_with_next_chain_before_a_table.hwpx b/tests/fixtures/hancom_saved/pages_keep_with_next_chain_before_a_table.hwpx new file mode 100644 index 00000000..5f2d3a86 Binary files /dev/null and b/tests/fixtures/hancom_saved/pages_keep_with_next_chain_before_a_table.hwpx differ diff --git a/tests/fixtures/hancom_saved/pages_keep_with_next_chain_goes_on.hwpx b/tests/fixtures/hancom_saved/pages_keep_with_next_chain_goes_on.hwpx new file mode 100644 index 00000000..c7533ab3 Binary files /dev/null and b/tests/fixtures/hancom_saved/pages_keep_with_next_chain_goes_on.hwpx differ diff --git a/tests/fixtures/hancom_saved/pages_keep_with_next_chain_of_several_lines_goes_on.hwpx b/tests/fixtures/hancom_saved/pages_keep_with_next_chain_of_several_lines_goes_on.hwpx new file mode 100644 index 00000000..a030770e Binary files /dev/null and b/tests/fixtures/hancom_saved/pages_keep_with_next_chain_of_several_lines_goes_on.hwpx differ diff --git a/tests/fixtures/hancom_saved/pages_keep_with_next_chain_splits_at_the_foot.hwpx b/tests/fixtures/hancom_saved/pages_keep_with_next_chain_splits_at_the_foot.hwpx new file mode 100644 index 00000000..dea80657 Binary files /dev/null and b/tests/fixtures/hancom_saved/pages_keep_with_next_chain_splits_at_the_foot.hwpx differ diff --git a/tests/fixtures/hancom_saved/pages_keep_with_next_chain_with_a_footnote.hwpx b/tests/fixtures/hancom_saved/pages_keep_with_next_chain_with_a_footnote.hwpx new file mode 100644 index 00000000..3c5bd471 Binary files /dev/null and b/tests/fixtures/hancom_saved/pages_keep_with_next_chain_with_a_footnote.hwpx differ diff --git a/tests/test_layout_page_estimate.py b/tests/test_layout_page_estimate.py index 7a5707d2..16f46f89 100644 --- a/tests/test_layout_page_estimate.py +++ b/tests/test_layout_page_estimate.py @@ -239,6 +239,15 @@ "pages_line_ends_200_above_page_foot": 2, # one ending 200 above it stays "pages_line_ends_1_above_page_foot": 2, # and one ending 1 above it "pages_line_ends_100_above_page_foot": 2, # and 100 above it: no room is kept below lines + # Documents of paragraphs, tables, pictures, shapes and footnotes whose paragraphs set to stay with the next + # follow each other (a chain): a paragraph of the chain reaching the page's foot splits there, the chain + # before it staying; one that has to go on to the next page whole (its next paragraph's first line, or a + # table, no longer fits) takes the whole chain before it along, footnotes of the chain taking their room. + "pages_keep_with_next_chain_goes_on": 2, + "pages_keep_with_next_chain_before_a_table": 4, + "pages_keep_with_next_chain_splits_at_the_foot": 2, + "pages_keep_with_next_chain_with_a_footnote": 7, + "pages_keep_with_next_chain_of_several_lines_goes_on": 2, "pages_keep_keep_with_next": 3, # keep with next "pages_columns_2_break": 4, # two columns and a column break # Columns changing inside the section (10 pt lines spaced 160%): three lines in one column, a paragraph