python-hwpx 2.21.0__py3-none-any.whl → 2.22.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
hwpx/table_patch.py CHANGED
@@ -600,6 +600,57 @@ def _insert_row_by_clone(table: str, ref_row: int, count: int = 1) -> str:
600
600
  return _rebuild(prefix, new_rows, suffix, rowcnt=len(new_rows))
601
601
 
602
602
 
603
+ def _set_column_widths(table: str, new_widths: dict[int, int]) -> str:
604
+ """Set logical column widths (HWPUNIT). Each cell's cellSz.width = sum of its
605
+ spanned columns' new widths. Byte-preserving cellSz edits; grid unchanged."""
606
+ _guard_flat(table)
607
+ prefix, rows, suffix = _parse_table(table)
608
+
609
+ def fix(tc: str):
610
+ ca = _si(tc, "cellAddr", "colAddr")
611
+ cs = _si(tc, "cellSpan", "colSpan") or 1
612
+ w = sum(int(new_widths.get(c, 0)) for c in range(ca, ca + cs))
613
+ if w > 0:
614
+ tc = _ss(tc, "cellSz", "width", w)
615
+ return tc
616
+
617
+ rows = [_map_cells(r, fix) for r in rows]
618
+ return _rebuild(prefix, rows, suffix)
619
+
620
+
621
+ def _autofit_columns(table: str, *, min_frac: float = 0.06, damp: float = 0.5) -> str:
622
+ """Rebalance column widths to content: content-heavy columns widen, light ones
623
+ narrow, table total width preserved. Demand = longest single-span cell's text
624
+ width (form_fit advance model), sqrt-damped so a paragraph column doesn't run
625
+ away; every column keeps a floor of *min_frac* of the total."""
626
+ from .form_fit.measure import estimate_text_width
627
+
628
+ prefix, rows, suffix = _parse_table(table)
629
+ cur = _uniform_col_widths(rows)
630
+ if cur is None:
631
+ raise TableStructureError("no uniform (all colSpan=1) row to autofit widths")
632
+ ncol = max(cur) + 1
633
+ total = sum(cur.values())
634
+ demand = {c: 0.0 for c in range(ncol)}
635
+ for r in rows:
636
+ for tc in _S_TC.findall(r):
637
+ if (_si(tc, "cellSpan", "colSpan") or 1) != 1:
638
+ continue
639
+ ca = _si(tc, "cellAddr", "colAddr")
640
+ txt = "".join(re.findall(r"<hp:t>(.*?)</hp:t>", tc, re.DOTALL))
641
+ demand[ca] = max(demand[ca], estimate_text_width(txt, 10.0))
642
+ weight = {c: max(demand[c], 1.0) ** damp for c in range(ncol)}
643
+ sw = sum(weight.values()) or 1.0
644
+ floor = total * min_frac
645
+ raw = {c: max(floor, total * weight[c] / sw) for c in range(ncol)}
646
+ scale = total / (sum(raw.values()) or 1.0)
647
+ new = {c: int(round(raw[c] * scale)) for c in range(ncol)}
648
+ # absorb rounding drift into the widest column so the total is exact
649
+ drift = total - sum(new.values())
650
+ new[max(new, key=lambda c: new[c])] += drift
651
+ return _set_column_widths(table, new)
652
+
653
+
603
654
  def _validate_or_raise(table: str) -> None:
604
655
  _grid, rep = build_grid(table.encode("utf-8"))
605
656
  if not rep.ok:
@@ -608,10 +659,17 @@ def _validate_or_raise(table: str) -> None:
608
659
 
609
660
  # --- section-level application (byte-region splice back) ----------------------
610
661
 
662
+ def _widths_arg(o: Mapping[str, Any]) -> dict[int, int]:
663
+ w = o["widths"]
664
+ return {int(k): int(v) for k, v in w.items()} if isinstance(w, Mapping) else {i: int(v) for i, v in enumerate(w)}
665
+
666
+
611
667
  _STRUCT_OPS = {
612
668
  "delete_column": lambda t, o: _collapse_empty_rows(_delete_columns(t, o["cols"] if "cols" in o else [o["col"]])),
613
669
  "delete_row": lambda t, o: _delete_rows(t, o["rows"] if "rows" in o else [o["row"]]),
614
670
  "insert_row_by_clone": lambda t, o: _insert_row_by_clone(t, o["ref_row"], int(o.get("count", 1))),
671
+ "set_column_widths": lambda t, o: _set_column_widths(t, _widths_arg(o)),
672
+ "autofit_columns": lambda t, o: _autofit_columns(t, min_frac=float(o.get("min_frac", 0.06)), damp=float(o.get("damp", 0.5))),
615
673
  }
