python-hwpx-automation 7.2.0__tar.gz → 7.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. {python_hwpx_automation-7.2.0/src/python_hwpx_automation.egg-info → python_hwpx_automation-7.3.1}/PKG-INFO +18 -4
  2. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/README.md +17 -3
  3. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/pyproject.toml +1 -1
  4. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/form_fill.py +7 -2
  5. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/identity.json +10 -10
  6. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/agent/blueprint/native.py +12 -4
  7. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/agent/blueprint/replay.py +4 -1
  8. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/agent/cli.py +9 -1
  9. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/agent/commands.py +13 -1
  10. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/agent/document.py +12 -3
  11. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/__init__.py +8 -1
  12. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/builder/core.py +153 -11
  13. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/builder/report.py +5 -0
  14. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/template_analyzer.py +6 -1
  15. python_hwpx_automation-7.3.1/src/hwpx_automation/office/rendering/__main__.py +7 -0
  16. python_hwpx_automation-7.3.1/src/hwpx_automation/office/rendering/cli.py +400 -0
  17. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/rendering/mac_session.py +7 -1
  18. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/rendering/oracle.py +5 -0
  19. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/ops_services/package_validation.py +3 -5
  20. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/public-modules.json +6 -4
  21. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1/src/python_hwpx_automation.egg-info}/PKG-INFO +18 -4
  22. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/python_hwpx_automation.egg-info/SOURCES.txt +2 -0
  23. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/LICENSE +0 -0
  24. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/MANIFEST.in +0 -0
  25. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/NOTICE +0 -0
  26. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/setup.cfg +0 -0
  27. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/__init__.py +0 -0
  28. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/__init__.pyi +0 -0
  29. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/__main__.py +0 -0
  30. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/agent_document.py +0 -0
  31. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/api.py +0 -0
  32. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/blind_eval.py +0 -0
  33. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/capabilities.py +0 -0
  34. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/compat.py +0 -0
  35. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/configuration.py +0 -0
  36. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/core/__init__.py +0 -0
  37. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/core/content.py +0 -0
  38. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/core/context.py +0 -0
  39. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/core/diff.py +0 -0
  40. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/core/document.py +0 -0
  41. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/core/formatting.py +0 -0
  42. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/core/handles.py +0 -0
  43. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/core/locations.py +0 -0
  44. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/core/locator.py +0 -0
  45. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/core/plan.py +0 -0
  46. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/core/resources.py +0 -0
  47. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/core/search.py +0 -0
  48. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/core/transactions.py +0 -0
  49. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/core/txn.py +0 -0
  50. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/document_state.py +0 -0
  51. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/errors.py +0 -0
  52. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/execution_lock.py +0 -0
  53. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/fastmcp_adapter.py +0 -0
  54. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/form_output_models.py +0 -0
  55. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/handlers/__init__.py +0 -0
  56. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/handlers/_shared.py +0 -0
  57. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/handlers/agent_document.py +0 -0
  58. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/handlers/authoring.py +0 -0
  59. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/handlers/content_edit.py +0 -0
  60. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/handlers/form_fill.py +0 -0
  61. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/handlers/layout_style.py +0 -0
  62. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/handlers/quality_render.py +0 -0
  63. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/handlers/read_export.py +0 -0
  64. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/handlers/specialized.py +0 -0
  65. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/handlers/tracked_changes.py +0 -0
  66. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/handlers/workflow.py +0 -0
  67. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/hwp_converter.py +0 -0
  68. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/hwp_support.py +0 -0
  69. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/hwpx_ops.py +0 -0
  70. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/identity.py +0 -0
  71. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/ingest_adapters.py +0 -0
  72. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/markdown_plan.py +0 -0
  73. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/mcp_cli.py +0 -0
  74. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/metadata/tools_meta.py +0 -0
  75. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/mixed_form.py +0 -0
  76. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/mutation_models.py +0 -0
  77. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/network_policy.py +0 -0
  78. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/__init__.py +0 -0
  79. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/agent/__init__.py +0 -0
  80. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/agent/_batch_publication.py +0 -0
  81. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/agent/_batch_verification.py +0 -0
  82. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/agent/_text_scope.py +0 -0
  83. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/agent/blueprint/__init__.py +0 -0
  84. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/agent/blueprint/bundle.py +0 -0
  85. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/agent/blueprint/catalog.py +0 -0
  86. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/agent/blueprint/dump.py +0 -0
  87. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/agent/blueprint/mapping.py +0 -0
  88. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/agent/blueprint/model.py +0 -0
  89. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/agent/catalog.py +0 -0
  90. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/agent/form_plan.py +0 -0
  91. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/agent/model.py +0 -0
  92. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/agent/path.py +0 -0
  93. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/agent/query.py +0 -0
  94. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/agent/story.py +0 -0
  95. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/advanced_generators.py +0 -0
  96. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/builder/__init__.py +0 -0
  97. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/design/__init__.py +0 -0
  98. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/design/_support.py +0 -0
  99. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/design/composer.py +0 -0
  100. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/design/harvest.py +0 -0
  101. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/design/plan.py +0 -0
  102. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/design/profile.py +0 -0
  103. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/design/profiles/application_form/fragments/body.xml +0 -0
  104. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/design/profiles/application_form/fragments/heading.xml +0 -0
  105. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/design/profiles/application_form/fragments/info_table.xml +0 -0
  106. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/design/profiles/application_form/fragments/title.xml +0 -0
  107. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/design/profiles/application_form/profile.json +0 -0
  108. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/design/profiles/application_form/template.hwpx +0 -0
  109. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/design/profiles/home_notice/fragments/body.xml +0 -0
  110. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/design/profiles/home_notice/fragments/heading.xml +0 -0
  111. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/design/profiles/home_notice/fragments/title.xml +0 -0
  112. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/design/profiles/home_notice/profile.json +0 -0
  113. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/design/profiles/home_notice/template.hwpx +0 -0
  114. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/design/profiles/official_notice/fragments/body.xml +0 -0
  115. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/design/profiles/official_notice/fragments/heading.xml +0 -0
  116. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/design/profiles/official_notice/fragments/info_table.xml +0 -0
  117. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/design/profiles/official_notice/fragments/title.xml +0 -0
  118. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/design/profiles/official_notice/profile.json +0 -0
  119. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/design/profiles/official_notice/template.hwpx +0 -0
  120. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/design/profiles/report/fragments/body.xml +0 -0
  121. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/design/profiles/report/fragments/heading.xml +0 -0
  122. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/design/profiles/report/fragments/info_table.xml +0 -0
  123. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/design/profiles/report/fragments/title.xml +0 -0
  124. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/design/profiles/report/profile.json +0 -0
  125. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/design/profiles/report/template.hwpx +0 -0
  126. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/design/validator.py +0 -0
  127. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/presets/__init__.py +0 -0
  128. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/presets/proposal.py +0 -0
  129. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/report_parser.py +0 -0
  130. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/authoring/style_profile.py +0 -0
  131. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/charting/__init__.py +0 -0
  132. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/compliance/__init__.py +0 -0
  133. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/compliance/official_lint.py +0 -0
  134. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/compliance/pii.py +0 -0
  135. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/document_ops/__init__.py +0 -0
  136. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/document_ops/comparison.py +0 -0
  137. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/document_ops/mail_merge.py +0 -0
  138. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/document_ops/redline.py +0 -0
  139. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/evalplan/__init__.py +0 -0
  140. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/evalplan/runtime.py +0 -0
  141. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/exam/__init__.py +0 -0
  142. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/exam/compose.py +0 -0
  143. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/exam/ir.py +0 -0
  144. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/exam/measure.py +0 -0
  145. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/exam/parser.py +0 -0
  146. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/exam/profile.py +0 -0
  147. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/form_fill/__init__.py +0 -0
  148. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/form_fill/classification.py +0 -0
  149. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/form_fill/fill_residue.py +0 -0
  150. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/form_fill/fit/__init__.py +0 -0
  151. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/form_fill/fit/apply.py +0 -0
  152. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/form_fill/fit/engine.py +0 -0
  153. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/form_fill/fit/measure.py +0 -0
  154. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/form_fill/fit/policy.py +0 -0
  155. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/form_fill/fit/report.py +0 -0
  156. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/form_fill/fit/seal.py +0 -0
  157. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/form_fill/fit/wordbox.py +0 -0
  158. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/form_fill/guidance.py +0 -0
  159. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/form_fill/quality.py +0 -0
  160. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/form_fill/split_run.py +0 -0
  161. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/form_fill/template_formfit.py +0 -0
  162. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/house_style/__init__.py +0 -0
  163. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/house_style/composition.py +0 -0
  164. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/house_style/data/bank.json +0 -0
  165. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/house_style/data/genres.json +0 -0
  166. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/house_style/gianmun.py +0 -0
  167. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/house_style/org_chart.py +0 -0
  168. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/quality/__init__.py +0 -0
  169. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/quality/page_guard.py +0 -0
  170. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/rendering/__init__.py +0 -0
  171. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/rendering/_hancom_open_rate.ps1 +0 -0
  172. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/rendering/_refresh_hwpx_mac.applescript +0 -0
  173. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/rendering/_render_hwpx.ps1 +0 -0
  174. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/rendering/_render_hwpx_mac.applescript +0 -0
  175. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/rendering/block_splits.py +0 -0
  176. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/rendering/detectors.py +0 -0
  177. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/rendering/diff.py +0 -0
  178. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/rendering/fixture_corpus.py +0 -0
  179. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/rendering/page_qa.py +0 -0
  180. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/rendering/qa_contracts.py +0 -0
  181. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/rendering/qa_metrics.py +0 -0
  182. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/rendering/worker.py +0 -0
  183. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/utilities/__init__.py +0 -0
  184. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/office/utilities/table_compute.py +0 -0
  185. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/ops_services/__init__.py +0 -0
  186. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/ops_services/_border_fill.py +0 -0
  187. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/ops_services/composition.py +0 -0
  188. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/ops_services/content_layout.py +0 -0
  189. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/ops_services/context.py +0 -0
  190. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/ops_services/form_fields.py +0 -0
  191. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/ops_services/media.py +0 -0
  192. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/ops_services/memo_style.py +0 -0
  193. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/ops_services/planning.py +0 -0
  194. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/ops_services/preview_export.py +0 -0
  195. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/ops_services/read_query.py +0 -0
  196. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/ops_services/save_policy.py +0 -0
  197. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/ops_services/tables.py +0 -0
  198. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/ops_services/transactions.py +0 -0
  199. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/preview_output_models.py +0 -0
  200. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/py.typed +0 -0
  201. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/quality.py +0 -0
  202. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/quality_generation.py +0 -0
  203. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/resources.py +0 -0
  204. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/runtime.py +0 -0
  205. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/runtime_services.py +0 -0
  206. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/server.py +0 -0
  207. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/storage.py +0 -0
  208. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/tool_bindings.py +0 -0
  209. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/tool_contract.py +0 -0
  210. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/upstream.py +0 -0
  211. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/utils/__init__.py +0 -0
  212. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/utils/helpers.py +0 -0
  213. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/utils/read_budget.py +0 -0
  214. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/visual_qa.py +0 -0
  215. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/workflow/__init__.py +0 -0
  216. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/workflow/adapters.py +0 -0
  217. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/workflow/dispatcher.py +0 -0
  218. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/workflow/evidence_lineage.py +0 -0
  219. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/workflow/models.py +0 -0
  220. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/workflow/policy.py +0 -0
  221. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/workflow/render_contracts.py +0 -0
  222. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/workflow/render_metrics.py +0 -0
  223. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/workflow/render_queue.py +0 -0
  224. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/workflow/render_security.py +0 -0
  225. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/workflow/render_transport.py +0 -0
  226. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/workflow/rendering.py +0 -0
  227. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/workflow/service.py +0 -0
  228. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/workflow/state_machine.py +0 -0
  229. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/workflow/store.py +0 -0
  230. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/hwpx_automation/workspace.py +0 -0
  231. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/python_hwpx_automation.egg-info/dependency_links.txt +0 -0
  232. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/python_hwpx_automation.egg-info/entry_points.txt +0 -0
  233. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/python_hwpx_automation.egg-info/requires.txt +0 -0
  234. {python_hwpx_automation-7.2.0 → python_hwpx_automation-7.3.1}/src/python_hwpx_automation.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-hwpx-automation
