athena-python-docx 0.37.0__tar.gz → 0.38.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 (491) hide show
  1. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/PKG-INFO +1 -1
  2. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/__init__.py +1 -1
  3. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/commands.py +6 -2
  4. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/table.py +7 -1
  5. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/text/run.py +101 -21
  6. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/pyproject.toml +1 -1
  7. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_drawing_image.py +56 -0
  8. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_enum_parity.py +14 -0
  9. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_ruby_reads_python_docx_parity.py +18 -2
  10. athena_python_docx-0.38.0/tests/test_run_break_inner_content.py +254 -0
  11. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_run_later_runs.py +59 -0
  12. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_run_mark_inheritance.py +35 -40
  13. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_table_add_row_grid_row.py +29 -0
  14. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/uv.lock +1 -1
  15. athena_python_docx-0.37.0/tests/test_run_break_inner_content.py +0 -121
  16. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/.gitignore +0 -0
  17. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/AGENTS.md +0 -0
  18. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/CLAUDE.md +0 -0
  19. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/DOCX_EXEC_LAB.md +0 -0
  20. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/README.md +0 -0
  21. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/_athena_extension.py +0 -0
  22. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/_batching.py +0 -0
  23. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/_buffer.py +0 -0
  24. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/_capabilities.py +0 -0
  25. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/_citations.py +0 -0
  26. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/_execution.py +0 -0
  27. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/_http.py +0 -0
  28. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/_http_doc.py +0 -0
  29. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/_image_utils.py +0 -0
  30. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/_postproc.py +0 -0
  31. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/_proxy.py +0 -0
  32. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/_ptc.py +0 -0
  33. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/_references.py +0 -0
  34. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/_run_order.py +0 -0
  35. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/_table_styles.py +0 -0
  36. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/_timeouts.py +0 -0
  37. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/_tracked.py +0 -0
  38. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/_xml_text.py +0 -0
  39. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/api.py +0 -0
  40. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/blkcntnr.py +0 -0
  41. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/bookmarks.py +0 -0
  42. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/charts.py +0 -0
  43. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/client.py +0 -0
  44. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/comments.py +0 -0
  45. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/document.py +0 -0
  46. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/drawing/__init__.py +0 -0
  47. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/enum/__init__.py +0 -0
  48. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/enum/base.py +0 -0
  49. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/enum/dml.py +0 -0
  50. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/enum/section.py +0 -0
  51. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/enum/shape.py +0 -0
  52. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/enum/style.py +0 -0
  53. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/enum/table.py +0 -0
  54. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/enum/text.py +0 -0
  55. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/errors.py +0 -0
  56. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/exceptions.py +0 -0
  57. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/fields.py +0 -0
  58. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/footnotes.py +0 -0
  59. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/image/__init__.py +0 -0
  60. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/image/exceptions.py +0 -0
  61. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/image/image.py +0 -0
  62. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/math.py +0 -0
  63. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/opc/__init__.py +0 -0
  64. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/opc/coreprops.py +0 -0
  65. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/oxml/__init__.py +0 -0
  66. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/revisions.py +0 -0
  67. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/sdt.py +0 -0
  68. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/section.py +0 -0
  69. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/session.py +0 -0
  70. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/settings.py +0 -0
  71. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/shape.py +0 -0
  72. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/shared.py +0 -0
  73. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/styles/__init__.py +0 -0
  74. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/styles/_table.py +0 -0
  75. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/styles/latent.py +0 -0
  76. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/styles/style.py +0 -0
  77. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/styles/styles.py +0 -0
  78. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/text/__init__.py +0 -0
  79. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/text/font.py +0 -0
  80. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/text/hyperlink.py +0 -0
  81. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/text/pagebreak.py +0 -0
  82. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/text/paragraph.py +0 -0
  83. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/text/parfmt.py +0 -0
  84. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/text/tabstops.py +0 -0
  85. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/toc.py +0 -0
  86. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/docx/typing.py +0 -0
  87. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/scripts/docx_exec_lab.py +0 -0
  88. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/scripts/docx_exec_lab_examples/fast_table_fill.py +0 -0
  89. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/scripts/docx_exec_lab_examples/find_replace_literal.py +0 -0
  90. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/scripts/docx_exec_lab_server.py +0 -0
  91. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/scripts/dump_wire_fixtures.py +0 -0
  92. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/scripts/release.sh +0 -0
  93. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/scripts/round_trip_smoke.py +0 -0
  94. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/scripts/smoke_test_block_not_found.py +0 -0
  95. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/scripts/validate_find_replace_asset.py +0 -0
  96. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/__init__.py +0 -0
  97. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/conftest.py +0 -0
  98. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/METHODOLOGY.md +0 -0
  99. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/README.md +0 -0
  100. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/__init__.py +0 -0
  101. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/ab_probe/__init__.py +0 -0
  102. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/ab_probe/agent_documents.py +0 -0
  103. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/ab_probe/default_style_run_formatting.py +0 -0
  104. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/ab_probe/header_footer_pictures.py +0 -0
  105. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/ab_probe/less_used_writes.py +0 -0
  106. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/ab_probe/merged_cell_identity.py +0 -0
  107. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/ab_probe/paragraph_text_setter.py +0 -0
  108. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/ab_probe/picture_runs.py +0 -0
  109. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/ab_probe/proxy_equality.py +0 -0
  110. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/ab_probe/return_types.py +0 -0
  111. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/ab_probe/xml_incompatible_text.py +0 -0
  112. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/ab_probe/zero_and_negative_indents.py +0 -0
  113. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/ab_probe_cases.py +0 -0
  114. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/ab_probe_runner.py +0 -0
  115. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/auto_gen_cases.py +0 -0
  116. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/binary_round_trip.py +0 -0
  117. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/cases.py +0 -0
  118. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/complex_cases.py +0 -0
  119. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/coverage_report.py +0 -0
  120. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/extract.py +0 -0
  121. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/extreme_cases.py +0 -0
  122. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/fake_session.py +0 -0
  123. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/firm_templates/README.md +0 -0
  124. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/firm_templates/__init__.py +0 -0
  125. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/firm_templates/_runner.py +0 -0
  126. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/firm_templates/extractor.py +0 -0
  127. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/firm_templates/test_pw_corpus.py +0 -0
  128. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/firm_templates/test_pw_research_digest.py +0 -0
  129. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/local_runner.py +0 -0
  130. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/mega_cases.py +0 -0
  131. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshot.py +0 -0
  132. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/01_basic_paragraph.json +0 -0
  133. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/02_multiple_headings.json +0 -0
  134. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/03_runs_with_formatting.json +0 -0
  135. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/04_font_name_and_size.json +0 -0
  136. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/05_font_color_rgb.json +0 -0
  137. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/06_font_character_properties.json +0 -0
  138. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/07_font_subscript_superscript.json +0 -0
  139. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/08_font_highlight.json +0 -0
  140. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/09_paragraph_alignment.json +0 -0
  141. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/100_table_negative_indexing.json +0 -0
  142. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/101_table_cells_flat_iteration.json +0 -0
  143. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/102_text_with_embedded_special_chars.json +0 -0
  144. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/104_core_properties_datetime.json +0 -0
  145. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/105_default_one_section.json +0 -0
  146. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/106_heading_paragraph_format.json +0 -0
  147. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/107_varying_row_heights.json +0 -0
  148. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/10_paragraph_indents.json +0 -0
  149. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/11_paragraph_spacing.json +0 -0
  150. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/12_paragraph_keep_options.json +0 -0
  151. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/13_paragraph_tab_stops.json +0 -0
  152. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/14_run_add_tab_and_break.json +0 -0
  153. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/15_run_add_break_page.json +0 -0
  154. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/16_paragraph_clear_and_insert_before.json +0 -0
  155. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/17_table_basic.json +0 -0
  156. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/18_table_cell_text.json +0 -0
  157. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/19_table_row_column_sizing.json +0 -0
  158. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/20_table_cell_vertical_alignment.json +0 -0
  159. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/21_table_alignment_and_autofit.json +0 -0
  160. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/22_table_cell_paragraphs_iteration.json +0 -0
  161. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/24_table_add_row_column.json +0 -0
  162. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/25_table_merge_cells.json +0 -0
  163. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/26_section_page_setup.json +0 -0
  164. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/27_section_margins.json +0 -0
  165. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/28_section_add_new.json +0 -0
  166. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/29_section_headers_linked.json +0 -0
  167. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/30_styles_iteration.json +0 -0
  168. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/31_styles_lookup_and_default.json +0 -0
  169. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/32_styles_add_paragraph_style.json +0 -0
  170. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/33_core_properties_set_and_get.json +0 -0
  171. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/34_inline_shapes_iterate_empty.json +0 -0
  172. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/35_full_report.json +0 -0
  173. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/36_replace_text_in_paragraph.json +0 -0
  174. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/37_iterate_runs_and_format_all_bold.json +0 -0
  175. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/38_font_all_properties.json +0 -0
  176. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/39_large_body_100_paragraphs.json +0 -0
  177. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/40_large_table_10x10.json +0 -0
  178. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/41_unicode_and_emoji.json +0 -0
  179. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/42_very_long_paragraph.json +0 -0
  180. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/43_paragraph_text_round_trip.json +0 -0
  181. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/44_paragraph_alignment_round_trip.json +0 -0
  182. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/45_cell_text_round_trip.json +0 -0
  183. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/46_run_text_setter_round_trip.json +0 -0
  184. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/47_font_size_round_trip.json +0 -0
  185. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/48_font_color_round_trip.json +0 -0
  186. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/49_resume_layout.json +0 -0
  187. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/50_multi_section_doc.json +0 -0
  188. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/52_iterate_everything.json +0 -0
  189. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/53_apply_style_to_paragraphs.json +0 -0
  190. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/54_empty_everything.json +0 -0
  191. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/55_single_character_runs.json +0 -0
  192. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/56_everything_in_one.json +0 -0
  193. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/57_runs_after_multiple_text_appends.json +0 -0
  194. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/58_modify_runs_in_place.json +0 -0
  195. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/59_indent_round_trip.json +0 -0
  196. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/60_space_round_trip.json +0 -0
  197. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/61_cell_paragraph_with_runs.json +0 -0
  198. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/62_many_cell_paragraphs.json +0 -0
  199. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/63_table_style_round_trip.json +0 -0
  200. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/64_many_sections.json +0 -0
  201. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/65_20x20_table_formatted.json +0 -0
  202. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/66_toc_like_structure.json +0 -0
  203. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/67_paragraph_insert_before_chain.json +0 -0
  204. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/68_invoice.json +0 -0
  205. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/69_newsletter.json +0 -0
  206. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/70_add_and_iterate_back.json +0 -0
  207. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/71_academic_paper.json +0 -0
  208. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/72_legal_contract.json +0 -0
  209. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/73_form_with_many_tables.json +0 -0
  210. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/74_paragraph_with_10_runs.json +0 -0
  211. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/75_paragraph_negative_first_line_indent.json +0 -0
  212. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/76_rgbcolor_from_string.json +0 -0
  213. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/77_length_unit_conversions.json +0 -0
  214. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/78_paragraph_copy_style.json +0 -0
  215. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/79_bulk_cell_formatting.json +0 -0
  216. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/80_apply_style_after_add_run.json +0 -0
  217. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/81_multi_page_with_breaks.json +0 -0
  218. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/82_add_text_on_existing_run.json +0 -0
  219. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/83_clear_then_repopulate_paragraph.json +0 -0
  220. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/84_table_reread_row_count.json +0 -0
  221. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/85_header_footer_access.json +0 -0
  222. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/86_font_read_unset_returns_none.json +0 -0
  223. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/87_500_paragraph_doc.json +0 -0
  224. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/88_mixed_content_iteration.json +0 -0
  225. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/89_alignment_clear_via_none.json +0 -0
  226. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/90_cell_add_paragraph_styled.json +0 -0
  227. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/91_many_small_tables.json +0 -0
  228. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/92_margins_every_section.json +0 -0
  229. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/93_font_bool_reads_after_set.json +0 -0
  230. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/94_page_break_before_paragraph.json +0 -0
  231. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/95_paragraph_hyperlinks_empty.json +0 -0
  232. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/96_paragraph_contains_page_break.json +0 -0
  233. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/97_document_styles_by_key.json +0 -0
  234. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/98_style_contains_check.json +0 -0
  235. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/99_run_add_picture_from_bytes.json +0 -0
  236. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/ex02_unicode_everywhere.json +0 -0
  237. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/ex03_1000_paragraphs.json +0 -0
  238. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/ex04_50x50_table.json +0 -0
  239. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/ex05_long_text_in_cell.json +0 -0
  240. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/ex06_hundred_tiny_runs.json +0 -0
  241. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/ex07_every_font_boolean.json +0 -0
  242. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/ex08_many_continuous_sections.json +0 -0
  243. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/ex09_many_tab_stops.json +0 -0
  244. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/ex10_complex_bom.json +0 -0
  245. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/ex11_banded_rows_formatting.json +0 -0
  246. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/ex12_section_reconfigure.json +0 -0
  247. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/ex13_cell_with_10_paragraphs.json +0 -0
  248. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/ex14_styled_report_table.json +0 -0
  249. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/ex15_paragraph_all_format_props.json +0 -0
  250. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/ex16_runs_interleaved_with_breaks.json +0 -0
  251. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/ex17_all_break_kinds.json +0 -0
  252. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/ex18_read_back_large_doc.json +0 -0
  253. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/ex19_mutate_all_runs.json +0 -0
  254. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/ex20_kitchen_sink_v2.json +0 -0
  255. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/mega01_book_chapter.json +0 -0
  256. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/mega02_research_proposal.json +0 -0
  257. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/mega03_financial_statement.json +0 -0
  258. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/mega04_recipe_card.json +0 -0
  259. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/mega05_user_manual.json +0 -0
  260. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/mega06_complex_newsletter.json +0 -0
  261. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/mega07_budget_spreadsheet.json +0 -0
  262. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/mega08_product_catalog.json +0 -0
  263. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/mega09_signed_contract.json +0 -0
  264. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/mega10_api_documentation.json +0 -0
  265. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/rw01_official_quickstart.json +0 -0
  266. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/rw02_paragraph_style_list.json +0 -0
  267. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/rw03_character_formatting.json +0 -0
  268. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/rw04_section_page_setup.json +0 -0
  269. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/rw05_toc_pattern.json +0 -0
  270. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/rw06_meeting_minutes.json +0 -0
  271. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/rw07_dense_formatting_demo.json +0 -0
  272. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/rw08_table_merged_header.json +0 -0
  273. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/rw09_bulk_run_iteration.json +0 -0
  274. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/rw10_colored_grid_table.json +0 -0
  275. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/rw11_header_text.json +0 -0
  276. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/rw12_first_page_footer.json +0 -0
  277. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/rw13_even_page_header.json +0 -0
  278. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/rw15_paragraph_style_instance.json +0 -0
  279. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/rw16_header_footer_tables.json +0 -0
  280. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/rw17_header_letterhead_filled.json +0 -0
  281. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/rw18_header_table_properties.json +0 -0
  282. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/rw19_header_table_rows_columns.json +0 -0
  283. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/op_snapshots/rw20_header_table_merges.json +0 -0
  284. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/ours_spec.json +0 -0
  285. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/parity_crawl.py +0 -0
  286. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/parity_diff.json +0 -0
  287. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/real_world_cases.py +0 -0
  288. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/round_trip_tests.py +0 -0
  289. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/stock_spec.json +0 -0
  290. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fidelity/test_e2e_against_staging.py +0 -0
  291. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fixtures/field_reads.json +0 -0
  292. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fixtures/note_reference_reads.json +0 -0
  293. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fixtures/ruby_reads.json +0 -0
  294. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/fixtures/symbol_reads.json +0 -0
  295. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/live/__init__.py +0 -0
  296. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/live/baseline.json +0 -0
  297. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/live/fixture_scripts.py +0 -0
  298. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/live/reader.py +0 -0
  299. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/live/runner.py +0 -0
  300. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/live/sdk_driver.py +0 -0
  301. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/live/stock_driver.py +0 -0
  302. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/live/test_live_runner.py +0 -0
  303. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/parity/README.md +0 -0
  304. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/parity/__init__.py +0 -0
  305. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/parity/baseline_gaps.json +0 -0
  306. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/parity/compare.py +0 -0
  307. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/parity/intentional_deviations.json +0 -0
  308. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/parity/introspect.py +0 -0
  309. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/parity/reports/GAP_ANALYSIS.md +0 -0
  310. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/parity/reports/gap_report.json +0 -0
  311. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/parity/run_parity.py +0 -0
  312. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/parity/snapshots/athena_latest.json +0 -0
  313. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/parity/snapshots/upstream_python_docx_1.2.0.json +0 -0
  314. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/parity/test_parity_gap.py +0 -0
  315. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_add_section.py +0 -0
  316. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_add_section_extract_items.py +0 -0
  317. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_alignment_values.py +0 -0
  318. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_athena_extension_creates_session_open.py +0 -0
  319. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_athena_extensions_contract.py +0 -0
  320. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_athena_extensions_registry.py +0 -0
  321. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_batch_chunking.py +0 -0
  322. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_batching_perf.py +0 -0
  323. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_battery_cases.py +0 -0
  324. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_block_not_found_error.py +0 -0
  325. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_buffer.py +0 -0
  326. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_cell_add_paragraph_wire_shape.py +0 -0
  327. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_cell_add_table_not_supported.py +0 -0
  328. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_cell_borders_spec.py +0 -0
  329. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_cell_fastpath_runs.py +0 -0
  330. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_cell_inner_add_hyperlink_stash.py +0 -0
  331. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_cell_inner_add_run_via_cell_insert.py +0 -0
  332. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_cell_inner_format_stash.py +0 -0
  333. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_cell_inner_run_format_stash.py +0 -0
  334. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_cell_inner_run_guard.py +0 -0
  335. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_cell_merge.py +0 -0
  336. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_cell_nested_tables.py +0 -0
  337. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_cell_paragraphs_listed.py +0 -0
  338. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_cell_text_plain_fastpath.py +0 -0
  339. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_cell_text_replace_semantics.py +0 -0
  340. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_citations.py +0 -0
  341. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_collapsed_range_format.py +0 -0
  342. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_color_format_theme.py +0 -0
  343. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_command_dataclasses.py +0 -0
  344. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_commands.py +0 -0
  345. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_comment_paragraphs.py +0 -0
  346. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_comments.py +0 -0
  347. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_comments_first_read.py +0 -0
  348. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_content_control_value.py +0 -0
  349. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_core_properties.py +0 -0
  350. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_document_asset_id_property.py +0 -0
  351. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_document_clear.py +0 -0
  352. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_document_create.py +0 -0
  353. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_document_create_from_template.py +0 -0
  354. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_document_factory_validation.py +0 -0
  355. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_document_settings_session_open.py +0 -0
  356. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_docx_exec_lab.py +0 -0
  357. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_docx_exec_lab_server.py +0 -0
  358. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_e2e_partial_failure_cascade.py +0 -0
  359. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_empty_hyperlinks.py +0 -0
  360. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_execution_scope.py +0 -0
  361. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_export_docx_clean.py +0 -0
  362. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_export_rescue_anchors.py +0 -0
  363. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_field_reads_python_docx_parity.py +0 -0
  364. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_field_refresh.py +0 -0
  365. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_field_runs.py +0 -0
  366. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_find_replace_session_open.py +0 -0
  367. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_flushed_write_failures.py +0 -0
  368. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_http_transport.py +0 -0
  369. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_hyperlink_coalescing.py +0 -0
  370. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_image_url_data_uri.py +0 -0
  371. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_inline_page_break.py +0 -0
  372. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_inline_shape_alt_text.py +0 -0
  373. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_inline_shape_size.py +0 -0
  374. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_inline_shape_type.py +0 -0
  375. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_inline_shapes_floating.py +0 -0
  376. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_insert_deferred.py +0 -0
  377. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_iter_inner_content.py +0 -0
  378. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_latent_style_writes.py +0 -0
  379. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_line_clear_break.py +0 -0
  380. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_list_styles.py +0 -0
  381. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_merged_cell_identity.py +0 -0
  382. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_merged_cell_secondary_slot.py +0 -0
  383. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_merged_cells.py +0 -0
  384. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_missing_modules.py +0 -0
  385. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_nested_table_refused.py +0 -0
  386. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_note_reference_reads_python_docx_parity.py +0 -0
  387. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_oxml_shim.py +0 -0
  388. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_page_break.py +0 -0
  389. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_paragraph_delete.py +0 -0
  390. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_paragraph_indent_reads.py +0 -0
  391. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_paragraph_runs_hyperlinks.py +0 -0
  392. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_paragraph_text_empty_run.py +0 -0
  393. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_paragraph_text_len_cache.py +0 -0
  394. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_parfmt_clears.py +0 -0
  395. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_parity_misc.py +0 -0
  396. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_parity_round2.py +0 -0
  397. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_partial_failure_cascade.py +0 -0
  398. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_pdf_export_and_page_renders.py +0 -0
  399. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_phase_a_behavior.py +0 -0
  400. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_phase_b_headers_footers.py +0 -0
  401. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_phase_c_tables.py +0 -0
  402. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_positional_cell_id.py +0 -0
  403. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_postproc_cell_format_rewrite.py +0 -0
  404. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_postproc_cell_run_format_rewrite.py +0 -0
  405. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_postproc_ref_restore.py +0 -0
  406. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_pr19766_review_fixes.py +0 -0
  407. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_proxy_equality.py +0 -0
  408. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_ptc.py +0 -0
  409. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_python_docx_api_parity.py +0 -0
  410. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_read_run_ranges.py +0 -0
  411. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_rendered_page_breaks.py +0 -0
  412. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_repeated_node_read.py +0 -0
  413. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_restart_numbering.py +0 -0
  414. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_revisions.py +0 -0
  415. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_round_trip_smoke_script.py +0 -0
  416. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_run_add_picture_anchor.py +0 -0
  417. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_run_edges.py +0 -0
  418. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_run_formatting_wire.py +0 -0
  419. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_run_identity.py +0 -0
  420. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_run_page_number_blocks.py +0 -0
  421. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_run_table_style_objects.py +0 -0
  422. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_run_text_python_docx.py +0 -0
  423. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_run_text_refreshes_paragraph.py +0 -0
  424. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_run_unmodeled_toggles.py +0 -0
  425. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_sdk_reads_served.py +0 -0
  426. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_section_read_lengths.py +0 -0
  427. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_add_paragraph_style.py +0 -0
  428. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_add_picture.py +0 -0
  429. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_add_run.py +0 -0
  430. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_cell_add_paragraph.py +0 -0
  431. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_comments_add_comment.py +0 -0
  432. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_comments_get.py +0 -0
  433. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_document_audit.py +0 -0
  434. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_document_element.py +0 -0
  435. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_enum_section.py +0 -0
  436. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_font_audit.py +0 -0
  437. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_header_footer.py +0 -0
  438. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_hyperlink.py +0 -0
  439. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_inline_shape.py +0 -0
  440. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_insert_paragraph_before.py +0 -0
  441. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_misc.py +0 -0
  442. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_paragraph_strict.py +0 -0
  443. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_paragraph_style.py +0 -0
  444. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_paragraph_style_strict.py +0 -0
  445. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_parfmt.py +0 -0
  446. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_row_col_cell.py +0 -0
  447. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_run_add_break.py +0 -0
  448. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_run_bool_setters.py +0 -0
  449. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_run_style.py +0 -0
  450. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_run_style_strict.py +0 -0
  451. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_run_text.py +0 -0
  452. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_run_underline.py +0 -0
  453. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_section_audit.py +0 -0
  454. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_section_dimensions.py +0 -0
  455. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_section_onoff.py +0 -0
  456. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_settings.py +0 -0
  457. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_shared_audit.py +0 -0
  458. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_style.py +0 -0
  459. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_styles.py +0 -0
  460. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_table_audit.py +0 -0
  461. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_table_cell.py +0 -0
  462. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_table_dimensions.py +0 -0
  463. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_silent_stub_table_layout.py +0 -0
  464. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_smoke_integration.py +0 -0
  465. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_style_acceptance.py +0 -0
  466. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_style_delete.py +0 -0
  467. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_style_font.py +0 -0
  468. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_style_formatting_writes.py +0 -0
  469. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_style_metadata_writes.py +0 -0
  470. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_style_names.py +0 -0
  471. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_style_setters_contract.py +0 -0
  472. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_style_tab_stops.py +0 -0
  473. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_style_tab_stops_read.py +0 -0
  474. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_symbol_reads_python_docx_parity.py +0 -0
  475. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_tab_stop_writes.py +0 -0
  476. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_table_anchors.py +0 -0
  477. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_table_clears.py +0 -0
  478. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_table_direction.py +0 -0
  479. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_table_read_reuse.py +0 -0
  480. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_table_set_cell_perf.py +0 -0
  481. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_table_style_id_resolution.py +0 -0
  482. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_temporarily_unavailable.py +0 -0
  483. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_text_control_characters.py +0 -0
  484. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_tracked_revisions_offsets.py +0 -0
  485. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_unmapped_alignment_reads.py +0 -0
  486. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_uv_lock_version.py +0 -0
  487. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_validate_find_replace_asset_script.py +0 -0
  488. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_wire_contract.py +0 -0
  489. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_xml_attr_guard.py +0 -0
  490. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_xml_incompatible_text.py +0 -0
  491. {athena_python_docx-0.37.0 → athena_python_docx-0.38.0}/tests/test_zod_wire_contract.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: athena-python-docx
