python-hwpx 3.6.0__tar.gz → 3.8.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 (375) hide show
  1. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/NOTICE +13 -1
  2. {python_hwpx-3.6.0/src/python_hwpx.egg-info → python_hwpx-3.8.0}/PKG-INFO +101 -11
  3. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/README.md +96 -10
  4. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/pyproject.toml +22 -1
  5. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/__init__.py +12 -0
  6. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/_document/persistence.py +196 -15
  7. python_hwpx-3.8.0/src/hwpx/agent/_batch_verification.py +383 -0
  8. python_hwpx-3.8.0/src/hwpx/agent/blueprint/replay.py +622 -0
  9. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/agent/commands.py +269 -483
  10. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/agent/form_plan.py +167 -92
  11. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/agent/model.py +56 -0
  12. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/authoring.py +206 -113
  13. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/body_patch.py +16 -0
  14. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/document.py +102 -8
  15. python_hwpx-3.8.0/src/hwpx/equation/__init__.py +46 -0
  16. python_hwpx-3.8.0/src/hwpx/equation/eqedit.py +317 -0
  17. python_hwpx-3.8.0/src/hwpx/equation/mathml.py +69 -0
  18. python_hwpx-3.8.0/src/hwpx/equation/render.py +83 -0
  19. python_hwpx-3.8.0/src/hwpx/equation/tokens.py +256 -0
  20. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/evalplan_fill.py +347 -173
  21. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/fill_residue.py +73 -36
  22. python_hwpx-3.8.0/src/hwpx/mutation_report.py +524 -0
  23. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/opc/package.py +7 -0
  24. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/oxml/table.py +216 -128
  25. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/patch.py +24 -0
  26. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/table_patch.py +16 -0
  27. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/__init__.py +8 -0
  28. python_hwpx-3.8.0/src/hwpx/tools/document_viewer.py +182 -0
  29. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/fuzz/catalog.py +105 -84
  30. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/layout_preview.py +95 -16
  31. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/markdown_export.py +76 -54
  32. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/object_finder.py +100 -84
  33. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/toc_fidelity.py +87 -56
  34. python_hwpx-3.8.0/src/hwpx/visual/fixture_corpus.py +210 -0
  35. {python_hwpx-3.6.0 → python_hwpx-3.8.0/src/python_hwpx.egg-info}/PKG-INFO +101 -11
  36. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/python_hwpx.egg-info/SOURCES.txt +15 -0
  37. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/python_hwpx.egg-info/requires.txt +5 -0
  38. python_hwpx-3.8.0/tests/test_authoring_v2_block_validation.py +180 -0
  39. python_hwpx-3.8.0/tests/test_document_viewer.py +136 -0
  40. python_hwpx-3.8.0/tests/test_equation_converter.py +139 -0
  41. python_hwpx-3.8.0/tests/test_equation_render.py +69 -0
  42. python_hwpx-3.8.0/tests/test_fuzz_catalog_derive_expected.py +162 -0
  43. python_hwpx-3.8.0/tests/test_mutation_report.py +323 -0
  44. python_hwpx-3.8.0/tests/test_mutation_report_projections.py +318 -0
  45. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_oxml_modularization.py +5 -1
  46. python_hwpx-3.6.0/src/hwpx/agent/blueprint/replay.py +0 -452
  47. python_hwpx-3.6.0/src/hwpx/visual/fixture_corpus.py +0 -175
  48. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/LICENSE +0 -0
  49. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/setup.cfg +0 -0
  50. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/_document/__init__.py +0 -0
  51. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/_document/_units.py +0 -0
  52. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/_document/fields.py +0 -0
  53. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/_document/layout.py +0 -0
  54. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/_document/media.py +0 -0
  55. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/_document/memos.py +0 -0
  56. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/_document/shapes.py +0 -0
  57. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/_document/tracked.py +0 -0
  58. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/agent/__init__.py +0 -0
  59. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/agent/blueprint/__init__.py +0 -0
  60. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/agent/blueprint/bundle.py +0 -0
  61. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/agent/blueprint/catalog.py +0 -0
  62. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/agent/blueprint/dump.py +0 -0
  63. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/agent/blueprint/mapping.py +0 -0
  64. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/agent/blueprint/model.py +0 -0
  65. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/agent/blueprint/native.py +0 -0
  66. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/agent/catalog.py +0 -0
  67. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/agent/cli.py +0 -0
  68. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/agent/document.py +0 -0
  69. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/agent/path.py +0 -0
  70. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/agent/query.py +0 -0
  71. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/agent/story.py +0 -0
  72. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/benchmark/__init__.py +0 -0
  73. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/benchmark/blind_eval.py +0 -0
  74. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/builder/__init__.py +0 -0
  75. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/builder/core.py +0 -0
  76. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/builder/report.py +0 -0
  77. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/conformance/__init__.py +0 -0
  78. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/conformance/badges.py +0 -0
  79. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/conformance/corpus/corpus.json +0 -0
  80. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/conformance/corpus/meeting_summary.hwpx +0 -0
  81. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/conformance/corpus/notice.hwpx +0 -0
  82. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/conformance/corpus/report_table.hwpx +0 -0
  83. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/conformance/corpus.py +0 -0
  84. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/conformance/report.py +0 -0
  85. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/conformance/roundtrip_batch.py +0 -0
  86. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/conformance/runner.py +0 -0
  87. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/data/Skeleton.hwpx +0 -0
  88. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/design/__init__.py +0 -0
  89. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/design/_support.py +0 -0
  90. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/design/composer.py +0 -0
  91. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/design/harvest.py +0 -0
  92. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/design/plan.py +0 -0
  93. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/design/profile.py +0 -0
  94. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/application_form/fragments/body.xml +0 -0
  95. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/application_form/fragments/heading.xml +0 -0
  96. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/application_form/fragments/info_table.xml +0 -0
  97. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/application_form/fragments/title.xml +0 -0
  98. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/application_form/profile.json +0 -0
  99. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/application_form/template.hwpx +0 -0
  100. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/home_notice/fragments/body.xml +0 -0
  101. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/home_notice/fragments/heading.xml +0 -0
  102. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/home_notice/fragments/title.xml +0 -0
  103. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/home_notice/profile.json +0 -0
  104. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/home_notice/template.hwpx +0 -0
  105. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/official_notice/fragments/body.xml +0 -0
  106. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/official_notice/fragments/heading.xml +0 -0
  107. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/official_notice/fragments/info_table.xml +0 -0
  108. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/official_notice/fragments/title.xml +0 -0
  109. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/official_notice/profile.json +0 -0
  110. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/official_notice/template.hwpx +0 -0
  111. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/report/fragments/body.xml +0 -0
  112. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/report/fragments/heading.xml +0 -0
  113. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/report/fragments/info_table.xml +0 -0
  114. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/report/fragments/title.xml +0 -0
  115. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/report/profile.json +0 -0
  116. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/design/profiles/report/template.hwpx +0 -0
  117. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/design/validator.py +0 -0
  118. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/exam/__init__.py +0 -0
  119. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/exam/compose.py +0 -0
  120. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/exam/ir.py +0 -0
  121. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/exam/measure.py +0 -0
  122. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/exam/parser.py +0 -0
  123. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/exam/profile.py +0 -0
  124. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/form_fill.py +0 -0
  125. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/form_fit/__init__.py +0 -0
  126. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/form_fit/apply.py +0 -0
  127. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/form_fit/engine.py +0 -0
  128. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/form_fit/measure.py +0 -0
  129. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/form_fit/policy.py +0 -0
  130. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/form_fit/report.py +0 -0
  131. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/form_fit/seal.py +0 -0
  132. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/form_fit/wordbox.py +0 -0
  133. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/formfill_quality.py +0 -0
  134. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/guidance_scan.py +0 -0
  135. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/ingest/__init__.py +0 -0
  136. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/ingest/base.py +0 -0
  137. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/ingest/hwpx_converter.py +0 -0
  138. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/layout/__init__.py +0 -0
  139. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/layout/lint.py +0 -0
  140. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/layout/report.py +0 -0
  141. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/opc/relationships.py +0 -0
  142. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/opc/security.py +0 -0
  143. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/opc/xml_utils.py +0 -0
  144. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/oxml/__init__.py +0 -0
  145. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/oxml/_document_impl.py +0 -0
  146. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/oxml/_document_primitives.py +0 -0
  147. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/oxml/body.py +0 -0
  148. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/oxml/canonical_defaults.py +0 -0
  149. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/oxml/common.py +0 -0
  150. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/oxml/document.py +0 -0
  151. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/oxml/document_parts.py +0 -0
  152. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/oxml/header.py +0 -0
  153. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/oxml/header_part.py +0 -0
  154. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/oxml/memo.py +0 -0
  155. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/oxml/namespaces.py +0 -0
  156. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/oxml/numbering.py +0 -0
  157. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/oxml/objects.py +0 -0
  158. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/oxml/paragraph.py +0 -0
  159. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/oxml/parser.py +0 -0
  160. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/oxml/run.py +0 -0
  161. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/oxml/schema.py +0 -0
  162. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/oxml/section.py +0 -0
  163. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/oxml/section_format.py +0 -0
  164. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/oxml/section_story.py +0 -0
  165. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/oxml/simple_parts.py +0 -0
  166. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/oxml/utils.py +0 -0
  167. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/package.py +0 -0
  168. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/presets/__init__.py +0 -0
  169. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/presets/proposal.py +0 -0
  170. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/py.typed +0 -0
  171. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/quality/__init__.py +0 -0
  172. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/quality/ledger.py +0 -0
  173. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/quality/policy.py +0 -0
  174. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/quality/report.py +0 -0
  175. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/quality/save_pipeline.py +0 -0
  176. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/template_formfit.py +0 -0
  177. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/templates.py +0 -0
  178. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/_schemas/header.xsd +0 -0
  179. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/_schemas/section.xsd +0 -0
  180. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/advanced_generators.py +0 -0
  181. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/archive_cli.py +0 -0
  182. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/doc_diff.py +0 -0
  183. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/exporter.py +0 -0
  184. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/fuzz/__init__.py +0 -0
  185. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/fuzz/__main__.py +0 -0
  186. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/fuzz/generator.py +0 -0
  187. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/fuzz/minimize.py +0 -0
  188. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/fuzz/runner.py +0 -0
  189. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/generic_inventory.py +0 -0
  190. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/id_integrity.py +0 -0
  191. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/idempotence.py +0 -0
  192. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/ir_equality.py +0 -0
  193. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/mail_merge.py +0 -0
  194. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/official_lint.py +0 -0
  195. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/package_reconcile.py +0 -0
  196. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/package_validator.py +0 -0
  197. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/page_guard.py +0 -0
  198. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/pii.py +0 -0
  199. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/read_fidelity.py +0 -0
  200. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/recover.py +0 -0
  201. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/redline.py +0 -0
  202. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/repair.py +0 -0
  203. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/report_parser.py +0 -0
  204. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/report_utils.py +0 -0
  205. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/roundtrip_diff.py +0 -0
  206. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/style_profile.py +0 -0
  207. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/table_cleanup.py +0 -0
  208. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/table_compute.py +0 -0
  209. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/table_navigation.py +0 -0
  210. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/template_analyzer.py +0 -0
  211. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/text_extract_cli.py +0 -0
  212. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/text_extractor.py +0 -0
  213. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/toc_author.py +0 -0
  214. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/tools/validator.py +0 -0
  215. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/visual/__init__.py +0 -0
  216. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/visual/_hancom_open_rate.ps1 +0 -0
  217. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/visual/_refresh_hwpx_mac.applescript +0 -0
  218. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/visual/_render_hwpx.ps1 +0 -0
  219. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/visual/_render_hwpx_mac.applescript +0 -0
  220. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/visual/detectors.py +0 -0
  221. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/visual/diff.py +0 -0
  222. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/visual/hancom_worker.py +0 -0
  223. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/visual/masks.py +0 -0
  224. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/visual/oracle.py +0 -0
  225. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/visual/page_qa.py +0 -0
  226. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/visual/qa_contracts.py +0 -0
  227. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/visual/qa_metrics.py +0 -0
  228. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/hwpx/visual/report.py +0 -0
  229. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/python_hwpx.egg-info/dependency_links.txt +0 -0
  230. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/python_hwpx.egg-info/entry_points.txt +0 -0
  231. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/src/python_hwpx.egg-info/top_level.txt +0 -0
  232. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_advanced_generators.py +0 -0
  233. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_agent_blueprint_contracts.py +0 -0
  234. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_agent_blueprint_dump.py +0 -0
  235. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_agent_blueprint_replay.py +0 -0
  236. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_agent_catalog.py +0 -0
  237. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_agent_cli.py +0 -0
  238. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_agent_commands.py +0 -0
  239. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_agent_contracts.py +0 -0
  240. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_agent_document.py +0 -0
  241. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_agent_mixed_form.py +0 -0
  242. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_agent_path_query.py +0 -0
  243. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_agent_story_commands.py +0 -0
  244. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_authoring_native_toc.py +0 -0
  245. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_authoring_profile_routing.py +0 -0
  246. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_authoring_profile_routing_oracle.py +0 -0
  247. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_authoring_quality_corpus.py +0 -0
  248. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_authoring_render_check.py +0 -0
  249. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_blind_eval_fixture.py +0 -0
  250. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_body_patch.py +0 -0
  251. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_builder_core.py +0 -0
  252. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_builder_plan_v2.py +0 -0
  253. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_builder_vertical_slice.py +0 -0
  254. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_byte_patch_identity.py +0 -0
  255. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_cell_line_spacing.py +0 -0
  256. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_comment_node_robustness.py +0 -0
  257. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_conformance.py +0 -0
  258. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_corpus_read_fidelity.py +0 -0
  259. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_corpus_toc_verify.py +0 -0
  260. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_coverage_promotion.py +0 -0
  261. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_coverage_targets.py +0 -0
  262. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_design_builder.py +0 -0
  263. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_deviations_registry.py +0 -0
  264. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_doc_diff.py +0 -0
  265. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_document_context_manager.py +0 -0
  266. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_document_facade_surface.py +0 -0
  267. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_document_formatting.py +0 -0
  268. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_document_plan.py +0 -0
  269. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_document_plan_computed_fields.py +0 -0
  270. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_document_save_api.py +0 -0
  271. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_evalplan_fill.py +0 -0
  272. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_exam_compose.py +0 -0
  273. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_exam_compose_oracle.py +0 -0
  274. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_exam_fixtures.py +0 -0
  275. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_exam_ir.py +0 -0
  276. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_exam_measure.py +0 -0
  277. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_exam_parser.py +0 -0
  278. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_exam_profile.py +0 -0
  279. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_existing_document_format_editing.py +0 -0
  280. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_fill_residue.py +0 -0
  281. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_form_fields.py +0 -0
  282. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_form_fill_split_run.py +0 -0
  283. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_form_fit.py +0 -0
  284. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_form_fit_integration.py +0 -0
  285. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_form_fit_seal.py +0 -0
  286. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_form_fit_seal_placement.py +0 -0
  287. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_form_fit_wordbox.py +0 -0
  288. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_formfill_differential_driver.py +0 -0
  289. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_formfill_quality.py +0 -0
  290. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_fuzz_loop.py +0 -0
  291. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_fuzz_regressions.py +0 -0
  292. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_gap_closure_tools.py +0 -0
  293. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_government_report_preset.py +0 -0
  294. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_government_table_profile.py +0 -0
  295. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_guidance_scan.py +0 -0
  296. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_hancom_render_worker.py +0 -0
  297. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_hp_tab_support.py +0 -0
  298. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_hwpxlib_corpus_read.py +0 -0
  299. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_id_generator_range.py +0 -0
  300. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_id_integrity.py +0 -0
  301. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_image_object_workflow.py +0 -0
  302. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_ingest.py +0 -0
  303. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_inline_models.py +0 -0
  304. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_integration_hwpx_compatibility.py +0 -0
  305. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_integration_roundtrip.py +0 -0
  306. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_kordoc_absorption.py +0 -0
  307. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_layout_cache_scope.py +0 -0
  308. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_layout_lint.py +0 -0
  309. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_layout_preview.py +0 -0
  310. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_m9_p0_spike_tools.py +0 -0
  311. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_mail_merge_fit.py +0 -0
  312. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_mail_merge_table_compute.py +0 -0
  313. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_mail_merge_xlsx.py +0 -0
  314. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_markdown_export.py +0 -0
  315. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_memo_and_style_editing.py +0 -0
  316. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_mixed_form_plan.py +0 -0
  317. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_namespace_handling.py +0 -0
  318. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_new_features.py +0 -0
  319. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_official_document_style.py +0 -0
  320. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_official_lint_gongmun_gate.py +0 -0
  321. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_official_lint_tableaware.py +0 -0
  322. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_opc_package.py +0 -0
  323. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_open_safety_corpus.py +0 -0
  324. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_openrate.py +0 -0
  325. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_oxml_parsing.py +0 -0
  326. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_packaging_license_metadata.py +0 -0
  327. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_packaging_py_typed.py +0 -0
  328. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_paragraph_keep_together.py +0 -0
  329. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_paragraph_section_management.py +0 -0
  330. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_pii.py +0 -0
  331. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_pii_leak_sweep.py +0 -0
  332. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_pii_structural.py +0 -0
  333. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_pii_wiring.py +0 -0
  334. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_proposal_preset.py +0 -0
  335. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_public_runtime_boundary.py +0 -0
  336. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_question_split_detector.py +0 -0
  337. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_read_fidelity.py +0 -0
  338. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_recover_broken_zip.py +0 -0
  339. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_redline_authoring.py +0 -0
  340. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_redline_verify.py +0 -0
  341. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_repair_repack.py +0 -0
  342. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_report_parser.py +0 -0
  343. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_report_utils.py +0 -0
  344. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_repr_snapshots.py +0 -0
  345. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_rhwp_t1_gates.py +0 -0
  346. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_rhwp_t2_verification.py +0 -0
  347. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_roundtrip_fidelity.py +0 -0
  348. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_row_heights.py +0 -0
  349. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_save_pipeline.py +0 -0
  350. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_save_pipeline_no_bypass.py +0 -0
  351. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_section_headers.py +0 -0
  352. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_set_paragraph_format_keep.py +0 -0
  353. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_skeleton_template_ids.py +0 -0
  354. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_split_cell.py +0 -0
  355. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_split_merged_cell.py +0 -0
  356. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_style_profile.py +0 -0
  357. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_table_cleanup.py +0 -0
  358. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_table_navigation.py +0 -0
  359. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_table_patch.py +0 -0
  360. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_table_patch_dryrun.py +0 -0
  361. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_table_patch_m105.py +0 -0
  362. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_tables_default_border.py +0 -0
  363. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_template_analyzer_enrichment.py +0 -0
  364. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_template_formfit.py +0 -0
  365. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_text_extractor_annotations.py +0 -0
  366. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_toc_author.py +0 -0
  367. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_toc_fidelity.py +0 -0
  368. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_validation_severity.py +0 -0
  369. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_validator_comment_nodes.py +0 -0
  370. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_version_metadata.py +0 -0
  371. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_visual_fixture_corpus.py +0 -0
  372. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_visual_oracle.py +0 -0
  373. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_visual_oracle_budget.py +0 -0
  374. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_visual_qa_contracts.py +0 -0
  375. {python_hwpx-3.6.0 → python_hwpx-3.8.0}/tests/test_visual_qa_metrics.py +0 -0
