python-hwpx 6.0.3__tar.gz → 6.2.1__tar.gz

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.
Files changed (326) hide show
  1. {python_hwpx-6.0.3/src/python_hwpx.egg-info → python_hwpx-6.2.1}/PKG-INFO +1 -1
  2. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/pyproject.toml +141 -1
  3. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/_document/_legacy.py +16 -1
  4. python_hwpx-6.2.1/src/hwpx/_document/highlight.py +164 -0
  5. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/_document/layout.py +128 -11
  6. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/_document/ns/__init__.py +1 -1
  7. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/_document/ns/page.py +125 -1
  8. python_hwpx-6.2.1/src/hwpx/_document/ns/parts.py +274 -0
  9. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/_document/ns/shapes.py +182 -1
  10. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/_document/ns/styles.py +269 -4
  11. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/_document/ns/text.py +30 -1
  12. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/_document/shapes.py +244 -1
  13. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/capabilities.py +273 -2
  14. python_hwpx-6.2.1/src/hwpx/data/contract_docs/support-matrix.md +137 -0
  15. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/errors.py +59 -0
  16. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/objects/__init__.py +2 -0
  17. python_hwpx-6.2.1/src/hwpx/objects/highlight.py +41 -0
  18. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/opc/package.py +123 -1
  19. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/opc/relationships.py +6 -0
  20. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/oxml/__init__.py +46 -2
  21. python_hwpx-6.2.1/src/hwpx/oxml/_document_primitives.py +1565 -0
  22. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/oxml/body.py +488 -0
  23. python_hwpx-6.2.1/src/hwpx/oxml/document_metadata.py +94 -0
  24. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/oxml/document_parts.py +321 -106
  25. python_hwpx-6.2.1/src/hwpx/oxml/drop_cap.py +218 -0
  26. python_hwpx-6.2.1/src/hwpx/oxml/dutmal_compose.py +125 -0
  27. python_hwpx-6.2.1/src/hwpx/oxml/field_marks.py +409 -0
  28. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/oxml/header.py +349 -4
  29. python_hwpx-6.2.1/src/hwpx/oxml/header_compat.py +317 -0
  30. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/oxml/header_part.py +260 -123
  31. python_hwpx-6.2.1/src/hwpx/oxml/history_part.py +162 -0
  32. python_hwpx-6.2.1/src/hwpx/oxml/master_page.py +104 -0
  33. python_hwpx-6.2.1/src/hwpx/oxml/master_page_authoring.py +153 -0
  34. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/oxml/namespaces.py +8 -0
  35. python_hwpx-6.2.1/src/hwpx/oxml/note_authoring.py +193 -0
  36. python_hwpx-6.2.1/src/hwpx/oxml/numbering_kinds.py +98 -0
  37. python_hwpx-6.2.1/src/hwpx/oxml/objects.py +1526 -0
  38. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/oxml/paragraph.py +325 -281
  39. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/oxml/run.py +23 -0
  40. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/oxml/section_format.py +79 -0
  41. python_hwpx-6.2.1/src/hwpx/oxml/section_layout.py +133 -0
  42. python_hwpx-6.2.1/src/hwpx/oxml/settings.py +155 -0
  43. python_hwpx-6.2.1/src/hwpx/oxml/simple_parts.py +123 -0
  44. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/oxml/table.py +253 -0
  45. python_hwpx-6.2.1/src/hwpx/oxml/version_part.py +68 -0
  46. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/table_patch.py +229 -30
  47. python_hwpx-6.2.1/src/hwpx/tools/document_merge.py +1306 -0
  48. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/id_integrity.py +15 -0
  49. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/mail_merge.py +2 -2
  50. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/markdown_export.py +25 -2
  51. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/package_validator.py +83 -0
  52. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/text_extractor.py +31 -2
  53. {python_hwpx-6.0.3 → python_hwpx-6.2.1/src/python_hwpx.egg-info}/PKG-INFO +1 -1
  54. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/python_hwpx.egg-info/SOURCES.txt +47 -0
  55. python_hwpx-6.2.1/tests/test_arc_authoring.py +294 -0
  56. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_authoring_defaults.py +101 -0
  57. python_hwpx-6.2.1/tests/test_border_fill_image_gradient.py +361 -0
  58. python_hwpx-6.2.1/tests/test_build_element_census.py +186 -0
  59. python_hwpx-6.2.1/tests/test_column_break.py +109 -0
  60. python_hwpx-6.2.1/tests/test_container_authoring.py +313 -0
  61. python_hwpx-6.2.1/tests/test_coverage_ledger.py +1056 -0
  62. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_coverage_promotion.py +267 -0
  63. python_hwpx-6.2.1/tests/test_deviations_registry.py +454 -0
  64. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_document_formatting.py +743 -0
  65. python_hwpx-6.2.1/tests/test_document_merge.py +1149 -0
  66. python_hwpx-6.2.1/tests/test_document_metadata.py +141 -0
  67. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_document_ops_runtime_boundary.py +52 -0
  68. python_hwpx-6.2.1/tests/test_drop_cap_authoring.py +141 -0
  69. python_hwpx-6.2.1/tests/test_dutmal_compose_authoring.py +209 -0
  70. python_hwpx-6.2.1/tests/test_editor_surface_inventory.py +119 -0
  71. python_hwpx-6.2.1/tests/test_field_marks.py +328 -0
  72. python_hwpx-6.2.1/tests/test_header_compat_authoring.py +293 -0
  73. python_hwpx-6.2.1/tests/test_highlight_authoring.py +289 -0
  74. python_hwpx-6.2.1/tests/test_id_integrity.py +54 -0
  75. python_hwpx-6.2.1/tests/test_index_mark.py +158 -0
  76. python_hwpx-6.2.1/tests/test_master_page_authoring.py +170 -0
  77. python_hwpx-6.2.1/tests/test_memo_shape_authoring.py +249 -0
  78. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_namespace_hygiene.py +36 -1
  79. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_ns_page.py +47 -0
  80. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_ns_refs.py +13 -4
  81. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_ns_shapes.py +2 -0
  82. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_opc_package.py +49 -0
  83. python_hwpx-6.2.1/tests/test_outline_numbering.py +120 -0
  84. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_oxml_modularization.py +45 -1
  85. python_hwpx-6.2.1/tests/test_page_number_control.py +294 -0
  86. python_hwpx-6.2.1/tests/test_para_pr_dedup.py +132 -0
  87. python_hwpx-6.2.1/tests/test_paragraph_property_version_switch.py +160 -0
  88. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_paragraph_section_management.py +29 -0
  89. python_hwpx-6.2.1/tests/test_part_hierarchy_read_models.py +250 -0
  90. python_hwpx-6.2.1/tests/test_polygon_authoring.py +354 -0
  91. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_product_boundary.py +45 -1
  92. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_redline_authoring.py +62 -0
  93. python_hwpx-6.2.1/tests/test_run_choice_atom_authoring.py +175 -0
  94. python_hwpx-6.2.1/tests/test_shape_open_safety_risk_check.py +156 -0
  95. python_hwpx-6.2.1/tests/test_tab_definition_version_switch.py +189 -0
  96. python_hwpx-6.2.1/tests/test_table_cell_equalize.py +92 -0
  97. python_hwpx-6.2.1/tests/test_table_label.py +150 -0
  98. python_hwpx-6.2.1/tests/test_table_split_merge.py +258 -0
  99. python_hwpx-6.2.1/tests/test_text_extractor_run_choice_atoms.py +160 -0
  100. python_hwpx-6.2.1/tests/test_title_mark.py +133 -0
  101. python_hwpx-6.0.3/src/hwpx/_document/ns/parts.py +0 -68
  102. python_hwpx-6.0.3/src/hwpx/data/contract_docs/support-matrix.md +0 -93
  103. python_hwpx-6.0.3/src/hwpx/oxml/_document_primitives.py +0 -696
  104. python_hwpx-6.0.3/src/hwpx/oxml/objects.py +0 -596
  105. python_hwpx-6.0.3/src/hwpx/oxml/simple_parts.py +0 -69
  106. python_hwpx-6.0.3/tests/test_coverage_ledger.py +0 -414
  107. python_hwpx-6.0.3/tests/test_deviations_registry.py +0 -12
  108. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/LICENSE +0 -0
  109. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/NOTICE +0 -0
  110. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/README.md +0 -0
  111. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/setup.cfg +0 -0
  112. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/__init__.py +0 -0
  113. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/_document/__init__.py +0 -0
  114. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/_document/_resolve.py +0 -0
  115. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/_document/_units.py +0 -0
  116. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/_document/fields.py +0 -0
  117. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/_document/headings.py +0 -0
  118. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/_document/media.py +0 -0
  119. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/_document/memos.py +0 -0
  120. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/_document/ns/_base.py +0 -0
  121. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/_document/ns/fields.py +0 -0
  122. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/_document/ns/media.py +0 -0
  123. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/_document/ns/notes.py +0 -0
  124. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/_document/ns/refs.py +0 -0
  125. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/_document/ns/tables.py +0 -0
  126. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/_document/ns/tracking.py +0 -0
  127. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/_document/persistence.py +0 -0
  128. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/_document/tracked.py +0 -0
  129. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/body_patch.py +0 -0
  130. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/data/Skeleton.hwpx +0 -0
  131. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/data/contract_docs/known-traps.md +0 -0
  132. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/data/contract_docs/mutation-semantics.md +0 -0
  133. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/data/contract_docs/recipes-traversal.md +0 -0
  134. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/document.py +0 -0
  135. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/equation/__init__.py +0 -0
  136. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/equation/authoring.py +0 -0
  137. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/equation/eqedit.py +0 -0
  138. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/equation/mathml.py +0 -0
  139. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/equation/render.py +0 -0
  140. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/equation/tokens.py +0 -0
  141. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/experimental.py +0 -0
  142. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/form_fit/__init__.py +0 -0
  143. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/form_fit/apply.py +0 -0
  144. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/form_fit/engine.py +0 -0
  145. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/form_fit/measure.py +0 -0
  146. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/form_fit/policy.py +0 -0
  147. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/form_fit/report.py +0 -0
  148. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/ingest/__init__.py +0 -0
  149. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/ingest/base.py +0 -0
  150. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/ingest/hwpx_converter.py +0 -0
  151. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/layout/__init__.py +0 -0
  152. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/layout/lint.py +0 -0
  153. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/layout/report.py +0 -0
  154. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/model.py +0 -0
  155. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/mutation_report.py +0 -0
  156. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/objects/binary_item.py +0 -0
  157. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/objects/checkbox.py +0 -0
  158. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/objects/form_field.py +0 -0
  159. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/objects/results.py +0 -0
  160. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/objects/tracked.py +0 -0
  161. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/opc/security.py +0 -0
  162. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/opc/xml_utils.py +0 -0
  163. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/oxml/_document_impl.py +0 -0
  164. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/oxml/canonical_defaults.py +0 -0
  165. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/oxml/color.py +0 -0
  166. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/oxml/common.py +0 -0
  167. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/oxml/document.py +0 -0
  168. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/oxml/memo.py +0 -0
  169. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/oxml/numbering.py +0 -0
  170. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/oxml/parser.py +0 -0
  171. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/oxml/schema.py +0 -0
  172. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/oxml/section.py +0 -0
  173. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/oxml/section_story.py +0 -0
  174. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/oxml/utils.py +0 -0
  175. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/package.py +0 -0
  176. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/patch.py +0 -0
  177. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/plan/__init__.py +0 -0
  178. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/plan/_execute.py +0 -0
  179. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/plan/_model.py +0 -0
  180. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/plan/_schema.py +0 -0
  181. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/plan/_validate.py +0 -0
  182. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/py.typed +0 -0
  183. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/quality/__init__.py +0 -0
  184. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/quality/ledger.py +0 -0
  185. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/quality/policy.py +0 -0
  186. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/quality/rendering.py +0 -0
  187. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/quality/report.py +0 -0
  188. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/quality/save_pipeline.py +0 -0
  189. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/templates.py +0 -0
  190. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/__init__.py +0 -0
  191. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/_schemas/header.xsd +0 -0
  192. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/_schemas/section.xsd +0 -0
  193. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/archive_cli.py +0 -0
  194. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/doc_diff.py +0 -0
  195. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/document_viewer.py +0 -0
  196. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/exporter.py +0 -0
  197. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/generic_inventory.py +0 -0
  198. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/idempotence.py +0 -0
  199. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/ir_equality.py +0 -0
  200. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/layout_preview.py +0 -0
  201. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/object_finder.py +0 -0
  202. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/package_reconcile.py +0 -0
  203. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/page_guard.py +0 -0
  204. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/read_fidelity.py +0 -0
  205. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/recover.py +0 -0
  206. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/redline.py +0 -0
  207. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/repair.py +0 -0
  208. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/report_utils.py +0 -0
  209. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/roundtrip_diff.py +0 -0
  210. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/table_cleanup.py +0 -0
  211. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/table_navigation.py +0 -0
  212. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/template_analyzer.py +0 -0
  213. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/text_extract_cli.py +0 -0
  214. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/toc_author.py +0 -0
  215. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/toc_fidelity.py +0 -0
  216. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/hwpx/tools/validator.py +0 -0
  217. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/python_hwpx.egg-info/dependency_links.txt +0 -0
  218. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/python_hwpx.egg-info/entry_points.txt +0 -0
  219. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/python_hwpx.egg-info/requires.txt +0 -0
  220. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/src/python_hwpx.egg-info/top_level.txt +0 -0
  221. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_add_control.py +0 -0
  222. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_add_heading.py +0 -0
  223. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_adoption_metrics_snapshot.py +0 -0
  224. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_authoring_quality_corpus.py +0 -0
  225. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_blind_eval_fixture.py +0 -0
  226. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_body_patch.py +0 -0
  227. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_byte_patch_identity.py +0 -0
  228. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_capabilities_surface.py +0 -0
  229. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_cell_line_spacing.py +0 -0
  230. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_chart_authoring.py +0 -0
  231. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_check_box_authoring.py +0 -0
  232. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_comment_node_robustness.py +0 -0
  233. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_contract_docs_sync.py +0 -0
  234. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_corpus_read_fidelity.py +0 -0
  235. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_coverage_targets.py +0 -0
  236. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_doc_diff.py +0 -0
  237. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_document_context_manager.py +0 -0
  238. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_document_facade_surface.py +0 -0
  239. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_document_save_api.py +0 -0
  240. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_document_viewer.py +0 -0
  241. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_documentation_code_fences.py +0 -0
  242. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_edit_plan.py +0 -0
  243. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_engine_surface_6_0.py +0 -0
  244. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_equation_authoring.py +0 -0
  245. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_equation_converter.py +0 -0
  246. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_equation_render.py +0 -0
  247. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_error_contract.py +0 -0
  248. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_error_messages.py +0 -0
  249. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_evalplan_core_primitives.py +0 -0
  250. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_existing_document_format_editing.py +0 -0
  251. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_form_field_authoring.py +0 -0
  252. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_form_fields.py +0 -0
  253. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_form_fill_core_primitives.py +0 -0
  254. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_form_fit.py +0 -0
  255. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_form_fit_integration.py +0 -0
  256. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_fuzz_catalog_derive_expected.py +0 -0
  257. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_fuzz_loop.py +0 -0
  258. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_fuzz_regressions.py +0 -0
  259. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_gap_closure_tools.py +0 -0
  260. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_golden_api.py +0 -0
  261. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_hp_tab_support.py +0 -0
  262. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_hwpxlib_corpus_read.py +0 -0
  263. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_id_generator_range.py +0 -0
  264. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_image_object_workflow.py +0 -0
  265. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_ingest.py +0 -0
  266. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_inline_models.py +0 -0
  267. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_integration_hwpx_compatibility.py +0 -0
  268. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_integration_roundtrip.py +0 -0
  269. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_layout_cache_scope.py +0 -0
  270. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_layout_lint.py +0 -0
  271. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_layout_preview.py +0 -0
  272. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_library_boundary_closure.py +0 -0
  273. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_llms_txt.py +0 -0
  274. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_mail_merge_xlsx.py +0 -0
  275. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_markdown_export.py +0 -0
  276. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_memo_and_style_editing.py +0 -0
  277. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_moved_surface_hints.py +0 -0
  278. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_mutation_report.py +0 -0
  279. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_namespace_handling.py +0 -0
  280. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_new_features.py +0 -0
  281. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_note_authoring_contract.py +0 -0
  282. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_ns_fields.py +0 -0
  283. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_ns_media.py +0 -0
  284. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_ns_notes.py +0 -0
  285. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_ns_tables.py +0 -0
  286. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_ns_tracking.py +0 -0
  287. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_open_safety_corpus.py +0 -0
  288. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_oxml_parsing.py +0 -0
  289. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_packaging_license_metadata.py +0 -0
  290. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_packaging_py_typed.py +0 -0
  291. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_paragraph_keep_together.py +0 -0
  292. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_public_artifact_hygiene.py +0 -0
  293. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_public_runtime_boundary.py +0 -0
  294. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_read_fidelity.py +0 -0
  295. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_recover_broken_zip.py +0 -0
  296. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_redline_verify.py +0 -0
  297. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_release_workflow_safety.py +0 -0
  298. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_repair_repack.py +0 -0
  299. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_report_utils.py +0 -0
  300. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_repr_snapshots.py +0 -0
  301. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_return_contract.py +0 -0
  302. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_roundtrip_fidelity.py +0 -0
  303. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_row_heights.py +0 -0
  304. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_save_pipeline.py +0 -0
  305. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_section_headers.py +0 -0
  306. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_section_properties_real_roundtrip.py +0 -0
  307. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_set_paragraph_format_keep.py +0 -0
  308. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_shape_geometry_contract.py +0 -0
  309. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_skeleton_template_ids.py +0 -0
  310. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_split_cell.py +0 -0
  311. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_split_merged_cell.py +0 -0
  312. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_stable_surface.py +0 -0
  313. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_style_resolution.py +0 -0
  314. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_table_cleanup.py +0 -0
  315. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_table_navigation.py +0 -0
  316. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_table_patch.py +0 -0
  317. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_table_patch_dryrun.py +0 -0
  318. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_table_patch_m105.py +0 -0
  319. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_tables_default_border.py +0 -0
  320. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_template_analyzer_enrichment.py +0 -0
  321. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_text_extractor_annotations.py +0 -0
  322. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_toc_author.py +0 -0
  323. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_toc_fidelity.py +0 -0
  324. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_validation_severity.py +0 -0
  325. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_validator_comment_nodes.py +0 -0
  326. {python_hwpx-6.0.3 → python_hwpx-6.2.1}/tests/test_version_metadata.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-hwpx