3
- Version: 0.37.0
3
+ Version: 0.38.0
4
4
  Summary: Drop-in replacement for python-docx that connects to Athena's DOCX Studio collaborative document stack
5
5
  Project-URL: Homepage, https://athenaintelligence.ai
6
6
  Author-email: Athena Intelligence <engineering@athenaintelligence.ai>
@@ -6,7 +6,7 @@ See CLAUDE.md for the API parity contract.
6
6
 
7
7
  from __future__ import annotations
8
8
 
9
- __version__ = "0.37.0"
9
+ __version__ = "0.38.0"
10
10
 
11
11
  from docx.api import Document
12
12
  from docx._buffer import discard_all, flush_all
@@ -568,14 +568,18 @@ class TablesInsertRow(Command):
568
568
  """``grid_row`` is python-docx's ``Table.add_row()``: the row goes after
569
569
  the table's last one, with a cell for each grid column as wide as the
570
570
  column, holding one empty paragraph, and nothing of the rows above.
571
- Without it, the row is Word's Insert Row next to ``row_index``. A
572
- docx-studio that predates the field ignores it."""
571
+ ``copy_format_from`` is the Athena extension
572
+ ``Table.add_row(copy_format_from=k)``: the row goes after the table's
573
+ last one, formatted as Word's Insert Row below row ``k`` formats it.
574
+ Without either, the row is Word's Insert Row next to ``row_index``. A
575
+ docx-studio that predates a field ignores it."""
573
576
 
574
577
  node_id: str
575
578
  row_index: int
576
579
  position: str # "above" | "below"
577
580
  count: int = 1
578
581
  grid_row: bool | None = None
582
+ copy_format_from: int | None = None
579
583
 
580
584
 
581
585
  @dataclass
@@ -1240,7 +1240,13 @@ class Table:
1240
1240
  self._session.doc.tables.get_properties({"nodeId": self._fresh_node_id()}),
1241
1241
  )
1242
1242
  if isinstance(info, dict):
1243
- return WD_PARAGRAPH_ALIGNMENT.from_docx_studio(info.get("alignment"))
1243
+ raw: object = info.get("alignment")
1244
+ member = WD_PARAGRAPH_ALIGNMENT.from_docx_studio(raw)
1245
+ # python-docx reads `w:jc` through `from_xml`, which raises
1246
+ # ValueError for a value it has no member for (`start`, `end`).
1247
+ if member is None and isinstance(raw, str) and raw:
1248
+ return WD_PARAGRAPH_ALIGNMENT.from_xml(raw)
1249
+ return member
1244
1250
  return None
1245
1251
 
1246
1252
  @alignment.setter
@@ -454,11 +454,16 @@ class Run:
454
454
  formatting, so the formatting waits for content the run takes. So
455
455
  does a break the read says is no node of the paragraph
456
456
  (``breakApart``): one only its source run holds, a column-break
457
- block's, or the page break that opens the paragraph."""
457
+ block's, or the page break that opens the paragraph. The paragraph's
458
+ closing page break run (``closingBreak``) is formatted where the
459
+ docx-studio reports ``capabilities.closingBreakRunFormat``, which puts
460
+ its breaks back in the paragraph to take it."""
458
461
  if not self._is_collapsed_range or not self._holds_break() or tracking(self._session):
459
462
  return None
460
463
  if self._inline_snapshot.get("breakApart") is True:
461
- return None
464
+ closing = self._inline_snapshot.get("closingBreak") is True
465
+ if not closing or not server_takes(self._session, "closingBreakRunFormat"):
466
+ return None
462
467
  if self._inline_snapshot.get("break") == "column" and not server_takes(self._session, "inlineColumnBreak"):
463
468
  return None
464
469
  return self._later_runs()
@@ -468,14 +473,23 @@ class Run:
468
473
  and no text (a picture's run, ``drawing``), which a whole-run
469
474
  ``FormatApply`` of its collapsed range formats, untracked, on a
470
475
  docx-studio that reports ``capabilities.pictureRuns``: python-docx
471
- formats the run's ``w:r``, and docx-studio its picture. Else None."""
476
+ formats the run's ``w:r``, and docx-studio its picture. A legacy
477
+ picture's or object's run (``w:pict``, ``w:object``: ``drawing.legacy``
478
+ with its ``imageId``) too, on one that reports
479
+ ``capabilities.legacyPictureRuns``; not a preserved shape's (a
480
+ ``drawing.legacy`` with no ``imageId``), which no picture holds and the
481
+ format cannot reach. Else None."""
472
482
  if not self._is_collapsed_range or tracking(self._session):
473
483
  return None
474
484
  drawing = self._inline_snapshot.get("drawing")
475
- if not isinstance(drawing, dict) or drawing.get("legacy") is True:
485
+ if not isinstance(drawing, dict):
476
486
  return None
477
487
  if not server_takes(self._session, "pictureRuns"):
478
488
  return None
489
+ if drawing.get("legacy") is True and (
490
+ not drawing.get("imageId") or not server_takes(self._session, "legacyPictureRuns")
491
+ ):
492
+ return None
479
493
  return self._later_runs()
480
494
 
481
495
  def _empty_run_identity(self) -> int | None:
@@ -866,6 +880,15 @@ class Run:
866
880
  items: list[tuple[int, float, dict, bool]] = [
867
881
  (offset, index, info, True) for index, (offset, info) in enumerate(pictures)
868
882
  ]
883
+ # python-docx yields "" for each stretch of the run that the pictures
884
+ # bound and that holds a page or column break and no text, by how
885
+ # many pictures come before it (``emptyTextAfterPictures``). From a
886
+ # docx-studio that does not say, a run of a break and pictures and
887
+ # no text yields the break's "" before them.
888
+ empty_after = self._empty_text_after_pictures()
889
+ if empty_after is None:
890
+ breaks = not raw and self._inline_snapshot.get("break") in ("page", "column")
891
+ empty_after = {0} if breaks else set()
869
892
  rendered = self._rendered_page_breaks()
870
893
  for info in rendered:
871
894
  offset = int(info.get("offset", 0) or 0)
@@ -875,15 +898,23 @@ class Run:
875
898
  items.append((offset, after - 0.5, info, False))
876
899
  items.sort(key=lambda item: (item[0], item[1]))
877
900
  cut = 0
901
+ pictures_yielded = 0
878
902
  for offset, _order, info, is_picture in items:
879
903
  at = min(max(offset, cut), len(raw))
880
904
  if _word_text(raw[cut:at], font):
881
905
  yield _word_text(raw[cut:at], font)
882
906
  if not is_picture:
883
907
  yield RenderedPageBreak(paragraph=self._page_break_paragraph(), offset=self._range[0] + at, info=info)
884
- elif info.get("legacy") is not True:
885
- yield Drawing(session=self._session, info=info)
908
+ else:
909
+ if pictures_yielded in empty_after:
910
+ yield ""
911
+ empty_after.discard(pictures_yielded)
912
+ if info.get("legacy") is not True:
913
+ yield Drawing(session=self._session, info=info)
914
+ pictures_yielded += 1
886
915
  cut = at
916
+ if pictures_yielded in empty_after:
917
+ yield ""
887
918
  if _word_text(raw[cut:], font):
888
919
  yield _word_text(raw[cut:], font)
889
920
  return
@@ -1240,6 +1271,34 @@ class Run:
1240
1271
  snapshot.setdefault("break", kind)
1241
1272
  if held:
1242
1273
  snapshot.pop("breakApart", None)
1274
+ self._note_empty_text_after_pictures()
1275
+
1276
+ def _note_empty_text_after_pictures(self) -> None:
1277
+ """This run took a page or column break at its end, after its pictures:
1278
+ python-docx yields its ``""`` after the last of them while no text
1279
+ follows it there (``emptyTextAfterPictures``). A picture's run
1280
+ (``drawing``) holds its picture among its pictures (``pictures``) then."""
1281
+ snapshot = self._inline_snapshot
1282
+ alone = snapshot.get("drawing")
1283
+ pictures = self._pictures() or ([(0, alone)] if isinstance(alone, dict) else [])
1284
+ if not pictures or self._raw_text()[pictures[-1][0]:]:
1285
+ return
1286
+ if isinstance(alone, dict):
1287
+ snapshot.pop("drawing")
1288
+ snapshot["pictures"] = [{"offset": 0, "drawing": alone}]
1289
+ places = snapshot.setdefault("emptyTextAfterPictures", [])
1290
+ if isinstance(places, list) and len(pictures) not in places:
1291
+ places.append(len(pictures))
1292
+
1293
+ def _empty_text_after_pictures(self) -> "set[int] | None":
1294
+ """Where :meth:`iter_inner_content` yields ``""`` among this run's
1295
+ pictures (``emptyTextAfterPictures``): each stretch the pictures bound
1296
+ that holds a page or column break and no text, by how many pictures
1297
+ come before it. None when the read does not say."""
1298
+ places: object = self._inline_snapshot.get("emptyTextAfterPictures")
1299
+ if not isinstance(places, list):
1300
+ return None
1301
+ return {p for p in places if isinstance(p, int) and not isinstance(p, bool)}
1243
1302
 
1244
1303
  def add_break(self, break_type: "WD_BREAK" = WD_BREAK.LINE) -> None:
1245
1304
  """Insert a break at the end of the run.