616
674
 
617
675
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-hwpx
3
- Version: 2.21.0
3
+ Version: 2.22.0
4
4
  Summary: 한글 없이 HWPX 문서를 열고, 편집하고, 생성하고, 검증하는 Python 자동화 라이브러리
5
5
  Author: python-hwpx Maintainers
6
6
  License-Expression: Apache-2.0
@@ -5,7 +5,7 @@ hwpx/form_fill.py,sha256=VUIU53Qa9Ho2aP72biDvJwnDW7ngdAzu3PSd5A7d1JM,9908
5
5
  hwpx/package.py,sha256=0rKjGCJbPQvrVBIy07Jpjsu3fI7HhbqFCGWTiTDsJpo,1141
6
6
  hwpx/patch.py,sha256=8G1wgGVgCp0KyhQb-y0ZI2FJEatCwLt_JpepHJmYhYQ,23799
7
7
  hwpx/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- hwpx/table_patch.py,sha256=Z5uM1jlyfSZLToGrd0dUo1-Ex_AAVcX8yY9MBXrMXHU,32064
8
+ hwpx/table_patch.py,sha256=OqQfw8udCnZsM5wL1lBBOPOpF43actIRogSOyU7LTtw,34731
9
9
  hwpx/template_formfit.py,sha256=dlxf98FatT5Nl4hN15TuMXqj5NlDTGSnLaqXVo_PoBo,23424
10
10
  hwpx/templates.py,sha256=28bYqeJVeDb1Cq8G9NZG9Mhnu4K2GamAKC4QhxvUZyA,1187
11
11
  hwpx/builder/__init__.py,sha256=8tcxClbeuNle_R_2nDAauyy518OCegG8qVPjwIKRUZA,849
@@ -153,10 +153,10 @@ hwpx/visual/diff.py,sha256=0X5T9IgwRZU3td-7vnPrlowovtGud7P_ymq0KVehlKk,5677
153
153
  hwpx/visual/masks.py,sha256=oXhgynAb4uKjJtZ2BGHHdAjyvWGqSFlZFQ-iJxzHiuo,1832
154
154
  hwpx/visual/oracle.py,sha256=VNO-tAB-lzTdW5XtSELxg_Aau369qRFBsJvNryHF_D8,31319
155
155
  hwpx/visual/report.py,sha256=2RhXN1KBYOZTim9FNpeUhaaDHR7oFxI6Z2DLUkDIiwE,1717
156
- python_hwpx-2.21.0.dist-info/licenses/LICENSE,sha256=_ubz4wv-BkkT3l3gu-QuH7JGeVjuRYGZoZK95eNsCHU,9688
157
- python_hwpx-2.21.0.dist-info/licenses/NOTICE,sha256=k48h6EaGQE8Y1c0dS9sIOOcz4YqkbcImWClF7pBOgsg,2473
158
- python_hwpx-2.21.0.dist-info/METADATA,sha256=K-bzEFVTuF3yVB7Fr3kJ0EGnRDmiy6_1hGrQIQRGq_k,19982
159
- python_hwpx-2.21.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
160
- python_hwpx-2.21.0.dist-info/entry_points.txt,sha256=4U6WXYWHxEiWp2VRHo97fvOYNh7ebu6roonk7chxKcY,453
161
- python_hwpx-2.21.0.dist-info/top_level.txt,sha256=R1iToqDh80Nf2oQhRjTN0rbN2X6kyDUizIocZjkhuxc,5
162
- python_hwpx-2.21.0.dist-info/RECORD,,
156
+ python_hwpx-2.22.0.dist-info/licenses/LICENSE,sha256=_ubz4wv-BkkT3l3gu-QuH7JGeVjuRYGZoZK95eNsCHU,9688
157
+ python_hwpx-2.22.0.dist-info/licenses/NOTICE,sha256=k48h6EaGQE8Y1c0dS9sIOOcz4YqkbcImWClF7pBOgsg,2473
158
+ python_hwpx-2.22.0.dist-info/METADATA,sha256=VugoM_5mzaiTEDsaOqqHtDWuNo8CjuTG73Z4uPGsOTU,19982
159
+ python_hwpx-2.22.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
160
+ python_hwpx-2.22.0.dist-info/entry_points.txt,sha256=4U6WXYWHxEiWp2VRHo97fvOYNh7ebu6roonk7chxKcY,453
161
+ python_hwpx-2.22.0.dist-info/top_level.txt,sha256=R1iToqDh80Nf2oQhRjTN0rbN2X6kyDUizIocZjkhuxc,5
162
+ python_hwpx-2.22.0.dist-info/RECORD,,