3
- Version: 7.2.0
3
+ Version: 7.3.1
4
4
  Summary: Task-oriented HWPX document automation for Python, with an optional MCP adapter.
5
5
  Author: Kohkyuhyun
6
6
  License-Expression: Apache-2.0
@@ -74,8 +74,9 @@ Dynamic: license-file
74
74
 
75
75
  <!-- release-state: release-approved -->
76
76
  > [!NOTE]
77
- > 발행이 승인된 7.2.0 후보 (release-approved; 원격 발행 대기): `python-hwpx 6.5.0 → python-hwpx-automation 7.2.0 → hwpx-plugin 2.3.0` (`5e5c23651f92785a`).
78
- > 공개 트레인: `python-hwpx 6.4.0 → python-hwpx-automation 7.1.0 → hwpx-plugin 2.2.0` (`ba0211fc854a0a97`).
77
+ > 발행이 승인된 7.3.1 후보 (release-approved; 원격 발행 대기): `python-hwpx 6.5.0 → python-hwpx-automation 7.3.1 → hwpx-plugin 2.3.0` (`5e5c23651f92785a`).
78
+ > 공개 트레인: `python-hwpx 6.5.0 → python-hwpx-automation 7.2.0 → hwpx-plugin 2.3.0` (`5e5c23651f92785a`).
79
+ > [발행·설치 영수증](docs/releases/2026-09-22-trustworthy-edit-engine.md)
79
80
  > 공개 좌표는 core·automation PyPI와 plugin GitHub