@@ -1521,17 +1580,25 @@ class Run:
1521
1580
  def _hold_picture(self, drawing: dict) -> None:
1522
1581
  """This run took the picture ``drawing`` at its end (``run.add_picture``),
1523
1582
  as the next read gives it: a run of a picture alone as a picture's run
1524
- (``drawing``), else among its pictures (``pictures``) where its text ends."""
1583
+ (``drawing``), else among its pictures (``pictures``) where its text ends.
1584
+ A run of a page or column break and no text holds it among its
1585
+ pictures, after the break's ``""`` (``emptyTextAfterPictures``)."""
1525
1586
  snapshot = self._inline_snapshot
1526
1587
  held = self._pictures()
1527
1588
  alone = snapshot.get("drawing")
1528
- if not held and not self._raw_text() and not isinstance(alone, dict):
1589
+ raw = self._raw_text()
1590
+ break_first = not held and not raw and not isinstance(alone, dict) and snapshot.get("break") in ("page", "column")
1591
+ if not held and not raw and not isinstance(alone, dict) and not break_first:
1529
1592
  snapshot["drawing"] = drawing
1530
1593
  return
1531
1594
  if isinstance(alone, dict):
1532
1595
  held = [(0, snapshot.pop("drawing"))]
1533
- held.append((len(self._raw_text()), drawing))
1596
+ held.append((len(raw), drawing))
1534
1597
  snapshot["pictures"] = [{"offset": offset, "drawing": info} for offset, info in held]
