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

### 고침

- 쪽 수 추정(실험, `estimate_pages`)이 세로쓰기 칸(`hp:subList@textDirection="VERTICAL"`)의 글을 가로 글처럼
칸 폭에서 나누어 행을 키우던 것을 고친다.
- 한/글은 세로쓰기 칸의 줄을 칸 높이를 따라 세로로 놓고 옆으로 늘어놓는다. 넘치는 글은 잘리고, 행은 선언
높이 그대로다.
- 세로쓰기 칸이 있는 흐르는 표는 쪽 끝에서 나뉘지 않고, 나누지 않게 둔 표처럼 통째로 다음 쪽으로 간다.
기준 문단 줄과 그 뒤 글은 앞 쪽에 남는다.
- 쪽 수 추정(실험, `estimate_pages`)이 줄 캐시 없는 표 칸에서 글자처럼 둔 표 옆에 글 앞·뒤로 둔 개체가 있는
문단을 지원 밖(`a nested table`)으로 두던 것을 고친다.
- 그런 개체는 자리를 차지하지 않으므로, 그 문단은 표 혼자인 문단처럼 표 높이의 한 줄이다.
Expand Down
31 changes: 27 additions & 4 deletions src/hwpx/layout/pages.py
Original file line number Diff line number Diff line change
Expand Up @@ -918,7 +918,30 @@ def _down(pos: Any) -> int:
return 0 if offset < 0 or offset >= 1 << 31 else offset


def _vertical(cell: Any) -> bool:
"""Whether *cell* holds vertical text (``hp:subList@textDirection``): Hancom lays its lines down the
cell's height, side by side across it, cuts what does not fit and never makes its row taller."""

sub_list = cell.find(f"{HP}subList")
return sub_list is not None and sub_list.get("textDirection", "HORIZONTAL") != "HORIZONTAL"


def _page_break(table: Any) -> str:
"""How a flowing table goes over a page end (``hp:tbl@pageBreak``): one holding a cell of vertical text
does not split, as if set not to (NONE): it moves to the next page whole."""

if any(_vertical(cell) for row in table.findall(f"{HP}tr") for cell in row.findall(f"{HP}tc")):
return "NONE"
return table.get("pageBreak", "CELL")