@@ -29,10 +29,22 @@ reimplemented from public references, without copying source code:
29
29
  from public model headers only. hwpxlib sample packages were used only as
30
30
  structure and roundtrip fixtures.
31
31
 
32
- These form-fill references were used as behavior evidence only. The
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.
36
+
37
+ These form-fill and equation references were used as behavior evidence only. The
33
38
  implementation is a clean-room Python implementation; no source code was
34
39
  copied, translated, vendored, or linked.
35
40
 
41
+ ## Optional dependency: latex2mathml
42
+
43
+ The optional ``preview`` extra depends on roniemartinez/latex2mathml (MIT), used
44
+ to convert LaTeX to MathML for the document preview viewer. It is an unmodified
45
+ third-party dependency installed via pip; its MIT license travels with the
46
+ package.
47
+
36
48
  ## Test fixtures: hwpxlib sample corpus
37
49
 
38
50
  tests/fixtures/hwpxlib_corpus/ contains .hwpx sample files vendored from
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-hwpx
3
- Version: 3.6.0
3
+ Version: 3.8.0
4
4
  Summary: 한글 없이 HWPX 문서를 열고, 편집하고, 생성하고, 검증하는 Python 자동화 라이브러리
5
5
  Author: python-hwpx Maintainers
6
6
  License-Expression: Apache-2.0