1598
+ if break_first:
1599
+ places = snapshot.setdefault("emptyTextAfterPictures", [])
1600
+ if isinstance(places, list) and 0 not in places:
1601
+ places.append(0)
1535
1602
 
1536
1603
  @athena_extension(
1537
1604
  issue=498,
@@ -1882,15 +1949,19 @@ class Run:
1882
1949
 
1883
1950
  # ---- formatting helpers ----
1884
1951
  def _formatting_from_snapshot(self) -> dict:
1885
- """This run's formatting as a ``format.apply`` inline bag.
1952
+ """This run's own formatting as a ``format.apply`` inline bag: the
1953
+ values the run sets itself, an explicit off toggle (``w:b w:val="0"``)
1954
+ included, as python-docx keeps the run's ``w:rPr`` for its next text.
1886
1955
 
1887
1956
  Only for a snapshot with every formatting key that is set
1888
- (``_marks_known``): a toggle that the snapshot does not hold is sent
1889
- as ``False``, so the new text does not take a toggle from the
1890
- paragraph's default run formatting either.
1957
+ (``_marks_known``). A toggle the run does not set stays unset: the new
1958
+ text, which docx-studio gives no formatting of its own (no
1959
+ ``inheritMarks``), shows what the styles give it, as python-docx's
1960
+ does. Sending it as ``False`` wrote an explicit off, so text written
1961
+ into an emptied heading run lost the heading's bold.
1891
1962
  """
1892
1963
  snapshot = self._inline_snapshot
1893
- out: dict = {key: False for key in _RUN_MARK_TOGGLES}
1964
+ out: dict = {}
1894
1965
  for key in _RUN_MARK_KEYS:
1895
1966
  value = snapshot.get(key)
1896
1967
  if value is not None:
@@ -2005,6 +2076,9 @@ class Run:
2005
2076
  )
2006
2077
  # Its break or picture holds the formatting now, which what it takes next continues.
2007
2078
  self._fresh = False
2079
+ # A closing page break run's breaks are the paragraph's now, as the next read gives them.
2080
+ if self._inline_snapshot.pop("closingBreak", None) is True:
2081
+ self._inline_snapshot.pop("breakApart", None)
2008
2082
  return
2009
2083
  if self._is_collapsed_range:
2010
2084
  pending = getattr(self, "_pending_inline", None)
@@ -2019,17 +2093,23 @@ class Run:
2019
2093
  {"target": self._text_range, "inline": kwargs, "wholeRun": True},
2020
2094
  ),
2021
2095
  )
2022
- self._edited_ruby()
2096
+ self._edited_ruby(formatted=True)
2023
2097
 
2024
- def _edited_ruby(self) -> None:
2098
+ def _edited_ruby(self, *, formatted: bool = False) -> None:
2025
2099
  """This run's text or formatting changed: docx-studio writes the base
2026
2100
  text of each ruby it held as text from then on (it keeps no guide
2027
- through an edit), so the run reads it as text. It writes the run's
2028
- symbol-block characters as a ``w:sym`` in the font the run owns too,
2029
- so a read's null ``symbolFont`` (a source ``w:t``'s characters) gives
2030
- way to the run's font (``_symbol_font``)."""
2101
+ through an edit), so the run reads it as text. A docx-studio that
2102
+ reports ``capabilities.rubyThroughFormat`` keeps the ruby through a
2103
+ whole-run format (``formatted``), as python-docx keeps the run's
2104
+ ``w:ruby``, so the run goes on reading it as the ruby's. It writes the
2105
+ run's symbol-block characters as a ``w:sym`` in the font the run owns
2106
+ too, so a read's null ``symbolFont`` (a source ``w:t``'s characters)
2107
+ gives way to the run's font (``_symbol_font``)."""
2031
2108
  snapshot = getattr(self, "_inline_snapshot", {})
