python-hwpx 6.6.0__tar.gz → 6.7.0__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 (452) hide show
  1. {python_hwpx-6.6.0/src/python_hwpx.egg-info → python_hwpx-6.7.0}/PKG-INFO +7 -22
  2. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/README.md +6 -21
  3. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/pyproject.toml +7 -1
  4. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/__init__.py +11 -0
  5. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/_legacy.py +2 -2
  6. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/fields.py +288 -24
  7. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/layout.py +64 -8
  8. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/media.py +38 -45
  9. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/memos.py +32 -12
  10. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/ns/fields.py +39 -3
  11. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/ns/media.py +16 -2
  12. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/ns/notes.py +2 -2
  13. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/ns/parts.py +31 -0
  14. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/ns/shapes.py +96 -9
  15. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/ns/styles.py +23 -5
  16. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/persistence.py +8 -67
  17. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/shapes.py +96 -7
  18. python_hwpx-6.7.0/src/hwpx/_moved_modules.py +118 -0
  19. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/data/contract_docs/known-traps.md +25 -1
  20. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/data/contract_docs/mutation-semantics.md +7 -4
  21. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/data/contract_docs/support-matrix.md +5 -5
  22. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/document.py +42 -5
  23. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/equation/authoring.py +25 -18
  24. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/equation/eqedit.py +61 -4
  25. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/equation/measure.py +89 -38
  26. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/equation/tokens.py +14 -1
  27. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/errors.py +13 -1
  28. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/experimental.py +8 -0
  29. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/form_fit/apply.py +7 -1
  30. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/form_fit/engine.py +15 -66
  31. python_hwpx-6.7.0/src/hwpx/form_fit/measure.py +1546 -0
  32. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/form_fit/policy.py +7 -1
  33. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/layout/lint.py +228 -27
  34. python_hwpx-6.7.0/src/hwpx/layout/pages.py +2245 -0
  35. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/objects/__init__.py +2 -1
  36. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/objects/form_field.py +51 -38
  37. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/opc/relationships.py +72 -1
  38. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/_document_primitives.py +17 -22
  39. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/_paragraph_text_edit.py +27 -0
  40. python_hwpx-6.7.0/src/hwpx/oxml/curves.py +231 -0
  41. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/document_parts.py +6 -3
  42. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/drop_cap.py +22 -17
  43. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/dutmal_compose.py +19 -10
  44. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/field_marks.py +38 -7
  45. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/header.py +39 -33
  46. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/header_fonts.py +37 -6
  47. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/header_part.py +20 -24
  48. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/master_page_authoring.py +67 -5
  49. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/memo.py +181 -3
  50. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/note_authoring.py +1 -1
  51. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/numbering_kinds.py +35 -1
  52. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/objects.py +44 -27
  53. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/paragraph.py +20 -13
  54. python_hwpx-6.7.0/src/hwpx/oxml/paragraph_heading.py +70 -0
  55. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/section.py +23 -5
  56. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/section_format.py +25 -6
  57. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/section_story.py +13 -1
  58. python_hwpx-6.7.0/src/hwpx/oxml/shape_position.py +282 -0
  59. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/table.py +36 -28
  60. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/table_sizes.py +97 -11
  61. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/quality/save_pipeline.py +4 -1
  62. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/table_patch.py +32 -8
  63. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/document_merge.py +4 -1
  64. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/exporter.py +136 -17
  65. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/id_integrity.py +22 -35
  66. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/layout_preview.py +5 -2
  67. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/package_validator.py +332 -22
  68. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/repair.py +38 -2
  69. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/template_analyzer.py +9 -6
  70. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/toc_author.py +8 -6
  71. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/validator.py +53 -0
  72. {python_hwpx-6.6.0 → python_hwpx-6.7.0/src/python_hwpx.egg-info}/PKG-INFO +7 -22
  73. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/python_hwpx.egg-info/SOURCES.txt +21 -0
  74. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_add_control.py +17 -5
  75. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_add_heading.py +74 -8
  76. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_cell_fields.py +47 -0
  77. python_hwpx-6.7.0/tests/test_cell_margins.py +311 -0
  78. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_cell_text_rewrite_semantics.py +10 -11
  79. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_container_authoring.py +60 -29
  80. python_hwpx-6.7.0/tests/test_curve_connector_authoring.py +160 -0
  81. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_document_formatting.py +56 -8
  82. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_document_merge.py +4 -1
  83. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_documentation_code_fences.py +2 -2
  84. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_drop_cap_authoring.py +34 -5
  85. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_dutmal_compose_authoring.py +34 -5
  86. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_engine_surface_6_0.py +14 -0
  87. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_equation_authoring.py +42 -2
  88. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_equation_converter.py +52 -0
  89. python_hwpx-6.7.0/tests/test_equation_measure_hancom_saved.py +97 -0
  90. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_equation_render.py +24 -0
  91. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_equation_size.py +12 -2
  92. python_hwpx-6.7.0/tests/test_export_notes.py +202 -0
  93. python_hwpx-6.7.0/tests/test_field_type_ids.py +120 -0
  94. python_hwpx-6.7.0/tests/test_form_fields.py +372 -0
  95. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_form_fit.py +201 -18
  96. python_hwpx-6.7.0/tests/test_form_fit_hancom_rules.py +721 -0
  97. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_gap_closure_tools.py +36 -16
  98. python_hwpx-6.7.0/tests/test_header_control_before_field.py +78 -0
  99. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_hwp5_save.py +23 -5
  100. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_image_object_workflow.py +64 -3
  101. python_hwpx-6.7.0/tests/test_instid_attribute_case.py +126 -0
  102. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_integration_hwpx_compatibility.py +1 -1
  103. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_layout_cache_scope.py +48 -0
  104. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_layout_lint.py +190 -2
  105. python_hwpx-6.7.0/tests/test_layout_page_estimate.py +758 -0
  106. python_hwpx-6.7.0/tests/test_list_continue.py +112 -0
  107. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_master_page_authoring.py +11 -0
  108. python_hwpx-6.7.0/tests/test_master_page_pages_taken.py +181 -0
  109. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_memo_and_style_editing.py +65 -0
  110. python_hwpx-6.7.0/tests/test_memo_fields.py +93 -0
  111. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_merge_cells_keeps_contents.py +4 -3
  112. python_hwpx-6.7.0/tests/test_moved_module_imports.py +105 -0
  113. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_native_edit_contract.py +6 -6
  114. python_hwpx-6.7.0/tests/test_ns_shapes.py +198 -0
  115. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_opc_package.py +7 -7
  116. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_oxml_modularization.py +4 -3
  117. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_package_hygiene.py +93 -2
  118. python_hwpx-6.7.0/tests/test_paragraph_property_lookup.py +51 -0
  119. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_product_boundary.py +8 -1
  120. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_recover_broken_zip.py +1 -1
  121. python_hwpx-6.7.0/tests/test_remove_unused_charts.py +104 -0
  122. python_hwpx-6.7.0/tests/test_remove_unused_images.py +84 -0
  123. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_repair_repack.py +4 -4
  124. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_save_pipeline.py +16 -0
  125. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_shape_open_safety_risk_check.py +41 -33
  126. python_hwpx-6.7.0/tests/test_shape_original_size.py +167 -0
  127. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_stable_surface.py +3 -2
  128. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_style_font_border_reads.py +78 -2
  129. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_style_resolution.py +53 -0
  130. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_tab_definition_version_switch.py +67 -26
  131. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_table_patch.py +1 -1
  132. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_template_analyzer_enrichment.py +1 -0
  133. python_hwpx-6.7.0/tests/test_text_box_fields.py +87 -0
  134. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_toc_author.py +9 -1
  135. python_hwpx-6.7.0/tests/test_validator_form_parameters.py +96 -0
  136. python_hwpx-6.7.0/tests/test_validator_hancom_open_rules.py +313 -0
  137. python_hwpx-6.7.0/tests/test_validator_section_ids.py +141 -0
  138. python_hwpx-6.6.0/src/hwpx/form_fit/measure.py +0 -854
  139. python_hwpx-6.6.0/src/hwpx/oxml/shape_position.py +0 -128
  140. python_hwpx-6.6.0/tests/test_cell_margins.py +0 -168
  141. python_hwpx-6.6.0/tests/test_form_fields.py +0 -101
  142. python_hwpx-6.6.0/tests/test_form_fit_hancom_rules.py +0 -191
  143. python_hwpx-6.6.0/tests/test_instid_attribute_case.py +0 -100
  144. python_hwpx-6.6.0/tests/test_master_page_pages_taken.py +0 -83
  145. python_hwpx-6.6.0/tests/test_ns_shapes.py +0 -79
  146. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/LICENSE +0 -0
  147. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/NOTICE +0 -0
  148. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/setup.cfg +0 -0
  149. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/__init__.py +0 -0
  150. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/_resolve.py +0 -0
  151. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/_units.py +0 -0
  152. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/headings.py +0 -0
  153. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/highlight.py +0 -0
  154. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/ns/__init__.py +0 -0
  155. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/ns/_base.py +0 -0
  156. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/ns/page.py +0 -0
  157. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/ns/refs.py +0 -0
  158. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/ns/tables.py +0 -0
  159. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/ns/text.py +0 -0
  160. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/ns/tracking.py +0 -0
  161. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/_document/tracked.py +0 -0
  162. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/body_patch.py +0 -0
  163. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/capabilities.py +0 -0
  164. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/data/Skeleton.hwpx +0 -0
  165. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/data/contract_docs/recipes-traversal.md +0 -0
  166. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/equation/__init__.py +0 -0
  167. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/equation/mathml.py +0 -0
  168. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/equation/render.py +0 -0
  169. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/form_fit/__init__.py +0 -0
  170. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/form_fit/report.py +0 -0
  171. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/__init__.py +0 -0
  172. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/binary.py +0 -0
  173. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/bodytext.py +0 -0
  174. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/cfb.py +0 -0
  175. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/controls.py +0 -0
  176. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/docinfo.py +0 -0
  177. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/docinfo_writer.py +0 -0
  178. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/errors.py +0 -0
  179. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/fileheader.py +0 -0
  180. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/header_xml.py +0 -0
  181. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/owpml.py +0 -0
  182. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/package.py +0 -0
  183. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/reader.py +0 -0
  184. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/records.py +0 -0
  185. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/section_common.py +0 -0
  186. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/section_writer.py +0 -0
  187. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/section_xml.py +0 -0
  188. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/shape_xml.py +0 -0
  189. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/shapes.py +0 -0
  190. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/summary.py +0 -0
  191. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/hwp5/writer.py +0 -0
  192. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/ingest/__init__.py +0 -0
  193. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/ingest/base.py +0 -0
  194. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/ingest/hwpx_converter.py +0 -0
  195. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/layout/__init__.py +0 -0
  196. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/layout/report.py +0 -0
  197. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/model.py +0 -0
  198. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/mutation_report.py +0 -0
  199. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/objects/binary_item.py +0 -0
  200. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/objects/checkbox.py +0 -0
  201. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/objects/highlight.py +0 -0
  202. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/objects/results.py +0 -0
  203. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/objects/tracked.py +0 -0
  204. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/opc/package.py +0 -0
  205. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/opc/security.py +0 -0
  206. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/opc/xml_utils.py +0 -0
  207. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/__init__.py +0 -0
  208. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/_document_impl.py +0 -0
  209. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/body.py +0 -0
  210. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/canonical_defaults.py +0 -0
  211. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/color.py +0 -0
  212. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/common.py +0 -0
  213. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/document.py +0 -0
  214. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/document_metadata.py +0 -0
  215. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/header_compat.py +0 -0
  216. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/history_part.py +0 -0
  217. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/hyperlink_form.py +0 -0
  218. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/master_page.py +0 -0
  219. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/namespaces.py +0 -0
  220. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/numbering.py +0 -0
  221. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/parser.py +0 -0
  222. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/run.py +0 -0
  223. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/schema.py +0 -0
  224. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/section_layout.py +0 -0
  225. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/settings.py +0 -0
  226. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/simple_parts.py +0 -0
  227. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/table_merge.py +0 -0
  228. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/utils.py +0 -0
  229. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/oxml/version_part.py +0 -0
  230. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/package.py +0 -0
  231. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/patch.py +0 -0
  232. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/plan/__init__.py +0 -0
  233. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/plan/_execute.py +0 -0
  234. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/plan/_model.py +0 -0
  235. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/plan/_schema.py +0 -0
  236. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/plan/_validate.py +0 -0
  237. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/py.typed +0 -0
  238. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/quality/__init__.py +0 -0
  239. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/quality/ledger.py +0 -0
  240. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/quality/policy.py +0 -0
  241. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/quality/rendering.py +0 -0
  242. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/quality/report.py +0 -0
  243. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/templates.py +0 -0
  244. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/__init__.py +0 -0
  245. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/_schemas/header.xsd +0 -0
  246. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/_schemas/owpml-body.xsd +0 -0
  247. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/_schemas/owpml-core.xsd +0 -0
  248. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/_schemas/owpml-header.xsd +0 -0
  249. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/_schemas/owpml-paralist.xsd +0 -0
  250. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/_schemas/owpml-xml.xsd +0 -0
  251. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/_schemas/section.xsd +0 -0
  252. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/archive_cli.py +0 -0
  253. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/doc_diff.py +0 -0
  254. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/document_viewer.py +0 -0
  255. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/generic_inventory.py +0 -0
  256. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/idempotence.py +0 -0
  257. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/ir_equality.py +0 -0
  258. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/mail_merge.py +0 -0
  259. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/markdown_export.py +0 -0
  260. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/object_finder.py +0 -0
  261. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/package_reconcile.py +0 -0
  262. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/page_guard.py +0 -0
  263. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/read_fidelity.py +0 -0
  264. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/recover.py +0 -0
  265. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/redline.py +0 -0
  266. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/report_utils.py +0 -0
  267. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/roundtrip_diff.py +0 -0
  268. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/table_cleanup.py +0 -0
  269. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/table_navigation.py +0 -0
  270. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/text_extract_cli.py +0 -0
  271. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/text_extractor.py +0 -0
  272. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/hwpx/tools/toc_fidelity.py +0 -0
  273. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/python_hwpx.egg-info/dependency_links.txt +0 -0
  274. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/python_hwpx.egg-info/entry_points.txt +0 -0
  275. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/python_hwpx.egg-info/requires.txt +0 -0
  276. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/src/python_hwpx.egg-info/top_level.txt +0 -0
  277. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_adoption_metrics_snapshot.py +0 -0
  278. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_arc_authoring.py +0 -0
  279. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_authoring_defaults.py +0 -0
  280. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_authoring_quality_corpus.py +0 -0
  281. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_blind_eval_fixture.py +0 -0
  282. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_body_patch.py +0 -0
  283. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_border_fill_image_gradient.py +0 -0
  284. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_border_line_types.py +0 -0
  285. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_build_element_census.py +0 -0
  286. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_byte_patch_identity.py +0 -0
  287. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_capabilities_surface.py +0 -0
  288. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_cell_border_neighbors.py +0 -0
  289. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_cell_line_spacing.py +0 -0
  290. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_char_pr_child_order.py +0 -0
  291. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_character_width_ratio.py +0 -0
  292. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_chart_authoring.py +0 -0
  293. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_check_box_authoring.py +0 -0
  294. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_color_arguments.py +0 -0
  295. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_column_break.py +0 -0
  296. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_comment_node_robustness.py +0 -0
  297. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_contract_docs_sync.py +0 -0
  298. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_corpus_read_fidelity.py +0 -0
  299. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_coverage_ledger.py +0 -0
  300. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_coverage_promotion.py +0 -0
  301. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_coverage_targets.py +0 -0
  302. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_deviations_registry.py +0 -0
  303. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_doc_diff.py +0 -0
  304. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_document_context_manager.py +0 -0
  305. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_document_facade_surface.py +0 -0
  306. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_document_metadata.py +0 -0
  307. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_document_ops_runtime_boundary.py +0 -0
  308. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_document_save_api.py +0 -0
  309. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_document_viewer.py +0 -0
  310. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_edit_fidelity_corpus.py +0 -0
  311. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_edit_plan.py +0 -0
  312. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_editor_surface_inventory.py +0 -0
  313. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_equalize_widths_per_row.py +0 -0
  314. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_error_contract.py +0 -0
  315. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_error_messages.py +0 -0
  316. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_evalplan_core_primitives.py +0 -0
  317. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_existing_document_format_editing.py +0 -0
  318. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_export_captions_dutmal.py +0 -0
  319. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_export_cell_text_spaces.py +0 -0
  320. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_export_list_labels.py +0 -0
  321. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_export_text_boxes.py +0 -0
  322. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_exporter_table_text.py +0 -0
  323. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_field_marks.py +0 -0
  324. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_fill_image_hancom_modes.py +0 -0
  325. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_form_field_authoring.py +0 -0
  326. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_form_fill_core_primitives.py +0 -0
  327. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_form_fit_integration.py +0 -0
  328. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_fuzz_catalog_derive_expected.py +0 -0
  329. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_fuzz_loop.py +0 -0
  330. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_fuzz_regressions.py +0 -0
  331. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_golden_api.py +0 -0
  332. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_header_compat_authoring.py +0 -0
  333. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_heading_style_shapes.py +0 -0
  334. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_highlight_authoring.py +0 -0
  335. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_hp_tab_support.py +0 -0
  336. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_hwp5_container.py +0 -0
  337. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_hwp5_hancom_files.py +0 -0
  338. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_hwp5_open.py +0 -0
  339. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_hwpxlib_corpus_read.py +0 -0
  340. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_hyperlink_hancom_form.py +0 -0
  341. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_id_generator_range.py +0 -0
  342. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_id_integrity.py +0 -0
  343. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_index_mark.py +0 -0
  344. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_ingest.py +0 -0
  345. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_inline_models.py +0 -0
  346. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_insert_row_merged_cells.py +0 -0
  347. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_integration_roundtrip.py +0 -0
  348. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_internal_calls_skip_moved_names.py +0 -0
  349. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_layout_preview.py +0 -0
  350. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_library_boundary_closure.py +0 -0
  351. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_line_width_arguments.py +0 -0
  352. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_list_defaults_vs_hancom.py +0 -0
  353. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_llms_txt.py +0 -0
  354. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_mail_merge_xlsx.py +0 -0
  355. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_manifest_resolve_once.py +0 -0
  356. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_markdown_export.py +0 -0
  357. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_markdown_export_shape_text.py +0 -0
  358. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_memo_shape_authoring.py +0 -0
  359. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_merge_cells_hancom_files.py +0 -0
  360. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_moved_surface_hints.py +0 -0
  361. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_mutation_report.py +0 -0
  362. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_namespace_handling.py +0 -0
  363. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_namespace_hygiene.py +0 -0
  364. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_namespace_normalization.py +0 -0
  365. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_new_features.py +0 -0
  366. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_note_authoring_contract.py +0 -0
  367. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_ns_fields.py +0 -0
  368. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_ns_media.py +0 -0
  369. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_ns_notes.py +0 -0
  370. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_ns_page.py +0 -0
  371. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_ns_refs.py +0 -0
  372. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_ns_tables.py +0 -0
  373. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_ns_tracking.py +0 -0
  374. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_number_formats.py +0 -0
  375. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_open_safety_corpus.py +0 -0
  376. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_outline_numbering.py +0 -0
  377. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_oxml_parsing.py +0 -0
  378. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_packaging_license_metadata.py +0 -0
  379. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_packaging_py_typed.py +0 -0
  380. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_page_number_control.py +0 -0
  381. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_page_number_format.py +0 -0
  382. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_page_orientation.py +0 -0
  383. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_para_pr_dedup.py +0 -0
  384. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_paragraph_align_distribute_space.py +0 -0
  385. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_paragraph_border.py +0 -0
  386. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_paragraph_format_targets.py +0 -0
  387. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_paragraph_hyperlink_style.py +0 -0
  388. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_paragraph_keep_together.py +0 -0
  389. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_paragraph_margin_default_scale.py +0 -0
  390. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_paragraph_property_version_switch.py +0 -0
  391. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_paragraph_remove_in_containers.py +0 -0
  392. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_paragraph_section_management.py +0 -0
  393. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_part_hierarchy_read_models.py +0 -0
  394. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_polygon_authoring.py +0 -0
  395. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_public_artifact_hygiene.py +0 -0
  396. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_public_runtime_boundary.py +0 -0
  397. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_read_fidelity.py +0 -0
  398. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_redline_authoring.py +0 -0
  399. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_redline_verify.py +0 -0
  400. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_release_workflow_safety.py +0 -0
  401. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_replace_text_barriers.py +0 -0
  402. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_report_utils.py +0 -0
  403. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_repr_snapshots.py +0 -0
  404. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_return_contract.py +0 -0
  405. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_roundtrip_fidelity.py +0 -0
  406. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_roundtrip_fidelity_corpus.py +0 -0
  407. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_row_heights.py +0 -0
  408. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_run_choice_atom_authoring.py +0 -0
  409. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_run_content_kinds.py +0 -0
  410. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_run_font_refs.py +0 -0
  411. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_run_style_base.py +0 -0
  412. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_safe_edit_example.py +0 -0
  413. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_save_keeps_inline_text_caches.py +0 -0
  414. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_save_verifies_once.py +0 -0
  415. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_section_clear_body.py +0 -0
  416. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_section_columns.py +0 -0
  417. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_section_headers.py +0 -0
  418. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_section_ids_order.py +0 -0
  419. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_section_properties_real_roundtrip.py +0 -0
  420. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_set_column_widths_uniform_split.py +0 -0
  421. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_set_paragraph_format_keep.py +0 -0
  422. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_shape_geometry_contract.py +0 -0
  423. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_shape_position_frames.py +0 -0
  424. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_skeleton_template_ids.py +0 -0
  425. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_split_cell.py +0 -0
  426. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_split_merged_cell.py +0 -0
  427. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_stale_layout_cache_sweep.py +0 -0
  428. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_story_edits_reach_control_copy.py +0 -0
  429. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_tab_character_writing.py +0 -0
  430. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_table_cell_equalize.py +0 -0
  431. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_table_cleanup.py +0 -0
  432. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_table_label.py +0 -0
  433. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_table_navigation.py +0 -0
  434. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_table_page_flow.py +0 -0
  435. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_table_patch_dryrun.py +0 -0
  436. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_table_patch_m105.py +0 -0
  437. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_table_patch_structure_fixes.py +0 -0
  438. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_table_split_merge.py +0 -0
  439. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_tables_default_border.py +0 -0
  440. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_text_extractor_annotations.py +0 -0
  441. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_text_extractor_nested_once.py +0 -0
  442. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_text_extractor_run_choice_atoms.py +0 -0
  443. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_text_inline_elements.py +0 -0
  444. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_text_replace_everywhere.py +0 -0
  445. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_title_mark.py +0 -0
  446. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_toc_fidelity.py +0 -0
  447. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_tracked_overlap_render_smoke.py +0 -0
  448. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_tracked_paragraph_delete_break.py +0 -0
  449. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_validation_severity.py +0 -0
  450. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_validator_comment_nodes.py +0 -0
  451. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/tests/test_validator_full_schema.py +0 -0
  452. {python_hwpx-6.6.0 → python_hwpx-6.7.0}/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.6.0
