diff --git a/CHANGELOG.md b/CHANGELOG.md index 13d7651d..448064bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,11 @@ ### 고침 +- 표 행·열·칸 구조 편집(객체 경로와 `apply_table_ops`)이 쪽을 넘어 나뉜 표의 저장 높이(`hp:sz`)를 그대로 둔다. 한/글은 + 그런 표에 첫 쪽 조각의 높이만 저장하므로(같은 행의 표가 놓인 자리에 따라 30463·17551·5865로 저장됨) 행 높이 증감을 + 더할 근거가 없다. 전에는 증감을 그대로 더해, 평가계획 채우기가 13행 표(행 28762, 저장 13222)에서 9행을 지우면 + 높이가 0이 됐다(#539 후속). 행 높이만큼 저장된 표는 전처럼 증감만큼 움직인다. + - 줄 캐시(`write_line_caches`)가 머리에 정의되지 않은 글머리표·번호를 가리키는 문단 모양(예: 글머리표 idRef 0)의 문단에는 캐시를 쓰지 않는다. 한/글은 정의가 없어도 글머리 자리를 잡아 첫 줄에 글자를 덜 넣는데, 전에는 글머리 없이 나눈 줄을 써서 한/글이 그 줄을 눌러 담을 수 있었다. diff --git a/src/hwpx/oxml/table_structure.py b/src/hwpx/oxml/table_structure.py index 3a1ef782..369e9f14 100644 --- a/src/hwpx/oxml/table_structure.py +++ b/src/hwpx/oxml/table_structure.py @@ -157,13 +157,20 @@ def _rows_height(self) -> int | None: def _grow_height(self, before: int | None) -> None: """Move the table's saved height (``hp:sz``) by what its rows gained or lost in the edit, as Hancom saves the height of the rows it lays out: the rows left as they were keep the height Hancom - gave them. With rows the estimate does not follow, the height stays as it was.""" + gave them. With rows the estimate does not follow, the height stays as it was. + + A table saved shorter than its rows is one Hancom split across pages: it saves the height of the + part on the table's first page (tables of the same rows save different heights by where they start), + and the rows' gain or loss does not say how that part changes. Its height also stays as it was, rather + than moving by the whole delta, which can take it to nothing.""" after = self._rows_height() size = self.element.find(f"{_HP}sz") if before is None or after is None or size is None or after == before: return height = int(size.get("height", 0) or 0) + if height < before: + return size.set("height", str(max(0, height + after - before))) diff --git a/tests/test_table_structure_edits.py b/tests/test_table_structure_edits.py index be2cf827..eeaeb1d4 100644 --- a/tests/test_table_structure_edits.py +++ b/tests/test_table_structure_edits.py @@ -376,3 +376,41 @@ def test_a_table_holding_a_table_keeps_it_through_an_edit() -> None: assert [table.cell(row, 0).tables[0].cell(0, 0).text for row in (0, 1)] == ["inner", "inner"] with HwpxDocument.open(document.to_bytes()) as reopened: assert reopened.tables[0].row_count == 3 + + +BLANK_3HAK = Path(__file__).parent / "fixtures" / "m105_evalplan" / "blank_form_3hak.hwpx" + + +def _saved_heights(data: bytes) -> list[int]: + with zipfile.ZipFile(io.BytesIO(data)) as archive: + section = etree.fromstring(archive.read("Contents/section0.xml")) + return [int(table.find(HP + "sz").get("height")) for table in section.iter(HP + "tbl")] + + +def test_a_table_hancom_split_across_pages_keeps_its_saved_height_through_row_edits() -> None: + # In this Hancom-saved evaluation plan, tables 19-21 have the same 15 rows and save 30463, 17551 and 5865: + # a table split across pages saves the height of its part on the first page, not its rows' height. Table 8 + # (13 rows of 28762 saved as 13222) is one of them. Moving that height by the rows' delta took it to 0 + # when the evaluation-plan fill deleted nine of its rows. + source = BLANK_3HAK.read_bytes() + saved = _saved_heights(source) + assert saved[19:22] == [30463, 17551, 5865] + op = {"op": "delete_row", "table_index": 8, "rows": list(range(4, 13))} + + by_bytes = apply_table_ops(source, [op]) + assert by_bytes.ok, by_bytes.skipped + assert _saved_heights(by_bytes.data)[8] == saved[8] == 13222 + + document = HwpxDocument.open(source) + document.tables.all[8].delete_rows(list(range(4, 13))) + assert _saved_heights(document.to_bytes())[8] == 13222 + + +def test_a_table_saved_as_tall_as_its_rows_still_moves_by_their_delta() -> None: + # Table 9 of the same form fits its page: it saves its rows' 25961, so a deleted row takes its height. + source = BLANK_3HAK.read_bytes() + assert _saved_heights(source)[9] == 25961 + + result = apply_table_ops(source, [{"op": "delete_row", "table_index": 9, "rows": [9]}]) + assert result.ok, result.skipped + assert 0 < _saved_heights(result.data)[9] < 25961