2032
- snapshot.pop("ruby", None)
2109
+ if "ruby" in snapshot and not (
2110
+ formatted and server_takes(getattr(self, "_session", None), "rubyThroughFormat")
2111
+ ):
2112
+ snapshot.pop("ruby", None)
2033
2113
  if "symbolFont" in snapshot and snapshot["symbolFont"] is None:
2034
2114
  snapshot.pop("symbolFont")
2035
2115
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "athena-python-docx"
7
- version = "0.37.0"
7
+ version = "0.38.0"
8
8
  description = "Drop-in replacement for python-docx that connects to Athena's DOCX Studio collaborative document stack"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -466,6 +466,62 @@ class TestRunsHoldingPictures:
466
466
  assert formats == []
467
467
  assert picture_run._pending_inline == {"italic": True}
468
468
 
469
+ @pytest.mark.parametrize(("capability", "sent"), [(True, True), (False, False)])
470
+ def test_formatting_a_legacy_objects_run_formats_it_where_docx_studio_formats_them(
471
+ self, mock_session: Any, fake_sd_methods: Any, capability: bool, sent: bool
472
+ ) -> None:
473
+ """``p.runs[1].italic = True`` of an embedded object's run
474
+ (``<w:r><w:object>…</w:object></w:r>``, read as ``drawing.legacy``):
475
+ python-docx formats its ``w:r``. A docx-studio without
476
+ ``capabilities.legacyPictureRuns`` keeps the formatting for the text the
477
+ run takes, as before."""
478
+ fake_sd_methods.scripted_returns["info"] = {
479
+ "capabilities": {"pictureRuns": True, "legacyPictureRuns": capability, "runIdentity": True}
480
+ }
481
+ paragraph = _paragraph(
482
+ mock_session,
483
+ fake_sd_methods,
484
+ [{"run": {"text": "The figures "}}, {"drawing": {"imageId": "p1:0:aa", "legacy": True}}, {"run": {"text": " sit here."}}],
485
+ )
486
+ object_run = paragraph.runs[1]
487
+
488
+ object_run.italic = True
489
+
490
+ formats = [params for op, params in fake_sd_methods.calls if op == "format.apply"]
491
+ if sent:
492
+ assert formats == [
493
+ {
494
+ "target": {"kind": "text", "blockId": "p1", "range": {"start": 12, "end": 12}},
495
+ "inline": {"italic": True},
496
+ "wholeRun": True,
497
+ "run": {"laterRuns": 0},
498
+ }
499
+ ]
500
+ else:
501
+ assert formats == []
502
+ assert object_run._pending_inline == {"italic": True}
503
+
504
+ def test_formatting_a_preserved_shapes_run_stays_pending(self, mock_session: Any, fake_sd_methods: Any) -> None:
505
+ """A shape docx-studio keeps as it was (``mc:AlternateContent``) reads
506
+ as ``drawing.legacy`` with no ``imageId``: no picture holds it, so a
507
+ format of its run would reply ``formatted: false``. Its formatting stays
508
+ pending, as before, also on a docx-studio that reports
509
+ ``legacyPictureRuns``."""
510
+ fake_sd_methods.scripted_returns["info"] = {
511
+ "capabilities": {"pictureRuns": True, "legacyPictureRuns": True, "runIdentity": True}
512
+ }
513
+ paragraph = _paragraph(
514
+ mock_session,
515
+ fake_sd_methods,
516
+ [{"run": {"text": "Before "}}, {"drawing": {"imageId": None, "legacy": True}}, {"run": {"text": " after"}}],
517
+ )
518
+ shape_run = paragraph.runs[1]
519
+
520
+ shape_run.italic = True
521
+
522
+ assert [params for op, params in fake_sd_methods.calls if op == "format.apply"] == []
523
+ assert shape_run._pending_inline == {"italic": True}
524
+
469
525
  def test_a_pictures_run_reads_its_formatting(self, mock_session: Any, fake_sd_methods: Any) -> None:
