python-hwpx 5.0.2__tar.gz → 5.2.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 (215) hide show
  1. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/NOTICE +9 -3
  2. {python_hwpx-5.0.2/src/python_hwpx.egg-info → python_hwpx-5.2.0}/PKG-INFO +1 -1
  3. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/pyproject.toml +3 -1
  4. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/__init__.py +4 -0
  5. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/_document/fields.py +81 -1
  6. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/_document/shapes.py +55 -0
  7. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/document.py +89 -0
  8. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/equation/__init__.py +9 -1
  9. python_hwpx-5.2.0/src/hwpx/equation/authoring.py +457 -0
  10. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/equation/tokens.py +12 -0
  11. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/experimental.py +8 -0
  12. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/oxml/paragraph.py +186 -1
  13. {python_hwpx-5.0.2 → python_hwpx-5.2.0/src/python_hwpx.egg-info}/PKG-INFO +1 -1
  14. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/python_hwpx.egg-info/SOURCES.txt +3 -0
  15. python_hwpx-5.2.0/tests/test_equation_authoring.py +345 -0
  16. python_hwpx-5.2.0/tests/test_form_field_authoring.py +222 -0
  17. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_oxml_modularization.py +4 -3
  18. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_product_boundary.py +1 -1
  19. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_stable_surface.py +2 -2
  20. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/LICENSE +0 -0
  21. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/README.md +0 -0
  22. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/setup.cfg +0 -0
  23. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/_document/__init__.py +0 -0
  24. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/_document/_units.py +0 -0
  25. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/_document/layout.py +0 -0
  26. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/_document/media.py +0 -0
  27. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/_document/memos.py +0 -0
  28. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/_document/persistence.py +0 -0
  29. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/_document/tracked.py +0 -0
  30. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/body_patch.py +0 -0
  31. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/data/Skeleton.hwpx +0 -0
  32. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/equation/eqedit.py +0 -0
  33. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/equation/mathml.py +0 -0
  34. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/equation/render.py +0 -0
  35. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/errors.py +0 -0
  36. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/form_fit/__init__.py +0 -0
  37. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/form_fit/apply.py +0 -0
  38. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/form_fit/engine.py +0 -0
  39. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/form_fit/measure.py +0 -0
  40. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/form_fit/policy.py +0 -0
  41. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/form_fit/report.py +0 -0
  42. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/ingest/__init__.py +0 -0
  43. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/ingest/base.py +0 -0
  44. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/ingest/hwpx_converter.py +0 -0
  45. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/layout/__init__.py +0 -0
  46. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/layout/lint.py +0 -0
  47. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/layout/report.py +0 -0
  48. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/mutation_report.py +0 -0
  49. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/opc/package.py +0 -0
  50. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/opc/relationships.py +0 -0
  51. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/opc/security.py +0 -0
  52. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/opc/xml_utils.py +0 -0
  53. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/oxml/__init__.py +0 -0
  54. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/oxml/_document_impl.py +0 -0
  55. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/oxml/_document_primitives.py +0 -0
  56. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/oxml/body.py +0 -0
  57. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/oxml/canonical_defaults.py +0 -0
  58. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/oxml/color.py +0 -0
  59. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/oxml/common.py +0 -0
  60. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/oxml/document.py +0 -0
  61. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/oxml/document_parts.py +0 -0
  62. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/oxml/header.py +0 -0
  63. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/oxml/header_part.py +0 -0
  64. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/oxml/memo.py +0 -0
  65. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/oxml/namespaces.py +0 -0
  66. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/oxml/numbering.py +0 -0
  67. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/oxml/objects.py +0 -0
  68. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/oxml/parser.py +0 -0
  69. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/oxml/run.py +0 -0
  70. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/oxml/schema.py +0 -0
  71. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/oxml/section.py +0 -0
  72. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/oxml/section_format.py +0 -0
  73. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/oxml/section_story.py +0 -0
  74. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/oxml/simple_parts.py +0 -0
  75. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/oxml/table.py +0 -0
  76. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/oxml/utils.py +0 -0
  77. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/package.py +0 -0
  78. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/patch.py +0 -0
  79. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/py.typed +0 -0
  80. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/quality/__init__.py +0 -0
  81. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/quality/ledger.py +0 -0
  82. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/quality/policy.py +0 -0
  83. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/quality/rendering.py +0 -0
  84. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/quality/report.py +0 -0
  85. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/quality/save_pipeline.py +0 -0
  86. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/table_patch.py +0 -0
  87. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/templates.py +0 -0
  88. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/__init__.py +0 -0
  89. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/_schemas/header.xsd +0 -0
  90. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/_schemas/section.xsd +0 -0
  91. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/archive_cli.py +0 -0
  92. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/doc_diff.py +0 -0
  93. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/document_viewer.py +0 -0
  94. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/exporter.py +0 -0
  95. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/generic_inventory.py +0 -0
  96. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/id_integrity.py +0 -0
  97. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/idempotence.py +0 -0
  98. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/ir_equality.py +0 -0
  99. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/layout_preview.py +0 -0
  100. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/mail_merge.py +0 -0
  101. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/markdown_export.py +0 -0
  102. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/object_finder.py +0 -0
  103. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/package_reconcile.py +0 -0
  104. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/package_validator.py +0 -0
  105. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/page_guard.py +0 -0
  106. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/read_fidelity.py +0 -0
  107. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/recover.py +0 -0
  108. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/redline.py +0 -0
  109. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/repair.py +0 -0
  110. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/report_utils.py +0 -0
  111. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/roundtrip_diff.py +0 -0
  112. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/table_cleanup.py +0 -0
  113. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/table_navigation.py +0 -0
  114. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/template_analyzer.py +0 -0
  115. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/text_extract_cli.py +0 -0
  116. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/text_extractor.py +0 -0
  117. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/toc_author.py +0 -0
  118. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/toc_fidelity.py +0 -0
  119. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/hwpx/tools/validator.py +0 -0
  120. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/python_hwpx.egg-info/dependency_links.txt +0 -0
  121. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/python_hwpx.egg-info/entry_points.txt +0 -0
  122. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/python_hwpx.egg-info/requires.txt +0 -0
  123. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/src/python_hwpx.egg-info/top_level.txt +0 -0
  124. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_add_control.py +0 -0
  125. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_adoption_metrics_snapshot.py +0 -0
  126. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_authoring_quality_corpus.py +0 -0
  127. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_blind_eval_fixture.py +0 -0
  128. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_byte_patch_identity.py +0 -0
  129. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_cell_line_spacing.py +0 -0
  130. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_comment_node_robustness.py +0 -0
  131. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_corpus_read_fidelity.py +0 -0
  132. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_coverage_promotion.py +0 -0
  133. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_coverage_targets.py +0 -0
  134. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_deviations_registry.py +0 -0
  135. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_doc_diff.py +0 -0
  136. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_document_context_manager.py +0 -0
  137. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_document_facade_surface.py +0 -0
  138. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_document_formatting.py +0 -0
  139. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_document_ops_runtime_boundary.py +0 -0
  140. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_document_save_api.py +0 -0
  141. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_document_viewer.py +0 -0
  142. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_documentation_code_fences.py +0 -0
  143. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_equation_converter.py +0 -0
  144. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_equation_render.py +0 -0
  145. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_error_messages.py +0 -0
  146. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_evalplan_core_primitives.py +0 -0
  147. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_existing_document_format_editing.py +0 -0
  148. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_form_fields.py +0 -0
  149. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_form_fill_core_primitives.py +0 -0
  150. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_form_fit.py +0 -0
  151. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_form_fit_integration.py +0 -0
  152. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_fuzz_catalog_derive_expected.py +0 -0
  153. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_fuzz_loop.py +0 -0
  154. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_fuzz_regressions.py +0 -0
  155. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_gap_closure_tools.py +0 -0
  156. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_golden_api.py +0 -0
  157. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_hp_tab_support.py +0 -0
  158. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_hwpxlib_corpus_read.py +0 -0
  159. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_id_generator_range.py +0 -0
  160. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_image_object_workflow.py +0 -0
  161. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_ingest.py +0 -0
  162. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_inline_models.py +0 -0
  163. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_integration_hwpx_compatibility.py +0 -0
  164. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_integration_roundtrip.py +0 -0
  165. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_layout_cache_scope.py +0 -0
  166. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_layout_lint.py +0 -0
  167. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_layout_preview.py +0 -0
  168. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_library_boundary_closure.py +0 -0
  169. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_llms_txt.py +0 -0
  170. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_mail_merge_xlsx.py +0 -0
  171. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_markdown_export.py +0 -0
  172. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_memo_and_style_editing.py +0 -0
  173. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_moved_surface_hints.py +0 -0
  174. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_mutation_report.py +0 -0
  175. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_namespace_handling.py +0 -0
  176. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_new_features.py +0 -0
  177. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_opc_package.py +0 -0
  178. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_open_safety_corpus.py +0 -0
  179. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_oxml_parsing.py +0 -0
  180. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_packaging_license_metadata.py +0 -0
  181. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_packaging_py_typed.py +0 -0
  182. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_paragraph_keep_together.py +0 -0
  183. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_paragraph_section_management.py +0 -0
  184. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_public_artifact_hygiene.py +0 -0
  185. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_public_runtime_boundary.py +0 -0
  186. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_read_fidelity.py +0 -0
  187. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_recover_broken_zip.py +0 -0
  188. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_redline_authoring.py +0 -0
  189. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_redline_verify.py +0 -0
  190. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_release_workflow_safety.py +0 -0
  191. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_repair_repack.py +0 -0
  192. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_report_utils.py +0 -0
  193. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_repr_snapshots.py +0 -0
  194. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_roundtrip_fidelity.py +0 -0
  195. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_row_heights.py +0 -0
  196. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_save_pipeline.py +0 -0
  197. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_section_headers.py +0 -0
  198. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_set_paragraph_format_keep.py +0 -0
  199. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_shape_geometry_contract.py +0 -0
  200. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_skeleton_template_ids.py +0 -0
  201. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_split_cell.py +0 -0
  202. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_split_merged_cell.py +0 -0
  203. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_table_cleanup.py +0 -0
  204. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_table_navigation.py +0 -0
  205. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_table_patch.py +0 -0
  206. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_table_patch_dryrun.py +0 -0
  207. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_table_patch_m105.py +0 -0
  208. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_tables_default_border.py +0 -0
  209. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_template_analyzer_enrichment.py +0 -0
  210. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_text_extractor_annotations.py +0 -0
  211. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_toc_author.py +0 -0
  212. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_toc_fidelity.py +0 -0
  213. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_validation_severity.py +0 -0
  214. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_validator_comment_nodes.py +0 -0
  215. {python_hwpx-5.0.2 → python_hwpx-5.2.0}/tests/test_version_metadata.py +0 -0
