diff --git a/CHANGELOG.md b/CHANGELOG.md index a2cdc978..bea13a20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ ### 고침 +- 쪽 수 추정(실험, `estimate_pages`)이 표 칸 안에 위아래나 어울림으로 둔 그림·도형의 높이를 행 높이에 넣는다. 글 없는 칸 + 문단에서 문단 위를 기준으로 둔 것(글자처럼 두지 않은 것)이면, 칸이 그 개체의 아래 끝(바깥 여백 포함)까지 온다. 전에는 그 + 개체를 세지 않아 행을 한 줄 높이로 잡았다. - 쪽 수 추정(실험, `estimate_pages`)이 병합 칸으로 묶인 행들이 쪽 바닥을 빈 높이 안에서만 넘을 때, 그 행들을 그 쪽 바닥에서 끝낸다. 모든 칸의 글이 바닥 위에 들어가고 남는 높이가 1282 이하면 다음 쪽으로 넘길 것이 없다. 전에는 표 윗 여백만 있는 빈 쪽이 하나 더 생겼다. diff --git a/src/hwpx/layout/pages.py b/src/hwpx/layout/pages.py index a2536c0a..e33a51b0 100644 --- a/src/hwpx/layout/pages.py +++ b/src/hwpx/layout/pages.py @@ -91,7 +91,9 @@ up from its paragraph's top stands at that top; the caches of a row Hancom split over a page end start over at the next page's top, and are read as one run of lines, each line that goes back up where it would stand in the unsplit cell, a first line below such a table with the room above it, - so the row splits again where it would). A flowing table's rows use + so the row splits again where it would). A picture or drawing placed top and bottom or wrapped + square from a cell paragraph holding no text makes the cell reach its foot (outer margins + included). A flowing table's rows use the body only down to just above the page's foot (101 above it, or 2 in a table set not to be adjusted), less the table's bottom outer margin: a row, or a cell line of a row split between its lines, ending lower goes on to the next page (where the table goes on below its top outer margin; @@ -530,9 +532,12 @@ def stack(self, paragraphs: list[Any], width: int, caches: bool) -> tuple[int, i if cached: # the lines Hancom laid out, each as tall as it drew it if pending is not None: height += pending + shape.prev + top = height height += sum(advance for _, advance in cached[:-1]) + cached[-1][0] size, pitch = cached[-1] pending = pitch - size + shape.next + if top + _objects_reach(runs) > height: # an object placed from the paragraph reaches lower + height, pending = top + _objects_reach(runs), shape.next lines += len(cached) continue style = self.style(paragraph.get("paraPrIDRef"), refs, paragraph) @@ -541,8 +546,11 @@ def stack(self, paragraphs: list[Any], width: int, caches: bool) -> tuple[int, i ) if pending is not None: height += pending + shape.prev + top = height height += (count - 1) * pitch + size pending = pitch - size + shape.next + if top + _objects_reach(runs) > height: # an object placed from the paragraph reaches lower + height, pending = top + _objects_reach(runs), shape.next lines += count return height, lines, pitch, size @@ -1063,6 +1071,27 @@ def _check_section(section: Any) -> None: raise _Unsupported("section settings (hp:secPr) after the first paragraph") +def _objects_reach(runs: list[Any]) -> int: + """How far below the top of their paragraph in a cell, holding no text, the pictures and drawings placed + from it reach: top and bottom or square, not set as a character, their outer margins included (the cell + holds them, and its row grows to). Tables in a cell are laid out on their own.""" + + reach = 0 + if _run_text(runs).strip(): # text goes on below such an object: not followed here + return reach + for obj in (child for run in runs for child in run): + name, pos = _local(obj), obj.find(f"{HP}pos") + if name not in _OBJECTS or name == "tbl" or pos is None or obj.find(f"{HP}sz") is None \ + or pos.get("treatAsChar") == "1" or obj.get("textWrap") not in ("TOP_AND_BOTTOM", "SQUARE") \ + or pos.get("vertRelTo") != "PARA" or pos.get("vertAlign", "TOP") != "TOP": + continue + offset = int(pos.get("vertOffset", 0)) + if offset >= 1 << 31: # kept unsigned: one placed up + offset -= 1 << 32 + reach = max(reach, offset + _extent(obj, "height")) + return reach + + def _placed_objects(runs: list[Any]) -> list[Any]: """The objects of the runs.""" diff --git a/tests/fixtures/hancom_saved/pages_table_as_character_cell_holding_a_drawing_top_and_bottom.hwpx b/tests/fixtures/hancom_saved/pages_table_as_character_cell_holding_a_drawing_top_and_bottom.hwpx new file mode 100644 index 00000000..c24c67dc Binary files /dev/null and b/tests/fixtures/hancom_saved/pages_table_as_character_cell_holding_a_drawing_top_and_bottom.hwpx differ diff --git a/tests/fixtures/hancom_saved/pages_table_cell_holding_a_drawing_500_down.hwpx b/tests/fixtures/hancom_saved/pages_table_cell_holding_a_drawing_500_down.hwpx new file mode 100644 index 00000000..f98ce376 Binary files /dev/null and b/tests/fixtures/hancom_saved/pages_table_cell_holding_a_drawing_500_down.hwpx differ diff --git a/tests/fixtures/hancom_saved/pages_table_cell_holding_a_drawing_after_a_line.hwpx b/tests/fixtures/hancom_saved/pages_table_cell_holding_a_drawing_after_a_line.hwpx new file mode 100644 index 00000000..991c6b27 Binary files /dev/null and b/tests/fixtures/hancom_saved/pages_table_cell_holding_a_drawing_after_a_line.hwpx differ diff --git a/tests/fixtures/hancom_saved/pages_table_cell_holding_a_drawing_top_and_bottom.hwpx b/tests/fixtures/hancom_saved/pages_table_cell_holding_a_drawing_top_and_bottom.hwpx new file mode 100644 index 00000000..f7ffff67 Binary files /dev/null and b/tests/fixtures/hancom_saved/pages_table_cell_holding_a_drawing_top_and_bottom.hwpx differ diff --git a/tests/fixtures/hancom_saved/pages_table_cell_holding_a_drawing_wrapped_square.hwpx b/tests/fixtures/hancom_saved/pages_table_cell_holding_a_drawing_wrapped_square.hwpx new file mode 100644 index 00000000..31f9c0a4 Binary files /dev/null and b/tests/fixtures/hancom_saved/pages_table_cell_holding_a_drawing_wrapped_square.hwpx differ diff --git a/tests/fixtures/hancom_saved/pages_table_cell_holding_a_picture_top_and_bottom.hwpx b/tests/fixtures/hancom_saved/pages_table_cell_holding_a_picture_top_and_bottom.hwpx new file mode 100644 index 00000000..5aa6ebfb Binary files /dev/null and b/tests/fixtures/hancom_saved/pages_table_cell_holding_a_picture_top_and_bottom.hwpx differ diff --git a/tests/test_layout_page_estimate.py b/tests/test_layout_page_estimate.py index 6a2d67d0..3a4bf0d2 100644 --- a/tests/test_layout_page_estimate.py +++ b/tests/test_layout_page_estimate.py @@ -86,6 +86,16 @@ "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_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 + "pages_table_as_character_cell_holding_a_drawing_top_and_bottom": 1, # in a table set as a character + "pages_table_cell_holding_a_drawing_500_down": 1, # 500 down from the paragraph's top + "pages_table_cell_holding_a_drawing_wrapped_square": 1, # wrapped square, narrow: the line beside it + "pages_table_cell_holding_a_picture_top_and_bottom": 1, # a picture + "pages_table_cell_holding_a_drawing_after_a_line": 1, # in the cell's second paragraph, after a line + + "pages_table_joined_rows_end_at_the_foot": 2, # the last two rows joined by a merged cell reach 300 past the # foot within the last one's room to spare: they end there, # the paragraph after the table at the next page's top