@@ -27,10 +27,13 @@ Requires-Dist: pillow>=10.0; extra == "visual"
27
27
  Requires-Dist: numpy>=1.26; extra == "visual"
28
28
  Provides-Extra: xlsx
29
29
  Requires-Dist: openpyxl>=3.1; extra == "xlsx"
30
+ Provides-Extra: preview
31
+ Requires-Dist: latex2mathml>=3.77; extra == "preview"
30
32
  Provides-Extra: dev
31
33
  Requires-Dist: build>=1.0; extra == "dev"
32
34
  Requires-Dist: twine>=4.0; extra == "dev"
33
35
  Requires-Dist: pytest>=7.4; extra == "dev"
36
+ Requires-Dist: latex2mathml>=3.77; extra == "dev"
34
37
  Provides-Extra: test
35
38
  Requires-Dist: build>=1.0; extra == "test"
36
39
  Requires-Dist: numpy>=1.26; extra == "test"
@@ -38,6 +41,7 @@ Requires-Dist: pillow>=10.0; extra == "test"
38
41
  Requires-Dist: pytest>=7.4; extra == "test"
39
42
  Requires-Dist: pytest-cov>=5.0; extra == "test"
40
43
  Requires-Dist: ruff>=0.12; extra == "test"
44
+ Requires-Dist: latex2mathml>=3.77; extra == "test"
41
45
  Provides-Extra: typecheck
