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

### 고침

- 쪽 수 추정(실험, `estimate_pages`)이 문단 글 뒤에 놓인 어울림(SQUARE) 개체를 지원 밖
(`... wrapped square after text`)으로 두던 것을, 그 개체의 조판 부호가 문단 첫 줄에 있을 때 고친다.
- 한/글은 개체의 문단 기준 세로 자리를 부호가 놓인 줄(문단을 단 전체 폭으로 놓았을 때)의 윗선에서 잰다.
부호가 첫 줄에 있으면 문단 윗선이라, 글 앞에 놓인 개체와 같다. 띠에 닿는 줄은 그 문단의 줄도 곁으로 간다.
- 첫 줄은 FormFit으로 문단을 단 전체 폭에서 나누어 가린다. 부호가 뒤 줄에 있으면 전처럼 지원 밖이다.
- 쪽 수 추정(실험, `estimate_pages`)이 글과 함께 움직이고 나뉠 수 있는 어울림(SQUARE) 표가 본문 바닥을 넘을 때,
줄 캐시가 없으면 지원 밖으로 두던 것을 고친다.
- 한/글은 그런 표를 흐르는 표처럼 쪽 끝에서 행 사이나 칸 줄 사이로 나눈다. 표가 걸친 쪽마다 곁 줄은 그 쪽
Expand Down
33 changes: 28 additions & 5 deletions src/hwpx/layout/pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@

import copy
import os
from collections.abc import Collection
from collections.abc import Callable, Collection
from dataclasses import dataclass, field, replace
from typing import TYPE_CHECKING, Any

Expand Down Expand Up @@ -2007,9 +2007,12 @@ def _wraps_top_and_bottom(obj: Any, column: int) -> bool:
return start < _MIN_SIDE and column - start - width < _MIN_SIDE


def _square_object(objects: list[Any], runs: list[Any], column: int) -> Any:
def _square_object(objects: list[Any], runs: list[Any], column: int,
on_first_line: Callable[[Any], bool] | None = None) -> Any:
"""The one object of a paragraph wrapped square across the column from the paragraph's top, before
any text, or ``None`` (:func:`_square_sides` says where the text goes beside it)."""
any text or after text that *on_first_line* says ends on the paragraph's first line (Hancom places it from
the top of the line its control falls on, the paragraph laid out the column's whole width), or ``None``
(:func:`_square_sides` says where the text goes beside it)."""

if len(objects) != 1:
return None
Expand All @@ -2025,10 +2028,29 @@ def _square_object(objects: list[Any], runs: list[Any], column: int) -> Any:
if child is obj:
break
if _local(child) == "t" and _t_text(child):
raise _Unsupported(f"{_local(obj)} wrapped square after text")
if on_first_line is None or not on_first_line(obj):
raise _Unsupported(f"{_local(obj)} wrapped square after text")
break
return obj


def _on_first_line(measure: _Measure, page: _Page, paragraph: Any, runs: list[Any], obj: Any) -> bool:
"""Whether *obj*'s control falls on the paragraph's first line laid out the column's whole width (FormFit's
lines, the object taking no room in them)."""

place = 0
for child in (child for run in runs for child in run):
if child is obj:
break
if _local(child) == "t":
place += len(_t_text(child))
size, refs, _ = _text_size(measure, runs)
style = measure.style(paragraph.get("paraPrIDRef"), refs, paragraph)
shape = measure.shape(paragraph.get("paraPrIDRef"))
starts = measure.line_starts(_run_text(runs), _line_widths(shape, page.column_width, style), size, style)
return all(start > place for start in starts[1:])


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,
Expand Down Expand Up @@ -2082,7 +2104,8 @@ def _wrapped_paragraph(measure: _Measure, page: _Page, paragraph: Any, wrap: _Wr
para = _paragraph(measure, page, paragraph, stacked=tuple(stacked))
shape = measure.shape(paragraph.get("paraPrIDRef"))
return replace(para, stack=_stack(measure, stacked, page.column_width, shape)), None
square = _square_object(objects, runs, page.column_width)
square = _square_object(objects, runs, page.column_width,
lambda obj: _on_first_line(measure, page, paragraph, runs, obj))
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
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
17 changes: 17 additions & 0 deletions tests/test_layout_page_estimate.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@
"pages_square_table_split_text_on_both_sides": 3, # in the middle: the lines in two pieces
"pages_square_table_wide_split_text_beside_it": 2, # 45626 wide, 2562 left beside it: two characters a
# line there
# A table 30000 x 20000 wrapped square at the column's left from its paragraph 8000 down (10 pt text spaced
# 160%), its control after 16 characters, on the paragraph's first line laid out the column's whole width:
# Hancom places it from the paragraph's top as one before the text, the lines reaching it beside it:
"pages_square_table_after_text": 1, # the paragraph's own lines beside it, 12 characters a line
"pages_square_table_after_text_2383_down": 1, # placed 2383 down: the paragraph's line above it the
# whole width, the paragraphs after beside it
"pages_square_table_before_text": 1, # its control before the text: the same
"pages_picture_1319_down_moved_to_the_next_page": 3, # a picture 8000 tall placed top and bottom 1319
# below its empty paragraph's line: alone at the
# next page's top, the line staying, the lines
Expand Down Expand Up @@ -1264,6 +1271,16 @@ def test_a_table_whose_row_addresses_skip_is_estimated_without_the_missing_rows(
assert estimate.pages == 1


def test_a_square_object_after_text_past_the_first_line_is_not_followed() -> None:
# The same table, its control after 60 characters: on the paragraph's second line laid out the column's
# whole width. Hancom places it from that line's top (9600), the lines from there on beside it. Not
# followed.
data = (FIXTURES / "pages_square_table_after_text_on_the_second_line.hwpx").read_bytes()

assert estimate_pages(data).unsupported == ("section 0: tbl wrapped square after text",)
assert estimate_pages(_without_caches(data)).unsupported == ("section 0: tbl wrapped square after text",)


def test_a_page_break_in_a_row_holding_a_table_beside_a_taller_cell_is_unsupported() -> None:
# Row 1's cell (1, 0) holds more lines than the cell beside it holding a table; the page end falls
# among them.
Expand Down
Loading