470
526
  """python-docx reads a picture's run's ``w:rPr``: docx-studio reads its
471
527
  formatting on the picture's inline beside its drawing."""
@@ -386,6 +386,20 @@ def test_getters_return_int_members(mock_session: Any, fake_sd_methods: Any) ->
386
386
  assert table.table_direction is True and table.table_direction == WD_TABLE_DIRECTION.RTL
387
387
 
388
388
 
389
+ @pytest.mark.parametrize("jc", ["start", "end"])
390
+ def test_a_table_of_start_or_end_justification_raises_as_python_docx_reads_it(
391
+ mock_session: Any, fake_sd_methods: Any, jc: str
392
+ ) -> None:
393
+ """python-docx 1.2.0 reads a table's ``w:jc`` through the paragraph
394
+ ``CT_Jc``, whose enum has no member for ``start`` or ``end``."""
395
+ from docx.table import Table
396
+
397
+ fake_sd_methods.scripted_returns["tables.get_properties"] = {"alignment": jc}
398
+ table = Table(session=mock_session, node_id="tbl-1")
399
+ with pytest.raises(ValueError, match=f"WD_PARAGRAPH_ALIGNMENT has no XML mapping for '{jc}'"):
400
+ _ = table.alignment
401
+
402
+
389
403
  @pytest.mark.parametrize(
390
404
  "assign",
391
405
  [
@@ -13,8 +13,10 @@ is installed.
13
13
 
14
14
  docx-studio keeps the base text in its run's text, the room it takes in the
15
15
  text-address space, and gives its spans as ``ruby``: edits addressed after the
16
- ruby land after it. Once the run's text or formatting changes, docx-studio
17
- writes the base text as text, and the run reads it so.
16
+ ruby land after it. Once the run's text changes, docx-studio writes the base
17
+ text as text, and the run reads it so; so does a format of the run, but on a
18
+ docx-studio that reports ``rubyThroughFormat``, which keeps the ruby through it
19
+ as python-docx does.
18
20
  """
19
21
 
20
22
  from __future__ import annotations
@@ -190,3 +192,17 @@ def test_an_edit_of_the_rubys_run_reads_its_base_text_as_text(
190
192
 
191
193
  assert _writes(fake_sd_methods)
192
194
  assert "ruby" not in ruby._inline_snapshot
195
+
196
+
197
+ def test_a_format_of_the_rubys_run_keeps_its_ruby_where_docx_studio_keeps_it(
198
+ mock_session: Any, fake_sd_methods: Any
199
+ ) -> None:
200
+ # A docx-studio that reports `rubyThroughFormat` keeps the run's `w:ruby`
201
+ # through a whole-run format, as python-docx keeps it: the run still reads "".
202
+ fake_sd_methods.scripted_returns["info"] = {"capabilities": {"rubyThroughFormat": True}}
203
+ ruby = _body(mock_session, fake_sd_methods).runs[1]
204
+
205
+ ruby.bold = True
206
+
207
+ assert ruby._inline_snapshot.get("ruby") == [[0, 1]]
208
+ assert ruby.text == ""
@@ -0,0 +1,254 @@
1
+ """A run holding only a page or column break, as ``Run.iter_inner_content()`` yields it.
2
+
3
+ python-docx reads a ``<w:br w:type="page"/>`` or ``<w:br w:type="column"/>`` as
4
+ text, an empty string, so a run holding one and no other text yields ``""``;
5
+ an empty ``w:r`` (a comment reference's) yields nothing. docx-studio reads such
6
+ a break's run as ``{"run": {"text": "", "break": "page"}}`` (or ``"column"``);
7
+ an older one sends no ``break`` and the run yields nothing, as before.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import Any
13
+ from unittest.mock import AsyncMock, MagicMock
14
+
15
+
16
+ def _make_paragraph(inlines: list[dict]) -> Any:
17
+ from docx.client import Session
18
+ from docx.text.paragraph import Paragraph
19
+
20
+ session = MagicMock(spec=Session)
21
+ session.asset_id = "asset_fake"
22
+ session.is_open = True
23
+ session.doc = MagicMock()
24
+ session.doc.get_node_by_id = AsyncMock(
25
+ return_value={"node": {"paragraph": {"inlines": inlines}}}
26
+ )
27
+ return Paragraph(session=session, node_id="p1", node_type="paragraph")
28
+
29
+
30
+ def _inner(paragraph: Any) -> list[list[Any]]:
31
+ return [list(run.iter_inner_content()) for run in paragraph.runs]
32
+
33
+
34
+ def test_a_run_holding_only_a_page_or_column_break_yields_empty_text() -> None:
35
+ p = _make_paragraph(
36
+ [
37
+ {"run": {"text": "Before the breaks.", "bold": True}},
38
+ {"run": {"text": "", "break": "page"}},
39
+ {"run": {"text": "", "break": "column"}},
40
+ {"run": {"text": "After them.", "italic": True}},
41
+ ]
42
+ )
43
+
44
+ assert _inner(p) == [["Before the breaks."], [""], [""], ["After them."]]
45
+ assert [run.text for run in p.runs] == ["Before the breaks.", "", "", "After them."]
46
+
47
+
48
+ def test_an_empty_run_yields_nothing() -> None:
49
+ # A comment reference's run, and a break's from a docx-studio that sends no ``break``.
50
+ p = _make_paragraph([{"run": {"text": "Commented."}}, {"run": {"text": ""}}])
51
+
52
+ assert _inner(p) == [["Commented."], []]
53
+
54
+
55
+ def test_the_empty_run_a_page_or_column_break_is_added_to_yields_empty_text(
56
+ mock_session: Any, fake_sd_methods: Any
57
+ ) -> None:
58
+ from docx.enum.text import WD_BREAK
59
+ from docx.text.paragraph import Paragraph
60
+
61
+ fake_sd_methods.scripted_returns["info"] = {"capabilities": {"inlinePageBreak": True}}
62
+ paragraph = Paragraph(session=mock_session, node_id="p-1", text_len=0)
63
+ page = paragraph.add_run()
64
+ page.add_break(WD_BREAK.PAGE)
65
+ column = paragraph.add_run()
66
+ column.add_break(WD_BREAK.COLUMN)
67
+ empty = paragraph.add_run()
68
+
69
+ assert list(page.iter_inner_content()) == [""]
70
+ assert list(column.iter_inner_content()) == [""]
71
+ assert list(empty.iter_inner_content()) == []
72
+
73
+
74
+ def _replaces(fake: Any) -> list[dict[str, Any]]:
75
+ return [params for op, params in fake.calls if op == "replace"]
76
+
77
+
78
+ def test_clearing_a_run_holding_a_break_removes_it_and_its_proxy_forgets_it(
79
+ mock_session: Any, fake_sd_methods: Any
80
+ ) -> None:
81
+ # python-docx: run.clear() removes all of the run's content, its w:br too.
82
+ from docx.enum.text import WD_BREAK
83
+ from docx.text.paragraph import Paragraph
84
+
85
+ fake_sd_methods.scripted_returns["info"] = {
86
+ "capabilities": {"inlinePageBreak": True, "wholeRunBreaks": True}
87
+ }
88
+ paragraph = Paragraph(session=mock_session, node_id="p-1", text_len=0)
89
+ texted = paragraph.add_run("Hello")
90
+ texted.add_break(WD_BREAK.PAGE)
91
+ alone = paragraph.add_run()
92
+ alone.add_break(WD_BREAK.PAGE)
93
+
94
+ texted.clear()
95
+ alone.clear()
96
+
97
+ assert list(texted.iter_inner_content()) == []
98
+ assert list(alone.iter_inner_content()) == []
99
+ # Each run's content went with a wholeRun Replace, the empty run's at its place.
100
+ assert [(r["text"], r.get("wholeRun")) for r in _replaces(fake_sd_methods)] == [
101
+ ("", True),
102
+ ("", True),
103
+ ]
104
+
105
+
106
+ def test_a_docx_studio_that_keeps_a_run_s_breaks_keeps_them_on_the_proxy(
107
+ mock_session: Any, fake_sd_methods: Any
108
+ ) -> None:
109
+ # Before `wholeRunBreaks`, a wholeRun Replace leaves a break at the run's edge.
110
+ from docx.enum.text import WD_BREAK
111
+ from docx.text.paragraph import Paragraph
112
+
113
+ fake_sd_methods.scripted_returns["info"] = {"capabilities": {"inlinePageBreak": True}}
114
+ paragraph = Paragraph(session=mock_session, node_id="p-1", text_len=0)
115
+ alone = paragraph.add_run()
116
+ alone.add_break(WD_BREAK.PAGE)
117
+
118
+ alone.clear()
119
+
120
+ assert list(alone.iter_inner_content()) == [""]
121
+ assert _replaces(fake_sd_methods) == []
122
+
123
+
124
+ def _kinds(paragraph: Any) -> list[list[Any]]:
125
+ """Each run's inner content, a picture as ``"Drawing"``."""
126
+ from docx.drawing import Drawing
127
+
128
+ return [
129
+ ["Drawing" if isinstance(item, Drawing) else item for item in run.iter_inner_content()]
130
+ for run in paragraph.runs
131
+ ]
132
+
133
+
134
+ def test_a_run_of_a_page_break_and_a_picture_yields_the_breaks_text_where_it_stands() -> None:
135
+ # `<w:r><w:br w:type="page"/><w:drawing/></w:r>`: python-docx 1.2.0 yields ["", Drawing];
136
+ # `<w:r><w:drawing/><w:br w:type="page"/></w:r>`: [Drawing, ""].
137
+ picture = {"offset": 0, "drawing": {"imageId": "p1:0:abc"}}
138
+ p = _make_paragraph(
139
+ [
140
+ {"run": {"text": "Before."}},
141
+ {"run": {"text": "", "break": "page", "pictures": [picture], "emptyTextAfterPictures": [0]}},
142
+ {"run": {"text": "", "break": "page", "pictures": [picture], "emptyTextAfterPictures": [1]}},
143
+ ]
144
+ )
145
+
146
+ assert _kinds(p) == [["Before."], ["", "Drawing"], ["Drawing", ""]]
147
+
148
+
149
+ def test_a_break_on_each_side_of_a_picture_yields_empty_text_on_each_side() -> None:
150
+ # `<w:r><w:br w:type="page"/><w:drawing/><w:br w:type="page"/></w:r>`: python-docx 1.2.0
151
+ # yields ["", Drawing, ""].
152
+ picture = {"offset": 0, "drawing": {"imageId": "p1:0:abc"}}
153
+ p = _make_paragraph(
154
+ [{"run": {"text": "", "break": "page", "pictures": [picture], "emptyTextAfterPictures": [0, 1]}}]
155
+ )
156
+
157
+ assert _kinds(p) == [["", "Drawing", ""]]
158
+
159
+
160
+ def test_text_then_a_picture_then_a_page_break_yields_the_breaks_text_after_the_picture() -> None:
161
+ # `<w:r><w:t>Logo</w:t><w:drawing/><w:br w:type="page"/></w:r>`: ["Logo", Drawing, ""].
162
+ picture = {"offset": 4, "drawing": {"imageId": "p1:0:abc"}}
163
+ p = _make_paragraph([{"run": {"text": "Logo", "pictures": [picture], "emptyTextAfterPictures": [1]}}])
164
+
165
+ assert _kinds(p) == [["Logo", "Drawing", ""]]
166
+
167
+
168
+ def test_without_the_breaks_place_its_text_comes_before_the_pictures() -> None:
169
+ # A docx-studio before `emptyTextAfterPictures`: the break first, as a page that opens with the picture.
170
+ picture = {"offset": 0, "drawing": {"imageId": "p1:0:abc"}}
171
+ p = _make_paragraph([{"run": {"text": "", "break": "page", "pictures": [picture]}}])
172
+
173
+ assert _kinds(p) == [["", "Drawing"]]
174
+
175
+
176
+ def test_a_run_of_text_and_a_picture_yields_no_empty_text() -> None:
177
+ picture = {"offset": 4, "drawing": {"imageId": "p1:0:abc"}}
178
+ p = _make_paragraph([{"run": {"text": "Logo", "pictures": [picture]}}])
179
+
180
+ assert _kinds(p) == [["Logo", "Drawing"]]
181
+
182
+
183
+ PNG = bytes.fromhex(
184
+ "89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c489"
185
+ "0000000d4944415478da63f8ffff3f0005fe02fea7d6a4b60000000049454e44ae426082"
186
+ )
187
+
188
+
189
+ def _live_paragraph(mock_session: Any, fake_sd_methods: Any) -> Any:
190
+ from docx.text.paragraph import Paragraph
191
+
192
+ fake_sd_methods.scripted_returns["info"] = {
193
+ "capabilities": {"inlinePageBreak": True, "pictureRuns": True, "runIdentity": True}
194
+ }
195
+ fake_sd_methods.scripted_returns["create.image"] = {"nodeId": "p-1", "image": {"nodeId": "p-1:0:cc"}}
196
+ return Paragraph(session=mock_session, node_id="p-1", text_len=0)
197
+
198
+
199
+ def _live_kinds(run: Any) -> list[Any]:
200
+ from docx.drawing import Drawing
201
+
202
+ return ["Drawing" if isinstance(item, Drawing) else item for item in run.iter_inner_content()]
203
+
204
+
205
+ def test_the_run_add_break_then_add_picture_built_yields_the_breaks_text_first(
206
+ mock_session: Any, fake_sd_methods: Any
207
+ ) -> None:
208
+ # python-docx: `r = p.add_run(); r.add_break(PAGE); r.add_picture(png)` → ["", Drawing].
209
+ import io
210
+
211
+ from docx.enum.text import WD_BREAK
212
+
213
+ run = _live_paragraph(mock_session, fake_sd_methods).add_run()
214
+ run.add_break(WD_BREAK.PAGE)
215
+ run.add_picture(io.BytesIO(PNG))
216
+
217
+ assert _live_kinds(run) == ["", "Drawing"]
218
+
219
+
220
+ def test_the_run_add_picture_then_add_break_built_yields_the_breaks_text_last(
221
+ mock_session: Any, fake_sd_methods: Any
222
+ ) -> None:
223
+ # python-docx: `r.add_picture(png); r.add_break(PAGE)` → [Drawing, ""]; one more picture, then
224
+ # a column break: [Drawing, "", Drawing, ""].
225
+ import io
226
+
227
+ from docx.enum.text import WD_BREAK
228
+
229
+ run = _live_paragraph(mock_session, fake_sd_methods).add_run()
230
+ run.add_picture(io.BytesIO(PNG))
231
+ run.add_break(WD_BREAK.PAGE)
232
+
233
+ assert _live_kinds(run) == ["Drawing", ""]
234
+
235
+ run.add_picture(io.BytesIO(PNG))
236
+ run.add_break(WD_BREAK.COLUMN)
237
+
238
+ assert _live_kinds(run) == ["Drawing", "", "Drawing", ""]
239
+
240
+
241
+ def test_a_break_on_each_side_of_a_picture_added_to_the_run_yields_empty_text_on_each_side(
242
+ mock_session: Any, fake_sd_methods: Any
243
+ ) -> None:
244
+ import io
245
+
246
+ from docx.enum.text import WD_BREAK
247
+
248
+ run = _live_paragraph(mock_session, fake_sd_methods).add_run()
249
+ run.add_break(WD_BREAK.PAGE)
250
+ run.add_picture(io.BytesIO(PNG))
251
+ run.add_break(WD_BREAK.PAGE)
252
+
253
+ assert _live_kinds(run) == ["", "Drawing", ""]
254
+