42
46
  Requires-Dist: mypy>=1.10; extra == "typecheck"
43
47
  Requires-Dist: pyright>=1.1.390; extra == "typecheck"
@@ -46,16 +50,35 @@ Dynamic: license-file
46
50
  <p align="center">
47
51
  <h1 align="center">python-hwpx</h1>
48
52
  <p align="center">
49
- <strong>한글 없이 HWPX 문서를 Python으로 읽고, 편집하고, 생성하고, 구조를 검증합니다.</strong>
53
+ <strong>한컴 없이 HWPX 안전하게 자동화하는 Python 계층 최소 범위 편집, 검증된 저작, 모든 쓰기에 영수증.</strong>
50
54
  </p>
51
55
  <p align="center">
52
56
  <a href="https://pypi.org/project/python-hwpx/"><img src="https://img.shields.io/pypi/v/python-hwpx?color=blue&label=PyPI" alt="PyPI"></a>
53
57
  <a href="https://pypi.org/project/python-hwpx/"><img src="https://img.shields.io/pypi/pyversions/python-hwpx" alt="Python"></a>
54
58
  <a href="https://github.com/airmang/python-hwpx/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-blue" alt="License"></a>
55
59
  <a href="https://airmang.github.io/python-hwpx/"><img src="https://img.shields.io/badge/docs-Sphinx-8CA1AF" alt="Docs"></a>
60
+ <a href="https://github.com/airmang/python-hwpx/actions/workflows/tests.yml"><img src="https://img.shields.io/github/actions/workflow/status/airmang/python-hwpx/tests.yml?branch=main&label=tests" alt="Tests"></a>
61
+ <a href="https://airmang.github.io/python-hwpx/corpus-metrics.html"><img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fairmang.github.io%2Fpython-hwpx%2F_static%2Fbadge-hancom-open.json" alt="Hancom open"></a>
56
62
  </p>
57
63
  </p>
58
64
 
65
+ <p align="center">한국어 | <a href="README_EN.md">English</a></p>
66
+
67
+ ---
68
+
69
+ > **python-hwpx는 한컴 없이 HWPX를 안전하게 자동화하는 Python 계층입니다.** 기존
70
+ > 문서는 최소 범위만 수정하고, 새 문서는 실제 한컴 수용이 검증된 형태로 생성하며,
71
+ > 모든 쓰기에 변경·보존·검증 영수증을 남기고, 완전한 해석과 렌더링은 전문 백엔드에
72
+ > 위임할 수 있습니다.
73
+
74
+ - **최소 범위 편집** — 미수정 part는 저장 시 바이트 그대로 유지됩니다(patch 경로
75
+ 바이트 보존 497/497, 동결 코퍼스 v2 · 2026-07-19).
76
+ - **검증된 저작** — 밑바닥 생성도 실제 한컴이 받아들이는 형태로 냅니다(산출물 한컴
77
+ 오픈 476/476 all-pass, 실저작 품질 게이트 58/58).
78
+ - **모든 쓰기에 영수증** — 대표 저장 경로는 [Safe Write Contract](docs/safe-write-contract.md)의
79
+ `MutationReport`(`hwpx.mutation-report/v1`)로 실제 쓰기 모드·보존 등급·검증 결과를
80
+ **측정해** 반환합니다.
81
+
59
82
  ---
60
83
 
61
84
  ## 🧩 HWPX Stack (3종)