3
- Version: 6.0.3
3
+ Version: 6.2.1
4
4
  Summary: 한글 없이 HWPX 문서를 열고, 편집하고, 생성하고, 검증하는 Python 문서 라이브러리
5
5
  Author: python-hwpx Maintainers
6
6
  License-Expression: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-hwpx"
7
- version = "6.0.3"
7
+ version = "6.2.1"
8
8
  description = "한글 없이 HWPX 문서를 열고, 편집하고, 생성하고, 검증하는 Python 문서 라이브러리"
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  license = "Apache-2.0"
@@ -153,6 +153,15 @@ files = [
153
153
  "src/hwpx/errors.py",
154
154
  "src/hwpx/mutation_report.py",
155
155
  "src/hwpx/capabilities.py",
156
+ # 6.14 트레인㊾-1 — pre-existing 49 errors 정리 후 편입(6.12 이전부터의
157
+ # 기존 부채, f7e4e67 리뷰가 지목). 대부분 _si()가 돌려주는 int|None을
158
+ # 산술/비교에 바로 쓰던 자리(스키마상 필수 hp:tc 속성이라 실제로는
159
+ # None일 수 없음 -- assert로 좁힘) + 한 함수 안에서 변수 이름을
160
+ # re.Match와 int 양쪽으로 재사용하던 자리(이름만 갈라 해소, 동작
161
+ # 불변) + 람다 기본인자가 오버로드 추론을 깨던 자리(named 중첩
162
+ # 함수로 치환). 동작 변경 없음(assert 실패는 이전에도 TypeError로
163
+ # 이미 죽던 자리를 더 명확한 메시지로 바꾼 것뿐).
164
+ "src/hwpx/table_patch.py",
156
165
  "src/hwpx/plan/__init__.py",
157
166
  "src/hwpx/plan/_model.py",
158
167
  "src/hwpx/plan/_validate.py",
@@ -187,8 +196,10 @@ files = [
187
196
  "src/hwpx/objects/form_field.py",
188
197
  "src/hwpx/objects/results.py",
189
198
  "src/hwpx/objects/tracked.py",
199
+ "src/hwpx/objects/highlight.py",
190
200
  "src/hwpx/_document/_resolve.py",
191
201
  "src/hwpx/_document/headings.py",
202
+ "src/hwpx/_document/highlight.py",
192
203
  "src/hwpx/_document/ns",
193
204
  "src/hwpx/_document/fields.py",
194
205
  "src/hwpx/_document/layout.py",
@@ -225,7 +236,79 @@ files = [
225
236
  "src/hwpx/tools/text_extractor.py",
226
237
  "tests/template_automation/helpers.py",
227
238
  "tests/template_automation/generate_fixtures.py",
239
+ # 6.4: part-hierarchy read models (gap #15) — born in the gate, per the
240
+ # WP-A rule above.
241
+ "src/hwpx/oxml/version_part.py",
242
+ "src/hwpx/oxml/master_page.py",
243
+ "src/hwpx/oxml/history_part.py",
244
+ # 6.6 (train 23): document options/compatibility authoring — born in the
245
+ # gate, per the WP-A rule above.
246
+ "src/hwpx/oxml/header_compat.py",
247
+ # 6.9 (train 33): document insertion/merge — born in the gate, per the
248
+ # WP-A rule above.
249
+ "src/hwpx/tools/document_merge.py",
250
+ # 6.9 (train 34): dutmal/compose authoring — born in the gate, per the
251
+ # WP-A rule above.
252
+ "src/hwpx/oxml/dutmal_compose.py",
253
+ # 6.11 (train 44): add_section() layout-carrier lookup, extracted from
254
+ # document_parts.py (1600-line cap) — born in the gate, per the WP-A
255
+ # rule above.
256
+ "src/hwpx/oxml/section_layout.py",
257
+ # 6.12 (train 48): opf:metadata (content.hpf document properties) read
258
+ # model — born in the gate, per the WP-A rule above.
259
+ "src/hwpx/oxml/document_metadata.py",
260
+ # 6.12 (train 48): drop cap (hp:rect dropcapstyle) shape-authoring
261
+ # overflow module, extracted from paragraph.py (1600-line cap) — born
262
+ # in the gate, per the WP-A rule above.
263
+ "src/hwpx/oxml/drop_cap.py",
264
+ # 6.13 (train 48): ensure_numbering's full body (bullet/number/outline
265
+ # kinds), moved out of header_part.py (1600-line cap, no headroom left
266
+ # once the new "outline" kind was added) — born in the gate, per the
267
+ # WP-A rule above.
268
+ "src/hwpx/oxml/numbering_kinds.py",
269
+ # 6.13 (train 48): master page (바탕쪽) part creation + manifest
270
+ # registration — new module from the start (document_parts.py had
271
+ # modest headroom but this needed more than fit safely), per the WP-A
272
+ # rule above.
273
+ "src/hwpx/oxml/master_page_authoring.py",
274
+ # 6.13 (train 48): date field (hp:fieldBegin type=DATE) + proofreading
275
+ # mark field (type=PROOFREADING_MARKS_SIGN) authoring — new module
276
+ # from the start; paragraph.py hit exactly 1600 lines with just the
277
+ # two delegator methods (zero headroom left, resolved by the
278
+ # extraction below), per the WP-A rule above.
279
+ "src/hwpx/oxml/field_marks.py",
280
+ # 6.14 (train 48/debt3): footnote/endnote (각주/미주) authoring moved
281
+ # out of paragraph.py verbatim (no behavior change) to restore real
282
+ # headroom -- same escape valve as dutmal_compose.py/objects.py/
283
+ # field_marks.py above, per the WP-A rule.
284
+ "src/hwpx/oxml/note_authoring.py",
228
285
  ]
286
+ # 칩 후보 1 ✅ 6.14 트레인㊾-1에서 해소: `src/hwpx/table_patch.py`(2073줄,
287
+ # 6.12 트레인㊸ 갭⑤ 정리 시 team-lead가 지목, f7e4e67 리뷰) — 위 files
288
+ # 리스트에 이미 편입돼 있다(pre-existing 49 errors 정리, 이 섹션 첫
289
+ # 항목 주석 참조). 이 블록 자체는 이력으로 남긴다.
290
+ #
291
+ # 다음 사이클 칩 후보 2(6.13 트레인㊼, titleMark 재프로브 부산물로
292
+ # team-lead가 지목): `HwpxDocument.add_heading`(document.py) →
293
+ # `_headings.bind_outline_level` → `_document/layout.py`의
294
+ # `set_paragraph_format(outline_level=)`이 호출될 때마다 **동일한
295
+ # paraPr이 이미 있어도 새 id로 다시 만든다**(기존 요소 재사용/dedup
296
+ # 조회 없음) — 실한컴은 저장 시점에 중복 paraPr을 자기가 병합한다
297
+ # (titleMark 재프로브에서 paraPrIDRef가 21→20으로 "되돌아간" 현상의
298
+ # 원인, 버그 아님·관측된 실동작으로 문서화됨). 우리 쪽에서 paraPr
299
+ # 재사용 lookup을 추가하면 왕복 시 이 되돌아감이 사라지고 저장 파일도
300
+ # 더 작아진다 — fix 안 함, 스코프 밖.
301
+ #
302
+ # 다음 사이클 칩 후보 3(6.13 트레인㊼): `src/hwpx/oxml/paragraph.py`가
303
+ # `add_date_field`/`add_proofreading_mark` 두 delegator를 추가하며
304
+ # 정확히 1600줄(WP-A owner-file 캡)을 채웠다 — 헤드룸 0. 다음에 이
305
+ # 파일에 뭔가 더 추가해야 하면(신규 필드류 저작 등) 곧바로 게이트가
306
+ # 막는다 — 실제 추출/재구조화 패스가 필요하다(어디를 오버플로 모듈로
307
+ # 뺄지는 이번 트레인에서 정하지 않음, 다음 트레인의 몫).
308
+ #
309
+ # 위 세 칩(table_patch 게이트 미편입·add_heading paraPr 비-dedup·
310
+ # paragraph.py 헤드룸 0)은 다음 사이클 초입 부채상환 트레인 후보로
311
+ # 함께 묶어 처리할 것 — team-lead 지시(6.13 트레인㊼ 정리 커밋).
229
312
  ignore_missing_imports = true
230
313
 
231
314
  [tool.pyright]
@@ -236,6 +319,15 @@ include = [
236
319
  "src/hwpx/errors.py",
237
320
  "src/hwpx/mutation_report.py",
238
321
  "src/hwpx/capabilities.py",
322
+ # 6.14 트레인㊾-1 — pre-existing 49 errors 정리 후 편입(6.12 이전부터의
323
+ # 기존 부채, f7e4e67 리뷰가 지목). 대부분 _si()가 돌려주는 int|None을
324
+ # 산술/비교에 바로 쓰던 자리(스키마상 필수 hp:tc 속성이라 실제로는
325
+ # None일 수 없음 -- assert로 좁힘) + 한 함수 안에서 변수 이름을
326
+ # re.Match와 int 양쪽으로 재사용하던 자리(이름만 갈라 해소, 동작
327
+ # 불변) + 람다 기본인자가 오버로드 추론을 깨던 자리(named 중첩
328
+ # 함수로 치환). 동작 변경 없음(assert 실패는 이전에도 TypeError로
329
+ # 이미 죽던 자리를 더 명확한 메시지로 바꾼 것뿐).
330
+ "src/hwpx/table_patch.py",
239
331
  "src/hwpx/plan/__init__.py",
240
332
  "src/hwpx/plan/_model.py",
241
333
  "src/hwpx/plan/_validate.py",
@@ -270,8 +362,10 @@ include = [
270
362
  "src/hwpx/objects/form_field.py",
271
363
  "src/hwpx/objects/results.py",
272
364
  "src/hwpx/objects/tracked.py",
365
+ "src/hwpx/objects/highlight.py",
273
366
  "src/hwpx/_document/_resolve.py",
274
367
  "src/hwpx/_document/headings.py",
368
+ "src/hwpx/_document/highlight.py",
275
369
  "src/hwpx/_document/ns",
276
370
  "src/hwpx/_document/fields.py",
277
371
  "src/hwpx/_document/layout.py",
@@ -308,6 +402,52 @@ include = [
308
402
  "src/hwpx/tools/text_extractor.py",
309
403
  "tests/template_automation/helpers.py",
310
404
  "tests/template_automation/generate_fixtures.py",
405
+ # 6.4: part-hierarchy read models (gap #15) — born in the gate, per the
406
+ # WP-A rule above.
407
+ "src/hwpx/oxml/version_part.py",
408
+ "src/hwpx/oxml/master_page.py",
409
+ "src/hwpx/oxml/history_part.py",
410
+ # 6.6 (train 23): document options/compatibility authoring — born in the
411
+ # gate, per the WP-A rule above.
412
+ "src/hwpx/oxml/header_compat.py",
413
+ # 6.9 (train 33): document insertion/merge — born in the gate, per the
414
+ # WP-A rule above.
415
+ "src/hwpx/tools/document_merge.py",
416
+ # 6.9 (train 34): dutmal/compose authoring — born in the gate, per the
417
+ # WP-A rule above.
418
+ "src/hwpx/oxml/dutmal_compose.py",
419
+ # 6.11 (train 44): add_section() layout-carrier lookup, extracted from
420
+ # document_parts.py (1600-line cap) — born in the gate, per the WP-A
421
+ # rule above.
422
+ "src/hwpx/oxml/section_layout.py",
423
+ # 6.12 (train 48): opf:metadata (content.hpf document properties) read
424
+ # model — born in the gate, per the WP-A rule above.
425
+ "src/hwpx/oxml/document_metadata.py",
426
+ # 6.12 (train 48): drop cap (hp:rect dropcapstyle) shape-authoring
427
+ # overflow module, extracted from paragraph.py (1600-line cap) — born
428
+ # in the gate, per the WP-A rule above.
429
+ "src/hwpx/oxml/drop_cap.py",
430
+ # 6.13 (train 48): ensure_numbering's full body (bullet/number/outline
431
+ # kinds), moved out of header_part.py (1600-line cap, no headroom left
432
+ # once the new "outline" kind was added) — born in the gate, per the
433
+ # WP-A rule above.
434
+ "src/hwpx/oxml/numbering_kinds.py",
435
+ # 6.13 (train 48): master page (바탕쪽) part creation + manifest
436
+ # registration — new module from the start (document_parts.py had
437
+ # modest headroom but this needed more than fit safely), per the WP-A
438
+ # rule above.
439
+ "src/hwpx/oxml/master_page_authoring.py",
440
+ # 6.13 (train 48): date field (hp:fieldBegin type=DATE) + proofreading
441
+ # mark field (type=PROOFREADING_MARKS_SIGN) authoring — new module
442
+ # from the start; paragraph.py hit exactly 1600 lines with just the
443
+ # two delegator methods (zero headroom left, resolved by the
444
+ # extraction below), per the WP-A rule above.
445
+ "src/hwpx/oxml/field_marks.py",
446
+ # 6.14 (train 48/debt3): footnote/endnote (각주/미주) authoring moved
447
+ # out of paragraph.py verbatim (no behavior change) to restore real
448
+ # headroom -- same escape valve as dutmal_compose.py/objects.py/
449
+ # field_marks.py above, per the WP-A rule.
450
+ "src/hwpx/oxml/note_authoring.py",
311
451
  ]
312
452
  pythonVersion = "3.10"
313
453
  typeCheckingMode = "basic"
@@ -69,6 +69,7 @@ from . import tracked as _tracked
69
69
 
70
70
  if TYPE_CHECKING:
71
71
  from ..form_fit.policy import FitPolicy
72
+ from ..objects.binary_item import BinaryItem
72
73
  from ..oxml import (
73
74
  HwpxOxmlDocument,
74
75
  HwpxOxmlHeader,
@@ -862,6 +863,8 @@ class _LegacyFacade:
862
863
  border_color: str = "#BFBFBF",
863
864
  border_width: str = "0.12 mm",
864
865
  fill_color: str | None = None,
866
+ fill_image: "str | BinaryItem | Mapping[str, object] | None" = None,
867
+ fill_gradient: Mapping[str, object] | None = None,
865
868
  active_borders: Sequence[str] | None = None,
866
869
  border_type: str = "SOLID",
867
870
  ) -> str:
@@ -869,13 +872,25 @@ class _LegacyFacade:
869
872
 
870
873
  ``border_type`` selects the OWPML line style (``SOLID``, ``DASH``,
871
874
  ``DOT``, ``DOUBLE_SLIM``, ``WAVE``, …); values outside the OWPML
872
- vocabulary are rejected.
875
+ vocabulary are rejected. ``fill_color``/``fill_image``/``fill_gradient``
876
+ are mutually exclusive (OWPML ``hc:fillBrush`` choice).
873
877
  """
874
878
 
879
+ from .ns.styles import _resolve_fill_gradient, _resolve_fill_image
880
+
881
+ given = sum(1 for v in (fill_color, fill_image, fill_gradient) if v is not None)
882
+ if given > 1:
883
+ raise HwpxValueError(
884
+ "fill_color/fill_image/fill_gradient are mutually exclusive",
885
+ code="style-border-fill-conflict",
886
+ suggestion="Pass exactly one of fill_color, fill_image, fill_gradient.",
887
+ )
875
888
  return self._root.ensure_border_fill(
876
889
  border_color=border_color,
877
890
  border_width=border_width,
878
891
  fill_color=fill_color,
892
+ fill_image=_resolve_fill_image(fill_image),
893
+ fill_gradient=_resolve_fill_gradient(fill_gradient),
879
894
  active_borders=active_borders,
880
895
  border_type=border_type,
881
896
  )
@@ -0,0 +1,164 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Highlight (``hp:markpenBegin``/``markpenEnd``) authoring and reading owner
3
+ behind the :class:`HwpxDocument` facade.
4
+
5
+ Real-corpus reversal (``hwpxlib_corpus/error__20251107__test*.hwpx``, the only
6
+ fixtures carrying this pair) plus the OWPML schema (``ParaList XML
7
+ schema.xml``) agree on the shape: both marks live inside a single ``hp:t``,
8
+ ``markpenBegin`` carries an optional ``color``, ``markpenEnd`` carries no
9
+ attributes at all — pairing is positional (innermost open begin closes first),
10
+ not by id. :mod:`hwpx.tools.text_extractor` already reads this shape with a
11
+ per-``hp:t`` stack; :func:`list_highlights` exposes the same reading as a
12
+ public model instead of inline text markers.
13
+
14
+ Authoring is scoped to one run's text the same way ``doc.tracking.delete``
15
+ scopes tracked deletes: a match that only exists once inline markup pieces
16
+ are concatenated crosses a boundary this module refuses to split.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import re
22
+ from typing import TYPE_CHECKING
23
+
24
+ from ..errors import HwpxValueError
25
+ from ..objects.highlight import Highlight
26
+
27
+ if TYPE_CHECKING:
28
+ from hwpx.document import HwpxDocument
29
+ from ..oxml import HwpxOxmlParagraph
30
+ from ..oxml.body import TextSpan
31
+
32
+ #: OWPML ``hc:RGBColorType`` — ``#`` followed by exactly six hex digits.
33
+ _COLOR_RE = re.compile(r"#[0-9A-Fa-f]{6}")
34
+
35
+ #: Matches the real-corpus fixtures' brightest observed value; any
36
+ #: schema-valid hex is otherwise accepted.
37
+ DEFAULT_COLOR = "#FFFF00"
38
+
39
+
40
+ def _validate_color(color: str) -> str:
41
+ if not isinstance(color, str) or not _COLOR_RE.fullmatch(color):
42
+ raise HwpxValueError(
43
+ f"highlight color must be a 6-digit hex value like '#FFFF00', got {color!r}",
44
+ code="text-highlight-color-invalid",
45
+ context={"color": color},
46
+ suggestion="Pass a colour matching #RRGGBB (hex digits, either case).",
47
+ )
48
+ return color
49
+
50
+
51
+ def _paragraph_has_highlightable_text(paragraph: "HwpxOxmlParagraph", match: str) -> bool:
52
+ """Return whether *match* lives inside one contiguous inline text piece.
53
+
54
+ Mirrors ``hwpx._document.tracked._paragraph_has_replaceable_text``: a
55
+ match that is only present after concatenating text across inline
56
+ markup (an existing highlight, a tracked-change mark, …) is real but
57
+ cannot be wrapped safely, so that case raises here instead of falling
58
+ through to a generic "not found".
59
+ """
60
+
61
+ crosses_inline_markup = False
62
+ for run in paragraph.runs:
63
+ model = run.to_model()
64
+ for span in model.text_spans:
65
+ if match not in span.text:
66
+ continue
67
+ if match in span.leading_text or any(
68
+ match in markup.trailing_text for markup in span.marks
69
+ ):
70
+ return True
71
+ crosses_inline_markup = True
72
+
73
+ if crosses_inline_markup:
74
+ raise HwpxValueError(
75
+ "match text crosses inline markup and cannot be wrapped safely",
76
+ code="text-highlight-match-crosses-markup",
77
+ context={"match": match},
78
+ suggestion="Target a substring that lives inside a single run.",
79
+ )
80
+ return False
81
+
82
+
83
+ def add_highlight(
84
+ doc: "HwpxDocument",
85
+ paragraph: "HwpxOxmlParagraph",
86
+ match: str,
87
+ *,
88
+ color: str = DEFAULT_COLOR,
89
+ ) -> Highlight:
90
+ """Wrap the first occurrence of *match* in *paragraph* in markpen marks."""
91
+
92
+ if not match:
93
+ raise HwpxValueError(
94
+ "match text must be a non-empty string",
95
+ code="text-highlight-match-empty",
96
+ suggestion="Pass the substring to highlight.",
97
+ )
98
+ validated_color = _validate_color(color)
99
+
100
+ if not _paragraph_has_highlightable_text(paragraph, match):
101
+ raise HwpxValueError(
102
+ "match text was not found in the paragraph",
103
+ code="text-highlight-match-not-found",
104
+ context={"match": match, "paragraphText": paragraph.text},
105
+ suggestion="Inspect paragraph.text for the actual string.",
106
+ )
107
+
108
+ paragraph.add_highlight(color=validated_color, match=match)
109
+ return Highlight(text=match, color=validated_color, paragraph=paragraph)
110
+
111
+
112
+ def _span_highlights(span: "TextSpan") -> list[tuple[str, "str | None"]]:
113
+ """Yield (text, color) for every markpen pair closed within *span*.
114
+
115
+ A LIFO stack, same as ``text_extractor._render_text_element``'s
116
+ ``highlight_stack`` — nested ``markpenBegin`` opens a new frame, the next
117
+ ``markpenEnd`` closes the innermost one. A begin left open at the end of
118
+ the span (no matching end inside this ``hp:t``) is still reported, same
119
+ as that reader closing it defensively at ``hp:t`` end; an end with
120
+ nothing open is silently dropped rather than fabricated.
121
+ """
122
+
123
+ results: list[tuple[str, "str | None"]] = []
124
+ stack: list[tuple["str | None", list[str]]] = []
125
+
126
+ for markup in span.marks:
127
+ element = markup.element
128
+ name = getattr(element, "name", None)
129
+ if name == "markpenBegin":
130
+ attributes = getattr(element, "attributes", None) or {}
131
+ stack.append((attributes.get("color"), []))
132
+ if markup.trailing_text:
133
+ stack[-1][1].append(markup.trailing_text)
134
+ continue
135
+ if name == "markpenEnd":
136
+ if stack:
137
+ mark_color, buffer = stack.pop()
138
+ results.append(("".join(buffer), mark_color))
139
+ if stack and markup.trailing_text:
140
+ stack[-1][1].append(markup.trailing_text)
141
+ continue
142
+ if stack and markup.trailing_text:
143
+ stack[-1][1].append(markup.trailing_text)
144
+
145
+ while stack:
146
+ mark_color, buffer = stack.pop()
147
+ results.append(("".join(buffer), mark_color))
148
+ return results
149
+
150
+
151
+ def list_highlights(doc: "HwpxDocument") -> tuple[Highlight, ...]:
152
+ """Return every markpen highlight in the document, in document order."""
153
+
154
+ results: list[Highlight] = []
155
+ for paragraph in doc.paragraphs:
156
+ for run in paragraph.runs:
157
+ model = run.to_model()
158
+ for span in model.text_spans:
159
+ for text, color in _span_highlights(span):
160
+ results.append(Highlight(text=text, color=color, paragraph=paragraph))
161
+ return tuple(results)
162
+
163
+
164
+ __all__ = ["DEFAULT_COLOR", "add_highlight", "list_highlights"]
@@ -15,6 +15,7 @@ from ..objects.results import (
15
15
  ParagraphFormatResult,
16
16
  Units,
17
17
  )
18
+ from ..oxml._document_primitives import NEW_NUM_KINDS
18
19
  from ..oxml.namespaces import HH
19
20
  from ._units import _mm_to_hwp_units, _pt_to_hwp_units
20
21
 
@@ -119,16 +120,31 @@ def set_paragraph_format(
119
120
  keep_with_next: bool | None = None,
120
121
  keep_lines: bool | None = None,
121
122
  page_break_before: bool | None = None,
123
+ column_break: bool | None = None,
122
124
  bottom_border: bool = False,
123
125
  border_color: str = "#BFBFBF",
124
126
  border_width: str = "0.12 mm",
127
+ tab_stops: Sequence[Mapping[str, Any]] | None = None,
128
+ auto_tab_left: bool | None = None,
129
+ auto_tab_right: bool | None = None,
125
130
  ) -> ParagraphFormatResult:
126
131
  """Apply paragraph-level formatting using human units.
127
132
 
128
133
  Millimetre inputs are converted to HWP units; paragraph spacing uses
129
134
  points; line spacing is stored as a percent value. ``keep_with_next`` /
130
135
  ``keep_lines`` / ``page_break_before`` set the paragraph's keep-together
131
- (``<hh:breakSetting>``) flags via a freshly minted paraPr.
136
+ (``<hh:breakSetting>``) flags via a freshly minted paraPr. ``column_break``
137
+ is a different mechanism -- ``hp:p``'s own ``columnBreak`` attribute, a
138
+ per-paragraph-instance forced break (not a shared paraPr style property
139
+ like ``page_break_before``) -- applied directly to each target paragraph.
140
+
141
+ ``tab_stops`` is a sequence of ``{"pos_mm": ..., "type": "LEFT"|"RIGHT"|
142
+ "CENTER"|"DECIMAL", "leader": "NONE"|...}`` mappings (``type``/``leader``
143
+ default to the real-corpus-majority ``"LEFT"``/``"NONE"``) — order is
144
+ meaningful, matching how real multi-stop documents list them
145
+ position-ascending. Passing ``tab_stops``/``auto_tab_left``/
146
+ ``auto_tab_right`` mints (or reuses — dedupe) a ``hh:tabPr`` and wires
147
+ the paragraph's ``tabPrIDRef`` to it.
132
148
  """
133
149
 
134
150
  if not doc._root.headers:
@@ -181,6 +197,10 @@ def set_paragraph_format(
181
197
  if page_break_before is not None:
182
198
  break_setting["page_break_before"] = bool(page_break_before)
183
199
 
200
+ wants_tab_definition = (
201
+ tab_stops is not None or auto_tab_left is not None or auto_tab_right is not None
202
+ )
203
+
184
204
  if (
185
205
  alignment is None
186
206
  and line_spacing_percent is None
@@ -188,6 +208,8 @@ def set_paragraph_format(
188
208
  and heading is None
189
209
  and not bottom_border
190
210
  and not break_setting
211
+ and not wants_tab_definition
212
+ and column_break is None
191
213
  ):
192
214
  raise HwpxValueError(
193
215
  "at least one paragraph formatting option is required",
@@ -195,6 +217,28 @@ def set_paragraph_format(
195
217
  suggestion="Pass alignment, line_spacing_percent, or another option to change.",
196
218
  )
197
219
 
220
+ tab_pr_id: str | None = None
221
+ if wants_tab_definition:
222
+ converted_stops: list[dict[str, object]] = []
223
+ for index, stop in enumerate(tab_stops or ()):
224
+ if "pos_mm" not in stop or stop["pos_mm"] is None:
225
+ raise HwpxValueError(
226
+ f"tab_stops[{index}] is missing 'pos_mm'",
227
+ code="paragraph-tab-pos-invalid",
228
+ context={"index": index},
229
+ suggestion="각 tab stop은 'pos_mm'(mm, 0 이상)가 필요합니다.",
230
+ )
231
+ converted_stops.append({
232
+ "pos": _mm_to_hwp_units(float(stop["pos_mm"])),
233
+ "type": stop.get("type"),
234
+ "leader": stop.get("leader"),
235
+ })
236
+ tab_pr_id = header.ensure_tab_definition(
237
+ tab_stops=converted_stops,
238
+ auto_tab_left=bool(auto_tab_left),
239
+ auto_tab_right=bool(auto_tab_right),
240
+ )
241
+
198
242
  border: dict[str, str] | None = None
199
243
  if bottom_border:
200
244
  border_fill_id = header.ensure_border_fill(
@@ -212,22 +256,40 @@ def set_paragraph_format(
212
256
  "ignoreMargin": "0",
213
257
  }
214
258
 
259
+ # column_break bypasses paraPr entirely (it's hp:p's own attribute, not
260
+ # a shared style) -- only mint a new paraPr when one of the *other*
261
+ # options actually needs it, so a column_break-only call doesn't churn
262
+ # a needless duplicate paraPr id.
263
+ wants_para_pr_change = (
264
+ alignment is not None
265
+ or line_spacing_percent is not None
266
+ or bool(margins)
267
+ or heading is not None
268
+ or bottom_border
269
+ or bool(break_setting)
270
+ or wants_tab_definition
271
+ )
272
+
215
273
  targets = _resolve_paragraph_targets(doc,
216
274
  paragraph_index=paragraph_index,
217
275
  paragraph_indexes=paragraph_indexes,
218
276
  )
219
277
  formatted: list[int] = []
220
278
  for index, paragraph in targets:
221
- para_pr_id = header.ensure_paragraph_format(
222
- base_para_pr_id=paragraph.para_pr_id_ref,
223
- alignment=alignment,
224
- line_spacing_percent=line_spacing_percent,
225
- margins=margins,
226
- heading=heading,
227
- border=border,
228
- break_setting=break_setting or None,
229
- )
230
- paragraph.para_pr_id_ref = para_pr_id
279
+ if wants_para_pr_change:
280
+ para_pr_id = header.ensure_paragraph_format(
281
+ base_para_pr_id=paragraph.para_pr_id_ref,
282
+ alignment=alignment,
283
+ line_spacing_percent=line_spacing_percent,
284
+ margins=margins,
285
+ heading=heading,
286
+ border=border,
287
+ break_setting=break_setting or None,
288
+ tab_pr_id_ref=tab_pr_id,
289
+ )
290
+ paragraph.para_pr_id_ref = para_pr_id
291
+ if column_break is not None:
292
+ paragraph.column_break = column_break
231
293
  formatted.append(index)
232
294
 
233
295
  return ParagraphFormatResult(
@@ -767,6 +829,61 @@ def set_page_number(
767
829
  )
768
830
 
769
831
 
832
+ def restart_page_number(
833
+ doc: "HwpxDocument",
834
+ paragraph: "HwpxOxmlParagraph",
835
+ *,
836
+ number: int = 1,
837
+ kind: str = "PAGE",
838
+ ) -> "HwpxOxmlInlineObject":
839
+ """Restart *kind*'s running count at *number* from *paragraph* onward.
840
+
841
+ Inserts ``<hp:ctrl><hp:newNum num="{number}" numType="{kind}"/></hp:ctrl>``
842
+ — a section-mid restart point, distinct from ``set_page_number`` (which
843
+ places the *display* field in a header/footer). ``kind`` defaults to
844
+ ``"PAGE"``, the only value real corpus (hwpxlib_corpus) observes; the
845
+ other six (``FOOTNOTE``/``ENDNOTE``/``PICTURE``/``TABLE``/``EQUATION``/
846
+ ``TOTAL_PAGE``) are schema-legal but unattested.
847
+ """
848
+
849
+ normalized_kind = str(kind or "PAGE").upper()
850
+ if normalized_kind not in NEW_NUM_KINDS:
851
+ raise HwpxValueError(
852
+ f"unsupported kind {kind!r}",
853
+ code="page-new-num-kind-invalid",
854
+ context={"kind": normalized_kind, "allowed": sorted(NEW_NUM_KINDS)},
855
+ suggestion="Use one of: " + ", ".join(sorted(NEW_NUM_KINDS)),
856
+ )
857
+ return paragraph.add_new_num(number=number, kind=normalized_kind)
858
+
859
+
860
+ def hide_page_elements(
861
+ doc: "HwpxDocument",
862
+ paragraph: "HwpxOxmlParagraph",
863
+ *,
864
+ header: bool = False,
865
+ footer: bool = False,
866
+ master_page: bool = False,
867
+ border: bool = False,
868
+ fill: bool = False,
869
+ page_num: bool = False,
870
+ ) -> "HwpxOxmlInlineObject":
871
+ """Hide the named page elements from *paragraph*'s page onward.
872
+
873
+ Inserts ``<hp:ctrl><hp:pageHiding .../></hp:ctrl>`` (``ParaList XML
874
+ schema.xml:148-163`` — six independent booleans, all default unhidden).
875
+ """
876
+
877
+ return paragraph.add_page_hiding(
878
+ header=header,
879
+ footer=footer,
880
+ master_page=master_page,
881
+ border=border,
882
+ fill=fill,
883
+ page_num=page_num,
884
+ )
885
+
886
+
770
887
  def remove_header(
771
888
  doc: "HwpxDocument",
772
889
  *,
@@ -9,7 +9,7 @@
9
9
 
10
10
  | 능력 영역 | 네임스페이스 |
11
11
  |---|---|
12
- | `shape-authoring`·`shape-escape-hatch`·`curve-objects`·`chart`·`equation` | `doc.shapes` |
12
+ | `shape-authoring`·`shape-escape-hatch`·`curve-objects`·`container-authoring`·`chart`·`equation` | `doc.shapes` |
13
13
  | `picture`(BinData 절반) | `doc.media` |
14
14
  | `redline` | `doc.tracking` |
15
15
  | `memo`·`footnote-endnote` | `doc.notes` |