From 96394bc1cf795c556dbe1ae5cbb3bf0155cf36d0 Mon Sep 17 00:00:00 2001 From: airmang <38392618+airmang@users.noreply.github.com> Date: Wed, 4 Nov 2026 01:02:36 +0900 Subject: [PATCH 1/2] fix(pages): break the lines beside a square-wrapped object placed on the paper Hancom narrows every line of the page whose top is above such an object's foot and whose characters reach below its top, in any paragraph there (those before its anchor too), or splits it in two pieces at one height with room on both sides. The estimate now breaks those lines beside the object's band as it does for a band that went on to a page's top, instead of leaving the section unsupported; the rest of a paragraph going on to the next page is broken there at the whole width. A square-wrapped object centred on the paper now leaves the room beside it measured from its centre. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 3 + src/hwpx/layout/pages.py | 101 +++++++++++++----- .../pages_square_object_on_the_paper.hwpx | Bin 0 -> 60420 bytes tests/test_layout_page_estimate.py | 21 ++-- 4 files changed, 88 insertions(+), 37 deletions(-) create mode 100644 tests/fixtures/hancom_saved/pages_square_object_on_the_paper.hwpx diff --git a/CHANGELOG.md b/CHANGELOG.md index 233a3d8b..74a4c345 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ - 쪽 수 추정이 표 칸 안에서 양쪽에 글 자리가 있는 어울림 개체 곁에 빈 줄만 오는 칸도 추정한다. 한/글처럼 빈 줄은 개체 곁에 한 높이 두 조각으로 둔다(곁에 글이 오면 지금처럼 지원 밖). 한/글이 저장한 줄 캐시가 있는 칸에서 그런 두 조각을 줄 둘로 세어 칸을 조각 수만큼 높게 잡던 것도 바로잡았다. +- 쪽 수 추정이 종이(또는 쪽) 기준으로 놓인 어울림 개체 곁의 글도 추정한다. 한/글처럼 그 쪽에서 개체 띠에 걸치는 줄은 + 앵커 문단 앞 문단의 줄까지 모두 좁히고, 양쪽에 자리가 있으면 한 높이에 두 조각으로 나눈다. 전에는 지원 밖으로 두었다. + 종이 가로 가운데에 놓인 어울림 개체의 곁 너비도 이제 바르게 잰다(전에는 오른쪽 기준처럼 쟀다). - 쪽 수 추정과 FormFit이 줄 바꿈 뒤 빈 run으로 끝나는 문단의 빈 마지막 줄을 한/글처럼 그 run의 크기로 잡는다(글자보다 작아도). 전에는 작은 run은 무시하고 큰 run은 줄 바꿈 앞 줄까지 키웠다. 끝 run이 더 크면 그 문단의 줄 캐시는 쓰지 않는다. - 쪽 수 추정이 첫 줄에 붙은 어울림 개체의 세로 위치를 한/글처럼 위 간격 위 문단 맨 위에서 잰다. 전에는 첫 줄 맨 위에서 diff --git a/src/hwpx/layout/pages.py b/src/hwpx/layout/pages.py index 4adaa347..b78a32d8 100644 --- a/src/hwpx/layout/pages.py +++ b/src/hwpx/layout/pages.py @@ -49,9 +49,13 @@ paragraph's first line with its spacing reaches it there, the paragraph goes on to the next page's top with it and the lines above stay where they are), 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 a line keeping its layout cache - that reaches it stays where it is (beside it); anything else reaching it is not followed. An + (1440) on either side of it across the text; one that leaves more (in one column) narrows each line + of its page whose top is above its foot and whose characters reach below its top, in any paragraph + on that page (those before its own too), by what it takes, or splits it in two pieces at one height + with room on both sides, as a square-wrapped object flowing with the text does (the rest of a + paragraph going on to the next page is broken there as if it were not there); a line keeping its + layout cache that reaches it stays where it is (beside it); a table or an object reaching it, a + paragraph holding objects or notes, or one beside two such bands, 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 @@ -1849,6 +1853,9 @@ class _Wrap: #: its object does not flow with the text (pos@flowWithText="0"): it stays on its page past the #: body's foot, and the band goes no further than that page stays: bool = False + #: its object is placed on the paper or the page: the band is in the body of its page, and the lines of + #: any paragraph there reaching it go beside it + paper: bool = False def lower(self, by: int) -> "_Wrap | None": """The band seen from *by* further down, or ``None`` once it is above.""" @@ -1929,8 +1936,9 @@ class _Para: wrap_anchor: int = 0 band: _Band | None = None #: for each object anchored here and placed on the paper or the page: (top, bottom) in the body, whether it - #: leaves a line's room beside it and the columns it reaches over (see :func:`_paper_bands`) - paper: tuple[tuple[int, int, bool, tuple[int, ...]], ...] = () + #: leaves a line's room beside it, the columns it reaches over and the band the lines beside it are broken + #: in (see :func:`_paper_bands`) + paper: tuple[tuple[int, int, bool, tuple[int, ...], _Wrap | None], ...] = () #: the top of such a band that pushes the lines below it to the body's foot and that keeps the object on its #: paragraph's page (pos@holdAnchorAndSO="1"): when the first line with its spacing reaches it there, the #: paragraph goes on to the next page's top with the object, the lines above staying where they are @@ -2015,6 +2023,7 @@ class _Reflow: starts: tuple[int, ...] = () end: int = 0 # the paragraph's end (see :func:`_end_size`) head: int = 0 # its label (see :func:`_head_size`) + wrapped: bool = False # its lines from *offset* on are broken beside a band (see :meth:`banded`) def at(self, para: _Para, width: int, line: int) -> _Para: """*para* with its lines from line *line* on (the lines before it laid out already) broken @@ -2034,7 +2043,7 @@ def at(self, para: _Para, width: int, line: int) -> _Para: cached = _line_metrics(self.measure, text, widths, sizes, self.style, self.shape, {}, looks) \ if mixed or looks is not None or self.shape.font_line else () return replace(para, lines=len(starts), cached=cached, - reflow=replace(self, width=width, offset=offset, starts=tuple(starts))) + reflow=replace(self, width=width, offset=offset, starts=tuple(starts), wrapped=False)) def banded(self, para: _Para, line: int, wrap: _Wrap) -> _Para: """*para* with its lines from line *line* on (the lines before it laid out already) broken again @@ -2066,7 +2075,7 @@ def banded(self, para: _Para, line: int, wrap: _Wrap) -> _Para: metrics = _at_one_height(_line_metrics(self.measure, text, lines, sizes, self.style, self.shape, {}, looks, end=self.end, head=0 if offset else self.head), firsts) return replace(para, lines=len(metrics), cached=metrics, - reflow=replace(self, offset=offset, starts=tuple(pieces))) + reflow=replace(self, offset=offset, starts=tuple(pieces), wrapped=True)) @dataclass(frozen=True) @@ -2669,17 +2678,21 @@ def _paper_band(measure: _Measure, page: _Page, paragraph: Any) -> tuple[int, in return bands[0] if bands else None -def _paper_bands(measure: _Measure, page: _Page, paragraph: Any) -> tuple[tuple[int, int, bool, tuple[int, ...]], ...]: +def _paper_bands(measure: _Measure, page: _Page, paragraph: Any + ) -> tuple[tuple[int, int, bool, tuple[int, ...], _Wrap | None], ...]: """For each object of *paragraph* placed on the paper, each on its own as if anchored apart: (top, bottom) in the body, whether it is wrapped square leaving a line's room beside it (Hancom sets the text reaching it - beside it, which is not followed, and the text above or below it as if it were not there), and the columns - it keeps the lines out of.""" + beside it, and the text above or below it as if it were not there), the columns it keeps the lines out of, + and, in one column, the band the lines of its page reaching it are broken in, those of the paragraphs before + its own too (one leaving a line's room on neither side of it that its text flow allows has none: the text + reaching it is not followed).""" return tuple(_object_paper_band(measure, page, child) for run in paragraph.findall(f"{HP}run") for child in run if _local(child) in _OBJECTS and _on_paper(child)) -def _object_paper_band(measure: _Measure, page: _Page, obj: Any) -> tuple[int, int, bool, tuple[int, ...]]: +def _object_paper_band(measure: _Measure, page: _Page, obj: Any + ) -> tuple[int, int, bool, tuple[int, ...], _Wrap | None]: """The band of *obj*, placed on the paper (see :func:`_paper_bands`).""" square = obj.get("textWrap") == "SQUARE" # no line beside it: less than a line's room on each side @@ -2699,7 +2712,13 @@ def _object_paper_band(measure: _Measure, page: _Page, obj: Any) -> tuple[int, i top = (height - tall) // 2 + (offset - (1 << 32) if offset >= 1 << 31 else offset) + base else: # from the body's (the paper's) top or foot top = (offset if align == "TOP" else height - offset - tall) + base - return top, top + tall, square and not across, columns + wrap = None + if square and not across and page.columns == 1: + left, right = _square_sides(obj, page) + if left or right: + wrap = _Wrap(top, top + tall, page.column_width - left - right, split=left if left and right else 0, + paper=True) + return top, top + tall, square and not across, columns, wrap def _held_top(page: _Page, paragraph: Any, band: tuple[int, int, bool, tuple[int, ...]]) -> int | None: @@ -2707,7 +2726,7 @@ def _held_top(page: _Page, paragraph: Any, band: tuple[int, int, bool, tuple[int lines reaching it below the body's foot and the object keeps to its paragraph's page (see :attr:`_Para.held`) -- in one column only; otherwise ``None``.""" - top, bottom, room, _ = band + top, bottom, room, _, _ = band obj = next(child for run in paragraph.findall(f"{HP}run") for child in run if _local(child) in _OBJECTS and _on_paper(child)) if room or page.columns != 1 or bottom < page.body or obj.find(f"{HP}pos").get("holdAnchorAndSO") != "1": @@ -3551,7 +3570,7 @@ def _square_anchor_line(measure: _Measure, page: _Page, paragraph: Any, runs: li def _square_sides(obj: Any, page: _Page) -> tuple[int, int]: """How wide the text beside a square-wrapped object is on its left and on its right in the column: a side narrower than 1440, or one its text flow leaves out, takes none (0). Placed from the column, - the paragraph or the paper's left or right (from the paper only in one column).""" + the paragraph or the paper's left or right (from the paper only in one column, and from its centre too).""" pos = obj.find(f"{HP}pos") width, offset = _extent(obj, "width"), _pos_offset(pos, "horzOffset") @@ -3560,7 +3579,11 @@ def _square_sides(obj: Any, page: _Page) -> tuple[int, int]: if pos.get("horzRelTo") == "PAPER": if page.columns > 1: raise _Unsupported(f"{_local(obj)} wrapped square from the paper in columns") - start = offset - page.left if pos.get("horzAlign") == "LEFT" else page.paper_width - width - offset - page.left + start = _paper_left(obj, page) + if start is None: + raise _Unsupported(f"{_local(obj)} wrapped square from the paper otherwise than from its left, centre " + "or right") + start -= page.left else: start = offset if pos.get("horzAlign") == "LEFT" else page.column_width - width - offset left, right = start, page.column_width - start - width @@ -4380,7 +4403,8 @@ class _Paginator: def __init__(self, body: int, columns: int, notes: _NoteShape, bands: dict[int, list[tuple[int, int]]] | None = None, widths: tuple[int, ...] = (), - sides: dict[int, list[tuple[int, int]]] | None = None, alone_at_top: bool = True) -> None: + sides: dict[int, list[tuple[int, int]]] | None = None, alone_at_top: bool = True, + squares: dict[int, list[_Wrap]] | None = None) -> None: self.body = body self.alone_at_top = alone_at_top # see _Page.alone_at_top self.widths = widths # each column's width when they differ @@ -4408,8 +4432,9 @@ def __init__(self, body: int, columns: int, notes: _NoteShape, self.wrap_frame = -1 # the frame a square-wrapped object's band is on self.wrap_moved = False # that band passed the body's foot: the object went on to the next page #: frame -> the bands of square-wrapped objects there that the text there is broken beside: one that - #: went on to its top (see :attr:`_Para.moved`), the parts of tables split over its ends - self.squares: dict[int, list[_Wrap]] = {} + #: went on to its top (see :attr:`_Para.moved`), the parts of tables split over its ends, the objects + #: placed on the paper leaving a line's room beside them (see :func:`_paper_bands`) + self.squares: dict[int, list[_Wrap]] = {frame: list(found) for frame, found in (squares or {}).items()} #: frames whose band is a top-and-bottom object that went on to their top (see :meth:`_move_band`) self.moved_tops: set[int] = set() #: a flowing table's band no line has reached yet: its frame, top, bottom there (None when it goes @@ -4447,7 +4472,7 @@ def _paragraph(self, index: int, paras: list[_Para], para: _Para) -> None: if self.floor is not None: start, self.floor = max(start, self.floor), None if (para.stack or para.band is not None or para.table is not None or para.anchor is not None) \ - and (self.frame in self.squares or self.frame + 1 in self.squares): + and any(not band.paper for frame in (self.frame, self.frame + 1) for band in self.squares.get(frame, ())): raise _Unsupported("objects or a table beside a square-wrapped object moved to the next page") if para.rises is not None and self.last_vp is not None: para, start = self._below_a_rising_object(index, paras, para, start) @@ -4868,11 +4893,13 @@ def _clear_of_paper(self, first: int, last: int, top: int = 0, end: int | None = raise _Unsupported("a table on a page with objects stacked in an empty paragraph" if stacked else "a table on a page with an object placed on the paper") - def _reach(self, frame: int, top: int, bottom: int) -> None: + def _reach(self, frame: int, top: int, bottom: int, broken: bool = False) -> None: """Note whether something from *top* to *bottom* in *frame* reaches the band of an object placed on - the paper that leaves a line's room beside it.""" + the paper that leaves a line's room beside it, other than a line *broken* beside it.""" - if any(top < foot and bottom > head for head, foot in self.sides.get(frame, ())): + if any(top < foot and bottom > head for head, foot in self.sides.get(frame, ())) \ + or not broken and any(band.paper and top < band.bottom and bottom > band.top + for band in self.squares.get(frame, ())): self.beside = True def _below_bands(self, top: int) -> int: @@ -4946,6 +4973,8 @@ def _lay(self, index: int, paras: list[_Para], para: _Para, start: int, broke: b start, below = self.body, True # its object's band, on this page, takes its first line: both go on continue hit = self._band_hit(para, done, remaining, start) + if hit is not None and para.reflow is not None and para.reflow.wrapped: + raise _Unsupported("lines beside a square-wrapped object reaching an object placed on the paper") if hit is not None and hit[0] == 0: # the line reaches an object placed on the paper: below it start, below = hit[1], True continue @@ -4975,6 +5004,9 @@ def _lay(self, index: int, paras: list[_Para], para: _Para, start: int, broke: b if remaining: start, below = self._next_frame(para, count, first_chunk), False line = para.lines - remaining + if para.reflow is not None and para.reflow.wrapped: # broken beside a band of the page before: + para = para.reflow.at(para, para.reflow.width, line) # the rest as if it were not there + remaining, line = para.lines, 0 again = self._at_width(para, line) if again is not para: para, remaining, line = again, again.lines, 0 @@ -5007,6 +5039,11 @@ def _at_band(self, para: _Para, line: int, start: int) -> _Para: if para.blank: # an empty line reaching it: two empty pieces beside an object with text on both sides reaches = start + para.height(line) > band.top return _in_two_pieces(para) if reaches and band.split and para.lines == 1 and not para.cached else para + if band.paper and (para.kept or start + para.span(line, para.lines - 1 - line) + para.height(para.lines - 1) + <= band.top): # a line keeping its layout cache stays where it is + return para + if para.reflow is None and band.paper: + raise _Unsupported("objects or notes beside a square-wrapped object placed on the paper") if para.reflow is None: raise _Unsupported("objects, notes or a layout cache beside a square-wrapped object moved to the next page") again = para.reflow.banded(para, line, replace(band, top=band.top - start, bottom=band.bottom - start)) @@ -5150,7 +5187,7 @@ def _place(self, para: _Para, first: int, count: int) -> None: self.page_notes[0] += note_height self.page_notes[1] += notes if not para.kept: - self._reach(frame, top, top + para.height(line)) + self._reach(frame, top, top + para.height(line), broken=True) @dataclass(frozen=True) @@ -5211,24 +5248,32 @@ def _paginate(measure: _Measure, section: Any, page: _Page, notes: _NoteShape, m held = _held_top(page, paragraph, on_paper[0]) if len(on_paper) == 1 else None paras.append(replace(para, paper=on_paper, held=held) if on_paper else para) paper = {index: para.paper for index, para in enumerate(paras) if para.paper} + if not page.rebreak and any(band[4] is not None for objects in paper.values() for band in objects): + return _paginate(measure, section, replace(page, rebreak=True), notes, moved) # lines broken beside them bands: dict[int, list[tuple[int, int]]] = {} sides: dict[int, list[tuple[int, int]]] = {} + squares: dict[int, list[_Wrap]] = {} for _ in range(4): # an object placed on the paper acts on the page its paragraph lands on - paginator = _Paginator(page.body, page.columns, notes, bands, page.widths, sides, page.alone_at_top) + paginator = _Paginator(page.body, page.columns, notes, bands, page.widths, sides, page.alone_at_top, + squares) frames = paginator.run(paras) firsts = [0] for count in paginator.counts: firsts.append(firsts[-1] + count) placed: dict[int, list[tuple[int, int]]] = {} beside: dict[int, list[tuple[int, int]]] = {} + wrapped: dict[int, list[_Wrap]] = {} for index, objects in paper.items(): # each on the columns of the page it reaches over first = paginator.out[firsts[index]][0] // page.columns * page.columns - for top, bottom, room, columns in objects: + for top, bottom, room, columns, wrap in objects: for column in columns: - (beside if room else placed).setdefault(first + column, []).append((top, bottom)) - if placed == bands and beside == sides: + if wrap is not None: + wrapped.setdefault(first + column, []).append(wrap) + else: + (beside if room else placed).setdefault(first + column, []).append((top, bottom)) + if placed == bands and beside == sides and wrapped == squares: break - bands, sides = placed, beside + bands, sides, squares = placed, beside, wrapped else: raise _Unsupported("objects placed on the paper whose pages do not settle") if paginator.beside: diff --git a/tests/fixtures/hancom_saved/pages_square_object_on_the_paper.hwpx b/tests/fixtures/hancom_saved/pages_square_object_on_the_paper.hwpx new file mode 100644 index 0000000000000000000000000000000000000000..c619a6490caefac097dcebd9df4caecdb22340c8 GIT binary patch literal 60420 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 QUq%A_VSt%bU 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_keeps_its_cached_place() -> None: +def test_a_line_reaching_a_picture_on_the_paper_with_room_beside_it_goes_beside_it() -> 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. + # same height, whether it keeps its cache or is broken anew. data = (FIXTURES / "pages_square_picture_on_paper_reaching_the_last_line.hwpx").read_bytes() _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",) + _assert_like_hancom(estimate_pages(_without_caches(data)), data, 1) @pytest.mark.parametrize( @@ -1947,16 +1952,14 @@ def test_a_line_reaching_a_picture_on_the_paper_with_room_beside_it_keeps_its_ca ("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: +def test_lines_beside_a_picture_on_the_paper_go_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. + # narrower, and without the caches breaks where Hancom broke it. 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",) + _assert_like_hancom(estimate_pages(_without_caches(data)), data, pages) @pytest.mark.parametrize(("name", "pages"), [("pages_square_picture_staying_above_the_foot", 2), From 1a84625c400f189d3d5b6adaf22d0b6788ddaa24 Mon Sep 17 00:00:00 2001 From: airmang <38392618+airmang@users.noreply.github.com> Date: Wed, 4 Nov 2026 02:09:57 +0900 Subject: [PATCH 2/2] fix(pages): measure the lines beside an object on the paper from the paragraph's margins Beside a square-wrapped object placed on the paper, Hancom runs a line from the paragraph's margin to the object, or from the object to the margin: the margin on the object's side takes no more than the object does. The estimate now does so instead of taking both away. Several such objects settle their pages one after another, so the section is laid out again up to once more for each of them. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 3 +- src/hwpx/layout/pages.py | 35 ++++++++++++++++-- ...re_object_on_the_paper_beside_margins.hwpx | Bin 0 -> 17579 bytes tests/test_layout_page_estimate.py | 5 +++ 4 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 tests/fixtures/hancom_saved/pages_square_object_on_the_paper_beside_margins.hwpx diff --git a/CHANGELOG.md b/CHANGELOG.md index 74a4c345..7be2f4c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ 한 높이 두 조각으로 둔다(곁에 글이 오면 지금처럼 지원 밖). 한/글이 저장한 줄 캐시가 있는 칸에서 그런 두 조각을 줄 둘로 세어 칸을 조각 수만큼 높게 잡던 것도 바로잡았다. - 쪽 수 추정이 종이(또는 쪽) 기준으로 놓인 어울림 개체 곁의 글도 추정한다. 한/글처럼 그 쪽에서 개체 띠에 걸치는 줄은 - 앵커 문단 앞 문단의 줄까지 모두 좁히고, 양쪽에 자리가 있으면 한 높이에 두 조각으로 나눈다. 전에는 지원 밖으로 두었다. + 앵커 문단 앞 문단의 줄까지 모두 좁히고, 양쪽에 자리가 있으면 한 높이에 두 조각으로 나눈다. 곁 줄은 문단 여백에서 개체까지 + (또는 개체에서 여백까지)이고 개체 쪽 여백은 더 빼지 않는다. 전에는 지원 밖으로 두었다. 종이 가로 가운데에 놓인 어울림 개체의 곁 너비도 이제 바르게 잰다(전에는 오른쪽 기준처럼 쟀다). - 쪽 수 추정과 FormFit이 줄 바꿈 뒤 빈 run으로 끝나는 문단의 빈 마지막 줄을 한/글처럼 그 run의 크기로 잡는다(글자보다 작아도). 전에는 작은 run은 무시하고 큰 run은 줄 바꿈 앞 줄까지 키웠다. 끝 run이 더 크면 그 문단의 줄 캐시는 쓰지 않는다. diff --git a/src/hwpx/layout/pages.py b/src/hwpx/layout/pages.py index b78a32d8..fc488bed 100644 --- a/src/hwpx/layout/pages.py +++ b/src/hwpx/layout/pages.py @@ -52,7 +52,9 @@ (1440) on either side of it across the text; one that leaves more (in one column) narrows each line of its page whose top is above its foot and whose characters reach below its top, in any paragraph on that page (those before its own too), by what it takes, or splits it in two pieces at one height - with room on both sides, as a square-wrapped object flowing with the text does (the rest of a + with room on both sides, as a square-wrapped object flowing with the text does -- a line running from + the paragraph's margin to the object or from the object to the margin, the margin on the object's + side taking no more (the rest of a paragraph going on to the next page is broken there as if it were not there); a line keeping its layout cache that reaches it stays where it is (beside it); a table or an object reaching it, a paragraph holding objects or notes, or one beside two such bands, is not followed. An @@ -1856,6 +1858,24 @@ class _Wrap: #: its object is placed on the paper or the page: the band is in the body of its page, and the lines of #: any paragraph there reaching it go beside it paper: bool = False + #: such an object's room on its left and on its right in a column *column* wide (see :func:`_square_sides`): + #: a line beside it runs from the paragraph's left margin to the object, or from the object to the + #: paragraph's right margin, the margin on the object's side taking no more (see :meth:`pieces`) + room: tuple[int, int] = (0, 0) + column: int = 0 + + def pieces(self, full: float, right: int) -> list[float]: + """The widths of a line beside an object with *room*, its pieces one on each side with text: a line + *full* wide, its right margin *right*.""" + + end = self.column - right + start = end - full + left, beside = self.room + widths = ([min(end, left) - start] if left else []) + ([end - max(start, self.column - beside)] + if beside else []) + if any(width <= 0 for width in widths): + raise _Unsupported("a paragraph's margin reaching past a square-wrapped object placed on the paper") + return widths def lower(self, by: int) -> "_Wrap | None": """The band seen from *by* further down, or ``None`` once it is above.""" @@ -2717,7 +2737,7 @@ def _object_paper_band(measure: _Measure, page: _Page, obj: Any left, right = _square_sides(obj, page) if left or right: wrap = _Wrap(top, top + tall, page.column_width - left - right, split=left if left and right else 0, - paper=True) + paper=True, room=(left, right), column=page.column_width) return top, top + tall, square and not across, columns, wrap @@ -3857,7 +3877,12 @@ def _wrapped_widths(measure: _Measure, text: str, widths: list[float], size: int firsts: set[int] = set() for index in range(max(narrow, default=0) + 2): full = widths[min(index, 1)] - if index in narrow and wrap.split: + if index in narrow and wrap.column: + pieces = wrap.pieces(full, shape.right) + if len(pieces) > 1: + firsts.add(len(lines)) + lines += pieces + elif index in narrow and wrap.split: firsts.add(len(lines)) lines += [wrap.split, full - wrap.cut - wrap.split] else: @@ -5253,7 +5278,9 @@ def _paginate(measure: _Measure, section: Any, page: _Page, notes: _NoteShape, m bands: dict[int, list[tuple[int, int]]] = {} sides: dict[int, list[tuple[int, int]]] = {} squares: dict[int, list[_Wrap]] = {} - for _ in range(4): # an object placed on the paper acts on the page its paragraph lands on + # an object placed on the paper acts on the page its paragraph lands on (each settled one may move the pages + # of those after it: a try more for each) + for _ in range(max(4, min(len(paper) + 2, 24))): paginator = _Paginator(page.body, page.columns, notes, bands, page.widths, sides, page.alone_at_top, squares) frames = paginator.run(paras) diff --git a/tests/fixtures/hancom_saved/pages_square_object_on_the_paper_beside_margins.hwpx b/tests/fixtures/hancom_saved/pages_square_object_on_the_paper_beside_margins.hwpx new file mode 100644 index 0000000000000000000000000000000000000000..d768b6f7f5b92f50ce4751f89ad1f44d16a78c40 GIT binary patch literal 17579 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 literal 0 HcmV?d00001 diff --git a/tests/test_layout_page_estimate.py b/tests/test_layout_page_estimate.py index 276703a9..cec12888 100644 --- a/tests/test_layout_page_estimate.py +++ b/tests/test_layout_page_estimate.py @@ -126,6 +126,11 @@ # its left 5000, reaching 4496 into the text (8000): the lines of the page reaching its band, those of the # paragraphs before its own too, are narrower, two pieces at one height where there is room on both sides "pages_square_object_on_the_paper": 8, + # ... nine times, each over two pages: the ten paragraphs 5 mm from the left and 10 mm from the right, 10 and + # 5 mm, or 10 and 10 mm hanging 5 mm, the table in the first one at the text's left, its middle or its right: + # a line beside it runs from the paragraph's margin to the table, or from the table to the margin, the margin + # on the table's side taking no more + "pages_square_object_on_the_paper_beside_margins": 19, "pages_square_band_table_as_character_alone": 1, # 47058 wide: beside it all the same, reaching past the room "pages_square_band_narrow_table_as_character_alone": 1, # 5000 wide: beside it, and the lines after # ... or text whose lines in the room beside the picture are as wide as it, and an object set as a