3
+ Version: 6.7.0
4
4
  Summary: 한글 없이 HWPX 문서를 열고, 편집하고, 생성하고, 검증하는 Python 문서 라이브러리
5
5
  Author: python-hwpx Maintainers
6
6
  License-Expression: Apache-2.0
@@ -68,7 +68,7 @@ Dynamic: license-file
68
68
 
69
69
  한컴오피스가 없어도 됩니다. HWPX는 ZIP+XML(OWPML) 포맷이라 순수 파이썬만으로
70
70
  읽고, 고치고, 새로 만들 수 있습니다 — Windows·macOS·Linux·CI, 그리고
71
- **파이썬이 도는 ChatGPT 채팅 안에서도** 그대로 동작합니다. 기존 문서의 저장은 요청한 보존 등급과 실제 검증 결과를 영수증으로 남깁니다.
71
+ **파이썬이 도는 ChatGPT 채팅 안에서도** 그대로 동작합니다.
72
72
  새 문서의 한컴 호환성은 아래에 날짜·버전을 명시한 코퍼스로 측정합니다.
73
73
 
74
74
  <p align="center">
@@ -109,7 +109,7 @@ from hwpx import HwpxDocument
109
109
 
110
110
  doc = HwpxDocument.new()
111
111
  doc.add_heading("2026 운영계획", level=1)
