Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,14 @@
- 쪽 수 추정(실험, `estimate_pages`)이 줄 캐시 없는 표 칸에서 글자처럼 둔 표 옆에 글 앞·뒤로 둔 개체가 있는
문단을 지원 밖(`a nested table`)으로 두던 것을 고친다.
- 그런 개체는 자리를 차지하지 않으므로, 그 문단은 표 혼자인 문단처럼 표 높이의 한 줄이다.
- 위아래로 둔 표 옆에 그런 개체가 있으면 한/글은 빈 줄을 표 아래에 둔다(표 둘일 때처럼). 이것은 전처럼 캐시
없이는 지원 밖이다.
- 위아래로 둔 표 옆에 그런 개체가 있으면 한/글은 빈 줄을 표 아래에 둔다(표 둘일 때처럼). 이제 캐시 없이도
따른다(아래 항목).
- 쪽 수 추정(실험, `estimate_pages`)이 글 없는 문단에 문단 위에서 위아래로 둔 개체(표·그림)와 글 앞·뒤로 둔
개체가 함께 있을 때, 빈 줄을 위아래 개체의 윗선에 두던 것을 고친다.
- 한/글은 공백이나 머리표가 있을 때처럼 빈 줄을 그 개체 아래에 전체 폭으로 둔다. 뜬 개체가 종이 기준이든 문단
기준이든, 글 앞이든 뒤든 같다.
- 위아래 개체만 있으면 전처럼 윗선에 폭 0이다.
- 본문과 표 칸 모두 그렇다. 표 칸에서는 전에 캐시 없이 지원 밖(`a nested table`)이었다.
- 쪽 수 추정(실험, `estimate_pages`)이 글자처럼 둔 개체 하나와 공백·줄 바꿈만 있는 문단을 언제나 한 줄로 세던
것을 고친다.
- 한/글은 개체 바로 뒤 공백 둘을 여백을 넘어도 그 줄에 두고, 그다음 공백이 여백이나 그 너머에서 시작하면
Expand Down
32 changes: 22 additions & 10 deletions src/hwpx/layout/pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand All @@ -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
Expand All @@ -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]:
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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:
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
22 changes: 12 additions & 10 deletions tests/test_layout_page_estimate.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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"),
[
Expand Down
Loading