def _cell_row(measure: _Measure, table: Any, cell: Any) -> _Row:
if _vertical(cell): # its lines go across it, the row as declared: one block, not split
margins = cell_margins_of(cell, table)
vertical = margins.top + margins.bottom
height = max(int(cell.find(f"{HP}cellSz").get("height", 0)), vertical)
return _Row(height, 1, height - vertical, height - vertical, vertical, cell.get("header") == "1",
first=height - vertical)
paragraphs = cell.findall(f"{HP}subList/{HP}p")
nested = _holds_table(cell)
drawn: tuple[tuple[int, int], ...] = ()
Expand Down Expand Up @@ -1660,7 +1683,7 @@ def _anchor(measure: _Measure, paragraph: Any, runs: list[Any], text: str, obj:
top, bottom = (0, 0) if margin is None else (_margin(margin, "top"), _margin(margin, "bottom"))
if _local(obj) == "tbl":
rows, cells = _table_rows(measure, obj)
table = _FlowTable(rows, obj.get("pageBreak", "CELL"), obj.get("repeatHeader") == "1", (top, bottom),
table = _FlowTable(rows, _page_break(obj), obj.get("repeatHeader") == "1", (top, bottom),
tuple(cells), caption=_caption(measure, obj), cut=_spare_cut(obj))
return _Anchor(line, table, 0)
return _Anchor(line, None, _drawn_height(obj, measure) + top + bottom)
Expand Down Expand Up @@ -1688,7 +1711,7 @@ def _object_line(
if name == "tbl":
rows, cells = _table_rows(measure, obj)
offset = int(pos.get("vertOffset", 0)) # one up (a negative offset, kept unsigned) starts at the line
table = _FlowTable(rows, obj.get("pageBreak", "CELL"), obj.get("repeatHeader") == "1", (top, bottom),
table = _FlowTable(rows, _page_break(obj), obj.get("repeatHeader") == "1", (top, bottom),
tuple(cells), 0 if offset < 0 or offset >= 1 << 31 else offset,
_caption(measure, obj), _spare_cut(obj))
return count, size, pitch, table
Expand Down Expand Up @@ -1928,7 +1951,7 @@ def _wrapped_paragraph(measure: _Measure, page: _Page, paragraph: Any,
rows, cells = _table_rows(measure, pusher)
margin = pusher.find(f"{HP}outMargin")
ends = (0, 0) if margin is None else (_margin(margin, "top"), _margin(margin, "bottom"))
table = _FlowTable(rows, pusher.get("pageBreak", "CELL"), pusher.get("repeatHeader") == "1", ends,
table = _FlowTable(rows, _page_break(pusher), pusher.get("repeatHeader") == "1", ends,
tuple(cells), caption=_caption(measure, pusher), cut=_spare_cut(pusher))
return replace(para, band=_Band(para.wrap_anchor, offset, table)), None
top = para.span(0, para.wrap_anchor) + offset
Expand Down Expand Up @@ -2029,7 +2052,7 @@ def _stack(measure: _Measure, objects: list[Any], column: int, shape: _Shape) ->
rows, cells = _table_rows(measure, obj)
margin = obj.find(f"{HP}outMargin")
ends = (0, 0) if margin is None else (_margin(margin, "top"), _margin(margin, "bottom"))
table = _FlowTable(rows, obj.get("pageBreak", "CELL"), obj.get("repeatHeader") == "1", ends,
table = _FlowTable(rows, _page_break(obj), obj.get("repeatHeader") == "1", ends,
tuple(cells), caption=_caption(measure, obj), cut=_spare_cut(obj))
stacked.append(_Stacked(int(pos.get("vertOffset", 0)), left, left + width, height, table))
return tuple(stacked)
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
29 changes: 29 additions & 0 deletions tests/test_layout_page_estimate.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,14 @@
"pages_table_nested_in_table_as_character": 1, # a table in a cell of a table set as a character
"pages_table_nested_row_split": 2, # a row holding a table splits after its first line of text
"pages_table_nested_row_moved": 2, # none of a row holding a table fits: it goes on whole
# A flowing table whose first column holds vertical text (10 pt, spaced 160%): Hancom lays its lines down
# the cell's height, side by side across it, and cuts what does not fit; the row stays as declared:
"pages_table_vertical_cell_short_text": 1, # three characters in a cell 3000 x 4000: one line
"pages_table_vertical_cell_text_past_its_height": 1, # thirty: ten lines, the row still 4000 tall
"pages_table_vertical_cell_text_past_its_width": 1, # sixty in a cell 1500 wide: twenty lines
"pages_table_vertical_cell_merged_over_rows": 1, # ten in a cell merged over four rows 2000 tall
"pages_table_merged_cells_over_the_page_end": 2, # twenty rows, cells of four merged, horizontal text:
# the table splits at the page end
"pages_table_nested_top_and_bottom_alone_then_a_line": 1, # a table placed top and bottom alone in
# a cell paragraph, then a line: right
# below the table
Expand Down Expand Up @@ -888,6 +896,27 @@ def test_a_cell_holding_a_table_among_text_or_top_and_bottom_is_as_tall_as_hanco
assert estimate_pages(_without_caches(data)).unsupported == ("section 0: a nested table",)


@pytest.mark.parametrize("fixture", ["pages_table_vertical_cells_over_the_page_end",
"pages_table_vertical_cell_of_all_rows_over_the_page_end",
"pages_table_vertical_cell_in_every_row_over_the_page_end",
"pages_table_vertical_cells_taller_than_a_page"])
def test_a_table_holding_vertical_text_moves_to_the_next_page_whole(fixture: str) -> None:
# pages_table_merged_cells_over_the_page_end with vertical text in its first column (cells of four rows
# merged, one of all twenty, or one in every row), and one of forty rows taller than a page: Hancom does
# not split it but moves it whole to the next page's top (running past the page's foot when taller), the
# anchor's line and the paragraphs after it staying on the first page.
data = (FIXTURES / f"{fixture}.hwpx").read_bytes()
hancom = _hancom_lines(data)

for document in (data, _without_caches(data)):
estimate = estimate_pages(document)
estimated = [[line.vertpos for line in lines] for lines in estimate.lines]
assert estimate.unsupported == ()
assert estimate.pages == 2
assert [mine for mine, theirs in zip(estimated, hancom) if theirs] == [theirs for theirs in hancom if theirs]
assert {line.page for lines in estimate.lines for line in lines} == {0}


def test_a_negative_outer_margin_counts_as_none() -> None:
# A table over two pages whose outer margins were -500 (kept as unsigned 32-bit numbers): Hancom laid it
# out as one without them and saved them as 0. With the margins back at -500 the estimate is the same.
Expand Down
Loading