112
- doc.add_paragraph("가. 추진 배경", style="개요 2")
112
+ doc.add_paragraph("추진 배경", style="개요 2") # 한/글이 "가." 번호를 붙인다
113
113
  doc.save_to_path("계획.hwpx")
114
114
  ```
115
115
 
@@ -130,7 +130,7 @@ doc.save_to_path("계획.hwpx")
130
130
  - **편집** — 문단·표·이미지·머리글/바닥글·메모·각주, 줄간격·여백·쪽번호 같은 서식
131
131
  - **양식 채우기** — 라벨로 셀을 찾아 값만 채우기, 행·열 조정 같은 구조 편집도 바이트 보존으로
132
132
  - **생성·일괄 처리** — 새 문서 저작, 목차·상호참조, mail merge, 텍스트 diff, 변경추적(redline)
133
- - **검증·안전** — 패키지 구조 검증 CLI, 열림 안전 게이트, 모든 저장에 영수증(`MutationReport`)
133
+ - **검증·안전** — 패키지 구조 검증 CLI, 열림 안전 게이트, 저장 결과 보고(`return_report=True`)
134
134
 
135
135
  자세한 내용: [5분 퀵스타트](docs/quickstart.md) · [사용 가이드](docs/usage.md) · [API 레퍼런스](https://airmang.github.io/python-hwpx/) · [예제](docs/examples.md)
136
136
 
@@ -152,20 +152,6 @@ report = doc.save_to_path("신청서-작성완료.hwpx", mode="patch", fallback=
152
152
  바이트 보존을 요구합니다. 수정 파트 내부의 보존과 시각적 배치는 별도 확인이
153
153
  필요합니다. 출력 재개봉과 내용 확인까지 포함한 경로는 [안전한 쓰기 계약](docs/safe-write-contract.md)을 따르세요.
154
154
 
155
- ### 저장에는 영수증이 따라옵니다
156
-
157
- ```python
158
- report = doc.save_to_path("결과.hwpx", return_report=True)
159
- print(report.actual_mode) # "patch" 또는 "rebuild" — 실제 저장 등급
160
- print(report.preservation.untouched_part_payloads.to_dict())
161
- # {"verified": 17, "changed": 0}
162
- ```
163
-
164
- `mode="patch", fallback="error"`는 보존 등급 미달 시 출력하지 않습니다.
165
- 기본 `auto`는 달성 가능한 등급을 선택합니다. `report.ok`는 요청 반영이나 시각
166
- 검증 완료를 뜻하지 않습니다.
167
- 전체 규칙: [안전한 쓰기 계약](docs/safe-write-contract.md).
168
-
169
155
  *예제 중 **독립 실행 예제** 표시가 없는 블록은 여러분의 기존 문서를 입력으로
170
156
  쓰는 조각입니다. 예제별 Python 블록 판정은
171
157
  [실행 ledger](docs/python-example-ledger.json)에 동결돼 있습니다.*
@@ -175,10 +161,9 @@ print(report.preservation.untouched_part_payloads.to_dict())
175
161
  만든 파일이 실제 한컴오피스에서 열리는지 재서 그대로 공개합니다. 측정마다
176
162
  스택과 날짜를 병기합니다:
177
163
 
178
- - **한컴 열림 120/120 · 렌더 검증 120/120** — 현행 스택(5.7.0 · 실한컴
179
- 12.0.0.3288 · 2026-08-03)의 축소 측정: 기준 스트라텀 + 신규 저작 표면
180
- 6종(각주·누름틀·수식·차트·체크박스·편집 계획). 영수증은 행마다 bucket이
181
- 있어 `jq` 한 줄로 재현됩니다
164
+ - **한컴 열림 120/120 · 렌더 검증 120/120** — 기준 문서군 + 저작 기능
165
+ 6종(각주·누름틀·수식·차트·체크박스·편집 계획) 축소 측정
166
+ (5.7.0 · 실한컴 12.0.0.3288 · 2026-08-03)
182
167
  - **한컴 열림 476/476** — 전수 동결 코퍼스(N=497) 측정
183
168
  (3.4.1 · 실한컴 12.0.0.3288 · 2026-07-19)
184
169
  - **미수정 영역 바이트 보존 497/497** · 개인정보 유출 0
@@ -17,7 +17,7 @@
17
17
 
18
18
  한컴오피스가 없어도 됩니다. HWPX는 ZIP+XML(OWPML) 포맷이라 순수 파이썬만으로
19
19
  읽고, 고치고, 새로 만들 수 있습니다 — Windows·macOS·Linux·CI, 그리고
20
- **파이썬이 도는 ChatGPT 채팅 안에서도** 그대로 동작합니다. 기존 문서의 저장은 요청한 보존 등급과 실제 검증 결과를 영수증으로 남깁니다.
20
+ **파이썬이 도는 ChatGPT 채팅 안에서도** 그대로 동작합니다.
21
21
  새 문서의 한컴 호환성은 아래에 날짜·버전을 명시한 코퍼스로 측정합니다.
22
22
 
23
23
  <p align="center">
@@ -58,7 +58,7 @@ from hwpx import HwpxDocument
58
58
 
59
59
  doc = HwpxDocument.new()
60
60
  doc.add_heading("2026 운영계획", level=1)
61
- doc.add_paragraph("가. 추진 배경", style="개요 2")
61
+ doc.add_paragraph("추진 배경", style="개요 2") # 한/글이 "가." 번호를 붙인다
62
62
  doc.save_to_path("계획.hwpx")
63
63
  ```
