diff --git a/CHANGELOG.md b/CHANGELOG.md index e16c84ec..9cae9b4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,8 +53,14 @@ - 쪽 수 추정(실험, `estimate_pages`)이 줄 캐시 없는 표 칸에서 글자처럼 둔 표 옆에 글 앞·뒤로 둔 개체가 있는 문단을 지원 밖(`a nested table`)으로 두던 것을 고친다. - 그런 개체는 자리를 차지하지 않으므로, 그 문단은 표 혼자인 문단처럼 표 높이의 한 줄이다. - - 위아래로 둔 표 옆에 그런 개체가 있으면 한/글은 빈 줄을 표 아래에 둔다(표 둘일 때처럼). 이것은 전처럼 캐시 - 없이는 지원 밖이다. + - 위아래로 둔 표 옆에 그런 개체가 있으면 한/글은 빈 줄을 표 아래에 둔다(표 둘일 때처럼). 이제 캐시 없이도 + 따른다(아래 항목). +- 쪽 수 추정(실험, `estimate_pages`)이 글 없는 문단에 문단 위에서 위아래로 둔 개체(표·그림)와 글 앞·뒤로 둔 + 개체가 함께 있을 때, 빈 줄을 위아래 개체의 윗선에 두던 것을 고친다. + - 한/글은 공백이나 머리표가 있을 때처럼 빈 줄을 그 개체 아래에 전체 폭으로 둔다. 뜬 개체가 종이 기준이든 문단 + 기준이든, 글 앞이든 뒤든 같다. + - 위아래 개체만 있으면 전처럼 윗선에 폭 0이다. + - 본문과 표 칸 모두 그렇다. 표 칸에서는 전에 캐시 없이 지원 밖(`a nested table`)이었다. - 쪽 수 추정(실험, `estimate_pages`)이 글자처럼 둔 개체 하나와 공백·줄 바꿈만 있는 문단을 언제나 한 줄로 세던 것을 고친다. - 한/글은 개체 바로 뒤 공백 둘을 여백을 넘어도 그 줄에 두고, 그다음 공백이 여백이나 그 너머에서 시작하면 diff --git a/src/hwpx/layout/pages.py b/src/hwpx/layout/pages.py index e9fdd496..ff81f213 100644 --- a/src/hwpx/layout/pages.py +++ b/src/hwpx/layout/pages.py @@ -1419,11 +1419,12 @@ def _placed_from(paragraph: Any) -> bool: def _object_before_text(runs: list[Any], headed: bool = False) -> Any: """The object placed top and bottom from its paragraph's top (a table, picture or drawing) that is all the - runs hold but text after it -- a space is text, and so is the bullet or number of a *headed* paragraph -- - or ``None``.""" + runs hold but text after it -- a space is text, and so is the bullet or number of a *headed* paragraph or + an object in front of or behind the text beside it (see :func:`_beside_floating`) -- or ``None``.""" - objects = [child for run in runs for child in run if _local(child) in _OBJECTS] - if len(objects) != 1 or not _placed_top_and_bottom(objects[0]) or not (_run_text(runs) or headed): + objects = [child for run in runs for child in run if _local(child) in _OBJECTS and not _floating(child)] + if len(objects) != 1 or not _placed_top_and_bottom(objects[0]) \ + or not (_run_text(runs) or headed or _beside_floating(runs)): return None for child in (child for run in runs for child in run): if child is objects[0]: @@ -1433,6 +1434,14 @@ def _object_before_text(runs: list[Any], headed: bool = False) -> Any: return None +def _beside_floating(runs: list[Any]) -> bool: + """Whether the runs hold an object in front of or behind the text: beside an object placed top and bottom + from the paragraph's top in a paragraph of no text, it sends the paragraph's empty line below that object, + as the whole width, as a space or a label does.""" + + return any(_local(child) in _OBJECTS and _floating(child) for run in runs for child in run) + + def _table_on_its_own(paragraph: Any) -> bool: """Whether a cell paragraph holds no text, not even a space, and nothing but one table placed top and bottom from its top (Hancom sets the empty line below two of them, not beside them, and so with an object @@ -1446,11 +1455,11 @@ def _table_on_its_own(paragraph: Any) -> bool: def _table_before_text(runs: list[Any]) -> Any: """The table placed top and bottom from its paragraph's top that is all the runs hold but text after it - (text, a space being enough), or ``None``.""" + (text, a space being enough, or an object in front of or behind the text beside it), or ``None``.""" - objects = [child for run in runs for child in run if _local(child) in _OBJECTS] + objects = [child for run in runs for child in run if _local(child) in _OBJECTS and not _floating(child)] if len(objects) != 1 or _local(objects[0]) != "tbl" or not _placed_top_and_bottom(objects[0]) \ - or not _run_text(runs): + or not (_run_text(runs) or _beside_floating(runs)): return None for child in (child for run in runs for child in run): if child is objects[0]: @@ -1748,8 +1757,10 @@ def _inline_content(measure: _Measure, paragraph: Any, runs: list[Any], anchored def _anchored_object(objects: list[Any], text: str, column: int, headed: bool = False) -> Any: """The one object not set as a character of a paragraph of text (a space is text, and so is the bullet or - number label of a *headed* paragraph: its line goes below the object) or of objects set as characters that - is placed top and bottom from the paragraph's top (offset 0), or ``None``.""" + number label of a *headed* paragraph: its line goes below the object; *headed* is also set for an empty + paragraph holding an object in front of or behind the text beside it, its empty line going below the object + as the whole width) or of objects set as characters that is placed top and bottom from the paragraph's top + (offset 0), or ``None``.""" placed = [obj for obj in objects if obj.find(f"{HP}pos").get("treatAsChar") != "1"] if len(placed) != 1 or not (text or headed or len(objects) > 1): @@ -1875,7 +1886,8 @@ def _paragraph(measure: _Measure, page: _Page, paragraph: Any, wrap: _Wrap | Non text = _run_text(runs) shape = measure.shape(paragraph.get("paraPrIDRef")) headed = bool(objects) and not text and measure.headed(paragraph) # its label is text too - anchored = _anchored_object(objects, text, page.column_width, headed) + beside = bool(objects) and not text and _beside_floating(runs) # its empty line goes below the object + anchored = _anchored_object(objects, text, page.column_width, headed or beside) marks = _marks(runs) and not _cached_metrics(paragraph) # to lay out like characters if marks: if anchored is not None or wrap is not None or square is not None: diff --git a/tests/fixtures/hancom_saved/pages_empty_paragraph_picture_and_a_picture_behind.hwpx b/tests/fixtures/hancom_saved/pages_empty_paragraph_picture_and_a_picture_behind.hwpx new file mode 100644 index 00000000..2e4a8e34 Binary files /dev/null and b/tests/fixtures/hancom_saved/pages_empty_paragraph_picture_and_a_picture_behind.hwpx differ diff --git a/tests/fixtures/hancom_saved/pages_empty_paragraph_table_alone.hwpx b/tests/fixtures/hancom_saved/pages_empty_paragraph_table_alone.hwpx new file mode 100644 index 00000000..bd287de1 Binary files /dev/null and b/tests/fixtures/hancom_saved/pages_empty_paragraph_table_alone.hwpx differ diff --git a/tests/fixtures/hancom_saved/pages_empty_paragraph_table_and_a_picture_behind_from_it.hwpx b/tests/fixtures/hancom_saved/pages_empty_paragraph_table_and_a_picture_behind_from_it.hwpx new file mode 100644 index 00000000..6bf36b2e Binary files /dev/null and b/tests/fixtures/hancom_saved/pages_empty_paragraph_table_and_a_picture_behind_from_it.hwpx differ diff --git a/tests/fixtures/hancom_saved/pages_empty_paragraph_table_and_a_picture_behind_on_the_paper.hwpx b/tests/fixtures/hancom_saved/pages_empty_paragraph_table_and_a_picture_behind_on_the_paper.hwpx new file mode 100644 index 00000000..775ce46c Binary files /dev/null and b/tests/fixtures/hancom_saved/pages_empty_paragraph_table_and_a_picture_behind_on_the_paper.hwpx differ diff --git a/tests/fixtures/hancom_saved/pages_empty_paragraph_table_and_a_picture_in_front.hwpx b/tests/fixtures/hancom_saved/pages_empty_paragraph_table_and_a_picture_in_front.hwpx new file mode 100644 index 00000000..9668d377 Binary files /dev/null and b/tests/fixtures/hancom_saved/pages_empty_paragraph_table_and_a_picture_in_front.hwpx differ diff --git a/tests/test_layout_page_estimate.py b/tests/test_layout_page_estimate.py index 1ccc1889..6a8f8e85 100644 --- a/tests/test_layout_page_estimate.py +++ b/tests/test_layout_page_estimate.py @@ -218,6 +218,18 @@ "pages_table_cell_picture_up_second_far": 1, # 30000 up: at its top too "pages_table_cell_picture_in_a_bulleted_paragraph": 1, # alone in a bulleted paragraph: the bullet under it "pages_picture_alone_after_a_line": 1, # in the body: the line on its top, the text after under it + # An empty paragraph 8000 down holding a table 6659 tall (outer margins 283 above and 1417 below) or a + # picture as tall placed top and bottom from its top, and a picture 10000 x 5000 in front of or behind the + # text: Hancom sets the empty line below the object (16359), as the whole width, as for a space or a label; + # without the floating picture on the object's top, taking no room (8000). + "pages_empty_paragraph_table_and_a_picture_behind_on_the_paper": 1, # the picture placed on the paper + "pages_empty_paragraph_table_and_a_picture_behind_from_it": 1, # placed from the paragraph, over the table + "pages_empty_paragraph_table_and_a_picture_in_front": 1, + "pages_empty_paragraph_picture_and_a_picture_behind": 1, # a picture placed top and bottom + "pages_empty_paragraph_table_alone": 1, + "pages_cell_table_top_and_bottom_and_a_drawing_behind_the_text": 1, # in a cell, a rectangle behind the + # text: the cell's line under the + # table too # A table set as a character (a row 1282 tall) 200 to 2000 narrower than the column (42520; 10 pt text spaced # 160%, a space 500 wide), spaces after it: the two right after it stay on its line wherever they start, a # further one starting at or past the margin goes on to the next line, the paragraph after below them: @@ -875,16 +887,6 @@ def test_an_empty_run_before_a_cells_text_takes_no_room(name: str) -> None: _assert_like_hancom(estimate_pages(_with_empty_runs_in_cells(data)), data, HANCOM_PAGES[name]) -def test_a_cell_table_placed_top_and_bottom_beside_a_floating_object_is_not_followed_without_caches() -> None: - # A cell paragraph holding a table placed top and bottom from its top and a rectangle behind the text: - # Hancom sets the paragraph's empty line below the table (as below two such tables), not beside it. With - # the caches the estimate follows the cell's lines; without them it does not. - data = (FIXTURES / "pages_cell_table_top_and_bottom_and_a_drawing_behind_the_text.hwpx").read_bytes() - - _assert_like_hancom(estimate_pages(data), data, 1) - assert estimate_pages(_without_caches(data)).unsupported == ("section 0: a nested table",) - - @pytest.mark.parametrize( ("fixture", "pages"), [