diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a18b542..233a3d8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ ### 고침 +- 쪽 수 추정이 표 칸 안에서 양쪽에 글 자리가 있는 어울림 개체 곁에 빈 줄만 오는 칸도 추정한다. 한/글처럼 빈 줄은 개체 곁에 + 한 높이 두 조각으로 둔다(곁에 글이 오면 지금처럼 지원 밖). 한/글이 저장한 줄 캐시가 있는 칸에서 그런 두 조각을 줄 둘로 + 세어 칸을 조각 수만큼 높게 잡던 것도 바로잡았다. - 쪽 수 추정과 FormFit이 줄 바꿈 뒤 빈 run으로 끝나는 문단의 빈 마지막 줄을 한/글처럼 그 run의 크기로 잡는다(글자보다 작아도). 전에는 작은 run은 무시하고 큰 run은 줄 바꿈 앞 줄까지 키웠다. 끝 run이 더 크면 그 문단의 줄 캐시는 쓰지 않는다. - 쪽 수 추정이 첫 줄에 붙은 어울림 개체의 세로 위치를 한/글처럼 위 간격 위 문단 맨 위에서 잰다. 전에는 첫 줄 맨 위에서 diff --git a/src/hwpx/layout/pages.py b/src/hwpx/layout/pages.py index adb8ad62..4adaa347 100644 --- a/src/hwpx/layout/pages.py +++ b/src/hwpx/layout/pages.py @@ -1014,6 +1014,7 @@ def wrapped_lines(self, paragraphs: list[Any], width: int, tables: bool = False raise _Unsupported("an object placed top and bottom or square after the text of a paragraph " "with a layout cache in a cell") lines, narrow, own = _clear_of(self, fixed, top, shape.prev, anchor, obj, bands, empty, width) + _beside_one_side(narrow, empty) if narrow and not _cache_lines(paragraph): raise _Unsupported("text laid out otherwise beside a square-wrapped object in a cell") else: @@ -1035,6 +1036,7 @@ def wrapped_lines(self, paragraphs: list[Any], width: int, tables: bool = False narrow = found else: raise _Unsupported("lines beside a square-wrapped object in a cell that do not settle") + _beside_one_side(narrow, empty) if narrow and ("\n" in text or style.head): raise _Unsupported("a paragraph with margins, a label or line breaks beside a square-wrapped " "object in a cell") @@ -1405,6 +1407,12 @@ def _drawn_lines(measure: _Measure, paragraphs: list[Any], width: int | None = N below = top # where the paragraph's next line would stand for index, segment in enumerate(segments): vertpos = int(segment.get("vertpos", 0)) + if index and vertpos == int(segments[index - 1].get("vertpos", 0)) \ + and int(segment.get("horzpos", 0)) > int(segments[index - 1].get("horzpos", 0)): + follows.append(follows[-1]) # a line's second piece beside an object with text on both sides: at + tops.append(tops[-1]) # the first's height + heights.append(int(segment.get("textheight", segment.get("vertsize", 0)))) + continue follows.append(below) table = pushing.get(index) if table is not None and vertpos == _cell_extent(measure, table) + _down(table.find(f"{HP}pos")) \ @@ -2456,10 +2464,23 @@ def _cell_band(measure: _Measure, obj: Any, top: int, width: int) -> tuple[int, left = left if left >= _MIN_SIDE and flow != "RIGHT_ONLY" else 0 right = right if right >= _MIN_SIDE and flow != "LEFT_ONLY" else 0 if left and right: - raise _Unsupported(f"text on both sides of {_local(obj)} wrapped square in a cell") + return up, foot, _BothSides(min(left, right)) return up, foot, (left or right) or None +class _BothSides(int): + """The room beside an object wrapped square in a cell with text on both sides of it (the narrower side's): an + empty line goes beside it, two empty pieces at one height as tall as the line; text there is not followed.""" + + +def _beside_one_side(narrow: dict[int, int], empty: bool) -> None: + """Text on lines *narrow* beside an object wrapped square in a cell with text on both sides of it (unless + the paragraph is *empty*) is not followed.""" + + if not empty and any(isinstance(room, _BothSides) for room in narrow.values()): + raise _Unsupported("text on both sides of an object wrapped square in a cell") + + def _clear_of(measure: _Measure, metrics: list[tuple[int, int]], top: int, prev: int, anchor: int, obj: Any, bands: list[tuple[int, int, int | None]], empty: bool, width: int ) -> tuple[list[tuple[int, int, int]], dict[int, int], tuple[int, int, int | None] | None]: @@ -2488,7 +2509,7 @@ def _clear_of(measure: _Measure, metrics: list[tuple[int, int]], top: int, prev: y, moved = band[1], True beside = [band[2] for band in around if band[2] is not None and y < band[1] and y + height > band[0]] if beside: - narrow[index] = min(beside) + narrow[index] = _BothSides(min(beside)) if any(isinstance(room, _BothSides) for room in beside) else min(beside) placed.append((y, height, advance)) return placed, narrow, own diff --git a/tests/fixtures/hancom_saved/pages_cell_object_on_both_sides_beside_empty_lines.hwpx b/tests/fixtures/hancom_saved/pages_cell_object_on_both_sides_beside_empty_lines.hwpx new file mode 100644 index 00000000..e9223424 Binary files /dev/null and b/tests/fixtures/hancom_saved/pages_cell_object_on_both_sides_beside_empty_lines.hwpx differ diff --git a/tests/test_layout_page_estimate.py b/tests/test_layout_page_estimate.py index 8bc0b3db..b3e74ca6 100644 --- a/tests/test_layout_page_estimate.py +++ b/tests/test_layout_page_estimate.py @@ -115,6 +115,11 @@ # table placed top and bottom 1958 down from it, then three paragraphs: Hancom places the inner table from the # paragraph's top above its spacing, as in the body, the cell's next paragraph below it from there "pages_cell_table_down_from_spacing": 4, + # Four times on a page of its own: a table whose cell's empty first paragraph holds a 15000 x 8000 rectangle + # wrapped square 12000 from its left, text on both sides of it, 0 or 2000 down, overlapping the text or not, + # then six empty paragraphs and two of text: the empty lines reaching it go beside it (two empty pieces at one + # height), the table as tall as with room on one side only + "pages_cell_object_on_both_sides_beside_empty_lines": 5, "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