From 9ff1b3359b8f359981a6101856378c277c0b6d23 Mon Sep 17 00:00:00 2001 From: airmang <38392618+airmang@users.noreply.github.com> Date: Fri, 2 Oct 2026 22:34:18 +0900 Subject: [PATCH 1/2] fix(layout): move objects past the page foot to the next page's top as Hancom does A picture or drawing wrapped square and flowing with the text whose band passed the body's foot was followed only through the layout caches. Hancom sets it at the next page's body top whatever its offset: the lines of its own page are the whole width, and the lines on the next page whose top is above its foot go beside it. The section is now laid out again with the object there: its paragraph and the ones after it the whole width, and the lines reaching it on the next page broken again beside it. A table wrapped square and set not to split moves the same way; one that may split is split over the page end instead and is still followed only through the caches. A picture or drawing placed top and bottom below its empty paragraph's line, flowing with the text, whose foot would pass the body's foot from where the paragraph stands, goes on alone to the next page's top too: the paragraph's line stays, and the lines on the next page reaching it go below it. The estimate sent the paragraph to the next page with it, with the layout caches as well. A line beside an object with text on both sides is two pieces even when its text ends in the first piece or it holds no text; the estimate now counts the empty second piece too. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 13 ++ src/hwpx/layout/pages.py | 192 ++++++++++++++++-- ...ture_1319_down_moved_to_the_next_page.hwpx | Bin 0 -> 9663 bytes ..._above_the_foot_short_lines_beside_it.hwpx | Bin 0 -> 9116 bytes ...ages_square_picture_moved_at_the_left.hwpx | Bin 0 -> 9592 bytes ...s_square_picture_moved_from_8000_down.hwpx | Bin 0 -> 9624 bytes ..._picture_moved_its_paragraph_going_on.hwpx | Bin 0 -> 9517 bytes ...picture_moved_text_going_on_beside_it.hwpx | Bin 0 -> 9711 bytes ...square_picture_moved_to_the_next_page.hwpx | Bin 0 -> 9178 bytes ...picture_staying_short_lines_beside_it.hwpx | Bin 0 -> 9146 bytes ...able_not_split_moved_to_the_next_page.hwpx | Bin 0 -> 9711 bytes tests/test_layout_page_estimate.py | 47 ++++- 12 files changed, 227 insertions(+), 25 deletions(-) create mode 100644 tests/fixtures/hancom_saved/pages_picture_1319_down_moved_to_the_next_page.hwpx create mode 100644 tests/fixtures/hancom_saved/pages_square_picture_above_the_foot_short_lines_beside_it.hwpx create mode 100644 tests/fixtures/hancom_saved/pages_square_picture_moved_at_the_left.hwpx create mode 100644 tests/fixtures/hancom_saved/pages_square_picture_moved_from_8000_down.hwpx create mode 100644 tests/fixtures/hancom_saved/pages_square_picture_moved_its_paragraph_going_on.hwpx create mode 100644 tests/fixtures/hancom_saved/pages_square_picture_moved_text_going_on_beside_it.hwpx create mode 100644 tests/fixtures/hancom_saved/pages_square_picture_moved_to_the_next_page.hwpx create mode 100644 tests/fixtures/hancom_saved/pages_square_picture_staying_short_lines_beside_it.hwpx create mode 100644 tests/fixtures/hancom_saved/pages_square_table_not_split_moved_to_the_next_page.hwpx diff --git a/CHANGELOG.md b/CHANGELOG.md index 184c06cf..7cf55a93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,19 @@ ### 고침 +- 쪽 수 추정(실험, `estimate_pages`)이 글과 함께 움직이는 어울림(SQUARE) 그림·도형이 본문 바닥을 넘을 때, 줄 캐시가 + 없으면 지원 밖으로 두던 것을 고친다. + - 한/글은 그 개체를 띄움과 상관없이 다음 쪽 본문 맨 위로 옮긴다. 개체가 있던 쪽의 줄은 모두 전체 폭이다. 다음 + 쪽에서 줄 위가 개체 아래 끝보다 위인 줄은 개체 곁으로 간다. + - 이제 그 쪽들을 다시 놓는다. 개체 문단과 뒤 문단을 전체 폭으로 나누고, 다음 쪽 맨 위의 개체 곁에서 줄을 다시 + 나눈다. 그 자리에 개체·각주가 든 문단이나 표가 오면 지원 밖이다. + - 나누지 않게 둔 어울림 표도 그렇게 옮겨 간다. 나뉘는 어울림 표는 쪽 끝에서 나뉘므로 전처럼 캐시가 있을 때만 + 따른다. + - 문단 줄 아래로 띄워 위아래로 둔 그림·도형도, 문단이 놓인 자리에서 개체 발이 바닥을 넘으면 다음 쪽 맨 위로 + 옮겨 간다. 문단 줄은 제자리에 남고, 다음 쪽에서 개체에 닿는 줄은 그 아래로 간다. 캐시가 있을 때도 전에는 + 문단째 다음 쪽으로 보냈다. + - 양옆에 글이 가는 개체 곁의 줄은, 글이 첫 조각에서 끝나거나 글이 없어도 두 조각이다(둘째는 빈 조각). 캐시 + 없는 추정도 이제 그렇게 센다. - 쪽 수 추정(실험, `estimate_pages`)이 줄 캐시 없는 표 칸에서 글자처럼 둔 표 옆에 글 앞·뒤로 둔 개체가 있는 문단을 지원 밖(`a nested table`)으로 두던 것을 고친다. - 그런 개체는 자리를 차지하지 않으므로, 그 문단은 표 혼자인 문단처럼 표 높이의 한 줄이다. diff --git a/src/hwpx/layout/pages.py b/src/hwpx/layout/pages.py index 71cee0b0..deca8abf 100644 --- a/src/hwpx/layout/pages.py +++ b/src/hwpx/layout/pages.py @@ -60,7 +60,10 @@ it stands at the top of the line its place in the text falls on, and that line and the rest of the paragraph come below it -- offset down, it stands that much lower, and the first line reaching it (in that paragraph or the - ones after) and the lines after come below it -- a table flowing with the text flows from there + ones after) and the lines after come below it (a picture or drawing flowing with the text whose + foot would pass the body's foot from where its paragraph stands goes on alone to the next page's + top instead, whatever its offset, the lines of its own page as if it were not there and those + reaching it there below it) -- a table flowing with the text flows from there over the page end, and that line goes below its end (so does the line of a paragraph holding only another such table, which then flows from there); offset down from a paragraph's first line that does not fit above the page's foot, it goes on to the next page with the line, and one whose top @@ -80,9 +83,13 @@ whose top is above its foot, in that paragraph and the ones after, is narrower by what the object and its outer margins take (a drop cap is such an object), or, with text on both sides, is two pieces at one height, the left one first -- a table by the height - of its rows; one whose band passes the body's foot goes on alone to the next page's top, which - moves no line: it is followed while its paragraph and the ones beside it keep their layout - caches -- and wrapped square with no room beside it (less than 1440 on each side its text flow + of its rows; a picture or drawing, or a table set not to split, whose band passes the body's foot + goes on alone to the next page's body top, whatever its offset: the lines of its own page are the whole width, and the + lines on the next page whose top is above its foot go beside it (without layout caches, broken + again there; a paragraph holding objects or a table there is not followed), while such a table + is followed only through the layout caches; a line beside an object with text on both sides is + two pieces even when its text ends in the first or it has none (the second empty) -- and wrapped + square with no room beside it (less than 1440 on each side its text flow allows), it pushes the text below it like a top-and-bottom object; a table flowing with the text is laid out row by row -- split between cell lines, moved row by row or moved whole -- with its header rows (any row with a header cell of its own) repeated; one set not to split takes its room under its anchor line when all its rows fit there, @@ -234,6 +241,15 @@ class _Unsupported(Exception): """Something the estimate does not follow.""" +class _BandMoves(Exception): + """A square-wrapped object flowing with the text, anchored in paragraph *index* without a layout cache, + passed the body's foot: the section is laid out again with it at the next page's top.""" + + def __init__(self, index: int) -> None: + super().__init__(index) + self.index = index + + def estimate_pages(document: "HwpxDocument | bytes | str | os.PathLike[str]") -> PageEstimate: """Estimate the pages Hancom lays *document* out on, and where each body line goes. @@ -1071,6 +1087,17 @@ class _Para: kept: bool = False #: the top-and-bottom objects stacked in this paragraph, which holds nothing else (see :func:`_stack`) stack: tuple[_Stacked, ...] = () + #: a square-wrapped object anchored here and flowing with the text went past the body's foot: Hancom sets + #: it at the next page's body top, whatever its offset (this band, from there) + moved: _Wrap | None = None + #: holding no text: its line goes beside such a band as it is + blank: bool = False + #: a top-and-bottom object offset from this paragraph (not a flowing table) pushes its lines below it: + #: when its band would pass the body's foot, Hancom moves it to the next page's top instead + wrap_push: bool = False + #: such an object alone in this empty paragraph, its foot this far below the paragraph's top (the line is + #: as tall as that): past the body's foot it goes on to the next page's top, the empty line staying + moves: int = 0 def height(self, line: int) -> int: return self.cached[line][0] if self.cached else self.size @@ -1100,6 +1127,8 @@ class _Reflow: width: int = 0 offset: int = 0 starts: tuple[int, ...] = () + end: int = 0 # the paragraph's end (see :func:`_end_size`) + head: int = 0 # its label (see :func:`_head_size`) 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 @@ -1121,6 +1150,38 @@ def at(self, para: _Para, width: int, line: int) -> _Para: return replace(para, lines=len(starts), cached=cached, reflow=replace(self, width=width, offset=offset, starts=tuple(starts))) + 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 + beside *wrap*'s band, as seen from that line's top: a line whose top is above the band's foot is as + wide as the room beside the object, or two pieces at one height with text on both sides; the lines + below are the whole width.""" + + mixed = len(set(self.sizes)) > 1 + widths = _indented(self.width - self.shape.left - self.shape.right, self.shape, self.style) + starts = self.starts + if line and not starts: # where its lines start as laid out so far + starts = tuple(self.measure.line_starts(self.text, widths, min(self.sizes), self.style, + list(self.sizes) if mixed else None, + None if self.looks is None else list(self.looks))) + if len(starts) != para.lines: + raise _Unsupported("lines beside a square-wrapped object moved to the next page") + offset = self.offset + (starts[line] if line else 0) + text = self.text[offset:] + if not text: # only a line break ending the paragraph is left + return para + sizes = list(self.sizes[offset:]) + looks = None if self.looks is None else list(self.looks[offset:]) + if offset: # the rest: none of its lines is the paragraph's first + widths = [widths[1], widths[1]] + several = sizes if len(set(sizes)) > 1 else None + lines, firsts = _wrapped_widths(self.measure, text, widths, min(sizes), self.style, self.shape, wrap, + several, looks) + pieces = self.measure.line_starts(text, lines, min(sizes), self.style, several, looks) + 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))) + @dataclass(frozen=True) class _Page: @@ -1136,6 +1197,9 @@ class _Page: paper_height: int = 0 hide_empty: bool = False # hp:visibility@hideFirstEmptyLine: a page's first empty lines are hidden gap: int = 0 # between columns of equal width + #: an object went on to the next page's top: the paragraphs without a layout cache keep their text, to + #: break again beside its band there + rebreak: bool = False def _page(section: Any) -> _Page: @@ -1789,7 +1853,7 @@ def _paragraph(measure: _Measure, page: _Page, paragraph: Any, wrap: _Wrap | Non cached = _line_metrics(measure, text, widths, sizes, style, shape, {}, looks, end=end, head=head) count = len(cached) or measure.lines(text, widths, size, style) pitch = _pitch(shape.kind, shape.value, size) - table = None + table, moves = None, 0 if alone: if wrap is not None: raise _Unsupported("an object beside a square-wrapped object") @@ -1797,6 +1861,10 @@ def _paragraph(measure: _Measure, page: _Page, paragraph: Any, wrap: _Wrap | Non size = max(measure.char_height(run.get("charPrIDRef")) for run in runs) # of any of its runs pitch = _pitch(shape.kind, shape.value, size) count, size, pitch, table = _object_line(measure, objects[0], count, size, pitch, page.column_width) + pos = objects[0].find(f"{HP}pos") + if table is None and pos.get("treatAsChar") != "1" and pos.get("flowWithText") != "0" \ + and 0 < int(pos.get("vertOffset", 0)) < 1 << 31: # below the paragraph's line + moves = size anchor = None if anchored is None else _anchor(measure, paragraph, runs, text, anchored, widths, size, style, cached, count) around = 0 if square is None or not text else _anchor_line(measure, paragraph, runs, text, square, widths, @@ -1814,6 +1882,13 @@ def _paragraph(measure: _Measure, page: _Page, paragraph: Any, wrap: _Wrap | Non if reflow and text: again = _Reflow(measure, text, tuple(sizes), None if looks is None else tuple(looks), style, shape) return again.at(para, page.column_width, 0) + if moves: + para = replace(para, moves=moves) + if page.rebreak and not _cached_metrics(paragraph) and not objects and not marks and not notes: + if not text: + return replace(para, blank=True) + return replace(para, reflow=_Reflow(measure, text, tuple(sizes), None if looks is None else tuple(looks), + style, shape, width=page.column_width, end=end, head=head)) return para @@ -1897,10 +1972,9 @@ def _square_sides(obj: Any, page: _Page) -> tuple[int, int]: return left, right -def _wrapped_paragraph(measure: _Measure, page: _Page, paragraph: Any, - wrap: _Wrap | None) -> tuple[_Para, _Wrap | None]: +def _wrapped_paragraph(measure: _Measure, page: _Page, paragraph: Any, wrap: _Wrap | None, moved: bool = False) -> tuple[_Para, _Wrap | None]: """The paragraph with the lines beside a square-wrapped object's band narrower, and the band as the - next paragraph sees it.""" + next paragraph sees it. With *moved*, the object went on to the next page's top: none here.""" runs = paragraph.findall(f"{HP}run") objects = _placed_objects(runs) @@ -1913,6 +1987,24 @@ def _wrapped_paragraph(measure: _Measure, page: _Page, paragraph: Any, return replace(para, stack=_stack(measure, stacked, page.column_width, shape)), None square = _square_object(objects, runs, page.column_width) pusher = _pushing_object(objects, _run_text(runs), page.column_width) + if moved: # the object went on to the next page's top: the paragraph's lines as if it were not there + # the object: wrapped square, pushing the lines below it, or alone below the empty line (a table that + # may split goes on over the page end instead; of tables, only one wrapped square set not to split is + # followed) + alone = objects[0] if len(objects) == 1 and objects[0].find(f"{HP}pos").get("treatAsChar") != "1" else None + obj = square if square is not None else pusher if pusher is not None else alone + if obj is None or wrap is not None or _local(obj) == "tbl" and (obj is not square + or obj.get("pageBreak", "CELL") != "NONE"): + raise _Unsupported("a square-wrapped or offset top-and-bottom object past the page foot") + tall = _extent(obj, "height", measure) + if _local(obj) == "tbl": # as tall as its rows + tall += sum(row.height for row in _rows(measure, obj)) - int(obj.find(f"{HP}sz").get("height", 0)) + if obj is square: + left, right = _square_sides(square, page) + band = _Wrap(0, tall, page.column_width - left - right, split=left if left and right else 0) + else: # the lines reaching it go below it + band = _Wrap(0, tall, 0, push=True) + return replace(_paragraph(measure, page, paragraph, None, obj), moved=band), None shape = measure.shape(paragraph.get("paraPrIDRef")) placed = [obj for obj in objects if obj.find(f"{HP}pos").get("treatAsChar") != "1"] if wrap is not None: @@ -1968,6 +2060,9 @@ def _wrapped_paragraph(measure: _Measure, page: _Page, paragraph: Any, if wrap is None: return para, None lead = para.prev - shape.prev # its first line moved below the band + if wrap.split and not para.cached and para.lines == 1 and lead < wrap.bottom and not _run_text(runs) \ + and all(obj is square for obj in objects): # an empty line beside it: two empty pieces + para = _in_two_pieces(para) beside = sum(1 for line in range(para.lines) if lead + para.span(0, line) < wrap.bottom) para = replace(para, wrap_lines=beside, wrap_bottom=wrap.bottom if starts else 0, wrap_fixed=bool(_cached_metrics(paragraph)), wrap_stays=wrap.stays, @@ -2064,7 +2159,7 @@ def _push(para: _Para, band: _Wrap, *, starts: bool) -> tuple[_Para, _Wrap | Non else: pushed, rest = para, band.lower(top + para.next) beside = sum(1 for line in range(para.lines) if para.span(0, line) < band.bottom) - return replace(pushed, wrap_lines=beside, wrap_bottom=band.bottom if starts else 0), rest + return replace(pushed, wrap_lines=beside, wrap_bottom=band.bottom if starts else 0, wrap_push=starts), rest def _wrapped_widths(measure: _Measure, text: str, widths: list[float], size: int, style: Any, shape: _Shape, @@ -2101,15 +2196,26 @@ def _wrapped_widths(measure: _Measure, text: str, widths: list[float], size: int raise _Unsupported("lines beside a square-wrapped object that do not settle") +def _in_two_pieces(para: _Para) -> _Para: + """*para*, one empty line, as Hancom writes it beside an object with text on both sides: two pieces at + one height.""" + + return replace(para, lines=2, cached=((para.size, 0), (para.size, para.pitch))) + + def _at_one_height(metrics: tuple[tuple[int, int], ...], firsts: set[int]) -> tuple[tuple[int, int], ...]: """*metrics* with each line of two pieces (*firsts*: its first piece) as tall as its taller piece, the - second piece at the first's height.""" + second piece at the first's height. When the text ends in a line's first piece, Hancom writes the second + one empty, at that height.""" lines = list(metrics) for first in sorted(firsts): if first + 1 < len(lines): (height, advance), (other, after) = lines[first], lines[first + 1] lines[first], lines[first + 1] = (max(height, other), 0), (max(height, other), max(advance, after)) + elif first == len(lines) - 1: # the text ends in it: an empty second piece + height, advance = lines[first] + lines[first:] = [(height, 0), (height, advance)] return tuple(lines) @@ -2434,6 +2540,8 @@ def __init__(self, body: int, columns: int, notes: _NoteShape, self.reserved: dict[int, int] = {} # frames a table starting past its anchor takes: where text starts 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 band of a square-wrapped object that went on to its top (see :attr:`_Para.moved`) + self.squares: dict[int, _Wrap] = {} #: a flowing table's band no line has reached yet: its frame, top, bottom there (None when it goes #: on over the page end), and the frame and position where the lines after it go on self.band: tuple[int, int, int | None, int, int] | None = None @@ -2463,6 +2571,9 @@ def _paragraph(self, index: int, paras: list[_Para], para: _Para) -> None: para = replace(para, column_break=False) 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): + raise _Unsupported("objects or a table beside a square-wrapped object moved to the next page") if para.stack: self._stacked(index, paras, para, start) return @@ -2483,6 +2594,9 @@ def _paragraph(self, index: int, paras: list[_Para], para: _Para) -> None: if table is not None: self._flow(para, table, start) return + reach = para.wrap_bottom if para.wrap_push else para.moves + if reach and start + reach > self.body and self.columns == 1: + raise _BandMoves(index) # its object goes on to the next page's top, its lines stay first = len(self.out) if self._lay(index, paras, para, start, broke): last = self.laid or para @@ -2491,6 +2605,8 @@ def _paragraph(self, index: int, paras: list[_Para], para: _Para) -> None: self.wrap_frame = self.out[first][0] self.wrap_moved = self.out[first][1] + para.wrap_bottom > self.body and not para.wrap_stays if self.wrap_moved and not para.wrap_fixed and (para.wrap_bottom or para.wrap_lines): + if para.wrap_bottom and self.columns == 1: # its object goes on to the next page's top: again + raise _BandMoves(index) raise _Unsupported("a square-wrapped or offset top-and-bottom object past the page foot") beside = self.out[first:first + para.wrap_lines] if not para.wrap_fixed and not para.wrap_free and any(frame != self.wrap_frame for frame, _ in beside): @@ -2811,7 +2927,7 @@ def _breaks(self, para: _Para, start: int) -> tuple[int, bool]: def _lay(self, index: int, paras: list[_Para], para: _Para, start: int, broke: bool) -> bool: """Place the lines of *para*; False when its notes ended the page after it.""" - para = self._at_width(para, 0) + para = self._at_band(self._at_width(para, 0), 0, start) remaining, first_chunk = para.lines, True fresh = self.last_vp is None or broke below = False # the next line was moved below an object's band @@ -2824,6 +2940,11 @@ def _lay(self, index: int, paras: list[_Para], para: _Para, start: int, broke: b count = self._chunk(index, paras, para, start, remaining, done, first_chunk) if count == 0 and (self.last_vp is None or fresh) and not below and not self.reserved.get(self.frame): count = 1 # a line taller than the page still takes an empty page (and overflows it) + if para.moved is not None: # its object goes on to the next page's top + if count == 0: + raise _Unsupported("a square-wrapped object past the page foot, its paragraph on the next page") + self._move_band(para.moved) + para = replace(para, moved=None) if hit is not None and hit[0] < count: # the lines above it stay, the next goes below it count = hit[0] self.out.extend((self.frame, start + para.span(done, j)) for j in range(count)) @@ -2841,7 +2962,11 @@ def _lay(self, index: int, paras: list[_Para], para: _Para, start: int, broke: b return False if remaining: start, below = self._next_frame(para, count, first_chunk), False - again = self._at_width(para, para.lines - remaining) + line = para.lines - remaining + again = self._at_width(para, line) + if again is not para: + para, remaining, line = again, again.lines, 0 + again = self._at_band(para, line, start) if again is not para: para, remaining = again, again.lines fresh = bool(remaining) @@ -2858,6 +2983,34 @@ def _at_width(self, para: _Para, line: int) -> _Para: width = self.widths[self.frame % self.columns] return para if width == para.reflow.width else para.reflow.at(para, width, line) + def _at_band(self, para: _Para, line: int, start: int) -> _Para: + """*para* with its lines from *line* on, laid from *start* down, broken again beside the band of a + square-wrapped object set at the current frame's top when they reach it.""" + + band = self.squares.get(self.frame) + if band is None or start >= band.bottom: + return para + if para.blank: # an empty line: two empty pieces beside an object with text on both sides + return _in_two_pieces(para) if band.split and para.lines == 1 and not para.cached else para + if para.reflow is None: + raise _Unsupported("objects, notes or a layout cache beside a square-wrapped object moved to the next page") + return para.reflow.banded(para, line, replace(band, top=band.top - start, bottom=band.bottom - start)) + + def _move_band(self, band: _Wrap) -> None: + """A square-wrapped object anchored in the paragraph being laid, flowing with the text, went past the + body's foot: Hancom sets it at the next page's body top whatever its offset, the lines there whose top + is above its foot beside it, and that page is used even when no line goes there.""" + + target = self.frame + 1 + if band.bottom > self.body or self.bands.get(target) or self.sides.get(target) or target in self.reserved \ + or target in self.squares: + raise _Unsupported("an object going on to the next page's top beside other objects") + if band.push: # a top-and-bottom one: the lines reaching it go below it + self.bands[target] = [(band.top, band.bottom)] + else: + self.squares[target] = band + self.table_end = max(self.table_end, target) + def _next_frame(self, para: _Para, count: int, first_chunk: bool) -> int: self.frame += 1 self.page_notes = [self.carry, 1] if self.carry else [0, 0] @@ -2944,10 +3097,21 @@ def _lay_section(measure: _Measure, section: Any) -> _SectionLayout: page = _page(section) notes = _note_shape(section) _check_section(section) + moved: set[int] = set() # the paragraphs whose square-wrapped object went on to the next page's top + while True: + try: + return _paginate(measure, section, replace(page, rebreak=True) if moved else page, notes, moved) + except _BandMoves as signal: + if signal.index in moved or len(moved) >= 16: + raise _Unsupported("a square-wrapped or offset top-and-bottom object past the page foot") from None + moved.add(signal.index) + + +def _paginate(measure: _Measure, section: Any, page: _Page, notes: _NoteShape, moved: set[int]) -> _SectionLayout: paras = [] wrap: _Wrap | None = None - for paragraph in section.findall(f"{HP}p"): - para, wrap = _wrapped_paragraph(measure, page, paragraph, wrap) + for index, paragraph in enumerate(section.findall(f"{HP}p")): + para, wrap = _wrapped_paragraph(measure, page, paragraph, wrap, index in moved) 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)) diff --git a/tests/fixtures/hancom_saved/pages_picture_1319_down_moved_to_the_next_page.hwpx b/tests/fixtures/hancom_saved/pages_picture_1319_down_moved_to_the_next_page.hwpx new file mode 100644 index 0000000000000000000000000000000000000000..d7e5b474b119cc248857ead949257432c6e565e4 GIT binary patch literal 9663 zcma)i1yo$i5-lDycyJpC?oJro-3jhCxVyW%LvT%S3+@)&HMj+LNJxM`-22{@{P)-T zGpkNp)jrj=y1Pzwx1tO*3>L(nKQf5G3n2+iaN&jE!DIunF?IH^H#M}kw+0y-I)m(N znJnDw={!O9ivJSie9Ha)1`-0|g^<94uBMJoU}i>l8*88d_(A%g3O)b}BQt>1)YjO} z1Y~Q@2T)TLXW#~qIyoEKniyK!*_!eJJWQPcqyqdv3)gp&BI4?QB9VeYY@Oa&xbguk zoSp68F)_KhxiMN8f`M!pjqMn%9A6Z(v1VdrW?^Ca&)Wc`&W7fu&O(1{&j)y~pe(AW ztRNz$qN1P-Ahj{Hv~%PGaDa0l+m{^6OU~HQ?vI?23&`3;-o?hq6x;&V+s=s(zzTTL zzx~RKd&)WNbFbhmLP<(!r&NInoy$(9z_!F1)Lcv#2i`@o2OSW zIPQXYvRB<7KigA^E4JfUt`36M99mkQ++$>7x_1`Lthr-vaNF9ykr`rjveZ*@ z>~`4A3$0g1dDU!i0ws|8pNU86qkWK8D|;|gQ->}k zAQnGU(-_w(PlW{{ukpUdon(GjxcL5-ok*+ud>RkdC6rB#8*}li$Qd`+oa0pRn}*sn zmAhme#Ww>hT%i-;YKqrCq+@p{qybm&X9J?%vmRHtQ;!t0)uPCrSI+f*DRz<{;gbB3 zhwx1n4r$9%ND5_{Jji{pzwsXZ0@-c@?#<0zt+;6D9U2JpWAvlNq0hb=Itd+mTA)@R zB?cXf!}FpWIJzx6??!|A?V?q?$lUCBhG)Zm36C17g%)zxTquI}^CPls*- z@_x^4#{xb=@j5)au3digGNFqNaQ5866;M|G`%Ne4fJTPC_M`gVp%~BS%Qf5QqWSn@ z8H;F_8RHE21Hh{mo$La7^hd)qWW9y9FkNRzKLj1}P<;qZ@;EOxs(p-O+H zqSk4s8=cKn!6S%EevCqgioz_;$^01EVcX`?AUysxJ0q#03h|Tq#8GRL0N{?#nJQ;O zIW2S?BN~FBy*7Xw>O~z~8&$kj&I>wif9J%}1A~;{qFo}bMUtun! zxw~GBO+)JyF1M!kSW3@+#do>MV%UkY4)#UM9}|9*kSo~hS?VB3o5 zL(lZr!nmS{*C&_3)fpMV89XdWStQV|w=DMlD0~xr@W;;Fv=vE^+l`Xv)TjnlD--2CRpnTviArX>ao?*(_o+X+65=<>nsXE#{eJS zvNG2S6lB6 zDakmgf}?UgDNwu$R`ROM<+GTyz4ng=WmT&It(JlXX?AE!90P-^&%ANTd?j&$p>Lte zKa-+u6g@QG*Q`~MG-Z8?Dk;IJtohMI`wI{wIABw!#*$)D9P@d;El#?eg4QmfcHKu+ z{;l@j8xyOB3H;Oj=^ZR}a+Bw>MDeO4hLKzGra}z$SxJqy4keFj-xXaK(fw4u(Cg2n zLz?#u>mFZuD?hJ((i)1B>$N9Oy%A zE~I`-*6V)1^1NJk?JN&Tc@w?d7b^U|nPbuwj#eGVf6KXbgv`J0?)D6m#9GfQaLPZV z`5>~gO-4wT_9;Ge4OM6JJIc@R-z<)PV3KLvBO7o%{vc66SA*;2{<;2^Z?K<{V^b*N zm&+CvW?x7j?@2m(u1pDVr{7+qn}~pO^{vrTIXKm{gY zAc8)T&fPlTGi|ZA8KLW`s`{|G1kY-p*%2^{Mu}5}D|4dtz$cXo_mmTlK}5dIRa}Lw zDEri~K^P8C4n5jcB|7zOsV;9J_Mf#z;9BfIYKtT0SMVyc$@Z*K3sYE!Ens^rxCSgV z5k(dzEas7E%)M|cvxUUz5XIYQ#OX|jL5mX>()=PT%f!;3OyXyx17~a_XX@DNwFHVy zK^YES)Rmb+(sW4DZ8YF^Ft-9oGc&~wwRpbYmA$swRl+qc)i!#JwJ_t9jMbA_m^lM} zOwVDFS6b*x-sqbJ5i_^Bx>lK$Wq|N%dJ@#HyyeRJjPOxbe4mPl5mzZTZBgSaCsDzR zCHX5{U3&eqnvj0stahU&%G4kj22_1SV`@hpxD`Gxw%A zp3QRBxJxUiT6g+IG$U2b-tp&pHJsSzc^*NJSJN7bW37@{U@6DL0It)WbqEjp1v z-&~~d+&d6G2Zf&aezzg23VYDhO)VoJ8lF8+(D(gutH9JrYHJb4d!iTSi#lA?Dd!b& zVRDlsNFir4Q#d|po6$}DIrdwia>BQM(G}Ro=Bel+GmB(V_GeF)$4fDo5i)$e0e|Yz z0BVqbnUtWf=hNveNv^asjP&BONijxWA>&YCNso$#zl2U>JCj&kBW|eX}o_a zTAzweY-u{20thn42r9=2Spme8ql^#SgLRiiaF=EQ?(MmcfG$+uY|>C|(hOawrtXx3 zg?0T)6a7mu`c!mdOEcjVY#?(ip>iy<{nQ~;Wd?#H`cKF|z9s!QfL_TQob0@tJv;$P zv!A5FLh`Dyms?pGy%B@eWv9sZjeqOYmfy@xu71Za%7>+9g#zV>p?w)*_9Xlx9j z$6K!IzyhIb%RJw^dX9wp(n&qp=ewPobH8wd0(}y1Sa}9G3Aq{_+7WfVzU8jYr%^w0 zgIM%l&R%Bz#O)&*icKl8Wodx9!t`g*2CW)R3{6BYX3rgLhk4M)uoeV77?F%_5|an1x5j z&)#m2t9Aeb{O%sOx*hv%m}D$vrl@kKt?Y39Hv2hFApiJ${2qigvaEY>Lxk8tOS~6(YE4%T99ZE!(FJ=Rku7*95|Yd3-AXL@ zFS63}*m|;9SW?vHOQser9P`0~P+>Dgl-LCo^LSt_Q2FR1iM8?`MV80?zIlfypI8I9 z%tA3AjK=NxN%+~$+u5!q&pV%u(0M04^h7~*i$T132Nx8wR7$L?$GXvR>z7{gN^oB& z!e@ieZ*4uMo!0%%*F0*qFQ(92l8oGlPj2^3GfwY*a&d9-UJEAIEFsK(h`WCJMXBBI z14DZ9N^{|pqY3Ir0;~t~XpnX1#H$`?$5y*9n!qyg>2hU3r2|BgWI9e1|1 zNiL~j9Ja*RrkNWH%WqIe8sQ@o56(?`tk(vuAJ#OZ>?d$~J`P%I?PXNt52t#xC+*ii zQJh?-Gr23j@o9$sskU;X_hXtO>mxEeL^V@Rl}gFBA2;gNl~A?IhxW4(xhB~HayU(R8J^-0&&_*sawaAr0^Nv;quO8=t$uCmUW6*Z4Ve1`R!d~;nhNh1g0<6o*0ex zm{UrAEU)zJmG9U3RuS5#cjIbJ)3aN9^P8fZ0x{d}Y&r+Y9-=$vZ`_)PM^=-lr;D+# zZ^Yq5>#THZZS8f2Xf{7&(@_t8#?xo9%t%(Soj;JGFu%7~+eED8EtV>RNis5oj%1D( z(;+4y%<-o1Pdm4*mcSG1l^*k8&qDg{1LgDiU@W!xTT9jT?uCUn2N2ssH6n^MS1f`} z-%t`w3@RMk^IehCC-DJqTA5-&W;T7be5lBRITxP@E6tK9HM8jx-!)sceEOfNSY3#4 zqpa8+bKVZp^Zh|Ps1#vou#@-ms(|!_YtT&rBK9bV=%EGKOe|eu8EykZDaf0sVBm`^ z)OBoL7o!h5kcYnv5}Fr{Cn^MgrJVZAT=3Bbiw&btS;OEUGmR^os_xXt89)+Mb*0he zvg}rU0JC`LRMiKS`Vsl|wJAdDlMl;EoB~rQPGL^c1;GHFM#qN67=+`3<6LRS`j)}- z;r!FZ?)Gwd1>=?w4@neR+Y#t_)ju+$8rGf60k1ch9f_40>hiE>{^Hw_L9^fG$%XST zUFMAewG3E}0Ky0|5Kry02@;H18ClT}K?16FX<27la2e zbEv&hb9ndRO}h??sAS%IR8b;DXeGZ2xB^5Q%d7Q<=Vk2hDKP>2IcwmqDbfd5w%6)i_KW zZQ(RO-Dagm)h{&T4|-0`f_ph+ZZELc$39Go-6%H@9Hm4_zQ>~_aw5V4Rt+_2q0(#& zrJx>wp6Y0&eHX-}=*abD2*>)#$Cv|c=RdbGs7l&ZJfgw%-=cBW!_aU1d?0!DxjuN` zX5>Jg%JV*x^`p|Bv3&?4!DyZfi(r)k1bfJ7)?}A6gf}n~S2LWlADLCK{D2??laE+_ zdlkNvZo!t{H9^yLd#LXagJ7#@IB3p19ycTjNGyMYMjxcRk^6Z6c2Mk*`i1jjH$fNg*cL0|B-v|50 zG=&>(8wTdrKBB>zL+hantFHk3b>K7NqI;LY?cpg+!&K=?!7+?LLugy{0?rixOoGfF z{zTdAYl4fYc=KY00$KXKgzx59lW>IOyQ>z_c^At8G67bum2>{)!2vM~!xHg=kr~Uy z-q1M-U*kWc%6~ILgb&P_-ipPyk6~__CKO6*X1tz?(KG#4>vbNO)IFz>Ol0|MQV9XK6w z%RLmhaP<6{Wp>Yr00R4%vXu9@MW*IhWcDNqH#<-({&u33?fnpBNo_83Tn(v{1&Whw z$e5(+J9?8IJY#e|S`4bwS{3y>kCy{K^5G6y({Qeb`szRRfM;FOmOIe9B$sgDFh)I1 zCngs4wFDoPyvW)|sT?!I`0|{STlv{wq$S7YoE)~%SyR{ETqlN-sKkQy#ky>nMwV)V zt{Ikg#lsXzIV-anZ%$9m2#@%?0IP^)tzk^YYnQqhiur30q3KnzIMZwq@^&V_vRAWw zN@+31BN5f?973XIMFSj|-0OePPYf_pV zSX1H-dv4WC_*m5+gvWlO&xH>e+KqHy8G1LvOtIvCLgM|jv+-+4=UG(3*!?{8R==XG z)RW;#?*Mx}up{D|Wj?g`yS@DH?!MgIEg0FyJVM!0AKYPEsBd+O6gf=ZHY-5jA(-wS z&2M=4pYfL+lICu?NU3>0%ex$1cNT>#O!#YY*V8J|n0IS&*7r0~dz3Ur-Pd=%T@{x= zEeK(Yc;ohk9pp)4;`SrsT9q~3-%;$tYTxx zR+i=1%bTj>P2f^&%#Hqds5I_(IBu#cHJj%h2QZ@^RICu=GjbkC62gcS;*_K;H00}7 zm*XbLM!jiojVvn)VI3!pHH*j$a86Xypo=sK%+Dk)nhuktDmOZcFw%U=Wh$;dkhO^- zE@sA10ruQ2lR)_Fvl*SF&KwK)?s-2}D-c3_sMhzndYEO>dw%+~==US;`k}hZ>+wjz zj{osz)}-HY`;RMBJH5x#@vi3|<97TGoj*?M?f9RCsa%~cKP*j`yBJB7E=2fAPRHpR zhU1w0Lg}iM>tD6Ta%LzZa!nY;EgZaKw_M3To0k91y)Zp7Qc1GaA{dCyA+WqPQCk%< zURGIDuaYcWG>Tlt6QeNxZiZ)}F+XWpo|o>kB`_K&iv~3al4z4?dvh1tG&>=U7ZC?( z8r)!DymmHS1#38}zOqb%iq0=!HYG8()FU;{TkX_tB~n-S!A%`eKqpR^1jFe)p4G~a zwL)Dat>n{&dX=dyG6i?uWPIMm3Xh@Ip|&^dl-eHVrC>jy6!EKyWVRtF&#A#mgtzX4 zt2!dHwK!XzZzP4&R`=HW5Hns%r)-6i3K|;Fl4#8lJ*8!N3@r7{>$`oE@ z@_7NgXP4``##?G3B3+x}>WYZ5D+e)*#$spE<|aEGDr&@gT9J%Lx+x?H>S4Z@MygDL8Hetq->ExW1y;ZoT(1o}(E4#2n(T??m5f_TbhOSAvfA776KpaG z9!XLIel-C*vvj95-ohG(*@+Q8UXRqEJoBeSs-ty;kQi-O^|K|Reh3Pz%%A(WnJ#O* zc{L706C)_Rl6(~55)qd~ymKAb5uC}a+930TxIDqdA!KH=Gpabtb(eGh0+jMG*LBSn z(^mtzWrLecf(z}(>j+{5S{bM9^^T*jjs1ND`F3~*%DkwsY%ENhW+o;`DDpLsm0-qw z4s7Mx&WIEn$WmTLfgebe7TYI!OaW|`TniDJ`VTJZPrmTN#Kr!Z`YT&!9~2tMl0Bx( zYB?H?C!^K^!_FW7imUz%NFEPyw@$!;$P~Eva>l0UXzB_wbz@R=bX7HVcV={UcOIYT zL+lkqgWcqh=N&_5t&i|aL&t)pgXI%cL=r+mhX$lePxS)ALjv`v+ggHpJA1XzN<7DR zc38%Pg*<#(e5MPe%-O#+g*`qsb0I*d?P0Mf$HU=~W1667N^r)@eDdE^vwa zjNOsrwh({;R2MKg-I(rB3Y*}9`6iw_Tuy*FzaH+=c*2O+LknVxm}Mz%8*rsB=>%jL zRhgxy!%3A^t?S?#r_CW#u2uQb`e1Z-wNtk9^dIYblzyrUPK3;DFB%!9g2@lc}>a_>9o$B}j9L zlaqmFK?{ABPH)SqdX-SK40toGeF;4&;%X_JBqdo=_2i{;ITbY&;IVP=qa#7+E>&(~-P zB)uZs%0(9<;N1j!Gm!Apot2c5zTKdbG0jDqeFkd2T@GzlGy{8IdKsPrCC>KSHWk1d zryn*G?OpWu8d7&HA#9P6`Cn$wGG4!FRp^Ptd2hRR4R-@Pe5TkZ`FD4zQhV1Bz+E+l z1|OS(yDKNAD#RcqFV18PKKwHT*@BP#98Js`lSXZWn9;=i1{@dM{iW4}0_Y_ zR4#9M3t4v9FwC>OZ9Yxj6jNuAEG9pw4H6uLVi{yovaBH(TZJc51Tcqkzy1~DZ+5!N zIMuL?lqaa%M3g;g_OtO9bKz*Sz&ut-Op!TgjivWITaP{H1wsA^UWJ8Eh{C=u1 ziP`!XTY!`y5wG%UKTe!?E;dg{Gas9HluW-bV>r$%4R_Q(>lF~GUHtB_n{M@Wwd=z{ z6Z||s%P(zxs^l#8yH+$m?PU~X#KOV#t}{m1-E@;J(eSotJr={wMHbCyl_cVDv~kT2vM(jAVFZ!wOpJFR?3Nc9y%;b){h zM$V0$X;~;+T75&~ef*Vt>vJ&4+o{Gjzfc14qM8y_cA`qIkdQoKLqTKef;{rV1oZ|V z)<_j+hwKW_gog0e6Ud>Mf=sP^>R$ZqA@5k~AUp;D`BRxukoWfu|8gzboyYa6A9Q`9 zvJd{8vhFaJ*r|%h3BRm0`VWS`!!kFtZOmPL4!{Vy7(wZCK>DgU3hUmxw#DhX8Wwql z>0=wzHG3;cqaB_arRW88UKphl^woTgBA$Y~U89VCTkHJDPF~!0C;6d^C>V-ljHVG< zhFu!=le_(HqHV7@!ndR^KjnNWOWCnGNcQ+?MGj#3HD+@KIy=>QS6bMx8wK258JAM8BEC@X)c`kejDbqiC1(#oLc9YM#cO#i*QdKz(9f7BQOBg^^h6N8>6xld>vn zEgQd>^&Z|QtWSG@<&GwrLfwZoF)@a#>1ttk z(bg3=(q{ge>Y~}`o2`LVl!1i8`eVTWs|4QX{QbVmODzNhDTIH(OZwm2FTYFue%a-P z_fIJvaQdISColc`Tkuc!|NAwMKji*+ojZ7R{k4Yued*(GfEU*Pdtdf%0QlGcYnS$K z`TxBk`nUWfSpF{s{<%H+JH+pwFn>cB5d6;%n%@C_ck_P(_z?cv>HjYIyT|-n(wgWW zBkaWm{N+D?$N1fG{Ebmg{0|HK!;Soo@cV-BHv%Tv|J34t420ij$KM#zfPY~8bFL`L Uz{0(-A%XvGV9$33W`uzFKh2&V&j0`b literal 0 HcmV?d00001 diff --git a/tests/fixtures/hancom_saved/pages_square_picture_above_the_foot_short_lines_beside_it.hwpx b/tests/fixtures/hancom_saved/pages_square_picture_above_the_foot_short_lines_beside_it.hwpx new file mode 100644 index 0000000000000000000000000000000000000000..fc9eea0e874f8a750461caa7bc7acd440943f0fa GIT binary patch literal 9116 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 literal 0 HcmV?d00001 diff --git a/tests/fixtures/hancom_saved/pages_square_picture_moved_at_the_left.hwpx b/tests/fixtures/hancom_saved/pages_square_picture_moved_at_the_left.hwpx new file mode 100644 index 0000000000000000000000000000000000000000..ce6e3087c903ef523e311fe6c17e420ec93ce530 GIT binary patch literal 9592 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 literal 0 HcmV?d00001 diff --git a/tests/fixtures/hancom_saved/pages_square_picture_moved_its_paragraph_going_on.hwpx b/tests/fixtures/hancom_saved/pages_square_picture_moved_its_paragraph_going_on.hwpx new file mode 100644 index 0000000000000000000000000000000000000000..03f3dd4f4952894a7431f74bd8b864c576edc0f9 GIT binary patch literal 9517 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 literal 0 HcmV?d00001 diff --git a/tests/fixtures/hancom_saved/pages_square_picture_moved_text_going_on_beside_it.hwpx b/tests/fixtures/hancom_saved/pages_square_picture_moved_text_going_on_beside_it.hwpx new file mode 100644 index 0000000000000000000000000000000000000000..d29939a87d6f0aeaf0fd0100c510d40fc0849c94 GIT binary patch literal 9711 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 XTq`Q_@CZ+2sL*c<)bo*1J@x(%eF7sm literal 0 HcmV?d00001 diff --git a/tests/fixtures/hancom_saved/pages_square_picture_moved_to_the_next_page.hwpx b/tests/fixtures/hancom_saved/pages_square_picture_moved_to_the_next_page.hwpx new file mode 100644 index 0000000000000000000000000000000000000000..d865353abdf4a490c8dc7ae3a7c03913618069a0 GIT binary patch literal 9178 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 Lo)3-s;qCtb-U1Zu literal 0 HcmV?d00001 diff --git a/tests/fixtures/hancom_saved/pages_square_picture_staying_short_lines_beside_it.hwpx b/tests/fixtures/hancom_saved/pages_square_picture_staying_short_lines_beside_it.hwpx new file mode 100644 index 0000000000000000000000000000000000000000..e1df42acfa38a168b970083c8eb112d45930cd86 GIT binary patch literal 9146 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 literal 0 HcmV?d00001 diff --git a/tests/fixtures/hancom_saved/pages_square_table_not_split_moved_to_the_next_page.hwpx b/tests/fixtures/hancom_saved/pages_square_table_not_split_moved_to_the_next_page.hwpx new file mode 100644 index 0000000000000000000000000000000000000000..394b1e335de90949071e2b2ed10f86528b3be3eb GIT binary patch literal 9711 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 k$lrVS-xw{xe_;HxuPewvfma~_03Q7H1g}ZD None: - # A picture or table wrapped square from its paragraph's top, 8000 tall, its band past the body's - # foot: Hancom sets it alone at the next page's top, the lines beside it there narrower and the lines - # left above the foot as wide as the column. It takes no line's height, so the lines keeping their - # caches stay where the caches put them; without the caches, where each line breaks is not followed. +def test_a_square_object_past_the_page_foot_goes_on_at_the_next_page(name: str, followed: bool) -> None: + # A picture wrapped square from its paragraph's top, 8000 tall, its band past the body's foot: Hancom + # sets it alone at the next page's top, the lines beside it there narrower and the lines left above the + # foot as wide as the column. With the caches the lines stay where the caches put them; without them + # they are broken again beside it there. A table wrapped square is split over the page end instead, its + # band going on at the next page's top: not followed without the caches. data = (FIXTURES / f"{name}.hwpx").read_bytes() _assert_like_hancom(estimate_pages(data), data, 2) - assert estimate_pages(_without_caches(data)).unsupported == ( - "section 0: a square-wrapped or offset top-and-bottom object past the page foot",) + if followed: + _assert_like_hancom(estimate_pages(_without_caches(data)), data, 2) + else: + assert estimate_pages(_without_caches(data)).unsupported == ( + "section 0: a square-wrapped or offset top-and-bottom object past the page foot",) def test_a_top_and_bottom_table_not_split_offset_past_the_page_foot_is_unsupported() -> None: From d062561ea9e14ea7b9f03c7c8e4b83a6d005771e Mon Sep 17 00:00:00 2001 From: airmang <38392618+airmang@users.noreply.github.com> Date: Sat, 3 Oct 2026 00:01:31 +0900 Subject: [PATCH 2/2] fix(layout): measure a pushed first line's object from where the line stood A top-and-bottom object offset from a paragraph of text pushes the paragraph's first line below it. Its band's foot is measured from where that line stood before it was pushed, but both checks of whether the band passes the body's foot added it to the line's new top, so an object ending well above the foot was taken past it: refused before, and sent to the next page by the previous commit. The push is now taken off. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 ++ src/hwpx/layout/pages.py | 15 ++++++++++----- ...re_pushing_its_first_line_above_the_foot.hwpx | Bin 0 -> 8617 bytes tests/test_layout_page_estimate.py | 3 +++ 4 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 tests/fixtures/hancom_saved/pages_picture_pushing_its_first_line_above_the_foot.hwpx diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cf55a93..634cbf0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ 문단째 다음 쪽으로 보냈다. - 양옆에 글이 가는 개체 곁의 줄은, 글이 첫 조각에서 끝나거나 글이 없어도 두 조각이다(둘째는 빈 조각). 캐시 없는 추정도 이제 그렇게 센다. + - 문단 첫 줄을 그림 아래로 밀어낸 위아래 그림은, 밀린 줄 자리에 그림 높이를 한 번 더 더해 바닥을 넘는다고 보아 + 지원 밖으로 두었다. 이제 밀리기 전 줄 자리에서 잰다. - 쪽 수 추정(실험, `estimate_pages`)이 줄 캐시 없는 표 칸에서 글자처럼 둔 표 옆에 글 앞·뒤로 둔 개체가 있는 문단을 지원 밖(`a nested table`)으로 두던 것을 고친다. - 그런 개체는 자리를 차지하지 않으므로, 그 문단은 표 혼자인 문단처럼 표 높이의 한 줄이다. diff --git a/src/hwpx/layout/pages.py b/src/hwpx/layout/pages.py index deca8abf..912d2e58 100644 --- a/src/hwpx/layout/pages.py +++ b/src/hwpx/layout/pages.py @@ -1095,6 +1095,9 @@ class _Para: #: a top-and-bottom object offset from this paragraph (not a flowing table) pushes its lines below it: #: when its band would pass the body's foot, Hancom moves it to the next page's top instead wrap_push: bool = False + #: how far such an object pushed the paragraph's first line down (its band's foot is measured from where + #: that line stood before) + wrap_shift: int = 0 #: such an object alone in this empty paragraph, its foot this far below the paragraph's top (the line is #: as tall as that): past the body's foot it goes on to the next page's top, the empty line staying moves: int = 0 @@ -2145,12 +2148,12 @@ def _push(para: _Para, band: _Wrap, *, starts: bool) -> tuple[_Para, _Wrap | Non if para.anchor is not None or para.table is not None: raise _Unsupported("an object beside a top-and-bottom object's band") metrics = list(para.cached) or [(para.size, para.pitch)] * para.lines - top, rest = 0, None + top, rest, shifted = 0, None, 0 for index, (height, advance) in enumerate(metrics): if top < band.bottom and top + height > band.top: shift = band.bottom - top if index == 0: - pushed = replace(para, prev=para.prev + shift) + pushed, shifted = replace(para, prev=para.prev + shift), shift else: metrics[index - 1] = (metrics[index - 1][0], metrics[index - 1][1] + shift) pushed = replace(para, cached=tuple(metrics)) @@ -2159,7 +2162,8 @@ def _push(para: _Para, band: _Wrap, *, starts: bool) -> tuple[_Para, _Wrap | Non else: pushed, rest = para, band.lower(top + para.next) beside = sum(1 for line in range(para.lines) if para.span(0, line) < band.bottom) - return replace(pushed, wrap_lines=beside, wrap_bottom=band.bottom if starts else 0, wrap_push=starts), rest + return replace(pushed, wrap_lines=beside, wrap_bottom=band.bottom if starts else 0, wrap_push=starts, + wrap_shift=shifted if starts else 0), rest def _wrapped_widths(measure: _Measure, text: str, widths: list[float], size: int, style: Any, shape: _Shape, @@ -2594,7 +2598,7 @@ def _paragraph(self, index: int, paras: list[_Para], para: _Para) -> None: if table is not None: self._flow(para, table, start) return - reach = para.wrap_bottom if para.wrap_push else para.moves + reach = para.wrap_bottom - para.wrap_shift if para.wrap_push else para.moves if reach and start + reach > self.body and self.columns == 1: raise _BandMoves(index) # its object goes on to the next page's top, its lines stay first = len(self.out) @@ -2603,7 +2607,8 @@ def _paragraph(self, index: int, paras: list[_Para], para: _Para) -> None: self.last_vp, self.last_pitch, self.pending_next = self.out[-1][1], last.advance(last.lines - 1), para.next if para.wrap_bottom: # the band starts here: it stays on this page self.wrap_frame = self.out[first][0] - self.wrap_moved = self.out[first][1] + para.wrap_bottom > self.body and not para.wrap_stays + self.wrap_moved = self.out[first][1] + para.wrap_bottom - para.wrap_shift > self.body \ + and not para.wrap_stays if self.wrap_moved and not para.wrap_fixed and (para.wrap_bottom or para.wrap_lines): if para.wrap_bottom and self.columns == 1: # its object goes on to the next page's top: again raise _BandMoves(index) diff --git a/tests/fixtures/hancom_saved/pages_picture_pushing_its_first_line_above_the_foot.hwpx b/tests/fixtures/hancom_saved/pages_picture_pushing_its_first_line_above_the_foot.hwpx new file mode 100644 index 0000000000000000000000000000000000000000..ba7d9e021acfa35ee78096664e9baf6648d2d819 GIT binary patch literal 8617 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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 2a91e2cf..bf550027 100644 --- a/tests/test_layout_page_estimate.py +++ b/tests/test_layout_page_estimate.py @@ -56,6 +56,9 @@ # below its empty paragraph's line: alone at the # next page's top, the line staying, the lines # there reaching it below it + "pages_picture_pushing_its_first_line_above_the_foot": 2, # a picture 30000 tall 1319 below a paragraph of + # 14 pt text: its line goes below the picture, + # which ends above the foot, and stays there "pages_square_picture_above_the_foot_short_lines_beside_it": 2, # its band above the foot: short lines # beside it in two pieces "pages_square_picture_staying_short_lines_beside_it": 2, # not flowing with the text, it stays on its