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

## [Unreleased]

### 고침

- 쪽 수 추정과 FormFit이 줄 바꿈 뒤 빈 run으로 끝나는 문단의 빈 마지막 줄을 한/글처럼 그 run의 크기로 잡는다(글자보다 작아도).
전에는 작은 run은 무시하고 큰 run은 줄 바꿈 앞 줄까지 키웠다. 끝 run이 더 크면 그 문단의 줄 캐시는 쓰지 않는다.
- 쪽 수 추정이 첫 줄에 붙은 어울림 개체의 세로 위치를 한/글처럼 위 간격 위 문단 맨 위에서 잰다. 전에는 첫 줄 맨 위에서
재서, 위 간격이 있는 문단에서 개체 곁에 좁아지는 줄을 위 간격만큼 아래로 잡았다.
- 셀 줄간격(`set_cell_line_spacing`)이 표를 품은 칸도 바꾼다. 칸 자신의 문단(표를 담은 문단 포함)만 줄간격을 바꾸고 그
문단들의 줄 캐시만 지우며, 안쪽 표와 글상자는 바이트 그대로 둔다. 전에는 그런 칸을 거부했고, 다른 칸에 표가 든 표에서
행을 고르면 안쪽 표의 같은 주소 칸까지 바꿨다.
- 복제한 것을 다시 복제해도 번호가 겹치지 않는다. 표를 품은 행을 복제한 뒤 열을 복제하면(또는 반대로) 안쪽 표의 `id`가
겹쳤고, `insert_block_by_clone`을 두 번 하면 문단 `id`가 겹쳤다. 이제 복제된 표는 한/글이 붙여 넣은 표처럼 문서에 없는
새 `id`와 문서 맨 위 `zOrder`를 받고, 문단은 문서에 없는 `id`를 받는다.
- `insert_block_by_clone`이 그림·도형·필드·메모가 든 칸을 행 복제처럼 복제한다. 전에는 같은 번호로 복사했다. 차트, 연결된
글상자, 칸 안 머리말·꼬리말 정의가 든 블록은 거부한다.
- `clone_table`이 표와 그 안의 표·개체에 한/글이 붙여 넣은 표처럼 새 번호를 준다. 전에는 문단 `id`만 바꿔 표 `id`와 개체
번호가 원본과 같았다. 차트, 연결된 글상자, 칸 안 머리말·꼬리말 정의가 든 표는 거부한다.
- `split_cell_vertical`이 새로 생긴 칸을 빈 문단 하나로 둔다(칸 나누기와 같음). 전에는 원래 칸의 문단을 글자만 비워
복사해서, 그 칸의 그림·도형이 같은 번호로 겹쳤다.
- 객체 모델 `set_column_widths`·`equalize_column_widths`가 칸을 안쪽 표보다 좁게 만들면 바이트 편집처럼 거부하고 표를
그대로 둔다(`TableStructureError`).

### 수정