80
81
  > Release·marketplace·실제 marketplace 설치 관찰 후에만 승격합니다.
81
82
  > [릴리스 runbook](docs/release-runbook.md)
@@ -132,7 +133,7 @@ Gemini CLI는 `~/.gemini/settings.json`, Cursor·Windsurf는 각 에디터의 MC
132
133
  "command": "uvx",
133
134
  "args": [
134
135
  "--from",
135
- "python-hwpx-automation[mcp]==7.1.0",
136
+ "python-hwpx-automation[mcp]==7.2.0",
136
137
  "hwpx-automation-mcp"
137
138
  ],
138
139
  "env": {
@@ -156,6 +157,19 @@ Gemini CLI는 `~/.gemini/settings.json`, Cursor·Windsurf는 각 에디터의 MC
156
157
  기존 `hwpx-mcp-server` 배포·import·콘솔·설정 키도 6.x 동안 그대로 동작합니다 —
157
158
  전체 목록과 유지 규칙: [6.x 호환 표면](docs/compatibility-6x.md)
158
159
 
160
+ ## 한컴으로 PDF 렌더하기
161
+
162
+ 한컴(macOS 한컴오피스 한글 또는 Windows 한글 COM)이 있는 컴퓨터에서 문서 하나를
163
+ PDF와 쪽 PNG로 렌더합니다. 별도 환경에서 실행하므로 호출하는 쪽의 python-hwpx
164
+ 고정 버전을 건드리지 않습니다.
165
+
166
+ ```bash
167
+ uvx --from "python-hwpx-automation[oracle]==7.3.1" \
168
+ hwpx render-pdf in.hwpx out.pdf --png out/page --json
169
+ ```
170
+
171
+ JSON 결과·종료 코드·플랫폼 요구 사항: [render-pdf](docs/render-pdf.md)
172
+
159
173
  ## 무엇을 하나
160
174
 
161
175
  기본 모드에서 다수의 HWPX 도구를 제공하며, 고급 모드(`HWPX_AUTOMATION_ADVANCED=1`)에서 점검·검증용 도구가 추가됩니다.
@@ -14,8 +14,9 @@
14
14
 
15
15
  <!-- release-state: release-approved -->
16
16
  > [!NOTE]
17
- > 발행이 승인된 7.2.0 후보 (release-approved; 원격 발행 대기): `python-hwpx 6.5.0 → python-hwpx-automation 7.2.0 → hwpx-plugin 2.3.0` (`5e5c23651f92785a`).
18
- > 공개 트레인: `python-hwpx 6.4.0 → python-hwpx-automation 7.1.0 → hwpx-plugin 2.2.0` (`ba0211fc854a0a97`).
17
+ > 발행이 승인된 7.3.1 후보 (release-approved; 원격 발행 대기): `python-hwpx 6.5.0 → python-hwpx-automation 7.3.1 → hwpx-plugin 2.3.0` (`5e5c23651f92785a`).
18
+ > 공개 트레인: `python-hwpx 6.5.0 → python-hwpx-automation 7.2.0 → hwpx-plugin 2.3.0` (`5e5c23651f92785a`).
19
+ > [발행·설치 영수증](docs/releases/2026-09-22-trustworthy-edit-engine.md)
19
20
  > 공개 좌표는 core·automation PyPI와 plugin GitHub
20
21
  > Release·marketplace·실제 marketplace 설치 관찰 후에만 승격합니다.
21
22
  > [릴리스 runbook](docs/release-runbook.md)
@@ -72,7 +73,7 @@ Gemini CLI는 `~/.gemini/settings.json`, Cursor·Windsurf는 각 에디터의 MC
72
73
  "command": "uvx",
73
74
  "args": [
74
75
  "--from",
75
- "python-hwpx-automation[mcp]==7.1.0",
76
+ "python-hwpx-automation[mcp]==7.2.0",
76
77
  "hwpx-automation-mcp"
77
78
  ],
78
79
  "env": {
@@ -96,6 +97,19 @@ Gemini CLI는 `~/.gemini/settings.json`, Cursor·Windsurf는 각 에디터의 MC
96
97
  기존 `hwpx-mcp-server` 배포·import·콘솔·설정 키도 6.x 동안 그대로 동작합니다 —
97
98
  전체 목록과 유지 규칙: [6.x 호환 표면](docs/compatibility-6x.md)
98
99
 
100
+ ## 한컴으로 PDF 렌더하기
101
+
102
+ 한컴(macOS 한컴오피스 한글 또는 Windows 한글 COM)이 있는 컴퓨터에서 문서 하나를
103
+ PDF와 쪽 PNG로 렌더합니다. 별도 환경에서 실행하므로 호출하는 쪽의 python-hwpx
104
+ 고정 버전을 건드리지 않습니다.
105
+
106
+ ```bash
107
+ uvx --from "python-hwpx-automation[oracle]==7.3.1" \
108
+ hwpx render-pdf in.hwpx out.pdf --png out/page --json
109
+ ```
110
+
111
+ JSON 결과·종료 코드·플랫폼 요구 사항: [render-pdf](docs/render-pdf.md)
112
+
99
113
  ## 무엇을 하나
100
114
 
101
115
  기본 모드에서 다수의 HWPX 도구를 제공하며, 고급 모드(`HWPX_AUTOMATION_ADVANCED=1`)에서 점검·검증용 도구가 추가됩니다.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-hwpx-automation"
7
- version = "7.2.0"
7
+ version = "7.3.1"
8
8
  description = "Task-oriented HWPX document automation for Python, with an optional MCP adapter."
9
9
  readme = "README.md"
10
10
  authors = [{ name = "Kohkyuhyun" }]
@@ -1122,10 +1122,15 @@ def _runtime_validation(path: str) -> dict[str, Any]:
1122
1122
  {
1123
1123
  "part": getattr(issue, "part_name", None),
1124
1124
  "message": getattr(issue, "message", str(issue)),
1125
+ "severity": getattr(issue, "severity", "error"),
1125
1126
  }
1126
1127
  for issue in getattr(document_report, "issues", ())
1127
1128
  ]
1128
- structure = {"ok": not structure_issues, "issues": structure_issues}
1129
+ # a schema warning from python-hwpx does not block the hand-off; only its errors do
1130
+ structure = {
1131
+ "ok": not any(issue["severity"] == "error" for issue in structure_issues),
1132
+ "issues": structure_issues,
1133
+ }
1129
1134
  if validate_package is None:
1130
1135
  package = _dependency_unavailable_report(
1131
1136
  "python-hwpx>=2.10.3 is required for HWPX package validation",
@@ -1208,7 +1213,7 @@ def _issue_payload(issue: Any) -> dict[str, Any]:
1208
1213
  return {
1209
1214
  "part": getattr(issue, "part_name", None),
1210
1215
  "message": getattr(issue, "message", str(issue)),
1211
- "level": getattr(issue, "level", "error"),
1216
+ "level": getattr(issue, "severity", "error"),
1212
1217
  }
1213
1218
 
1214
1219
 
@@ -7,26 +7,26 @@
7
7
  "candidate": {
8
8
  "pythonHwpx": "6.5.0",
9
9
  "canonicalDistribution": "python-hwpx-automation",
10
- "canonicalAutomation": "7.2.0",
10
+ "canonicalAutomation": "7.3.1",
11
11
  "compatibilityDistribution": "hwpx-mcp-server",
12
- "compatibility": "7.2.0",
12
+ "compatibility": "7.3.1",
13
13
  "plugin": "2.3.0",
14
14
  "contractHash": "5e5c23651f92785a"
15
15
  },
16
16
  "currentPublic": {
17
- "pythonHwpx": "6.4.0",
17
+ "pythonHwpx": "6.5.0",
18
18
  "primaryDistribution": "python-hwpx-automation",
19
- "primaryApplication": "7.1.0",
20
- "plugin": "2.2.0",
21
- "contractHash": "ba0211fc854a0a97"
19
+ "primaryApplication": "7.2.0",
20
+ "plugin": "2.3.0",
21
+ "contractHash": "5e5c23651f92785a"
22
22
  },
23
23
  "promotionGate": "Three states are mandatory: unreleased-candidate while auditing; release-approved only after separate owner approval and while currentPublic still names the previously observed coherent stack; released only in a follow-up commit after remote truth is observed for core, canonical automation, the compatibility distribution, the plugin GitHub release, the marketplace entry, and a real marketplace install. The automation tag workflow publishes only release-approved, leaves currentPublic unchanged, and hands an attached receipt to plugin publication.",
24
24
  "previousPublic": {
25
- "pythonHwpx": "6.4.0",
25
+ "pythonHwpx": "6.5.0",
26
26
  "primaryDistribution": "python-hwpx-automation",
27
- "primaryApplication": "7.1.0",
28
- "plugin": "2.2.0",
29
- "contractHash": "ba0211fc854a0a97"
27
+ "primaryApplication": "7.2.0",
28
+ "plugin": "2.3.0",
29
+ "contractHash": "5e5c23651f92785a"
30
30
  },
31
31
  "publicationEvidence": null
32
32
  },
@@ -19,6 +19,15 @@ from ..model import AgentContractError
19
19
 
20
20
  _HWP_UNITS_PER_MM = 7200 / 25.4
21
21
  _INLINE_KINDS = frozenset({"table", "picture", "shape", "footnote", "endnote"})
22
+ #: Names a blueprint may give a click-here field. Hancom opens the field only
23
+ #: under its own type name, CLICK_HERE.
24
+ _CLICK_HERE_NAMES = frozenset({"FORM", "CLICKHERE", "NURUMTUL", "누름틀"})
25
+
26
+
27
+ def hancom_field_type(field_type: object) -> str | None:
28
+ """CLICK_HERE for any name of a click-here field, None for another field type."""
29
+ normalized = str(field_type or "ClickHere").replace("_", "").replace("-", "").upper()
30
+ return "CLICK_HERE" if normalized in _CLICK_HERE_NAMES else None
22
31
 
23
32
 
24
33
  @dataclass(frozen=True, slots=True)
@@ -463,9 +472,8 @@ class TypedNativeBridge:
463
472
  def _create_form_field(self, node_id: str, paragraph: Any) -> None:
464
473
  node = self.nodes[node_id]
465
474
  properties = node["properties"]
466
- field_type = str(properties.get("fieldType") or "ClickHere")
467
- normalized_type = field_type.replace("_", "").replace("-", "").upper()
468
- if normalized_type not in {"FORM", "CLICKHERE", "NURUMTUL", "누름틀"}:
475
+ field_type = hancom_field_type(properties.get("fieldType"))
476
+ if field_type is None:
469
477
  raise AgentContractError("unsupported_content", "only FORM fields are replayable", target=node_id)
470
478
  host_id = self.references.get((node_id, "hostRun"))
471
479
  end_id = self.references.get((node_id, "endRun"))
@@ -486,7 +494,7 @@ class TypedNativeBridge:
486
494
  "id": begin_id,
487
495
  "fieldid": field_id,
488
496
  "name": str(properties.get("name") or "")[:512],
489
- "type": "CLICK_HERE" if normalized_type in {"FORM", "CLICKHERE"} else field_type,
497
+ "type": field_type,
490
498
  "editable": "0" if properties.get("readOnly") else "1",
491
499
  "dirty": "0",
492
500
  "zorder": "-1",
@@ -23,7 +23,7 @@ from .bundle import BlueprintBundle, read_blueprint_bundle
23
23
  from .dump import dump_document_blueprint
24
24
  from .mapping import ReplayPlan, materialize_dependencies, plan_replay
25
25
  from .model import BlueprintReplayResult, validate_replay_request
26
- from .native import CreatedBinding, TypedNativeBridge
26
+ from .native import CreatedBinding, TypedNativeBridge, hancom_field_type
27
27
 
28
28
  IdempotencyStore = MutableMapping[str, Any]
29
29
  FaultInjector = Callable[[str, int | None], None]
@@ -80,6 +80,9 @@ def _semantic_nodes(
80
80
  identity = mapped.get("identity") if mapped else None
81
81
  if isinstance(identity, Mapping) and identity.get("name"):
82
82
  properties["style"] = identity["name"]
83
+ if node["kind"] == "form-field" and "fieldType" in properties:
84
+ # A click-here field is written, and so read back, as CLICK_HERE whatever the blueprint called it.
85
+ properties["fieldType"] = hancom_field_type(properties["fieldType"]) or properties["fieldType"]
83
86
  normalized.append(
84
87
  {
85
88
  "blueprintId": str(node["blueprintId"]),
@@ -591,10 +591,18 @@ def main(
591
591
  stdin = cast(TextIO, stdin or sys.stdin)
592
592
  stdout = cast(TextIO, stdout or sys.stdout)
593
593
  stderr = cast(TextIO, stderr or sys.stderr)
594
+ arguments = list(argv) if argv is not None else sys.argv[1:]
595
+ # Dispatched ahead of the parser, whose help and choice errors stay
596
+ # byte-identical to the frozen core CLI. The rendering owner is imported
597
+ # only here, so every other command stays free of the Hancom side.
598
+ if arguments[:1] == ["render-pdf"]:
599
+ from ..rendering.cli import main as render_main
600
+
601
+ return render_main(arguments, prog="hwpx", stdout=stdout, stderr=stderr)
594
602
  parser = build_parser()
595
603
  try:
596
604
  with contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr):
597
- args = parser.parse_args(list(argv) if argv is not None else None)
605
+ args = parser.parse_args(arguments)
598
606
  if args.command == "help":
599
607
  if args.kind == "blueprint":
600
608
  if args.json_help:
@@ -73,6 +73,8 @@ _PARAGRAPH_ALIGNMENTS = frozenset(
73
73
  _TABLE_ALIGNMENTS = frozenset({"LEFT", "CENTER", "RIGHT", "INSIDE", "OUTSIDE"})
74
74
  _VERTICAL_ALIGNMENTS = frozenset({"TOP", "CENTER", "BOTTOM"})
75
75
  _INLINE_KINDS = frozenset({"table", "picture", "shape", "footnote", "endnote"})
76
+ #: Notes sit in an hp:ctrl: Hancom drops a note outside one and crashes on an empty one.
77
+ _NOTE_TAGS = frozenset({"footNote", "endNote"})
76
78
  _SHAPE_KINDS = frozenset({"line", "rect", "ellipse", "arc", "polygon", "curve", "connectLine"})
77
79
 
78
80
  # Creation needs a few geometry fields that are readable, rather than editable,
@@ -450,7 +452,12 @@ def _insert_inline(
450
452
  _tag_like(paragraph.element, "run"),
451
453
  {"charPrIDRef": paragraph.char_pr_id_ref or "0"},
452
454
  )
453
- run.append(element)
455
+ if _local_name(element) in _NOTE_TAGS:
456
+ control = run.makeelement(_tag_like(paragraph.element, "ctrl"), {})
457
+ control.append(element)
458
+ run.append(control)
459
+ else:
460
+ run.append(element)
454
461
  if index >= len(siblings):
455
462
  paragraph.element.append(run)
456
463
  else:
@@ -458,6 +465,8 @@ def _insert_inline(
458
465
  anchor_run = anchor.getparent() if hasattr(anchor, "getparent") else next(
459
466
  child for child in paragraph.element if anchor in list(child)
460
467
  )
468
+ if _local_name(anchor_run) == "ctrl":
469
+ anchor_run = anchor_run.getparent()
461
470
  paragraph.element.insert(list(paragraph.element).index(anchor_run), run)
462
471
  paragraph.section.mark_dirty()
463
472
  return run
@@ -468,6 +477,9 @@ def _remove_inline_element(element: Any, paragraph: Any) -> None:
468
477
  child for child in paragraph.element if element in list(child)
469
478
  )
470
479
  run.remove(element)
480
+ if _local_name(run) == "ctrl" and not list(run):
481
+ control, run = run, run.getparent()
482
+ run.remove(control)
471
483
  if not list(run) and run in list(paragraph.element):
472
484
  paragraph.element.remove(run)
473
485
  paragraph.section.mark_dirty()
@@ -55,6 +55,15 @@ def _mm(value: object) -> float | None:
55
55
  return round(units / _HWP_UNITS_PER_MM, 3)
56
56
 
57
57
 
58
+ def _drawn_page_size(page_size: Any) -> tuple[int, int]:
59
+ """Width and height as Hancom draws the page: as stored for
60
+ ``landscape="WIDELY"`` (portrait), turned otherwise (``NARROWLY``)."""
61
+
62
+ if page_size.orientation == "WIDELY":
63
+ return page_size.width, page_size.height
64
+ return page_size.height, page_size.width
65
+
66
+
58
67
  def _first_child(element: Any, local_name: str) -> Any | None:
59
68
  for child in element:
60
69
  if _local_name(child) == local_name:
@@ -249,7 +258,7 @@ class HwpxAgentDocument:
249
258
  self._add_record(root)
250
259
  for section_index, section in enumerate(self.document.sections, start=1):
251
260
  section_path = SemanticPath().child(indexed_segment("section", section_index))
252
- page_size = section.properties.page_size
261
+ page_width, page_height = _drawn_page_size(section.properties.page_size)
253
262
  section_record = NodeRecord(
254
263
  kind="section",
255
264
  path=section_path.canonical,
@@ -259,8 +268,8 @@ class HwpxAgentDocument:
259
268
  "index": section_index,
260
269
  "partId": Path(section.part_name).name,
261
270
  "paragraphCount": len(section.paragraphs),
262
- "pageWidthMm": _mm(page_size.width),
263
- "pageHeightMm": _mm(page_size.height),
271
+ "pageWidthMm": _mm(page_width),
272
+ "pageHeightMm": _mm(page_height),
264
273
  },
265
274
  native=section,
266
275
  parent_path="/",
@@ -2030,6 +2030,10 @@ def _normalize_v2_page(value: Any) -> BuilderPageSize | None:
2030
2030
  width = _float_value(value.get("widthMm", value.get("width_mm")), default=210)
2031
2031
  height = _float_value(value.get("heightMm", value.get("height_mm")), default=297)
2032
2032
  orientation = str(value.get("orientation") or "PORTRAIT").strip() or "PORTRAIT"
2033
+ # Hancom's own landscape values, as read from a document: WIDELY is
2034
+ # portrait, NARROWLY landscape. Hand the builder the plain names so every
2035
+ # python-hwpx version draws the same page.
2036
+ orientation = {"WIDELY": "PORTRAIT", "NARROWLY": "LANDSCAPE"}.get(orientation.upper(), orientation)
2033
2037
  return BuilderPageSize(width_mm=width, height_mm=height, orientation=orientation)
2034
2038
 
2035
2039
 
@@ -2681,7 +2685,10 @@ def _flow_oversized_authored_table(table: Any) -> None:
2681
2685
  section = table.paragraph.section
2682
2686
  page = section.properties.page_size
2683
2687
  margins = section.properties.page_margins
2684
- body_height = page.height - margins.top - margins.bottom
2688
+ # Hancom turns every page but landscape="WIDELY", so a landscape page's
2689
+ # drawn height is its stored width.
2690
+ drawn_height = page.height if page.orientation == "WIDELY" else page.width
2691
+ body_height = drawn_height - margins.top - margins.bottom
2685
2692
  if body_height > 0 and table.height > body_height:
2686
2693
  table.set_treat_as_char(False)
2687
2694
 
@@ -1,8 +1,11 @@
1
1
  # SPDX-License-Identifier: Apache-2.0
2
2
  from __future__ import annotations
3
3
 
4
+ from contextvars import ContextVar
4
5
  from dataclasses import dataclass, field
6
+ from datetime import datetime, timezone
5
7
  from importlib import import_module
8
+ from inspect import signature
6
9
  from os import PathLike
7
10
  from pathlib import Path
8
11
  from typing import Any, ClassVar, Mapping, Sequence, cast
@@ -23,6 +26,29 @@ BuilderChild = (
23
26
  )
24
27
  _HWP_UNITS_PER_MM = 7200 / 25.4
25
28
  _A4_HWP_SIZE = (59528, 84188)
29
+ #: ``hp:pagePr@landscape`` as Hancom writes it: WIDELY draws the page as stored
30
+ #: (portrait), NARROWLY turns it (landscape).
31
+ _HANCOM_PAGE_ORIENTATION = {
32
+ "PORTRAIT": "WIDELY",
33
+ "WIDELY": "WIDELY",
34
+ "LANDSCAPE": "NARROWLY",
35
+ "NARROWLY": "NARROWLY",
36
+ }
37
+
38
+
39
+ def _hancom_page_form(orientation: str, width: int, height: int) -> tuple[str, int, int]:
40
+ """Return *orientation* and the page size in Hancom's own form.
41
+
42
+ Hancom stores both orientations with the paper's portrait size and turns
43
+ the page for NARROWLY. python-hwpx 6.5 writes the value as given and later
44
+ versions write the same form, so every core draws the same page. Unknown
45
+ values pass through unchanged.
46
+ """
47
+
48
+ value = _HANCOM_PAGE_ORIENTATION.get(orientation.strip().upper())
49
+ if value is None:
50
+ return orientation, width, height
51
+ return value, min(width, height), max(width, height)
26
52
 
27
53
  # Builder presets hook at Document.lower(), where a single
28
54
  # preset context can be passed into Heading/Run/Bullet lowering without
@@ -257,6 +283,8 @@ class Paragraph:
257
283
  children: Sequence[Run | PageNumber] = field(default_factory=tuple)
258
284
  align: str | None = None
259
285
  style: str | None = None
286
+ #: Optional caller name; ``save_to_path`` reports where it was written.
287
+ key: str | None = None
260
288
 
261
289
  def _alignment_para_pr(self, document: HwpxDocument) -> str | None:
262
290
  """Resolve ``align`` into the paragraph-property reference that emits it.
@@ -503,6 +531,8 @@ class Table:
503
531
  merges: Sequence[str] = field(default_factory=tuple)
504
532
  header_shading: str | None = None
505
533
  column_widths: Sequence[int | float] = field(default_factory=tuple)
534
+ #: Optional caller name; ``save_to_path`` reports the paragraph holding it.
535
+ key: str | None = None
506
536
 
507
537
  def lower(
508
538
  self,
@@ -794,13 +824,117 @@ def _merge_flags(*flag_sets: dict[str, bool]) -> dict[str, bool]:
794
824
  return merged
795
825
 
796
826
 
827
+ # Keyed Paragraph/Table nodes -> the paragraph they were written to. While a
828
+ # Document collects anchors, each keyed child records the paragraph counts
829
+ # before it is lowered; after its section is lowered, the first paragraph added
830
+ # since then is its element. Positions are read at the very end, by identity,
831
+ # so a native TOC inserted afterwards cannot shift them.
832
+ _AnchorEntry = tuple[str, tuple[int, ...], Any]
833
+ _ANCHOR_SINK: ContextVar[list[_AnchorEntry] | None] = ContextVar(
834
+ "hwpx_builder_anchor_sink", default=None
835
+ )
836
+
837
+
838
+ def _paragraph_counts(document: HwpxDocument) -> tuple[int, ...]:
839
+ return tuple(len(section.paragraphs) for section in document.oxml.sections)
840
+
841
+
842
+ def _anchor_mark(document: HwpxDocument, child: object) -> None:
843
+ sink = _ANCHOR_SINK.get()
844
+ key = getattr(child, "key", None)
845
+ if sink is not None and key is not None:
846
+ sink.append((key, _paragraph_counts(document), None))
847
+
848
+
849
+ def _anchor_resolve(document: HwpxDocument) -> None:
850
+ sink = _ANCHOR_SINK.get()
851
+ if not sink:
852
+ return
853
+ sections = document.oxml.sections
854
+ for position, (key, before, element) in enumerate(sink):
855
+ if element is not None:
856
+ continue
857
+ for index, section in enumerate(sections):
858
+ start = before[index] if index < len(before) else 0
859
+ paragraphs = section.paragraphs
860
+ if len(paragraphs) > start:
861
+ sink[position] = (key, before, paragraphs[start].element)
862
+ break
863
+
864
+
865
+ def _anchor_positions(
866
+ document: HwpxDocument, sink: Sequence[_AnchorEntry]
867
+ ) -> dict[str, dict[str, int]]:
868
+ positions: dict[int, dict[str, int]] = {}
869
+ for section_index, section in enumerate(document.oxml.sections):
870
+ for paragraph_index, paragraph in enumerate(section.paragraphs):
871
+ positions[id(paragraph.element)] = {
872
+ "section": section_index,
873
+ "paragraph": paragraph_index,
874
+ }
875
+ return {
876
+ key: dict(positions[id(element)])
877
+ for key, _before, element in sink
878
+ if element is not None and id(element) in positions
879
+ }
880
+
881
+
882
+ def _check_unique_keys(sections: Sequence["Section"]) -> None:
883
+ seen: set[str] = set()
884
+ for section in sections:
885
+ for child in section.children:
886
+ key = getattr(child, "key", None)
887
+ if key is None:
888
+ continue
889
+ if key in seen:
890
+ raise ValueError(f"duplicate builder node key: {key!r}")
891
+ seen.add(key)
892
+
893
+
894
+ def _write_package_metadata(document: HwpxDocument, metadata: Metadata) -> None:
895
+ """Write *metadata* to ``content.hpf`` (Hancom: File > Document Info).
896
+
897
+ OPF has no organization field, so ``organization`` stays in the save
898
+ report's ``metadata`` only. Created/modified are stamped at build time
899
+ instead of keeping the blank template's dates.
900
+ """
901
+
902
+ stamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
903
+ document.parts.set_document_metadata(
904
+ title=metadata.title or None,
905
+ creator=metadata.author or None,
906
+ created_date=stamp,
907
+ modified_date=stamp,
908
+ )
909
+
910
+
911
+ _SCHEMA_CHECK_NOT_RUN = "OWPML schema check could not run"
912
+
913
+
914
+ def _schema_lint(document_report: object) -> str:
915
+ """``schema_lint`` from python-hwpx's own validation, never assumed.
916
+
917
+ python-hwpx 6.6+ checks headers and sections against the full OWPML schema
918
+ and reports violations as warnings. Older cores only check a lax section
919
+ stub, so nothing was schema-checked: report ``not_checked``, not ``pass``.
920
+ """
921
+
922
+ if "full_schema" not in signature(validate_document).parameters:
923
+ return "not_checked"
924
+ issues = getattr(document_report, "issues", ())
925
+ if any(_SCHEMA_CHECK_NOT_RUN in str(getattr(issue, "message", "")) for issue in issues):
926
+ return "not_checked"
927
+ if getattr(document_report, "errors", ()):
928
+ return "fail"
929
+ return "warning" if getattr(document_report, "warnings", ()) else "pass"
930
+
931
+
797
932
  def _hard_gates(
798
933
  package_report: object,
799
934
  document_report: object,
800
935
  reopen_report: ReopenReport,
801
936
  editor_open_safety_report: object | None = None,
802
937
  ) -> dict[str, str]:
803
- document_warnings = getattr(document_report, "warnings", ())
804
938
  editor_open_safety_ok = (
805
939
  True
806
940
  if editor_open_safety_report is None
@@ -809,7 +943,7 @@ def _hard_gates(
809
943
  return {
810
944
  "package_validation": "pass" if getattr(package_report, "ok", False) else "fail",
811
945
  "document_errors": "pass" if getattr(document_report, "ok", False) else "fail",
812
- "schema_lint": "warning" if document_warnings else "pass",
946
+ "schema_lint": _schema_lint(document_report),
813
947
  "reopen": "pass" if reopen_report.ok else "fail",
814
948
  "editor_open_safety": "pass" if editor_open_safety_ok else "fail",
815
949
  "id_integrity": "unavailable",
@@ -840,10 +974,11 @@ class Section:
840
974
  else:
841
975
  width = _mm_to_hwp_units(self.page.width_mm)
842
976
  height = _mm_to_hwp_units(self.page.height_mm)
977
+ orientation, width, height = _hancom_page_form(self.page.orientation, width, height)
843
978
  document.page.set_size(
844
979
  width=width,
845
980
  height=height,
846
- orientation=self.page.orientation,
981
+ orientation=orientation,
847
982
  section_index=section_index,
848
983
  )
849
984
  if self.margins is not None:
@@ -863,6 +998,7 @@ class Section:
863
998
  self.footer.lower(document, section_index=section_index, preset=preset)
864
999
  pending_native_tocs = native_toc_sink if native_toc_sink is not None else []
865
1000
  for child in self.children:
1001
+ _anchor_mark(document, child)
866
1002
  if isinstance(child, (Paragraph, PageBreak)):
867
1003
  if isinstance(child, Paragraph):
868
1004
  child.lower(document, preset=preset)
@@ -901,6 +1037,7 @@ class Section:
901
1037
  )
902
1038
  continue
903
1039
  raise NotImplementedError(f"{type(child).__name__} lowering is not implemented yet")
1040
+ _anchor_resolve(document)
904
1041
  if native_toc_sink is None:
905
1042
  # standalone Section.lower call: resolve deferred TOCs now
906
1043
  _apply_native_tocs(document, pending_native_tocs)
@@ -924,16 +1061,11 @@ class Document:
924
1061
  return flags
925
1062
 
926
1063
  def lower(self) -> HwpxDocument:
1064
+ _check_unique_keys(self.sections)
927
1065
  document = HwpxDocument.new()
928
1066
  preset = _builder_preset(self.preset)
929
1067
  if self.metadata is not None:
930
- for label, value in (
931
- ("제목", self.metadata.title),
932
- ("작성자", self.metadata.author),
933
- ("기관", self.metadata.organization),
934
- ):
935
- if value:
936
- document.add_paragraph(f"{label}: {value}", inherit_style=False)
1068
+ _write_package_metadata(document, self.metadata)
937
1069
  pending_native_tocs: list[tuple[int, NativeToc]] = []
938
1070
  for index, section in enumerate(self.sections):
939
1071
  section.lower(
@@ -945,8 +1077,17 @@ class Document:
945
1077
  _apply_native_tocs(document, pending_native_tocs)
946
1078
  return document
947
1079
 
1080
+ def _lower_with_anchors(self) -> tuple[HwpxDocument, dict[str, dict[str, int]]]:
1081
+ sink: list[_AnchorEntry] = []
1082
+ token = _ANCHOR_SINK.set(sink)
1083
+ try:
1084
+ document = self.lower()
1085
+ finally:
1086
+ _ANCHOR_SINK.reset(token)
1087
+ return document, _anchor_positions(document, sink)
1088
+
948
1089
  def save_to_path(self, path: str | PathLike[str]) -> BuilderSaveReport:
949
- document = self.lower()
1090
+ document, anchors = self._lower_with_anchors()
950
1091
  # Funnel the write through the single SavePipeline and keep its uniform
951
1092
  # report (plan §2 Phase B). Transparent policy -> behaviour-identical to
952
1093
  # the prior ``document.save_to_path`` for a from-scratch (new) document.
@@ -982,6 +1123,7 @@ class Document:
982
1123
  feature_flags=feature_flags,
983
1124
  editor_open_safety=editor_open_safety_report,
984
1125
  visual_complete=visual_complete,
1126
+ anchors=anchors,
985
1127
  )
986
1128
  return report
987
1129
 
@@ -62,6 +62,10 @@ class BuilderSaveReport:
62
62
  # through (plan §2 Phase B). Additive: ``None`` only if a caller builds a
63
63
  # report by hand without going through ``Document.save_to_path``.
64
64
  visual_complete: VisualCompleteReport | None = None
65
+ # ``key`` of each keyed Paragraph/Table -> ``{"section": i, "paragraph": j}``
66
+ # in the saved file (for a Table, the paragraph that holds it), so later
67
+ # edits need not find the node again by its text.
68
+ anchors: dict[str, dict[str, int]] = field(default_factory=dict)
65
69
 
66
70
  def __post_init__(self) -> None:
67
71
  hard_gates = dict(self.hard_gates)
@@ -80,6 +84,7 @@ class BuilderSaveReport:
80
84
  "path": str(self.path),
81
85
  "metadata": dict(self.metadata or {}),
82
86
  "hard_gates": dict(self.hard_gates),
87
+ "anchors": {key: dict(value) for key, value in self.anchors.items()},
83
88
  "fidelity_contract": {
84
89
  "proves": list(FIDELITY_CONTRACT["proves"]),
85
90
  "does_not_prove": list(FIDELITY_CONTRACT["does_not_prove"]),
@@ -333,6 +333,11 @@ def _extract_section_layout(section_path: str, section_xml: ET.Element) -> Secti
333
333
  margin = page_pr.find(f"{_HP_TAG}margin") if page_pr is not None else None
334
334
  margins = _margin_summary(margin)
335
335
  page_width = _int_attr(page_pr, "width")
336
+ page_height = _int_attr(page_pr, "height")
337
+ # Hancom draws a page as stored only for landscape="WIDELY" (portrait);
338
+ # NARROWLY, the schema default, turns it. Report the page as drawn.
339
+ if page_pr is not None and page_pr.get("landscape") != "WIDELY":
340
+ page_width, page_height = page_height, page_width
336
341
  body_width = None
337
342
  if page_width is not None:
338
343
  body_width = max(
@@ -345,7 +350,7 @@ def _extract_section_layout(section_path: str, section_xml: ET.Element) -> Secti
345
350
  return SectionLayoutSummary(
346
351
  section_path=section_path,
347
352
  page_width=page_width,
348
- page_height=_int_attr(page_pr, "height"),
353
+ page_height=page_height,
349
354
  margins=margins,
350
355
  computed_body_width=body_width,
351
356
  )
@@ -0,0 +1,7 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Run ``python -m hwpx_automation.office.rendering render-pdf IN OUT``."""
3
+
4
+ from .cli import main
5
+
6
+ if __name__ == "__main__":
7
+ raise SystemExit(main())