64
64
 
@@ -79,7 +79,7 @@ doc.save_to_path("계획.hwpx")
79
79
  - **편집** — 문단·표·이미지·머리글/바닥글·메모·각주, 줄간격·여백·쪽번호 같은 서식
80
80
  - **양식 채우기** — 라벨로 셀을 찾아 값만 채우기, 행·열 조정 같은 구조 편집도 바이트 보존으로
81
81
  - **생성·일괄 처리** — 새 문서 저작, 목차·상호참조, mail merge, 텍스트 diff, 변경추적(redline)
82
- - **검증·안전** — 패키지 구조 검증 CLI, 열림 안전 게이트, 모든 저장에 영수증(`MutationReport`)
82
+ - **검증·안전** — 패키지 구조 검증 CLI, 열림 안전 게이트, 저장 결과 보고(`return_report=True`)
83
83
 
84
84
  자세한 내용: [5분 퀵스타트](docs/quickstart.md) · [사용 가이드](docs/usage.md) · [API 레퍼런스](https://airmang.github.io/python-hwpx/) · [예제](docs/examples.md)
85
85
 
@@ -101,20 +101,6 @@ report = doc.save_to_path("신청서-작성완료.hwpx", mode="patch", fallback=
101
101
  바이트 보존을 요구합니다. 수정 파트 내부의 보존과 시각적 배치는 별도 확인이
102
102
  필요합니다. 출력 재개봉과 내용 확인까지 포함한 경로는 [안전한 쓰기 계약](docs/safe-write-contract.md)을 따르세요.
103
103
 
104
- ### 저장에는 영수증이 따라옵니다
105
-
106
- ```python
107
- report = doc.save_to_path("결과.hwpx", return_report=True)
108
- print(report.actual_mode) # "patch" 또는 "rebuild" — 실제 저장 등급
109
- print(report.preservation.untouched_part_payloads.to_dict())
110
- # {"verified": 17, "changed": 0}
111
- ```
112
-
113
- `mode="patch", fallback="error"`는 보존 등급 미달 시 출력하지 않습니다.
114
- 기본 `auto`는 달성 가능한 등급을 선택합니다. `report.ok`는 요청 반영이나 시각
115
- 검증 완료를 뜻하지 않습니다.
116
- 전체 규칙: [안전한 쓰기 계약](docs/safe-write-contract.md).
117
-
118
104
  *예제 중 **독립 실행 예제** 표시가 없는 블록은 여러분의 기존 문서를 입력으로
119
105
  쓰는 조각입니다. 예제별 Python 블록 판정은
120
106
  [실행 ledger](docs/python-example-ledger.json)에 동결돼 있습니다.*
@@ -124,10 +110,9 @@ print(report.preservation.untouched_part_payloads.to_dict())
124
110
  만든 파일이 실제 한컴오피스에서 열리는지 재서 그대로 공개합니다. 측정마다
125
111
  스택과 날짜를 병기합니다:
126
112
 
127
- - **한컴 열림 120/120 · 렌더 검증 120/120** — 현행 스택(5.7.0 · 실한컴
128
- 12.0.0.3288 · 2026-08-03)의 축소 측정: 기준 스트라텀 + 신규 저작 표면
129
- 6종(각주·누름틀·수식·차트·체크박스·편집 계획). 영수증은 행마다 bucket이
130
- 있어 `jq` 한 줄로 재현됩니다
113
+ - **한컴 열림 120/120 · 렌더 검증 120/120** — 기준 문서군 + 저작 기능
114
+ 6종(각주·누름틀·수식·차트·체크박스·편집 계획) 축소 측정
115
+ (5.7.0 · 실한컴 12.0.0.3288 · 2026-08-03)
131
116
  - **한컴 열림 476/476** — 전수 동결 코퍼스(N=497) 측정
132
117
  (3.4.1 · 실한컴 12.0.0.3288 · 2026-07-19)
133
118
  - **미수정 영역 바이트 보존 497/497** · 개인정보 유출 0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-hwpx"
7
- version = "6.6.0"
7
+ version = "6.7.0"
8
8
  description = "한글 없이 HWPX 문서를 열고, 편집하고, 생성하고, 검증하는 Python 문서 라이브러리"
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  license = "Apache-2.0"
@@ -295,6 +295,9 @@ files = [
295
295
  # Table width/height distribution (distribute cell width row by row) moved out
296
296
  # of table.py -- born in the gate, per the WP-A rule.
297
297
  "src/hwpx/oxml/table_sizes.py",
298
+ # A paragraph shape's heading (outline levels 8 to 10 in Hancom's hp:switch form),
299
+ # written and read in one place -- born in the gate, per the WP-A rule.
300
+ "src/hwpx/oxml/paragraph_heading.py",
298
301
  # Floating-shape reference frames (set_position) and shape-text
299
302
  # vertical alignment -- objects.py's overflow module (1600-line cap),
300
303
  # born in the gate, per the WP-A rule.
@@ -501,6 +504,9 @@ include = [
501
504
  # Table width/height distribution (distribute cell width row by row) moved out
502
505
  # of table.py -- born in the gate, per the WP-A rule.
503
506
  "src/hwpx/oxml/table_sizes.py",
507
+ # A paragraph shape's heading (outline levels 8 to 10 in Hancom's hp:switch form),
508
+ # written and read in one place -- born in the gate, per the WP-A rule.
509
+ "src/hwpx/oxml/paragraph_heading.py",
504
510
  # Floating-shape reference frames (set_position) and shape-text
505
511
  # vertical alignment -- objects.py's overflow module (1600-line cap),
506
512
  # born in the gate, per the WP-A rule.
@@ -45,6 +45,10 @@ _EXPERIMENTAL_EXPORTS = {
45
45
  "LayoutPreview": "hwpx.tools.layout_preview",
46
46
  "PreviewPage": "hwpx.tools.layout_preview",
47
47
  "render_layout_preview": "hwpx.tools.layout_preview",
48
+ # 쪽 수 추정(한/글 없이, 줄마다 쪽·단·세로 위치). 계약 유동.
49
+ "EstimatedLine": "hwpx.layout.pages",
50
+ "PageEstimate": "hwpx.layout.pages",
51
+ "estimate_pages": "hwpx.layout.pages",
48
52
  # 문서 프리뷰 뷰어(3.8.0 신규). 계약 유동.
49
53
  "DocumentViewer": "hwpx.tools.document_viewer",
50
54
  "render_document_viewer": "hwpx.tools.document_viewer",
@@ -347,6 +351,13 @@ def __dir__() -> list[str]:
347
351
  )
348
352
 
349
353
 
354
+ # ``import hwpx.builder`` goes through the import system, not ``__getattr__``,
355
+ # so the moved module names get their destination from a finder instead.
356
+ from . import _moved_modules
357
+
358
+ _moved_modules.install(_MOVED_TO_COMPANION)
359
+
360
+
350
361
  # --- stable 최상위 표면 (eager import) ------------------------------------------
351
362
  from .tools.text_extractor import (
352
363
  DEFAULT_NAMESPACES,
@@ -615,7 +615,7 @@ class _LegacyFacade:
615
615
  field_id: str | None = None,
616
616
  author: str | None = None,
617
617
  created: datetime | str | None = None,
618
- number: int = 1,
618
+ number: int | None = None,
619
619
  anchor_char_pr_id_ref: str | int | None = None,
620
620
  ) -> tuple[HwpxOxmlMemo, HwpxOxmlParagraph, str]:
621
621
  """Create a memo and ensure it is visible by anchoring a MEMO field."""
@@ -801,7 +801,7 @@ class _LegacyFacade:
801
801
  field_id: str | None = None,
802
802
  author: str | None = None,
803
803
  created: datetime | str | None = None,
804
- number: int = 1,
804
+ number: int | None = None,
805
805
  char_pr_id_ref: str | int | None = None,
806
806
  ) -> str:
807
807
  """Attach a MEMO field control to *paragraph* so Hangul shows *memo*."""
@@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, Any, Iterator, Mapping, Sequence, cast
8
8
 
9
9
  from ..errors import HwpxStateError, HwpxValueError
10
10
  from ..objects.checkbox import CheckBox
11
- from ..objects.form_field import CellField, FieldLocation, FieldParameter, FormField
11
+ from ..objects.form_field import CellField, FieldLocation, FieldParameter, FormField, TextBoxField
12
12
  from ..objects.results import FieldFillResult
13
13
  from ..oxml import HwpxOxmlParagraph
14
14
  from ..oxml.namespaces import HP
@@ -88,6 +88,41 @@ def _sanitize_field_text(value: str) -> str:
88
88
  return _TEXT_ILLEGAL.sub("", value)
89
89
 
90
90
 
91
+ # Characters an hp:t holds as child elements; they belong to the text they sit in.
92
+ _TEXT_CHARACTER_NAMES = {"tab": "\t", "lineBreak": "\n", "hyphen": "", "nbSpace": "\u00a0", "fwSpace": "\u3000"}
93
+
94
+
95
+ def _text_node_value(node: Any) -> str:
96
+ """The text of an ``hp:t``, with ``hp:lineBreak`` as a newline and ``hp:tab`` as a tab."""
97
+
98
+ parts = [node.text or ""]
99
+ for child in node:
100
+ parts.append(_TEXT_CHARACTER_NAMES.get(_local_name(child), ""))
101
+ parts.append(child.tail or "")
102
+ return "".join(parts)
103
+
104
+
105
+ def _write_text_node(node: Any, value: str) -> None:
106
+ """Put *value* in the ``hp:t`` *node* in place of its text.
107
+
108
+ A line break (CRLF, LF or CR) becomes ``hp:lineBreak``, the element Hancom
109
+ turns such a character into when it opens the file. The characters of the
110
+ old text (tabs, line breaks) go; other marks stay, emptied.
111
+ """
112
+
113
+ for child in list(node):
114
+ if _local_name(child) in _TEXT_CHARACTER_NAMES:
115
+ node.remove(child)
116
+ else:
117
+ child.tail = ""
118
+ lines = value.replace("\r\n", "\n").replace("\r", "\n").split("\n")
119
+ node.text = lines[0]
120
+ for line in lines[1:]:
121
+ line_break = node.makeelement(f"{_HP}lineBreak", {})
122
+ node.append(line_break)
123
+ line_break.tail = line
124
+
125
+
91
126
  def _field_type_tokens(*values: str | None) -> set[str]:
92
127
  tokens: set[str] = set()
93
128
  for value in values:
@@ -125,6 +160,11 @@ def _field_identifier(field_begin: Any) -> str:
125
160
 
126
161
 
127
162
  def _field_end_matches(field_begin: Any, field_end: Any) -> bool:
163
+ # An end that names its begin (beginIDRef) is that begin's end only: a fieldid
164
+ # is shared by every field of a type (Hancom's control id), so it pairs only
165
+ # ends that name no begin.
166
+ if field_begin.get("id") and field_end.get("beginIDRef"):
167
+ return field_begin.get("id") == field_end.get("beginIDRef")
128
168
  begin_keys = {
129
169
  value
130
170
  for value in (
@@ -160,6 +200,18 @@ def _field_parameters(field_begin: Any) -> list[dict[str, str]]:
160
200
  return parameters
161
201
 
162
202
 
203
+ def _field_name(field_begin: Any, parameters: Sequence[dict[str, str]]) -> str:
204
+ """The field's name. A ``name`` attribute is the name even when empty: Hancom
205
+ treats such a field as unnamed (it lists and fills fields by name only)."""
206
+
207
+ if field_begin.get("name") is not None:
208
+ return field_begin.get("name", "").strip()
209
+ name = _first_attr(field_begin, _FORM_FIELD_NAME_ATTRS)
210
+ if not name:
211
+ name = _field_parameter_value(parameters, "fieldName", "fieldname", "field_name", "name", "title")
212
+ return name
213
+
214
+
163
215
  def _first_attr(element: Any, names: Sequence[str]) -> str:
164
216
  for name in names:
165
217
  value = (element.get(name) or "").strip()
@@ -178,6 +230,21 @@ def _field_parameter_value(parameters: Sequence[dict[str, str]], *names: str) ->
178
230
  return ""
179
231
 
180
232
 
233
+ _COMMAND_DIRECTION = re.compile(r"(?:^|[\s:])Direction:wstring:(\d+):")
234
+
235
+
236
+ def _command_direction(parameters: Sequence[dict[str, str]]) -> str:
237
+ """The prompt a field keeps only in its ``Command`` string
238
+ (``Clickhere:set:N:Direction:wstring:<length>:<prompt> HelpState:...``)."""
239
+
240
+ command = _field_parameter_value(parameters, "Command")
241
+ found = _COMMAND_DIRECTION.search(command)
242
+ if found is None:
243
+ return ""
244
+ start = found.end()
245
+ return command[start : start + int(found.group(1))]
246
+
247
+
181
248
  def _clear_form_field_layout_cache(paragraph: Any) -> int:
182
249
  removed = 0
183
250
  for child in list(paragraph):
@@ -208,6 +275,40 @@ def _find_field_end_position(
208
275
  return None
209
276
 
210
277
 
278
+ def _find_field_end_in_following_paragraphs(
279
+ paragraph_element: Any, field_begin: Any
280
+ ) -> tuple[Any, list[Any], int, int, list[Any]] | None:
281
+ """The end of a field whose content runs past its paragraph: the later
282
+ paragraph beside it that holds the fieldEnd naming this field's id, that
283
+ paragraph's runs, the end's run and child index, and the paragraphs in
284
+ between. Only an end that names the begin's id counts here."""
285
+
286
+ ids = {value for value in (field_begin.get("id"), field_begin.get("fieldid")) if value}
287
+ if not ids:
288
+ return None
289
+ middle: list[Any] = []
290
+ sibling = paragraph_element.getnext()
291
+ while sibling is not None:
292
+ if _local_name(sibling) == "p":
293
+ runs = [child for child in sibling if _local_name(child) == "run"]
294
+ for run_index, run in enumerate(runs):
295
+ for child_index, child in enumerate(run):
296
+ if _local_name(child) != "ctrl":
297
+ continue
298
+ for field_end in child.findall(f"{_HP}fieldEnd"):
299
+ if _field_end_matches(field_begin, field_end) and (
300
+ ids & {field_end.get("beginIDRef"), field_end.get("fieldid")}
301
+ ):
302
+ return sibling, runs, run_index, child_index, middle
303
+ middle.append(sibling)
304
+ sibling = sibling.getnext()
305
+ return None
306
+
307
+
308
+ def _run_texts(runs: Sequence[Any]) -> str:
309
+ return "".join(_text_node_value(node) for run in runs for node in run if _local_name(node) == "t")
310
+
311
+
211
312
  def _field_text_nodes(
212
313
  doc: "HwpxDocument",
213
314
  runs: Sequence[Any],
@@ -244,13 +345,14 @@ def _form_field_payload(
244
345
  has_end: bool,
245
346
  ) -> dict[str, Any]:
246
347
  parameters = _field_parameters(field_begin)
247
- name = _first_attr(field_begin, _FORM_FIELD_NAME_ATTRS)
248
- if not name:
249
- name = _field_parameter_value(parameters, "fieldName", "fieldname", "field_name", "name", "title")
348
+ name = _field_name(field_begin, parameters)
250
349
  prompt = _first_attr(field_begin, _FORM_FIELD_PROMPT_ATTRS)
251
350
  if not prompt:
252
351
  # "Direction" is the real-Hancom 안내문 parameter (P0 gold contract).
253
352
  prompt = _field_parameter_value(parameters, "Direction", *_FORM_FIELD_PARAM_NAMES)
353
+ if not prompt:
354
+ # Some fields keep it only inside their Command string.
355
+ prompt = _command_direction(parameters)
254
356
  instruction = _field_parameter_value(parameters, "instruction", "guide", "help", "description", "desc")
255
357
  if not instruction:
256
358
  instruction = prompt
@@ -325,17 +427,38 @@ def _iter_form_field_matches(doc: "HwpxDocument") -> list[dict[str, Any]]:
325
427
  )
326
428
  end_run_index: int | None = None
327
429
  end_child_index: int | None = None
430
+ span = None
328
431
  if end_position is not None:
329
432
  end_run_index, end_child_index, _field_end = end_position
433
+ else:
434
+ span = _find_field_end_in_following_paragraphs(paragraph_element, field_begin)
330
435
  text_nodes = _field_text_nodes(
331
436
  doc,
332
437
  runs,
333
438
  begin_run_index=run_index,
334
439
  begin_child_index=child_index,
335
- end_run_index=end_run_index,
440
+ end_run_index=end_run_index if span is None else len(runs) - 1,
336
441
  end_child_index=end_child_index,
337
442
  )
338
- current_value = "".join("".join(node.itertext()) for node in text_nodes)
443
+ current_value = "".join(_text_node_value(node) for node in text_nodes)
444
+ if span is not None:
445
+ # the content goes on over paragraphs: their texts, one per line
446
+ end_paragraph, end_runs, span_run, span_child, middle = span
447
+ tail = _field_text_nodes(
448
+ doc,
449
+ end_runs,
450
+ begin_run_index=0,
451
+ begin_child_index=-1,
452
+ end_run_index=span_run,
453
+ end_child_index=span_child,
454
+ )
455
+ current_value = "\n".join(
456
+ [
457
+ current_value,
458
+ *(_run_texts([c for c in p if _local_name(c) == "run"]) for p in middle),
459
+ "".join(_text_node_value(node) for node in tail),
460
+ ]
461
+ )
339
462
  payload = _form_field_payload(
340
463
  doc,
341
464
  index=len(matches),
@@ -347,7 +470,7 @@ def _iter_form_field_matches(doc: "HwpxDocument") -> list[dict[str, Any]]:
347
470
  ctrl=child,
348
471
  field_begin=field_begin,
349
472
  current_value=current_value,
350
- has_end=end_position is not None,
473
+ has_end=end_position is not None or span is not None,
351
474
  )
352
475
  payload["_paragraph"] = paragraph
353
476
  payload["_runs"] = runs
@@ -356,6 +479,7 @@ def _iter_form_field_matches(doc: "HwpxDocument") -> list[dict[str, Any]]:
356
479
  payload["_end_run_index"] = end_run_index
357
480
  payload["_end_child_index"] = end_child_index
358
481
  payload["_text_nodes"] = text_nodes
482
+ payload["_span"] = span
359
483
  matches.append(payload)
360
484
  paragraph_index += 1
361
485
  return matches
@@ -401,7 +525,13 @@ def _form_field_from_match(doc: "HwpxDocument", match: Mapping[str, Any]) -> For
401
525
 
402
526
 
403
527
  def list_form_fields(doc: "HwpxDocument") -> tuple[FormField, ...]:
404
- """Return native form/click-here fields in document order."""
528
+ """Return native form/click-here fields in document order.
529
+
530
+ Every field in the document is listed. An unnamed field (an empty ``name``) is
531
+ listed with an empty name and told apart by its id: :func:`fill_form_field`
532
+ does not find it by name, as Hancom does not, but ``field_index`` and
533
+ ``field_id`` reach it.
534
+ """
405
535
 
406
536
  return tuple(_form_field_from_match(doc, match) for match in _iter_form_field_matches(doc))
407
537
 
@@ -442,6 +572,80 @@ def fill_cell_fields(doc: "HwpxDocument", value: str, *, name: str, index: int |
442
572
  return tuple(fields)
443
573
 
444
574
 
575
+ def list_text_box_fields(doc: "HwpxDocument") -> tuple[TextBoxField, ...]:
576
+ """Named text boxes in document order: Hancom lists a text box whose ``hp:drawText``
577
+ has a name among its fields (and leaves an unnamed one out)."""
578
+
579
+ return tuple(
580
+ TextBoxField(draw_text, section)
581
+ for section in doc.sections
582
+ for draw_text in section.element.iter(f"{_HP}drawText")
583
+ if (draw_text.get("name") or "").strip()
584
+ )
585
+
586
+
587
+ def fill_text_box_fields(
588
+ doc: "HwpxDocument", value: str, *, name: str, index: int | None = None
589
+ ) -> tuple[TextBoxField, ...]:
590
+ """Set the text of every text box called *name*, or of the *index*-th of them only."""
591
+
592
+ wanted = (name or "").strip()
593
+ fields = [field for field in list_text_box_fields(doc) if wanted and field.name == wanted]
594
+ if index is not None:
595
+ fields = fields[index : index + 1] if index >= 0 else []
596
+ if not fields:
597
+ where = f"{wanted!r}" if index is None else f"{wanted!r} at index {index}"
598
+ raise HwpxValueError(
599
+ f"no text box named {where}",
600
+ code="field-text-box-not-found",
601
+ context={"name": wanted, "index": index},
602
+ suggestion="List doc.fields.text_boxes to see the text box names.",
603
+ )
604
+ for field in fields:
605
+ field.text = value
606
+ return tuple(fields)
607
+
608
+
609
+ def text_box_text(draw_text: Any) -> str:
610
+ """The text of a text box, a line per paragraph."""
611
+
612
+ return "\n".join(
613
+ "".join(_text_node_value(node) for node in paragraph.iter(f"{_HP}t"))
614
+ for paragraph in draw_text.findall(f"{_HP}subList/{_HP}p")
615
+ )
616
+
617
+
618
+ def set_text_box_text(draw_text: Any, section: Any, value: str) -> None:
619
+ """Put *value* in a text box as Hancom fills a text box field: the box keeps its
620
+ first paragraph and that paragraph's first run, which hold the value alone."""
621
+
622
+ sub_list = draw_text.find(f"{_HP}subList")
623
+ if sub_list is None:
624
+ sub_list = draw_text.makeelement(f"{_HP}subList", {})
625
+ draw_text.insert(0, sub_list)
626
+ paragraphs = sub_list.findall(f"{_HP}p")
627
+ first = paragraphs[0] if paragraphs else sub_list.makeelement(
628
+ f"{_HP}p", {"id": "0", "paraPrIDRef": "0", "styleIDRef": "0", "pageBreak": "0", "columnBreak": "0", "merged": "0"}
629
+ )
630
+ if not paragraphs:
631
+ sub_list.append(first)
632
+ for extra in paragraphs[1:]:
633
+ sub_list.remove(extra)
634
+ runs = first.findall(f"{_HP}run")
635
+ run = runs[0] if runs else first.makeelement(f"{_HP}run", {"charPrIDRef": "0"})
636
+ if not runs:
637
+ first.insert(0, run)
638
+ for extra in runs[1:]:
639
+ first.remove(extra)
640
+ for child in list(run):
641
+ run.remove(child)
642
+ text = run.makeelement(f"{_HP}t", {})
643
+ run.append(text)
644
+ _write_text_node(text, value)
645
+ _clear_form_field_layout_cache(first)
646
+ section.mark_dirty()
647
+
648
+
445
649
  _PROMPT_TEXT_COLOR = "#FF0000"
446
650
 
447
651
 
@@ -686,6 +890,7 @@ def _select_form_field(
686
890
  match
687
891
  for match in matches
688
892
  if wanted_name
893
+ and match.get("name")
689
894
  and wanted_name
690
895
  in {
691
896
  str(match.get("name", "")).strip().casefold(),
@@ -735,10 +940,62 @@ def _insert_form_field_text_run(
735
940
  runs: list[Any] = match["_runs"]
736
941
  begin_run = runs[int(match["_begin_run_index"])]
737
942
  text_node = begin_run.makeelement(f"{_HP}t", {})
738
- text_node.text = _sanitize_field_text(value)
943
+ _write_text_node(text_node, _sanitize_field_text(value))
739
944
  begin_run.insert(int(match["_begin_child_index"]) + 1, text_node)
740
945
 
741
946
 
947
+ def _drop_nested_fields(match: Mapping[str, Any]) -> None:
948
+ """Hancom's way with a field that holds other fields: the value replaces
949
+ them with the rest of the content, so the begin and end of every field
950
+ lying wholly between this field's begin and end go."""
951
+
952
+ runs: list[Any] = match["_runs"]
953
+ end_run_index = match.get("_end_run_index")
954
+ if end_run_index is None:
955
+ return
956
+ begin_run_index = int(match["_begin_run_index"])
957
+ inside: list[tuple[Any, Any]] = []
958
+ for run_index in range(begin_run_index, int(end_run_index) + 1):
959
+ children = list(runs[run_index])
960
+ start = int(match["_begin_child_index"]) + 1 if run_index == begin_run_index else 0
961
+ stop = int(match["_end_child_index"]) if run_index == end_run_index else len(children)
962
+ inside.extend((runs[run_index], child) for child in children[start:stop] if _local_name(child) == "ctrl")
963
+ begun = {mark.get("id") for _, ctrl in inside for mark in ctrl.findall(f"{_HP}fieldBegin")}
964
+ ended = {mark.get("beginIDRef") for _, ctrl in inside for mark in ctrl.findall(f"{_HP}fieldEnd")}
965
+ nested = (begun & ended) - {None, ""}
966
+ for run, ctrl in inside:
967
+ marks = [(mark.get("id") if _local_name(mark) == "fieldBegin" else mark.get("beginIDRef"))
968
+ for mark in ctrl if _local_name(mark) in ("fieldBegin", "fieldEnd")]
969
+ if marks and all(mark in nested for mark in marks):
970
+ run.remove(ctrl)
971
+
972
+
973
+ def _collapse_field_span(match: Mapping[str, Any]) -> None:
974
+ """Hancom's way with a field whose content runs over paragraphs: the
975
+ content between the begin and the end goes, paragraphs and all, and the
976
+ end with the rest of its paragraph joins the begin's paragraph."""
977
+
978
+ end_paragraph, end_runs, end_run_index, end_child_index, middle = match["_span"]
979
+ paragraph_element = match["_paragraph"].element
980
+ runs: list[Any] = match["_runs"]
981
+ begin_run_index = int(match["_begin_run_index"])
982
+ begin_run = runs[begin_run_index]
983
+ for child in list(begin_run)[int(match["_begin_child_index"]) + 1 :]:
984
+ begin_run.remove(child)
985
+ for run in runs[begin_run_index + 1 :]:
986
+ paragraph_element.remove(run)
987
+ for element in middle:
988
+ element.getparent().remove(element)
989
+ end_run = end_runs[end_run_index]
990
+ moved = end_run.makeelement(end_run.tag, dict(end_run.attrib))
991
+ for child in list(end_run)[end_child_index:]:
992
+ moved.append(child)
993
+ position = paragraph_element.index(begin_run) + 1
994
+ for offset, run in enumerate([moved, *end_runs[end_run_index + 1 :]]):
995
+ paragraph_element.insert(position + offset, run)
996
+ end_paragraph.getparent().remove(end_paragraph)
997
+
998
+
742
999
  def fill_form_field(
743
1000
  doc: "HwpxDocument",
744
1001
  value: str,
@@ -766,6 +1023,13 @@ def fill_form_field(
766
1023
  field_id=field_id,
767
1024
  name=name,
768
1025
  )
1026
+ if not match.get("has_end"):
1027
+ raise HwpxValueError(
1028
+ f"the field {match.get('name') or match.get('index')!r} has no end, so it has no content to replace",
1029
+ code="field-end-missing",
1030
+ context={"name": match.get("name"), "index": match.get("index")},
1031
+ suggestion="Hancom leaves such a field unchanged too; give it an end or remove it.",
1032
+ )
769
1033
  paragraph = match["_paragraph"]
770
1034
  runs = match["_runs"]
771
1035
  before_value = str(match.get("current_value", ""))
@@ -802,23 +1066,23 @@ def fill_form_field(
802
1066
 
803
1067
  text_nodes: list[Any] = match.get("_text_nodes", [])
804
1068
  sanitized = _sanitize_field_text(write_value)
805
- if text_nodes:
1069
+ if match.get("_span") is None:
1070
+ _drop_nested_fields(match)
1071
+ if match.get("_span") is not None:
1072
+ _collapse_field_span(match)
1073
+ _insert_form_field_text_run(doc, match, sanitized)
1074
+ elif text_nodes:
806
1075
  primary = text_nodes[0]
807
- primary.text = sanitized
808
- for child in list(primary):
809
- child.tail = ""
1076
+ _write_text_node(primary, sanitized)
810
1077
  for node in text_nodes[1:]:
811
- node.text = ""
812
- for child in list(node):
813
- child.tail = ""
814
- if match.get("is_placeholder"):
815
- # Contract (P0 gold): Hancom swaps the screen-only prompt style for
816
- # the surrounding style when a value replaces the placeholder.
817
- begin_run = runs[int(match["_begin_run_index"])]
818
- begin_ref = begin_run.get("charPrIDRef")
819
- primary_run = primary.getparent()
820
- if begin_ref is not None and primary_run is not None:
821
- primary_run.set("charPrIDRef", begin_ref)
1078
+ _write_text_node(node, "")
1079
+ # Hancom writes the value in the shape of the run holding the field's
1080
+ # begin, whatever shape the old value or the screen-only prompt had.
1081
+ begin_run = runs[int(match["_begin_run_index"])]
1082
+ begin_ref = begin_run.get("charPrIDRef")
1083
+ primary_run = primary.getparent()
1084
+ if begin_ref is not None and primary_run is not None:
1085
+ primary_run.set("charPrIDRef", begin_ref)
822
1086
  else:
823
1087
  _insert_form_field_text_run(doc, match, sanitized)
824
1088