@@ -30,9 +30,15 @@ reimplemented from public references, without copying source code:
30
30
  structure and roundtrip fixtures.
31
31
 
32
32
  - OpenBapul/hml-equation-parser (Apache-2.0): HULK-style EqEdit <-> LaTeX token
33
- mapping, referenced for reader-direction <hp:equation> script rendering. The
34
- token maps are a clean-room re-derivation from the public EqEdit grammar; no
35
- source code was copied, translated, or vendored.
33
+ mapping, referenced for reader-direction <hp:equation> script rendering and
34
+ for the authoring-direction LaTeX -> EqEdit conversion. The token maps are a
35
+ clean-room re-derivation from the public EqEdit grammar; no source code was
36
+ copied, translated, or vendored.
37
+ - chrisryugj/kordoc (MIT): the authoring-direction approach for native
38
+ <hp:equation> generation — the XML shell contract, reserved-word quoting,
39
+ and untrusted-input guards (source-size and recursion-depth caps) were
40
+ referenced as behavior evidence. Clean-room Python implementation; no source
41
+ code was copied, translated, or vendored.
36
42
 
37
43
  These form-fill and equation references were used as behavior evidence only. The
38
44
  implementation is a clean-room Python implementation; no source code was
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-hwpx
3
- Version: 5.0.2
3
+ Version: 5.2.0
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 = "5.0.2"
7
+ version = "5.2.0"
8
8
  description = "한글 없이 HWPX 문서를 열고, 편집하고, 생성하고, 검증하는 Python 문서 라이브러리"
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  license = "Apache-2.0"
@@ -148,6 +148,7 @@ files = [
148
148
  "src/hwpx/equation/__init__.py",
149
149
  "src/hwpx/equation/tokens.py",
150
150
  "src/hwpx/equation/eqedit.py",
151
+ "src/hwpx/equation/authoring.py",
151
152
  "src/hwpx/equation/mathml.py",
152
153
  "src/hwpx/equation/render.py",
153
154
  "src/hwpx/tools/document_viewer.py",
@@ -202,6 +203,7 @@ include = [
202
203
  "src/hwpx/equation/__init__.py",
203
204
  "src/hwpx/equation/tokens.py",
204
205
  "src/hwpx/equation/eqedit.py",
206
+ "src/hwpx/equation/authoring.py",
205
207
  "src/hwpx/equation/mathml.py",
206
208
  "src/hwpx/equation/render.py",
207
209
  "src/hwpx/tools/document_viewer.py",
@@ -48,6 +48,10 @@ _EXPERIMENTAL_EXPORTS = {
48
48
  # 문서 프리뷰 뷰어(3.8.0 신규). 계약 유동.
49
49
  "DocumentViewer": "hwpx.tools.document_viewer",
50
50
  "render_document_viewer": "hwpx.tools.document_viewer",
51
+ # 수식 저작(5.2.0 신규, LaTeX -> EqEdit). 계약 유동.
52
+ "UnsupportedLatexError": "hwpx.equation.authoring",
53
+ "estimate_equation_size": "hwpx.equation.authoring",
54
+ "latex_to_eqedit": "hwpx.equation.authoring",
51
55
  }
52
56
 
53
57
  # Emptied in 5.0. The 4.x notice on these names said they would go in the next
@@ -211,10 +211,16 @@ def _form_field_payload(
211
211
  name = _field_parameter_value(parameters, "fieldName", "fieldname", "field_name", "name", "title")
212
212
  prompt = _first_attr(field_begin, _FORM_FIELD_PROMPT_ATTRS)
213
213
  if not prompt:
214
- prompt = _field_parameter_value(parameters, *_FORM_FIELD_PARAM_NAMES)
214
+ # "Direction" is the real-Hancom 안내문 parameter (P0 gold contract).
215
+ prompt = _field_parameter_value(parameters, "Direction", *_FORM_FIELD_PARAM_NAMES)
215
216
  instruction = _field_parameter_value(parameters, "instruction", "guide", "help", "description", "desc")
216
217
  if not instruction:
217
218
  instruction = prompt
219
+ memo = _field_parameter_value(parameters, "HelpState", "memo")
220
+ dirty = (field_begin.get("dirty") or "").strip()
221
+ # Contract: while dirty != "1" the content between begin/end is the prompt
222
+ # placeholder (screen-only, not printed), not a user value.
223
+ is_placeholder = dirty != "1" and bool(prompt) and current_value == prompt
218
224
  return {
219
225
  "index": index,
220
226
  "field_id": _field_identifier(field_begin),
@@ -223,9 +229,13 @@ def _form_field_payload(
223
229
  "name": name,
224
230
  "prompt": prompt,
225
231
  "instruction": instruction,
232
+ "memo": memo,
233
+ "dirty": dirty,
234
+ "is_placeholder": is_placeholder,
226
235
  "current_value": current_value,
227
236
  "field_type": field_begin.get("type", ""),
228
237
  "control_type": ctrl.get("type", ""),
238
+ "_field_begin": field_begin,
229
239
  "section_index": section_index,
230
240
  "paragraph_index": paragraph_index,
231
241
  "paragraph_index_in_section": paragraph_index_in_section,
@@ -320,6 +330,62 @@ def list_form_fields(doc: "HwpxDocument") -> list[dict[str, Any]]:
320
330
  ]
321
331
 
322
332
 
333
+ _PROMPT_TEXT_COLOR = "#FF0000"
334
+
335
+
336
+ def add_form_field(
337
+ doc: "HwpxDocument",
338
+ name: str,
339
+ *,
340
+ prompt: str = "",
341
+ memo: str = "",
342
+ editable: bool = True,
343
+ paragraph: Any | None = None,
344
+ section: Any | None = None,
345
+ section_index: int | None = None,
346
+ ) -> dict[str, Any]:
347
+ """Create a click-here (누름틀) form field and return its field payload.
348
+
349
+ The emitted XML follows the real-Hancom CLICKHERE contract
350
+ (reverse-engineered from Hancom Office 12.0.0.3288 gold documents). The
351
+ prompt (안내문) is materialized as a screen-only red-italic run, exactly as
352
+ Hancom authors it. The created field is immediately re-read through the
353
+ standard ``list_form_fields`` matcher — creation fails loudly if the
354
+ standard consumer would not recognize it (no special-casing by design).
355
+ """
356
+
357
+ if not str(name).strip():
358
+ raise ValueError("form field name must be a non-empty string")
359
+ if paragraph is None:
360
+ paragraph = doc.add_paragraph(
361
+ "", section=section, section_index=section_index, include_run=False,
362
+ )
363
+
364
+ prompt_char_pr: str | None = None
365
+ if _sanitize_field_text(prompt):
366
+ prompt_char_pr = doc.ensure_run_style(italic=True, color=_PROMPT_TEXT_COLOR)
367
+
368
+ control = paragraph.add_form_field(
369
+ name,
370
+ prompt=prompt,
371
+ memo=memo,
372
+ editable=editable,
373
+ prompt_char_pr_id_ref=prompt_char_pr,
374
+ )
375
+ _clear_form_field_layout_cache(paragraph.element)
376
+
377
+ field_begin = control.element.find(f"{_HP}fieldBegin")
378
+ created_id = field_begin.get("id", "") if field_begin is not None else ""
379
+ for match in _iter_form_field_matches(doc):
380
+ if match.get("id") == created_id:
381
+ return {
382
+ key: value for key, value in match.items() if not key.startswith("_")
383
+ }
384
+ raise RuntimeError(
385
+ "created form field was not recognized by the standard form-field matcher"
386
+ )
387
+
388
+
323
389
  def _select_form_field(
324
390
  doc: "HwpxDocument",
325
391
  matches: Sequence[dict[str, Any]],
@@ -447,9 +513,23 @@ def fill_form_field(
447
513
  node.text = ""
448
514
  for child in list(node):
449
515
  child.tail = ""
516
+ if match.get("is_placeholder"):
517
+ # Contract (P0 gold): Hancom swaps the screen-only prompt style for
518
+ # the surrounding style when a value replaces the placeholder.
519
+ begin_run = runs[int(match["_begin_run_index"])]
520
+ begin_ref = begin_run.get("charPrIDRef")
521
+ primary_run = primary.getparent()
522
+ if begin_ref is not None and primary_run is not None:
523
+ primary_run.set("charPrIDRef", begin_ref)
450
524
  else:
451
525
  _insert_form_field_text_run(doc, match, sanitized)
452
526
 
527
+ # Contract (P0 gold): a field that went through fill machinery carries
528
+ # dirty="1"; while dirty != "1" readers treat the content as the prompt.
529
+ field_begin = match.get("_field_begin")
530
+ if field_begin is not None:
531
+ field_begin.set("dirty", "1")
532
+
453
533
  if fit_result is not None:
454
534
  _apply_form_field_fit_style(doc, match, fit_result)
455
535
 
@@ -227,3 +227,58 @@ def add_ellipse(
227
227
  line_color=line_color, line_width=line_width,
228
228
  fill_color=fill_color, treat_as_char=treat_as_char,
229
229
  )
230
+
231
+
232
+ def add_equation(
233
+ doc: "HwpxDocument",
234
+ script: str,
235
+ *,
236
+ paragraph: HwpxOxmlParagraph | None = None,
237
+ section: HwpxOxmlSection | None = None,
238
+ section_index: int | None = None,
239
+ base_unit: int = 1100,
240
+ size: tuple[int, int] | None = None,
241
+ char_pr_id_ref: str | int | None = None,
242
+ ) -> HwpxOxmlInlineObject:
243
+ """Insert an inline equation and prove the standard reader recognizes it.
244
+
245
+ The emitted XML follows the real-Hancom ``<hp:equation>`` contract
246
+ (specs/054-equation-authoring/evidence/p0/equation-contract.md). After
247
+ insertion the equation is re-read through the standard section scan —
248
+ creation fails loudly if the script did not land verbatim (no
249
+ special-casing by design).
250
+ """
251
+ from ..equation.authoring import estimate_equation_size
252
+ from ..equation.eqedit import MAX_SOURCE_LENGTH
253
+ from ..oxml.namespaces import HP
254
+
255
+ text = (script or "").strip()
256
+ if not text:
257
+ raise ValueError("equation script must be a non-empty string")
258
+ if len(text) > MAX_SOURCE_LENGTH:
259
+ raise ValueError("equation script exceeds size limit")
260
+ if paragraph is None:
261
+ paragraph = doc.add_paragraph(
262
+ "", section=section, section_index=section_index,
263
+ include_run=False,
264
+ )
265
+ if size is None:
266
+ size = estimate_equation_size(text, base_unit=base_unit)
267
+ inline_object = paragraph.add_equation(
268
+ text, base_unit=base_unit, size=size, char_pr_id_ref=char_pr_id_ref,
269
+ )
270
+
271
+ created_id = inline_object.element.get("id", "")
272
+ for owning_section in doc.sections:
273
+ for candidate in owning_section.element.iter(f"{HP}equation"):
274
+ if candidate.get("id") != created_id:
275
+ continue
276
+ script_element = candidate.find(f"{HP}script")
277
+ if script_element is None or (script_element.text or "") != text:
278
+ raise RuntimeError(
279
+ "created equation did not store its script verbatim"
280
+ )
281
+ return inline_object
282
+ raise RuntimeError(
283
+ "created equation was not recognized by the standard section scan"
284
+ )
@@ -1388,6 +1388,95 @@ class HwpxDocument:
1388
1388
  section_index=section_index,
1389
1389
  )
1390
1390
 
1391
+ def add_form_field(
1392
+ self,
1393
+ name: str,
1394
+ *,
1395
+ prompt: str = "",
1396
+ memo: str = "",
1397
+ editable: bool = True,
1398
+ paragraph: HwpxOxmlParagraph | None = None,
1399
+ section: HwpxOxmlSection | None = None,
1400
+ section_index: int | None = None,
1401
+ ) -> dict[str, Any]:
1402
+ """Create a click-here (누름틀) form field. **Experimental contract.**
1403
+
1404
+ Emits the real-Hancom CLICKHERE shape (안내문 placeholder run included)
1405
+ so the created field is indistinguishable from a Hancom-authored one:
1406
+ ``list_form_fields``/``fill_form_field`` recognize it with no
1407
+ special-casing, and real Hancom Office enumerates and fills it.
1408
+
1409
+ Args:
1410
+ name: Field name (non-empty).
1411
+ prompt: 안내문 shown while the field is empty. Screen-only —
1412
+ Hancom does not print it.
1413
+ memo: Help text (``HelpState``).
1414
+ paragraph: Target paragraph (e.g. inside a table cell). When
1415
+ omitted a new paragraph is appended to *section*.
1416
+
1417
+ Returns:
1418
+ The created field's payload, same shape as a ``list_form_fields``
1419
+ entry.
1420
+ """
1421
+
1422
+ return _fields.add_form_field(
1423
+ self,
1424
+ name,
1425
+ prompt=prompt,
1426
+ memo=memo,
1427
+ editable=editable,
1428
+ paragraph=paragraph,
1429
+ section=section,
1430
+ section_index=section_index,
1431
+ )
1432
+
1433
+
1434
+ def add_equation(
1435
+ self,
1436
+ script: str,
1437
+ *,
1438
+ paragraph: HwpxOxmlParagraph | None = None,
1439
+ section: HwpxOxmlSection | None = None,
1440
+ section_index: int | None = None,
1441
+ base_unit: int = 1100,
1442
+ size: tuple[int, int] | None = None,
1443
+ char_pr_id_ref: str | int | None = None,
1444
+ ) -> HwpxOxmlInlineObject:
1445
+ """Insert an inline equation from an EqEdit script. **Experimental contract.**
1446
+
1447
+ Emits the real-Hancom ``<hp:equation>`` shape (contract:
1448
+ ``specs/054-equation-authoring/evidence/p0/equation-contract.md``): the
1449
+ EqEdit source is stored verbatim in ``<hp:script>``, no layout cache is
1450
+ written (Hancom re-lays-out on open), and the shape is inline so it
1451
+ renders in the page flow. The created element is immediately re-read
1452
+ through the standard section scan — creation fails loudly if the
1453
+ standard consumer would not see it (no special-casing by design).
1454
+
1455
+ To author from LaTeX, convert first (typed refusal outside the
1456
+ verified token set)::
1457
+
1458
+ from hwpx.equation import latex_to_eqedit
1459
+ doc.add_equation(latex_to_eqedit(r"\\frac{a}{b}"))
1460
+
1461
+ Args:
1462
+ script: EqEdit script stored as-is (e.g. ``{a} over {b}``).
1463
+ paragraph: Target paragraph (e.g. inside a table cell). When
1464
+ omitted a new paragraph is appended to *section*.
1465
+ base_unit: Equation base font size in 1/100 pt.
1466
+ size: Optional explicit ``(width, height)`` HWPUNIT pair;
1467
+ defaults to a proportional estimate (Hancom re-measures).
1468
+ """
1469
+
1470
+ return _shapes.add_equation(
1471
+ self,
1472
+ script,
1473
+ paragraph=paragraph,
1474
+ section=section,
1475
+ section_index=section_index,
1476
+ base_unit=base_unit,
1477
+ size=size,
1478
+ char_pr_id_ref=char_pr_id_ref,
1479
+ )
1391
1480
 
1392
1481
  def set_page_size(
1393
1482
  self,
@@ -1,5 +1,5 @@
1
1
  # SPDX-License-Identifier: Apache-2.0
2
- """Reader-direction equation support: EqEdit → LaTeX → MathML.
2
+ """Equation support: EqEdit → LaTeX → MathML (reader) and LaTeX → EqEdit (authoring).
3
3
 
4
4
  Clean-room re-derivation of the HULK-style EqEdit vocabulary; see NOTICE for the
5
5
  referenced projects. ``latex2mathml`` is an optional dependency
@@ -9,6 +9,11 @@ block rather than dropping the equation.
9
9
 
10
10
  from __future__ import annotations
11
11
 
12
+ from .authoring import (
13
+ UnsupportedLatexError,
14
+ estimate_equation_size,
15
+ latex_to_eqedit,
16
+ )
12
17
  from .eqedit import (
13
18
  MAX_GROUP_DEPTH,
14
19
  MAX_SOURCE_LENGTH,
@@ -39,8 +44,11 @@ __all__ = [
39
44
  "EquationConversionError",
40
45
  "EquationRender",
41
46
  "MathMLUnavailableError",
47
+ "UnsupportedLatexError",
42
48
  "eqedit_to_latex",
49
+ "estimate_equation_size",
43
50
  "latex2mathml_available",
51
+ "latex_to_eqedit",
44
52
  "latex_to_mathml",
45
53
  "render_equation",
46
54
  ]