@@ -66,11 +89,10 @@ Dynamic: license-file
66
89
  | 🔌 MCP 서버 | [`hwpx-mcp-server`](https://github.com/airmang/hwpx-mcp-server) | MCP 클라이언트(Claude Desktop, VS Code 등)에서 HWPX 조작 |
67
90
  | 🎯 에이전트 스킬 | [`hwpx-plugin`](https://github.com/airmang/hwpx-plugins) | 에이전트가 HWPX를 바로 쓰게 해주는 first-party 플러그인·스킬 번들 |
68
91
 
69
- `python-hwpx`는 HWPX 파싱·편집·생성을 제공하는 코어 라이브러리이며,
70
- `hwpx-mcp-server`와 `hwpx-plugin`은 같은 프로젝트가 직접 유지보수하는 first-party 연동 구성요소입니다.
71
- “first-party”는 프로젝트 유지보수 관계를 뜻하며, 한컴 또는 제3자의 공식 인증을 뜻하지 않습니다.
92
+ `hwpx-mcp-server`와 `hwpx-plugin`은 같은 프로젝트가 직접 유지보수하는 first-party 연동
93
+ 구성요소입니다(유지보수 관계를 뜻하며, 한컴 외부 기관의 인증을 뜻하지 않습니다).
72
94
 
73
- 현재 PyPI 공개 릴리스는 `python-hwpx 3.6.0`입니다. 일반
95
+ 현재 PyPI 공개 릴리스는 `python-hwpx 3.8.0`입니다. 일반
74
96
  `pip install python-hwpx`로 이 릴리스를 설치할 수 있습니다.
75
97
  현재 패키지 분류는 `Development Status :: 3 - Alpha`입니다. 이 분류는 API와 제품의
76
98
  성숙도를 나타내며, 공개 버전이나 플러그인의 최소 호환 버전을 대신하지 않습니다.
@@ -80,13 +102,22 @@ Dynamic: license-file
80
102
  ## 실측으로 말합니다 — Published Corpus
81
103
 
82
104
  이 스택의 산출물은 주장 대신 **실제 한컴오피스 전수 측정**으로 검증됩니다
83
- (동결 코퍼스 N=497, 2026-07-19, 상세·주의사항은
105
+ (동결 코퍼스 v2, N=497 산출물, 2026-07-19, 실한컴 12.0.0.3288 COM/GUI 오라클;
106
+ 상세·주의사항은
84
107
  [실측 코퍼스 메트릭](https://airmang.github.io/python-hwpx/corpus-metrics.html)):
85
108
 
86
- - **한컴 오픈 수용률 100%** (476/476 all-pass, 하한 ≥99.4%) · 파싱 96.2%
87
- - **미수정 영역 바이트 보존 100%** (497/497, patch 경로) · **개인정보 0-leak**
88
- - 렌더 검증 416건 + 정직 버킷(변경추적 문서의 PDF export는 한컴 자체가 거부 — 실측 한계로 발행)
89
- - 양식 채움 차등은 wild 공개 양식에서 49.2% — **낮은 숫자도 그대로 발행**하고 잔여 과제로 명기합니다
109
+ - **한컴 오픈 수용률 476/476 all-pass** (동결 코퍼스 v2 · 2026-07-19 · 실한컴 COM
110
+ `Open()` 판정 · rule-of-three 하한 99.37%) · 파싱 96.2%(458/476)
111
+ - **미수정 영역 바이트 보존 497/497** (patch 경로 한정, zip-part diff · 오라클 불요)
112
+ · **개인정보 0-leak** (35문서/합성 140값)
113
+ - 렌더 검증 416/476 (실한컴 `SaveAs("PDF")`) + 정직 버킷 43건(변경추적 문서의 PDF
114
+ export는 한컴 자체가 거부 — 실측 한계로 발행) + 미검증 17건
115
+ - wild 공개 양식 채움은 구조결함 픽스 후 **무음 서식파괴 16.7%**(판정 66조합, 못 담는 타깃은 typed 거부 35건·산출분 pass 17/28) — **낮은 숫자도 그대로 발행**하고 잔여(페이지 리플·표 shape)를 명명합니다
116
+
117
+ <p align="center">
118
+ <img src="https://raw.githubusercontent.com/airmang/python-hwpx/main/docs/images/redline-hancom.png" alt="python-hwpx가 남긴 변경추적과 AI 에이전트 메모가 실제 한/글에서 열린 화면" width="720">
119
+ </p>
120
+ <p align="center"><sub>python-hwpx로 작성한 변경추적(취소선·삽입)과 AI 에이전트 메모 — 실제 한/글에서 연 화면입니다.</sub></p>
90
121
 
91
122
  > 이 숫자들은 *생성물 수용률* 축입니다(우리가 만든 파일을 실제 한컴이 받아들이는가).
92
123
  > 문서 *파싱 recall*과는 다른 축이므로 파서 프로젝트 수치와 병치 비교하지 마세요.
@@ -195,6 +226,59 @@ hwpx-analyze-template 보고서.hwpx
195
226
 
196
227
  > 전체 기능·클래스·메서드 목록은 [사용 가이드](docs/usage.md)와 [API 레퍼런스](https://airmang.github.io/python-hwpx/api_reference.html)를 참고하세요.
197
228
 
229
+ ## 안전한 쓰기 계약 (Safe Write Contract)
230
+
231
+ 대표 저장 경로(`save_to_path` · `save_to_stream` · `to_bytes`)는 **요청한 보존 등급을
232
+ 쓰기 전에 판정하고, 실제로 무엇을 바꿨는지 측정한 영수증**을 돌려줍니다.
233
+
234
+ ```python
235
+ from hwpx.mutation_report import PreservationDowngradeError
236
+
237
+ # 영수증과 함께 저장 — 달성 가능한 가장 강한 보존 등급 자동 선택(mode="auto" 기본)
238
+ report = doc.save_to_path("결과.hwpx", return_report=True)
239
+ print(report.actual_mode) # "patch" | "rebuild"
240
+ print(report.preservation.untouched_part_payloads.to_dict()) # {"verified": 17, "changed": 0}
241
+
242
+ # patch 등급 강제 — 미달이면 아무것도 쓰지 않고 예외(fail-closed)
243
+ try:
244
+ doc.save_to_path("결과.hwpx", mode="patch", fallback="error")
245
+ except PreservationDowngradeError as exc:
246
+ print(exc.offending_parts, exc.suggestion)
247
+ ```
248
+
249
+ - `mode="patch" | "rebuild" | "auto"`(기본 `auto`) · `fallback="error" | "rebuild"`(기본 `error`)
250
+ - `mode="patch"` + `fallback="error"`에서 미수정 part의 바이트 동일성을 지킬 수 없으면
251
+ **아무것도 쓰지 않고** `PreservationDowngradeError`를 던집니다(무음 rebuild 없음).
252
+ - `MutationReport`는 `requestedMode`/`actualMode`/`fallbackUsed`, 변경 part와 좌표 명시
253
+ 범위, 보존 3층(part 페이로드·ZIP 레코드·전체 패키지), 검증 3항목(`passed`/`failed`/`not_performed`)을
254
+ **측정해** 반환합니다.
255
+
256
+ > 파라미터 전체와 `MutationReport` 스키마는 [안전한 쓰기 계약 문서](docs/safe-write-contract.md)를 참고하세요.
257
+
258
+ ## 지원 매트릭스
259
+
260
+ 능력 영역별 실제 등급입니다(동결 코퍼스 v2 · 2026-07-19 · 실한컴 12.0.0.3288 오라클).
261
+ 등급 어휘: **Parse / Preserve / Edit / Create / Render-verified /
262
+ Unsupported-but-preserved / Unsupported-and-rejected**.
263
+
264
+ | 능력 영역 | 상태 | 증거 |
265
+ |---|---|---|
266
+ | 문단·표 저작/편집 | Parse·Preserve·Edit·Create·Render-verified | 오픈 476/476 · 실저작 게이트 58/58 · 렌더 416 |
267
+ | 표 구조 변경(행·열·표, 오토핏) | Preserve·Edit | `hwpx.table_patch` · 바이트 보존 497/497 |
268
+ | 양식 채움(byte-splice) | Preserve·Edit | `hwpx.patch`·`table_patch`·`body_patch` · 보존 497/497 (wild 무음 서식파괴 16.7%·typed 거부 35/66, 잔여 명명) |
269
+ | 그림 삽입/치환 | Edit·Create | `add_picture`·`replace_picture` (복잡 개체는 한컴 확인 권장) |
270
+ | 차트 | Unsupported-but-preserved | 생성 API 없음 · 기존 차트 part는 patch 보존 |
271
+ | 수식 | Parse·Unsupported-but-preserved | 저작 API 없음 · 기존 수식 파싱·patch 보존 |
272
+ | 변경추적(redline) | Edit·Create | `add_tracked_*` · 실한컴 `IsTrackChange=1` (한컴이 PDF export 거부 → `render_unavailable` 정직 집계) |
273
+ | 메모(코멘트) | Edit·Create·Render-verified | `add_memo*` · 실 Windows 한컴 검증 |
274
+ | 각주/미주 | Edit·Create | `add_footnote`·`add_endnote` (렌더 독립 게이트 미측정) |
275
+ | 네이티브 목차/상호참조 | Create·Render-verified | `add_native_toc`·`toc_verify` · 구조 15/15 · 페이지 정합 5/5 |
276
+ | 암호화 HWPX | Unsupported-and-rejected | 복호화 없음 · 암호화 part는 파싱 단계 예외로 거부 |
277
+ | HWP 5.x 바이너리 | Unsupported-and-rejected | ZIP 아님 → 열기 시 `BadZipFile` (HWPX로 변환 후 사용) |
278
+ | 누름틀(form field) 생성 | Parse·Edit | 기존 필드 조회·서식보존 채움 · **신규 누름틀 생성 도구는 미제공** |
279
+
280
+ > 각 등급의 판정 근거와 상세 증거 포인터는 [지원 매트릭스 문서](docs/support-matrix.md)를 참고하세요.
281
+
198
282
  ## 대항 라이브러리 비교
199
283
 
200
284
  | | python-hwpx | pyhwpx | pyhwp |
@@ -219,6 +303,7 @@ hwpx-analyze-template 보고서.hwpx
219
303
  - **[🚀 빠른 시작](docs/quickstart.md)** · **[📚 사용 가이드](docs/usage.md)** — 첫 파일 열기부터 문단·표·메모·섹션 편집, 텍스트 추출·검증까지
220
304
  - **[💡 예제 모음](docs/examples.md)** · [`examples/`](examples/) — `build_release_checklist.py`(메모·스타일 편집 HWPX 생성), `extract_text.py`(CLI 텍스트 추출), `find_objects.py`(OWPML 노드 추적) 등
221
305
  - **[📐 스키마 개요](docs/schema-overview.md)** · **[🔧 설치 검증](docs/installation.md)**
306
+ - **[🔬 HWPX 내부 실전 가이드](docs/internals/)** — 실제 한/글 동작에서 확인된 HWPUNIT·조판 캐시·목차 필드·OPC 재패킹·메모·오라클 한계
222
307
  - **[📖 전체 문서 (Sphinx)](https://airmang.github.io/python-hwpx/)** — API 레퍼런스·50+ 실전 패턴·FAQ
223
308
  - **[📝 CHANGELOG](CHANGELOG.md)** · **[🤝 CONTRIBUTING](CONTRIBUTING.md)** · **[👥 CONTRIBUTORS](CONTRIBUTORS.md)**
224
309
 
@@ -226,6 +311,11 @@ hwpx-analyze-template 보고서.hwpx
226
311
 
227
312
  버그 리포트, 기능 제안, PR 모두 환영합니다.
228
313
 
314
+ - **[help wanted 이슈](https://github.com/airmang/python-hwpx/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22)** — 지금 들어오기 좋은 입구입니다. 시작 전에 이슈에 코멘트로 방향을 남겨주세요.
315
+ - **[마일스톤](https://github.com/airmang/python-hwpx/milestones)** — 공개 로드맵입니다. 프로젝트가 어디로 가는지 여기서 보입니다.
316
+ - **[Discussions](https://github.com/airmang/python-hwpx/discussions)** — 질문·아이디어는 이슈 대신 여기로.
317
+ - **[내부 실전 가이드](docs/internals/)** — HWPX 내부 구조가 처음이라면 여기부터. 개발 흐름은 [CONTRIBUTING.md](CONTRIBUTING.md)에 있습니다.
318
+
229
319
  ```bash
230
320
  git clone https://github.com/airmang/python-hwpx.git
231
321
  cd python-hwpx
@@ -1,16 +1,35 @@
1
1
  <p align="center">
2
2
  <h1 align="center">python-hwpx</h1>
3
3
  <p align="center">
4
- <strong>한글 없이 HWPX 문서를 Python으로 읽고, 편집하고, 생성하고, 구조를 검증합니다.</strong>
4
+ <strong>한컴 없이 HWPX 안전하게 자동화하는 Python 계층 최소 범위 편집, 검증된 저작, 모든 쓰기에 영수증.</strong>
5
5
  </p>
6
6
  <p align="center">
7
7
  <a href="https://pypi.org/project/python-hwpx/"><img src="https://img.shields.io/pypi/v/python-hwpx?color=blue&label=PyPI" alt="PyPI"></a>
8
8
  <a href="https://pypi.org/project/python-hwpx/"><img src="https://img.shields.io/pypi/pyversions/python-hwpx" alt="Python"></a>
9
9
  <a href="https://github.com/airmang/python-hwpx/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-blue" alt="License"></a>
10
10
  <a href="https://airmang.github.io/python-hwpx/"><img src="https://img.shields.io/badge/docs-Sphinx-8CA1AF" alt="Docs"></a>
11
+ <a href="https://github.com/airmang/python-hwpx/actions/workflows/tests.yml"><img src="https://img.shields.io/github/actions/workflow/status/airmang/python-hwpx/tests.yml?branch=main&label=tests" alt="Tests"></a>
12
+ <a href="https://airmang.github.io/python-hwpx/corpus-metrics.html"><img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fairmang.github.io%2Fpython-hwpx%2F_static%2Fbadge-hancom-open.json" alt="Hancom open"></a>
11
13
  </p>
12
14
  </p>
13
15
 
16
+ <p align="center">한국어 | <a href="README_EN.md">English</a></p>
17
+
18
+ ---
19
+
20
+ > **python-hwpx는 한컴 없이 HWPX를 안전하게 자동화하는 Python 계층입니다.** 기존
21
+ > 문서는 최소 범위만 수정하고, 새 문서는 실제 한컴 수용이 검증된 형태로 생성하며,
22
+ > 모든 쓰기에 변경·보존·검증 영수증을 남기고, 완전한 해석과 렌더링은 전문 백엔드에
23
+ > 위임할 수 있습니다.
24
+
25
+ - **최소 범위 편집** — 미수정 part는 저장 시 바이트 그대로 유지됩니다(patch 경로
26
+ 바이트 보존 497/497, 동결 코퍼스 v2 · 2026-07-19).
27
+ - **검증된 저작** — 밑바닥 생성도 실제 한컴이 받아들이는 형태로 냅니다(산출물 한컴
28
+ 오픈 476/476 all-pass, 실저작 품질 게이트 58/58).
29
+ - **모든 쓰기에 영수증** — 대표 저장 경로는 [Safe Write Contract](docs/safe-write-contract.md)의
30
+ `MutationReport`(`hwpx.mutation-report/v1`)로 실제 쓰기 모드·보존 등급·검증 결과를
31
+ **측정해** 반환합니다.
32
+
14
33
  ---
15
34
 
16
35
  ## 🧩 HWPX Stack (3종)
@@ -21,11 +40,10 @@
21
40
  | 🔌 MCP 서버 | [`hwpx-mcp-server`](https://github.com/airmang/hwpx-mcp-server) | MCP 클라이언트(Claude Desktop, VS Code 등)에서 HWPX 조작 |
22
41
  | 🎯 에이전트 스킬 | [`hwpx-plugin`](https://github.com/airmang/hwpx-plugins) | 에이전트가 HWPX를 바로 쓰게 해주는 first-party 플러그인·스킬 번들 |
23
42
 
24
- `python-hwpx`는 HWPX 파싱·편집·생성을 제공하는 코어 라이브러리이며,
25
- `hwpx-mcp-server`와 `hwpx-plugin`은 같은 프로젝트가 직접 유지보수하는 first-party 연동 구성요소입니다.
26
- “first-party”는 프로젝트 유지보수 관계를 뜻하며, 한컴 또는 제3자의 공식 인증을 뜻하지 않습니다.
43
+ `hwpx-mcp-server`와 `hwpx-plugin`은 같은 프로젝트가 직접 유지보수하는 first-party 연동
44
+ 구성요소입니다(유지보수 관계를 뜻하며, 한컴 외부 기관의 인증을 뜻하지 않습니다).
27
45
 
28
- 현재 PyPI 공개 릴리스는 `python-hwpx 3.6.0`입니다. 일반
46
+ 현재 PyPI 공개 릴리스는 `python-hwpx 3.8.0`입니다. 일반
29
47
  `pip install python-hwpx`로 이 릴리스를 설치할 수 있습니다.
30
48
  현재 패키지 분류는 `Development Status :: 3 - Alpha`입니다. 이 분류는 API와 제품의
31
49
  성숙도를 나타내며, 공개 버전이나 플러그인의 최소 호환 버전을 대신하지 않습니다.
@@ -35,13 +53,22 @@
35
53
  ## 실측으로 말합니다 — Published Corpus
36
54
 
37
55
  이 스택의 산출물은 주장 대신 **실제 한컴오피스 전수 측정**으로 검증됩니다
38
- (동결 코퍼스 N=497, 2026-07-19, 상세·주의사항은
56
+ (동결 코퍼스 v2, N=497 산출물, 2026-07-19, 실한컴 12.0.0.3288 COM/GUI 오라클;
57
+ 상세·주의사항은
39
58
  [실측 코퍼스 메트릭](https://airmang.github.io/python-hwpx/corpus-metrics.html)):
40
59
 
41
- - **한컴 오픈 수용률 100%** (476/476 all-pass, 하한 ≥99.4%) · 파싱 96.2%
42
- - **미수정 영역 바이트 보존 100%** (497/497, patch 경로) · **개인정보 0-leak**
43
- - 렌더 검증 416건 + 정직 버킷(변경추적 문서의 PDF export는 한컴 자체가 거부 — 실측 한계로 발행)
44
- - 양식 채움 차등은 wild 공개 양식에서 49.2% — **낮은 숫자도 그대로 발행**하고 잔여 과제로 명기합니다
60
+ - **한컴 오픈 수용률 476/476 all-pass** (동결 코퍼스 v2 · 2026-07-19 · 실한컴 COM
61
+ `Open()` 판정 · rule-of-three 하한 99.37%) · 파싱 96.2%(458/476)
62
+ - **미수정 영역 바이트 보존 497/497** (patch 경로 한정, zip-part diff · 오라클 불요)
63
+ · **개인정보 0-leak** (35문서/합성 140값)
64
+ - 렌더 검증 416/476 (실한컴 `SaveAs("PDF")`) + 정직 버킷 43건(변경추적 문서의 PDF
65
+ export는 한컴 자체가 거부 — 실측 한계로 발행) + 미검증 17건
66
+ - wild 공개 양식 채움은 구조결함 픽스 후 **무음 서식파괴 16.7%**(판정 66조합, 못 담는 타깃은 typed 거부 35건·산출분 pass 17/28) — **낮은 숫자도 그대로 발행**하고 잔여(페이지 리플·표 shape)를 명명합니다
67
+
68
+ <p align="center">
69
+ <img src="https://raw.githubusercontent.com/airmang/python-hwpx/main/docs/images/redline-hancom.png" alt="python-hwpx가 남긴 변경추적과 AI 에이전트 메모가 실제 한/글에서 열린 화면" width="720">
70
+ </p>
71
+ <p align="center"><sub>python-hwpx로 작성한 변경추적(취소선·삽입)과 AI 에이전트 메모 — 실제 한/글에서 연 화면입니다.</sub></p>
45
72
 
46
73
  > 이 숫자들은 *생성물 수용률* 축입니다(우리가 만든 파일을 실제 한컴이 받아들이는가).
47
74
  > 문서 *파싱 recall*과는 다른 축이므로 파서 프로젝트 수치와 병치 비교하지 마세요.
@@ -150,6 +177,59 @@ hwpx-analyze-template 보고서.hwpx
150
177
 
151
178
  > 전체 기능·클래스·메서드 목록은 [사용 가이드](docs/usage.md)와 [API 레퍼런스](https://airmang.github.io/python-hwpx/api_reference.html)를 참고하세요.
152
179
 
180
+ ## 안전한 쓰기 계약 (Safe Write Contract)
181
+
182
+ 대표 저장 경로(`save_to_path` · `save_to_stream` · `to_bytes`)는 **요청한 보존 등급을
183
+ 쓰기 전에 판정하고, 실제로 무엇을 바꿨는지 측정한 영수증**을 돌려줍니다.
184
+
185
+ ```python
186
+ from hwpx.mutation_report import PreservationDowngradeError
187
+
188
+ # 영수증과 함께 저장 — 달성 가능한 가장 강한 보존 등급 자동 선택(mode="auto" 기본)
189
+ report = doc.save_to_path("결과.hwpx", return_report=True)
190
+ print(report.actual_mode) # "patch" | "rebuild"
191
+ print(report.preservation.untouched_part_payloads.to_dict()) # {"verified": 17, "changed": 0}
192
+
193
+ # patch 등급 강제 — 미달이면 아무것도 쓰지 않고 예외(fail-closed)
194
+ try:
195
+ doc.save_to_path("결과.hwpx", mode="patch", fallback="error")
196
+ except PreservationDowngradeError as exc:
197
+ print(exc.offending_parts, exc.suggestion)
198
+ ```
199
+
200
+ - `mode="patch" | "rebuild" | "auto"`(기본 `auto`) · `fallback="error" | "rebuild"`(기본 `error`)
201
+ - `mode="patch"` + `fallback="error"`에서 미수정 part의 바이트 동일성을 지킬 수 없으면
202
+ **아무것도 쓰지 않고** `PreservationDowngradeError`를 던집니다(무음 rebuild 없음).
203
+ - `MutationReport`는 `requestedMode`/`actualMode`/`fallbackUsed`, 변경 part와 좌표 명시
204
+ 범위, 보존 3층(part 페이로드·ZIP 레코드·전체 패키지), 검증 3항목(`passed`/`failed`/`not_performed`)을
205
+ **측정해** 반환합니다.
206
+
207
+ > 파라미터 전체와 `MutationReport` 스키마는 [안전한 쓰기 계약 문서](docs/safe-write-contract.md)를 참고하세요.
208
+
209
+ ## 지원 매트릭스
210
+
211
+ 능력 영역별 실제 등급입니다(동결 코퍼스 v2 · 2026-07-19 · 실한컴 12.0.0.3288 오라클).
212
+ 등급 어휘: **Parse / Preserve / Edit / Create / Render-verified /
213
+ Unsupported-but-preserved / Unsupported-and-rejected**.
214
+
215
+ | 능력 영역 | 상태 | 증거 |
216
+ |---|---|---|
217
+ | 문단·표 저작/편집 | Parse·Preserve·Edit·Create·Render-verified | 오픈 476/476 · 실저작 게이트 58/58 · 렌더 416 |
218
+ | 표 구조 변경(행·열·표, 오토핏) | Preserve·Edit | `hwpx.table_patch` · 바이트 보존 497/497 |
219
+ | 양식 채움(byte-splice) | Preserve·Edit | `hwpx.patch`·`table_patch`·`body_patch` · 보존 497/497 (wild 무음 서식파괴 16.7%·typed 거부 35/66, 잔여 명명) |
220
+ | 그림 삽입/치환 | Edit·Create | `add_picture`·`replace_picture` (복잡 개체는 한컴 확인 권장) |
221
+ | 차트 | Unsupported-but-preserved | 생성 API 없음 · 기존 차트 part는 patch 보존 |
222
+ | 수식 | Parse·Unsupported-but-preserved | 저작 API 없음 · 기존 수식 파싱·patch 보존 |
223
+ | 변경추적(redline) | Edit·Create | `add_tracked_*` · 실한컴 `IsTrackChange=1` (한컴이 PDF export 거부 → `render_unavailable` 정직 집계) |
224
+ | 메모(코멘트) | Edit·Create·Render-verified | `add_memo*` · 실 Windows 한컴 검증 |
225
+ | 각주/미주 | Edit·Create | `add_footnote`·`add_endnote` (렌더 독립 게이트 미측정) |
226
+ | 네이티브 목차/상호참조 | Create·Render-verified | `add_native_toc`·`toc_verify` · 구조 15/15 · 페이지 정합 5/5 |
227
+ | 암호화 HWPX | Unsupported-and-rejected | 복호화 없음 · 암호화 part는 파싱 단계 예외로 거부 |
228
+ | HWP 5.x 바이너리 | Unsupported-and-rejected | ZIP 아님 → 열기 시 `BadZipFile` (HWPX로 변환 후 사용) |
229
+ | 누름틀(form field) 생성 | Parse·Edit | 기존 필드 조회·서식보존 채움 · **신규 누름틀 생성 도구는 미제공** |
230
+
231
+ > 각 등급의 판정 근거와 상세 증거 포인터는 [지원 매트릭스 문서](docs/support-matrix.md)를 참고하세요.
232
+
153
233
  ## 대항 라이브러리 비교
154
234
 
155
235
  | | python-hwpx | pyhwpx | pyhwp |
@@ -174,6 +254,7 @@ hwpx-analyze-template 보고서.hwpx
174
254
  - **[🚀 빠른 시작](docs/quickstart.md)** · **[📚 사용 가이드](docs/usage.md)** — 첫 파일 열기부터 문단·표·메모·섹션 편집, 텍스트 추출·검증까지
175
255
  - **[💡 예제 모음](docs/examples.md)** · [`examples/`](examples/) — `build_release_checklist.py`(메모·스타일 편집 HWPX 생성), `extract_text.py`(CLI 텍스트 추출), `find_objects.py`(OWPML 노드 추적) 등
176
256
  - **[📐 스키마 개요](docs/schema-overview.md)** · **[🔧 설치 검증](docs/installation.md)**
257
+ - **[🔬 HWPX 내부 실전 가이드](docs/internals/)** — 실제 한/글 동작에서 확인된 HWPUNIT·조판 캐시·목차 필드·OPC 재패킹·메모·오라클 한계
177
258
  - **[📖 전체 문서 (Sphinx)](https://airmang.github.io/python-hwpx/)** — API 레퍼런스·50+ 실전 패턴·FAQ
178
259
  - **[📝 CHANGELOG](CHANGELOG.md)** · **[🤝 CONTRIBUTING](CONTRIBUTING.md)** · **[👥 CONTRIBUTORS](CONTRIBUTORS.md)**
179
260
 
@@ -181,6 +262,11 @@ hwpx-analyze-template 보고서.hwpx
181
262
 
182
263
  버그 리포트, 기능 제안, PR 모두 환영합니다.
183
264
 
265
+ - **[help wanted 이슈](https://github.com/airmang/python-hwpx/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22)** — 지금 들어오기 좋은 입구입니다. 시작 전에 이슈에 코멘트로 방향을 남겨주세요.
266
+ - **[마일스톤](https://github.com/airmang/python-hwpx/milestones)** — 공개 로드맵입니다. 프로젝트가 어디로 가는지 여기서 보입니다.
267
+ - **[Discussions](https://github.com/airmang/python-hwpx/discussions)** — 질문·아이디어는 이슈 대신 여기로.
268
+ - **[내부 실전 가이드](docs/internals/)** — HWPX 내부 구조가 처음이라면 여기부터. 개발 흐름은 [CONTRIBUTING.md](CONTRIBUTING.md)에 있습니다.
269
+
184
270
  ```bash
185
271
  git clone https://github.com/airmang/python-hwpx.git
186
272
  cd python-hwpx
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-hwpx"
7
- version = "3.6.0"
7
+ version = "3.8.0"
8
8
  description = "한글 없이 HWPX 문서를 열고, 편집하고, 생성하고, 검증하는 Python 자동화 라이브러리"
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  license = "Apache-2.0"
@@ -38,10 +38,16 @@ visual = [
38
38
  xlsx = [
39
39
  "openpyxl>=3.1",
40
40
  ]
41
+ # Document preview viewer equation rendering: LaTeX -> MathML. Optional; without
42
+ # it the preview fails closed to a LaTeX code block instead of dropping equations.
43
+ preview = [
44
+ "latex2mathml>=3.77",
45
+ ]
41
46
  dev = [
42
47
  "build>=1.0",
43
48
  "twine>=4.0",
44
49
  "pytest>=7.4",
50
+ "latex2mathml>=3.77",
45
51
  ]
46
52
  test = [
47
53
  "build>=1.0",
@@ -50,6 +56,7 @@ test = [
50
56
  "pytest>=7.4",
51
57
  "pytest-cov>=5.0",
52
58
  "ruff>=0.12",
59
+ "latex2mathml>=3.77",
53
60
  ]
54
61
  typecheck = [
55
62
  "mypy>=1.10",
@@ -109,6 +116,13 @@ python_version = "3.10"
109
116
  follow_imports = "skip"
110
117
  files = [
111
118
  "src/hwpx/document.py",
119
+ "src/hwpx/mutation_report.py",
120
+ "src/hwpx/equation/__init__.py",
121
+ "src/hwpx/equation/tokens.py",
122
+ "src/hwpx/equation/eqedit.py",
123
+ "src/hwpx/equation/mathml.py",
124
+ "src/hwpx/equation/render.py",
125
+ "src/hwpx/tools/document_viewer.py",
112
126
  "src/hwpx/_document/_units.py",
113
127
  "src/hwpx/_document/fields.py",
114
128
  "src/hwpx/_document/layout.py",
@@ -156,6 +170,13 @@ ignore_missing_imports = true
156
170
  # both central facades and every extracted/touched runtime owner to the gate.
157
171
  include = [
158
172
  "src/hwpx/document.py",
173
+ "src/hwpx/mutation_report.py",
174
+ "src/hwpx/equation/__init__.py",
175
+ "src/hwpx/equation/tokens.py",
176
+ "src/hwpx/equation/eqedit.py",
177
+ "src/hwpx/equation/mathml.py",
178
+ "src/hwpx/equation/render.py",
179
+ "src/hwpx/tools/document_viewer.py",
159
180
  "src/hwpx/_document/_units.py",
160
181
  "src/hwpx/_document/fields.py",
161
182
  "src/hwpx/_document/layout.py",
@@ -75,6 +75,10 @@ from .tools.layout_preview import (
75
75
  PreviewPage,
76
76
  render_layout_preview,
77
77
  )
78
+ from .tools.document_viewer import (
79
+ DocumentViewer,
80
+ render_document_viewer,
81
+ )
78
82
  from .ingest import (
79
83
  ConversionAttempt,
80
84
  DocumentConverter,
@@ -85,6 +89,10 @@ from .ingest import (
85
89
  HwpxMarkdownConverter,
86
90
  UnsupportedDocumentFormat,
87
91
  )
92
+ from .mutation_report import (
93
+ MutationReport,
94
+ PreservationDowngradeError,
95
+ )
88
96
  from .patch import (
89
97
  BytePreservingPatchResult,
90
98
  ParagraphTextPatch,
@@ -146,6 +154,8 @@ __all__ = [
146
154
  "ParagraphInfo",
147
155
  "PackageValidationReport",
148
156
  "BytePreservingPatchResult",
157
+ "MutationReport",
158
+ "PreservationDowngradeError",
149
159
  "PlanValidationReport",
150
160
  "ParagraphTextPatch",
151
161
  "PatchApplied",
@@ -200,6 +210,8 @@ __all__ = [
200
210
  "validate_package",
201
211
  "paragraph_patch",
202
212
  "render_layout_preview",
213
+ "render_document_viewer",
214
+ "DocumentViewer",
203
215
  "register_template",
204
216
  "table_compute",
205
217
  ]