- 쪽 수 추정이 글 없이 어울림 개체와 글자처럼 취급한 개체 하나만 있는 문단도 추정한다. 한/글처럼 그 개체를 곁 줄에 둔다(곁보다
Expand Down Expand Up @@ -50,14 +71,10 @@

### 고침

- 쪽 수 추정과 FormFit이 줄 바꿈 뒤 빈 run으로 끝나는 문단의 빈 마지막 줄을 한/글처럼 그 run의 크기로 잡는다(글자보다 작아도).
전에는 작은 run은 무시하고 큰 run은 줄 바꿈 앞 줄까지 키웠다. 끝 run이 더 크면 그 문단의 줄 캐시는 쓰지 않는다.
- 쪽 수 추정이 아래로 놓인 위아래 표의 띠가 오기 전에 쪽 나눔이 있어도 문서를 추정한다. 한/글처럼 표는 제자리에 두고,
쪽 나눔 문단은 표가 끝나는 쪽의 다음 쪽에서 시작한다. 전에는 그런 문서를 추정하지 않았다.
- 쪽 수 추정이 한 문단에 종이 기준으로 놓인 개체가 여럿이어도 문서를 추정한다. 한/글처럼 개체마다 따로 띠로 보고 본문 줄을
그 띠 밖에 둔다. 전에는 그런 문서를 추정하지 않았다.
- 쪽 수 추정이 첫 줄에 붙은 어울림 개체의 세로 위치를 한/글처럼 위 간격 위 문단 맨 위에서 잰다. 전에는 첫 줄 맨 위에서
재서, 위 간격이 있는 문단에서 개체 곁에 좁아지는 줄을 위 간격만큼 아래로 잡았다.
- 쪽 수 추정이 글자 크기·모양이 섞인 표 칸 문단에 위아래 개체가 있어도 문서를 추정한다. 개체가 붙은 줄까지는 위에, 나머지
줄은 그 아래에 둔다. 전에는 그런 문서를 추정하지 않았다.
- 쪽 수 추정이 표 칸 안 어울림 개체 곁의 문단에 왼쪽·오른쪽 여백이나 들여쓰기가 있어도 문서를 추정한다. 한/글처럼 개체가
Expand Down
20 changes: 16 additions & 4 deletions docs/nested-cell-fill-safety.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,25 @@ deleted row or column) or stays in the first part of a split cell.
cell's (see "Cloning cell content" below); `blank` clones hold no table.
- An edit that would leave a cell narrower than the table nested in it (its
width plus the cell's side margins) refuses, unless the cell was already that
narrow. `autofit_columns` holds such a column at that width.
narrow. `autofit_columns` holds such a column at that width. The object
model's `set_column_widths` and `equalize_column_widths` refuse the same way
and leave the table as it was.
- `merge_table` merges with the next table on the same nesting level.
- `set_cell_line_spacing` changes the line spacing of the addressed cell's own
paragraphs, including the one holding a table, and drops only their line
caches; a table or text box in the cell keeps its bytes. Address an inner
table by its own `table_index` to change its cells.

## Cloning cell content

`insert_row_by_clone`, `insert_column_by_clone` and `split_cell` copy a cell's
content the way Hancom numbers what it pastes (observed by copying and pasting
in Hangul and comparing the saved HWPX):
`insert_row_by_clone`, `insert_column_by_clone`, `insert_block_by_clone`,
`split_cell` and `clone_table` copy content the way Hancom numbers what it
pastes (observed by copying and pasting in Hangul and comparing the saved
HWPX). Copying copies again never repeats a number: paragraphs take ids unused
in the document.

- A table copied, whole or nested in a copied cell, gets a fresh `id` and a
`zOrder` above the document's, as a pasted table does.
- Pictures, shapes, equations, footnotes and endnotes get a fresh `id` and
`instid` (two different numbers) used nowhere in the document, and a `zOrder`
above the document's. The shapes inside a group keep their `id` and `zOrder`
Expand All @@ -66,6 +76,8 @@ in Hangul and comparing the saved HWPX):
- A connector copied loses its ends (`subjectIDRef="0"`), as Hancom's does.
- Content that points into another part or definition refuses: a chart, linked
text boxes, a header or footer defined in the cell.
- `split_cell_vertical` keeps the content in the first part; each new part
holds one empty paragraph of the cell's format.

## Evidence and downstream use

Expand Down
37 changes: 37 additions & 0 deletions src/hwpx/oxml/table_sizes.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,42 @@ def _column_floors(table: "HwpxOxmlTable") -> list[int]:
return floors


def _keeping_nested_fit(edit: Any) -> Any:
"""*edit* (a width edit of a table) refused, the table left as it was, when it leaves a cell
narrower than the table nested in it (its width plus the cell's side margins) unless the cell
was already that narrow -- as the byte edits refuse it (:func:`hwpx.table_patch._check_nested_fit`)."""

from copy import deepcopy
from functools import wraps

@wraps(edit)
def run(table: "HwpxOxmlTable", *args: Any) -> None:
from lxml import etree # type: ignore[reportAttributeAccessIssue] # lxml has no complete bundled typing

from ..table_patch import TableStructureError, _check_nested_fit

element = table.element
if element.find(f".//{_HP}tbl") is None:
return edit(table, *args)
saved = deepcopy(element)
before = etree.tostring(element, encoding="unicode", with_tail=False)
edit(table, *args)
try:
_check_nested_fit(before, etree.tostring(element, encoding="unicode", with_tail=False))
except TableStructureError:
for child in list(element):
element.remove(child)
element.attrib.clear()
element.attrib.update(saved.attrib)
element.text = saved.text
element.extend(list(saved))
raise
return None

return run


@_keeping_nested_fit
def set_column_widths(table: "HwpxOxmlTable", weights: Sequence[int | float]) -> None:
if len(weights) != table.column_count:
raise ValueError("column width weights must match table column count")
Expand Down Expand Up @@ -256,6 +292,7 @@ def _zone_moves(
return moves


@_keeping_nested_fit
def equalize_column_widths(table: "HwpxOxmlTable") -> None:
grid = table._build_cell_grid()
rows = _rows_of_cells(table.iter_grid(), table.row_count)
Expand Down
3 changes: 1 addition & 2 deletions src/hwpx/oxml/table_structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ def insert_rows(self, ref_row: int, count: int = 1, *, side: str = "below", blan
same column in *ref_row* and, unless *blank*, its text; with *blank* it holds one empty
paragraph of that format, as Hancom inserts it. A cell merged across the new rows grows over
them, and next to a merged cell that ends (above: starts) at *ref_row* each new row gets an
empty cell of its format. A table holding a table is refused
(:class:`hwpx.table_patch.TableStructureError`).
empty cell of its format. A table nested in a copied cell is copied with fresh numbers.
"""

self._restructure({
Expand Down
Loading
Loading