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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

### 고침

- 쪽 수 추정(실험, `estimate_pages`)이 곁에 자리를 남기는 종이·쪽 기준 어울림(SQUARE) 개체의 띠에 닿는 줄을, 그 줄이
줄 캐시를 가질 때 따른다. 한/글은 그런 줄을 같은 높이·간격으로 개체 곁에 좁혀 둔다(양옆에 자리가 있으면 한 높이에
두 조각). 캐시 없는 줄·표·개체가 닿으면 전처럼 지원 밖이다.
- 쪽 수 추정(실험, `estimate_pages`)이 같은 폭의 여러 단 쪽에서 종이 기준으로 둔 위아래 배치 개체가 본문 너비를
다 덮지 않으면 지원 밖(`objects placed on the paper`)으로 두던 것을 고친다. 한/글은 개체가 가로로 걸치는
단에서만 줄을 개체 아래로 보내고, 걸치지 않는 단의 줄은 개체가 없을 때 자리에 둔다. 이제 종이의 왼쪽·가운데·
Expand Down
20 changes: 14 additions & 6 deletions src/hwpx/layout/pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
reaches over from the paper's left, centre or right, the others running past it), and so does one
wrapped square there that leaves less than a line's room
(1440) on either side of it across the text; one that leaves more (in one column) changes nothing
when no line, table or object of its page reaches its band, and is not followed otherwise. An
when no line, table or object of its page reaches its band, and a line keeping its layout cache
that reaches it stays where it is (beside it); anything else reaching it is not followed. An
object anchored at the top of a paragraph's first line that such a band pushes down stands right
below the band, the line below the object; a table flowing with the text alone in its paragraph
stands there too, its paragraph's line below it. A table or picture set as a character is one
Expand Down Expand Up @@ -930,6 +931,8 @@ class _Para:
reflow: _Reflow | None = None
#: holding nothing, in a section hiding a page's first empty lines (see :meth:`_Paginator._paragraph`)
hides: bool = False
#: its lines are Hancom's own, from the paragraph's valid layout cache
kept: bool = False

def height(self, line: int) -> int:
return self.cached[line][0] if self.cached else self.size
Expand Down Expand Up @@ -2005,8 +2008,9 @@ def __init__(self, body: int, columns: int, notes: _NoteShape,
self.widths = widths # each column's width when they differ
#: frame -> (top, bottom) of the objects placed on the paper there: no line in them
self.bands = bands or {}
#: frame -> (top, bottom) of the ones leaving a line's room beside them, and whether a line, a table
#: or an object reaches one of them (set beside it by Hancom, which is not followed)
#: frame -> (top, bottom) of the ones leaving a line's room beside them, and whether a line without
#: a layout cache, a table or an object reaches one of them (set beside it by Hancom, which is not
#: followed: a line keeping its cache stays where it is)
self.sides = sides or {}
self.beside = False
self.columns = columns
Expand Down Expand Up @@ -2081,7 +2085,8 @@ def _flow(self, para: _Para, table: _FlowTable, start: int) -> None:
self._flow_below_band(para, table, self._below_bands(start))
return
self.out.append((self.frame, start)) # the anchor paragraph's line, under the table's top
self._reach(self.frame, start, start + para.height(0))
if not para.kept:
self._reach(self.frame, start, start + para.height(0))
before = self.frame
top = start - para.prev + table.offset + table.above # from the paragraph's top, above its spacing
frame, end = _flow_table(table, self.frame, top, self.body)
Expand All @@ -2108,7 +2113,8 @@ def _flow_below_band(self, para: _Para, table: _FlowTable, top: int) -> None:
self.page_notes = [0, 0] if frame != self.frame else self.page_notes
self.frame, self.table_end = frame, max(self.table_end, frame)
self.out.append((self.frame, line))
self._reach(self.frame, line, line + para.height(0))
if not para.kept:
self._reach(self.frame, line, line + para.height(0))
self.last_vp, self.last_pitch, self.pending_next = line, para.advance(0), para.next

def _anchored(self, index: int, paras: list[_Para], para: _Para, start: int) -> None:
Expand Down Expand Up @@ -2422,7 +2428,8 @@ def _place(self, para: _Para, first: int, count: int) -> None:
note_height, notes, _ = para.notes.get(line, (0, 0, 0))
self.page_notes[0] += note_height
self.page_notes[1] += notes
self._reach(frame, top, top + para.height(line))
if not para.kept:
self._reach(frame, top, top + para.height(line))


@dataclass(frozen=True)
Expand All @@ -2442,6 +2449,7 @@ def _lay_section(measure: _Measure, section: Any) -> _SectionLayout:
for paragraph in section.findall(f"{HP}p"):
para, wrap = _wrapped_paragraph(measure, page, paragraph, wrap)
band = _paper_band(measure, page, paragraph)
para = replace(para, kept=bool(_cached_metrics(paragraph)))
paras.append(para if band is None else replace(para, paper=band))
paper = {index: para.paper for index, para in enumerate(paras) if para.paper is not None}
bands: dict[int, list[tuple[int, int]]] = {}
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
30 changes: 26 additions & 4 deletions tests/test_layout_page_estimate.py
Original file line number Diff line number Diff line change
Expand Up @@ -461,12 +461,34 @@ def test_a_header_wrapped_square_with_room_beside_it_is_not_followed() -> None:
assert estimate_pages(out.getvalue()).unsupported == ("section 0: text beside an object placed on the paper",)


def test_a_line_reaching_a_picture_on_the_paper_with_room_beside_it_is_not_followed() -> None:
# The picture's top 100 above the fifth line's foot: Hancom sets that line beside it, narrower.
def test_a_line_reaching_a_picture_on_the_paper_with_room_beside_it_keeps_its_cached_place() -> None:
# The picture's top 100 above the fifth line's foot: Hancom sets that line beside it, narrower, at the
# same height. Keeping its cache, it is followed; without it, where it breaks is not.
data = (FIXTURES / "pages_square_picture_on_paper_reaching_the_last_line.hwpx").read_bytes()

for source in (data, _without_caches(data)):
assert estimate_pages(source).unsupported == ("section 0: text beside an object placed on the paper",)
_assert_like_hancom(estimate_pages(data), data, 1)
assert estimate_pages(_without_caches(data)).unsupported == (
"section 0: text beside an object placed on the paper",)


@pytest.mark.parametrize(
("name", "pages"),
[
("pages_square_picture_on_paper_lines_beside_it", 2), # at the text's left, 800 below its top
("pages_square_picture_on_paper_lines_on_both_sides", 2), # at its middle: two pieces a line
("pages_square_picture_on_paper_lines_beside_it_on_page_2", 5), # anchored after a page break
],
)
def test_lines_keeping_their_caches_beside_a_picture_on_the_paper_stay_where_hancom_put_them(name: str,
pages: int) -> None:
# A picture wrapped square from the paper's top with room beside it, long paragraphs flowing beside
# it (the line of the paragraph before its anchor too): each line keeps its height and spacing there,
# narrower. Without the caches, where the lines beside it break is not followed.
data = (FIXTURES / f"{name}.hwpx").read_bytes()

_assert_like_hancom(estimate_pages(data), data, pages)
assert estimate_pages(_without_caches(data)).unsupported == (
"section 0: text beside an object placed on the paper",)


def _with_empty_runs_in_cells(data: bytes) -> bytes:
Expand Down
Loading