docvortex 0.2.1__py3-none-any.whl

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 (364) hide show
  1. docvortex/__init__.py +21 -0
  2. docvortex/analyzers/__init__.py +3 -0
  3. docvortex/analyzers/native/__init__.py +37 -0
  4. docvortex/analyzers/native/_shared/__init__.py +3 -0
  5. docvortex/analyzers/native/_shared/hyperlink.py +16 -0
  6. docvortex/analyzers/native/_shared/image.py +8 -0
  7. docvortex/analyzers/native/_shared/markup/__init__.py +47 -0
  8. docvortex/analyzers/native/_shared/markup/anchors.py +30 -0
  9. docvortex/analyzers/native/_shared/markup/formula.py +37 -0
  10. docvortex/analyzers/native/_shared/markup/projector.py +57 -0
  11. docvortex/analyzers/native/_shared/markup/styles.py +19 -0
  12. docvortex/analyzers/native/_shared/mathml.py +17 -0
  13. docvortex/analyzers/native/_shared/names.py +7 -0
  14. docvortex/analyzers/native/_shared/xycut.py +414 -0
  15. docvortex/analyzers/native/contracts.py +44 -0
  16. docvortex/analyzers/native/csv.py +351 -0
  17. docvortex/analyzers/native/epub/__init__.py +18 -0
  18. docvortex/analyzers/native/epub/constants.py +45 -0
  19. docvortex/analyzers/native/epub/converter.py +80 -0
  20. docvortex/analyzers/native/epub/errors.py +20 -0
  21. docvortex/analyzers/native/epub/metadata.py +31 -0
  22. docvortex/analyzers/native/epub/package.py +649 -0
  23. docvortex/analyzers/native/epub/xhtml.py +306 -0
  24. docvortex/analyzers/native/html/__init__.py +6 -0
  25. docvortex/analyzers/native/html/anchors.py +271 -0
  26. docvortex/analyzers/native/html/constants.py +25 -0
  27. docvortex/analyzers/native/html/contracts.py +7 -0
  28. docvortex/analyzers/native/html/converter.py +117 -0
  29. docvortex/analyzers/native/html/document.py +389 -0
  30. docvortex/analyzers/native/html/errors.py +12 -0
  31. docvortex/analyzers/native/html/resources.py +365 -0
  32. docvortex/analyzers/native/html/selector.py +421 -0
  33. docvortex/analyzers/native/models.py +209 -0
  34. docvortex/analyzers/native/ofd/__init__.py +14 -0
  35. docvortex/analyzers/native/ofd/constants.py +59 -0
  36. docvortex/analyzers/native/ofd/converter.py +31 -0
  37. docvortex/analyzers/native/ofd/errors.py +16 -0
  38. docvortex/analyzers/native/ofd/geometry.py +215 -0
  39. docvortex/analyzers/native/ofd/images.py +124 -0
  40. docvortex/analyzers/native/ofd/metadata.py +53 -0
  41. docvortex/analyzers/native/ofd/models.py +170 -0
  42. docvortex/analyzers/native/ofd/package.py +346 -0
  43. docvortex/analyzers/native/ofd/path.py +201 -0
  44. docvortex/analyzers/native/ofd/reading_order.py +293 -0
  45. docvortex/analyzers/native/ofd/resources.py +122 -0
  46. docvortex/analyzers/native/ofd/scene.py +347 -0
  47. docvortex/analyzers/native/ofd/table.py +240 -0
  48. docvortex/analyzers/native/ofd/text.py +521 -0
  49. docvortex/analyzers/native/office/__init__.py +3 -0
  50. docvortex/analyzers/native/office/doc/__init__.py +3 -0
  51. docvortex/analyzers/native/office/doc/bookmarks.py +81 -0
  52. docvortex/analyzers/native/office/doc/doc_converter.py +573 -0
  53. docvortex/analyzers/native/office/doc/fib.py +261 -0
  54. docvortex/analyzers/native/office/doc/fields.py +101 -0
  55. docvortex/analyzers/native/office/doc/formatting.py +171 -0
  56. docvortex/analyzers/native/office/doc/images.py +162 -0
  57. docvortex/analyzers/native/office/doc/lists.py +356 -0
  58. docvortex/analyzers/native/office/doc/models.py +164 -0
  59. docvortex/analyzers/native/office/doc/parser.py +851 -0
  60. docvortex/analyzers/native/office/doc/pieces.py +256 -0
  61. docvortex/analyzers/native/office/doc/records.py +40 -0
  62. docvortex/analyzers/native/office/doc/sprm.py +269 -0
  63. docvortex/analyzers/native/office/doc/styles.py +215 -0
  64. docvortex/analyzers/native/office/docx/__init__.py +3 -0
  65. docvortex/analyzers/native/office/docx/context.py +71 -0
  66. docvortex/analyzers/native/office/docx/docx_converter.py +614 -0
  67. docvortex/analyzers/native/office/docx/equationxml.py +119 -0
  68. docvortex/analyzers/native/office/docx/fields.py +816 -0
  69. docvortex/analyzers/native/office/docx/formatting_types.py +23 -0
  70. docvortex/analyzers/native/office/docx/main.py +50 -0
  71. docvortex/analyzers/native/office/docx/numbering.py +491 -0
  72. docvortex/analyzers/native/office/docx/office_xml.py +57 -0
  73. docvortex/analyzers/native/office/docx/package_normalizer.py +248 -0
  74. docvortex/analyzers/native/office/docx/resources.py +526 -0
  75. docvortex/analyzers/native/office/docx/styles.py +693 -0
  76. docvortex/analyzers/native/office/docx/tables.py +515 -0
  77. docvortex/analyzers/native/office/equation/__init__.py +3 -0
  78. docvortex/analyzers/native/office/equation/image.py +470 -0
  79. docvortex/analyzers/native/office/equation/latex_dict.py +324 -0
  80. docvortex/analyzers/native/office/equation/mtef.py +885 -0
  81. docvortex/analyzers/native/office/equation/mtef_v5.py +941 -0
  82. docvortex/analyzers/native/office/equation/omml.py +561 -0
  83. docvortex/analyzers/native/office/equation/ooxml.py +62 -0
  84. docvortex/analyzers/native/office/errors.py +33 -0
  85. docvortex/analyzers/native/office/image.py +307 -0
  86. docvortex/analyzers/native/office/legacy/__init__.py +3 -0
  87. docvortex/analyzers/native/office/legacy/binary.py +43 -0
  88. docvortex/analyzers/native/office/legacy/officeart.py +362 -0
  89. docvortex/analyzers/native/office/legacy/ole.py +110 -0
  90. docvortex/analyzers/native/office/limits.py +12 -0
  91. docvortex/analyzers/native/office/odf/__init__.py +3 -0
  92. docvortex/analyzers/native/office/odf/chart.py +76 -0
  93. docvortex/analyzers/native/office/odf/constants.py +70 -0
  94. docvortex/analyzers/native/office/odf/converters.py +403 -0
  95. docvortex/analyzers/native/office/odf/errors.py +18 -0
  96. docvortex/analyzers/native/office/odf/metadata.py +91 -0
  97. docvortex/analyzers/native/office/odf/models.py +176 -0
  98. docvortex/analyzers/native/office/odf/package.py +270 -0
  99. docvortex/analyzers/native/office/odf/styles.py +329 -0
  100. docvortex/analyzers/native/office/odf/table.py +469 -0
  101. docvortex/analyzers/native/office/odf/text.py +1002 -0
  102. docvortex/analyzers/native/office/ooxml_chart.py +1016 -0
  103. docvortex/analyzers/native/office/opc.py +38 -0
  104. docvortex/analyzers/native/office/ppt/__init__.py +3 -0
  105. docvortex/analyzers/native/office/ppt/models.py +118 -0
  106. docvortex/analyzers/native/office/ppt/parser.py +1895 -0
  107. docvortex/analyzers/native/office/ppt/ppt_converter.py +292 -0
  108. docvortex/analyzers/native/office/ppt/records.py +131 -0
  109. docvortex/analyzers/native/office/ppt/style_text.py +247 -0
  110. docvortex/analyzers/native/office/pptx/__init__.py +3 -0
  111. docvortex/analyzers/native/office/pptx/context.py +113 -0
  112. docvortex/analyzers/native/office/pptx/lists.py +558 -0
  113. docvortex/analyzers/native/office/pptx/main.py +20 -0
  114. docvortex/analyzers/native/office/pptx/package_normalizer.py +321 -0
  115. docvortex/analyzers/native/office/pptx/pptx_converter.py +323 -0
  116. docvortex/analyzers/native/office/pptx/resources.py +329 -0
  117. docvortex/analyzers/native/office/pptx/shapes.py +393 -0
  118. docvortex/analyzers/native/office/pptx/text_styles.py +625 -0
  119. docvortex/analyzers/native/office/pptx/titles.py +178 -0
  120. docvortex/analyzers/native/office/rich_text.py +420 -0
  121. docvortex/analyzers/native/office/rtf/__init__.py +3 -0
  122. docvortex/analyzers/native/office/rtf/converter.py +708 -0
  123. docvortex/analyzers/native/office/rtf/lexer.py +213 -0
  124. docvortex/analyzers/native/office/rtf/math.py +339 -0
  125. docvortex/analyzers/native/office/rtf/models.py +185 -0
  126. docvortex/analyzers/native/office/rtf/parser.py +1553 -0
  127. docvortex/analyzers/native/office/spreadsheet/__init__.py +3 -0
  128. docvortex/analyzers/native/office/spreadsheet/html.py +81 -0
  129. docvortex/analyzers/native/office/spreadsheet/models.py +79 -0
  130. docvortex/analyzers/native/office/spreadsheet/projector.py +928 -0
  131. docvortex/analyzers/native/office/streams.py +18 -0
  132. docvortex/analyzers/native/office/xls/__init__.py +3 -0
  133. docvortex/analyzers/native/office/xls/chart.py +132 -0
  134. docvortex/analyzers/native/office/xls/embedded_chart.py +299 -0
  135. docvortex/analyzers/native/office/xls/models.py +109 -0
  136. docvortex/analyzers/native/office/xls/number_format.py +521 -0
  137. docvortex/analyzers/native/office/xls/parser.py +1145 -0
  138. docvortex/analyzers/native/office/xls/records.py +201 -0
  139. docvortex/analyzers/native/office/xls/strings.py +205 -0
  140. docvortex/analyzers/native/office/xls/xls_converter.py +354 -0
  141. docvortex/analyzers/native/office/xlsx/__init__.py +3 -0
  142. docvortex/analyzers/native/office/xlsx/main.py +20 -0
  143. docvortex/analyzers/native/office/xlsx/ooxml_ole.py +522 -0
  144. docvortex/analyzers/native/office/xlsx/package_normalizer.py +310 -0
  145. docvortex/analyzers/native/office/xlsx/xlsx_converter.py +716 -0
  146. docvortex/analyzers/native/pdf/__init__.py +3 -0
  147. docvortex/analyzers/native/pdf/auxiliary_text.py +1671 -0
  148. docvortex/analyzers/native/pdf/char_geometry.py +1662 -0
  149. docvortex/analyzers/native/pdf/code_blocks.py +535 -0
  150. docvortex/analyzers/native/pdf/formulas.py +1985 -0
  151. docvortex/analyzers/native/pdf/geometry.py +281 -0
  152. docvortex/analyzers/native/pdf/graphics.py +1501 -0
  153. docvortex/analyzers/native/pdf/index_blocks.py +268 -0
  154. docvortex/analyzers/native/pdf/inline/__init__.py +3 -0
  155. docvortex/analyzers/native/pdf/inline/common.py +112 -0
  156. docvortex/analyzers/native/pdf/inline/detection.py +590 -0
  157. docvortex/analyzers/native/pdf/inline/matching.py +1185 -0
  158. docvortex/analyzers/native/pdf/inline/materialize.py +457 -0
  159. docvortex/analyzers/native/pdf/inline/scripts.py +975 -0
  160. docvortex/analyzers/native/pdf/inline/types.py +385 -0
  161. docvortex/analyzers/native/pdf/line_layout.py +1106 -0
  162. docvortex/analyzers/native/pdf/line_merging.py +1223 -0
  163. docvortex/analyzers/native/pdf/models.py +246 -0
  164. docvortex/analyzers/native/pdf/native_text.py +1004 -0
  165. docvortex/analyzers/native/pdf/pipeline.py +1390 -0
  166. docvortex/analyzers/native/pdf/script_geometry.py +636 -0
  167. docvortex/analyzers/native/pdf/shared.py +30 -0
  168. docvortex/analyzers/native/pdf/spatial_text.py +383 -0
  169. docvortex/analyzers/native/pdf/table_annotations.py +446 -0
  170. docvortex/analyzers/native/pdf/table_constants.py +48 -0
  171. docvortex/analyzers/native/pdf/table_detection.py +227 -0
  172. docvortex/analyzers/native/pdf/table_filled_grid.py +218 -0
  173. docvortex/analyzers/native/pdf/table_geometry.py +40 -0
  174. docvortex/analyzers/native/pdf/table_materialization.py +444 -0
  175. docvortex/analyzers/native/pdf/table_recovery/__init__.py +15 -0
  176. docvortex/analyzers/native/pdf/table_recovery/candidate.py +356 -0
  177. docvortex/analyzers/native/pdf/table_recovery/contracts.py +154 -0
  178. docvortex/analyzers/native/pdf/table_recovery/engine.py +609 -0
  179. docvortex/analyzers/native/pdf/table_recovery/geometry.py +164 -0
  180. docvortex/analyzers/native/pdf/table_recovery/sparse_common.py +85 -0
  181. docvortex/analyzers/native/pdf/table_recovery/sparse_hybrid.py +804 -0
  182. docvortex/analyzers/native/pdf/table_recovery/sparse_multiline.py +1122 -0
  183. docvortex/analyzers/native/pdf/table_recovery/text.py +414 -0
  184. docvortex/analyzers/native/pdf/table_recovery/text_grid.py +618 -0
  185. docvortex/analyzers/native/pdf/table_recovery/vector.py +1931 -0
  186. docvortex/analyzers/native/pdf/table_rows.py +34 -0
  187. docvortex/analyzers/native/pdf/table_rules.py +1129 -0
  188. docvortex/analyzers/native/pdf/table_text_styles.py +283 -0
  189. docvortex/analyzers/native/pdf/tables.py +147 -0
  190. docvortex/analyzers/native/pdf/text_assembly/__init__.py +3 -0
  191. docvortex/analyzers/native/pdf/text_assembly/annotations.py +581 -0
  192. docvortex/analyzers/native/pdf/text_assembly/assembly.py +292 -0
  193. docvortex/analyzers/native/pdf/text_assembly/common.py +477 -0
  194. docvortex/analyzers/native/pdf/text_assembly/footnotes.py +394 -0
  195. docvortex/analyzers/native/pdf/text_assembly/merging.py +1274 -0
  196. docvortex/analyzers/native/pdf/text_assembly/rows.py +692 -0
  197. docvortex/analyzers/native/pdf/text_blocks.py +82 -0
  198. docvortex/analyzers/native/pdf/text_styles.py +55 -0
  199. docvortex/analyzers/native/pdf/title_analysis/__init__.py +3 -0
  200. docvortex/analyzers/native/pdf/title_analysis/body_profile.py +215 -0
  201. docvortex/analyzers/native/pdf/title_analysis/common.py +117 -0
  202. docvortex/analyzers/native/pdf/title_analysis/document_profile.py +164 -0
  203. docvortex/analyzers/native/pdf/title_analysis/lane_titles.py +758 -0
  204. docvortex/analyzers/native/pdf/title_analysis/page_titles.py +1024 -0
  205. docvortex/analyzers/native/pdf/title_analysis/prototype.py +194 -0
  206. docvortex/analyzers/native/pdf/title_analysis/structural.py +1081 -0
  207. docvortex/analyzers/native/pdf/titles.py +75 -0
  208. docvortex/analyzers/native/pdf/typography.py +19 -0
  209. docvortex/analyzers/native/pdf/visual_annotations.py +1262 -0
  210. docvortex/api.py +180 -0
  211. docvortex/assets/__init__.py +5 -0
  212. docvortex/assets/store.py +51 -0
  213. docvortex/cli.py +54 -0
  214. docvortex/codecs/__init__.py +3 -0
  215. docvortex/codecs/html/__init__.py +22 -0
  216. docvortex/codecs/html/contracts.py +236 -0
  217. docvortex/codecs/html/materializer.py +331 -0
  218. docvortex/codecs/html/parser.py +763 -0
  219. docvortex/codecs/html/resources.py +34 -0
  220. docvortex/codecs/json.py +17 -0
  221. docvortex/content/__init__.py +5 -0
  222. docvortex/content/inline.py +248 -0
  223. docvortex/content/markup/__init__.py +44 -0
  224. docvortex/content/markup/anchors.py +188 -0
  225. docvortex/content/markup/formula.py +280 -0
  226. docvortex/content/markup/projector.py +1237 -0
  227. docvortex/content/markup/styles.py +327 -0
  228. docvortex/content/mathml.py +167 -0
  229. docvortex/content/normalization.py +188 -0
  230. docvortex/content/spans.py +183 -0
  231. docvortex/content/table/__init__.py +18 -0
  232. docvortex/content/table/blocks.py +152 -0
  233. docvortex/content/table/content.py +425 -0
  234. docvortex/content/table/document.py +104 -0
  235. docvortex/content/table/html.py +399 -0
  236. docvortex/content/table/models.py +76 -0
  237. docvortex/content/table/rules.py +42 -0
  238. docvortex/content/table/structure.py +221 -0
  239. docvortex/content/tree.py +5 -0
  240. docvortex/document/__init__.py +3 -0
  241. docvortex/document/contracts.py +22 -0
  242. docvortex/document/detection.py +389 -0
  243. docvortex/document/filetypes.py +175 -0
  244. docvortex/document/page_range.py +167 -0
  245. docvortex/document/pdf/__init__.py +19 -0
  246. docvortex/document/pdf/classify.py +1138 -0
  247. docvortex/document/pdf/constants.py +132 -0
  248. docvortex/document/pdf/diagnostics.py +350 -0
  249. docvortex/document/pdf/document.py +582 -0
  250. docvortex/document/pdf/font_runtime.py +335 -0
  251. docvortex/document/pdf/geometry.py +31 -0
  252. docvortex/document/pdf/images.py +654 -0
  253. docvortex/document/pdf/native_annotations.py +367 -0
  254. docvortex/document/pdf/native_contracts.py +169 -0
  255. docvortex/document/pdf/native_coordinates.py +216 -0
  256. docvortex/document/pdf/native_lifecycle.py +16 -0
  257. docvortex/document/pdf/native_objects.py +902 -0
  258. docvortex/document/pdf/native_text_geometry.py +315 -0
  259. docvortex/document/pdf/pdfium.py +325 -0
  260. docvortex/document/pdf/raster.py +46 -0
  261. docvortex/document/pdf/text/__init__.py +62 -0
  262. docvortex/document/pdf/text/contracts.py +211 -0
  263. docvortex/document/pdf/text/extract.py +165 -0
  264. docvortex/document/pdf/text/geometry.py +16 -0
  265. docvortex/document/pdf/text/groups.py +162 -0
  266. docvortex/document/pdf/visual_geometry.py +201 -0
  267. docvortex/document/pdf/visuals.py +343 -0
  268. docvortex/document/source.py +92 -0
  269. docvortex/errors.py +21 -0
  270. docvortex/export/__init__.py +3 -0
  271. docvortex/export/bundle.py +98 -0
  272. docvortex/export/files.py +66 -0
  273. docvortex/export/middle.py +208 -0
  274. docvortex/foundation/__init__.py +3 -0
  275. docvortex/foundation/geometry.py +125 -0
  276. docvortex/foundation/hyperlink.py +65 -0
  277. docvortex/foundation/image.py +48 -0
  278. docvortex/foundation/image_encoding.py +30 -0
  279. docvortex/foundation/image_payload.py +280 -0
  280. docvortex/foundation/language.py +92 -0
  281. docvortex/foundation/platform.py +38 -0
  282. docvortex/foundation/text.py +153 -0
  283. docvortex/foundation/type_identity.py +20 -0
  284. docvortex/foundation/xml_names.py +20 -0
  285. docvortex/options.py +30 -0
  286. docvortex/postprocess/__init__.py +3 -0
  287. docvortex/postprocess/content.py +53 -0
  288. docvortex/postprocess/document.py +19 -0
  289. docvortex/postprocess/lists.py +236 -0
  290. docvortex/postprocess/page_blocks.py +214 -0
  291. docvortex/postprocess/pages.py +95 -0
  292. docvortex/postprocess/paragraphs.py +580 -0
  293. docvortex/postprocess/visual.py +715 -0
  294. docvortex/render/__init__.py +48 -0
  295. docvortex/render/_internal/__init__.py +3 -0
  296. docvortex/render/_internal/common/__init__.py +3 -0
  297. docvortex/render/_internal/common/context.py +43 -0
  298. docvortex/render/_internal/common/html_table.py +178 -0
  299. docvortex/render/_internal/common/index.py +33 -0
  300. docvortex/render/_internal/common/list_items.py +158 -0
  301. docvortex/render/_internal/common/planner.py +140 -0
  302. docvortex/render/_internal/docx/__init__.py +3 -0
  303. docvortex/render/_internal/docx/assets.py +202 -0
  304. docvortex/render/_internal/docx/inline.py +434 -0
  305. docvortex/render/_internal/docx/math.py +220 -0
  306. docvortex/render/_internal/docx/renderer.py +905 -0
  307. docvortex/render/_internal/docx/styles.py +195 -0
  308. docvortex/render/_internal/docx/table.py +442 -0
  309. docvortex/render/_internal/epub/__init__.py +5 -0
  310. docvortex/render/_internal/epub/assets.py +173 -0
  311. docvortex/render/_internal/epub/package.py +249 -0
  312. docvortex/render/_internal/epub/renderer.py +1156 -0
  313. docvortex/render/_internal/html/__init__.py +3 -0
  314. docvortex/render/_internal/html/inline.py +346 -0
  315. docvortex/render/_internal/html/renderer.py +1041 -0
  316. docvortex/render/_internal/html/sanitizer.py +478 -0
  317. docvortex/render/_internal/html/table.py +121 -0
  318. docvortex/render/_internal/latex/__init__.py +1 -0
  319. docvortex/render/_internal/latex/assets.py +85 -0
  320. docvortex/render/_internal/latex/inline.py +145 -0
  321. docvortex/render/_internal/latex/renderer.py +506 -0
  322. docvortex/render/_internal/latex/table.py +347 -0
  323. docvortex/render/_internal/markdown/__init__.py +3 -0
  324. docvortex/render/_internal/markdown/assets.py +78 -0
  325. docvortex/render/_internal/markdown/blocks.py +635 -0
  326. docvortex/render/_internal/markdown/escaping.py +51 -0
  327. docvortex/render/_internal/markdown/inline.py +260 -0
  328. docvortex/render/_internal/markdown/renderer.py +93 -0
  329. docvortex/render/_internal/markdown/table.py +281 -0
  330. docvortex/render/_internal/pdf/__init__.py +3 -0
  331. docvortex/render/_internal/pdf/assets.py +197 -0
  332. docvortex/render/_internal/pdf/formula.py +417 -0
  333. docvortex/render/_internal/pdf/inline.py +343 -0
  334. docvortex/render/_internal/pdf/renderer.py +734 -0
  335. docvortex/render/_internal/pdf/styles.py +206 -0
  336. docvortex/render/_internal/pdf/table.py +272 -0
  337. docvortex/render/_internal/structured_content/__init__.py +3 -0
  338. docvortex/render/_internal/structured_content/renderer.py +193 -0
  339. docvortex/render/api.py +199 -0
  340. docvortex/render/contracts.py +205 -0
  341. docvortex/render/docx.py +38 -0
  342. docvortex/render/epub.py +35 -0
  343. docvortex/render/fragments.py +70 -0
  344. docvortex/render/html.py +29 -0
  345. docvortex/render/latex.py +24 -0
  346. docvortex/render/markdown.py +50 -0
  347. docvortex/render/pdf.py +25 -0
  348. docvortex/render/structured_content.py +23 -0
  349. docvortex/resources/epub/docvortex.css +91 -0
  350. docvortex/resources/fasttext-langdetect/lid.176.ftz +0 -0
  351. docvortex/resources/fonts/DroidSansFallbackFull.ttf +0 -0
  352. docvortex/resources/fonts/NOTICE +190 -0
  353. docvortex/resources/fonts/manifest.json +9 -0
  354. docvortex/resources/html/docvortex.css +601 -0
  355. docvortex/resources/html/docvortex.min.css +1 -0
  356. docvortex/result.py +91 -0
  357. docvortex/schema.py +1141 -0
  358. docvortex/version.py +3 -0
  359. docvortex-0.2.1.dist-info/METADATA +193 -0
  360. docvortex-0.2.1.dist-info/RECORD +364 -0
  361. docvortex-0.2.1.dist-info/WHEEL +5 -0
  362. docvortex-0.2.1.dist-info/entry_points.txt +2 -0
  363. docvortex-0.2.1.dist-info/licenses/LICENSE.md +21 -0
  364. docvortex-0.2.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,195 @@
1
+ """DOCX renderer 的页面几何与 Word 样式定义。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from docx.document import Document
6
+ from docx.enum.style import WD_STYLE_TYPE
7
+ from docx.enum.text import WD_ALIGN_PARAGRAPH
8
+ from docx.oxml import OxmlElement
9
+ from docx.oxml.ns import qn
10
+ from docx.shared import Mm, Pt, RGBColor
11
+ from docx.styles.style import _ParagraphStyle
12
+
13
+ BODY_STYLE = "Normal"
14
+ CODE_STYLE = "DocVortex Code"
15
+ SPATIAL_TABLE_STYLE = "DocVortex Spatial Table"
16
+ CAPTION_STYLE = "DocVortex Caption"
17
+ FOOTNOTE_STYLE = "DocVortex Footnote"
18
+ FORMULA_FALLBACK_STYLE = "DocVortex Formula Fallback"
19
+
20
+ _HEADING_SIZES = (20, 18, 16, 14, 13, 12, 11, 10.5, 10.5)
21
+
22
+
23
+ def configure_document(document: Document) -> None:
24
+ """设置 A4 页面几何,并创建 renderer 使用的全部显式样式。"""
25
+ section = document.sections[0]
26
+ section.page_width = Mm(210)
27
+ section.page_height = Mm(297)
28
+ section.top_margin = Mm(20)
29
+ section.bottom_margin = Mm(20)
30
+ section.left_margin = Mm(20)
31
+ section.right_margin = Mm(20)
32
+
33
+ normal = document.styles[BODY_STYLE]
34
+ _configure_paragraph_style(
35
+ normal,
36
+ western_font="Times New Roman",
37
+ east_asia_font="宋体",
38
+ size_pt=10.5,
39
+ space_before_pt=0,
40
+ space_after_pt=6,
41
+ line_spacing=1.15,
42
+ )
43
+ for level, size_pt in enumerate(_HEADING_SIZES, start=1):
44
+ heading = document.styles[f"Heading {level}"]
45
+ _configure_paragraph_style(
46
+ heading,
47
+ western_font="Arial",
48
+ east_asia_font="黑体",
49
+ size_pt=size_pt,
50
+ bold=True,
51
+ space_before_pt=10 if level <= 3 else 8,
52
+ space_after_pt=5 if level <= 3 else 4,
53
+ line_spacing=1.0,
54
+ )
55
+ heading.paragraph_format.keep_with_next = True
56
+
57
+ code = _get_or_add_paragraph_style(document, CODE_STYLE)
58
+ _configure_paragraph_style(
59
+ code,
60
+ western_font="Courier New",
61
+ east_asia_font="宋体",
62
+ size_pt=9,
63
+ space_before_pt=4,
64
+ space_after_pt=6,
65
+ line_spacing=1.0,
66
+ )
67
+ _set_paragraph_shading(code, "F3F4F6")
68
+
69
+ spatial_table = _get_or_add_paragraph_style(document, SPATIAL_TABLE_STYLE)
70
+ _configure_paragraph_style(
71
+ spatial_table,
72
+ western_font="Courier New",
73
+ east_asia_font="宋体",
74
+ size_pt=9,
75
+ space_before_pt=4,
76
+ space_after_pt=6,
77
+ line_spacing=1.0,
78
+ )
79
+
80
+ caption = _get_or_add_paragraph_style(document, CAPTION_STYLE)
81
+ _configure_paragraph_style(
82
+ caption,
83
+ western_font="Times New Roman",
84
+ east_asia_font="宋体",
85
+ size_pt=9,
86
+ space_before_pt=3,
87
+ space_after_pt=5,
88
+ line_spacing=1.0,
89
+ )
90
+ caption.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
91
+ caption.paragraph_format.keep_with_next = True
92
+
93
+ footnote = _get_or_add_paragraph_style(document, FOOTNOTE_STYLE)
94
+ _configure_paragraph_style(
95
+ footnote,
96
+ western_font="Times New Roman",
97
+ east_asia_font="宋体",
98
+ size_pt=8,
99
+ color="666666",
100
+ space_before_pt=2,
101
+ space_after_pt=4,
102
+ line_spacing=1.0,
103
+ )
104
+
105
+ formula_fallback = _get_or_add_paragraph_style(document, FORMULA_FALLBACK_STYLE)
106
+ _configure_paragraph_style(
107
+ formula_fallback,
108
+ western_font="Courier New",
109
+ east_asia_font="宋体",
110
+ size_pt=9,
111
+ color="555555",
112
+ space_before_pt=4,
113
+ space_after_pt=5,
114
+ line_spacing=1.0,
115
+ )
116
+ formula_fallback.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
117
+
118
+
119
+ def usable_width_twips(document: Document) -> int:
120
+ """返回首个 section 扣除左右页边距后的可用宽度,单位 twip。"""
121
+ section = document.sections[0]
122
+ usable_emu = int(section.page_width) - int(section.left_margin) - int(section.right_margin)
123
+ return max(1, round(usable_emu / 635))
124
+
125
+
126
+ def usable_width_emu(document: Document) -> int:
127
+ """返回首个 section 扣除左右页边距后的可用宽度,单位 EMU。"""
128
+ section = document.sections[0]
129
+ return max(1, int(section.page_width) - int(section.left_margin) - int(section.right_margin))
130
+
131
+
132
+ def _get_or_add_paragraph_style(document: Document, name: str) -> _ParagraphStyle:
133
+ """获取既有段落样式,缺失时创建同名样式。"""
134
+ styles = document.styles
135
+ try:
136
+ style = styles[name]
137
+ except KeyError:
138
+ style = styles.add_style(name, WD_STYLE_TYPE.PARAGRAPH)
139
+ if not isinstance(style, _ParagraphStyle):
140
+ raise TypeError(f"Style is not a paragraph style: {name}")
141
+ return style
142
+
143
+
144
+ def _configure_paragraph_style(
145
+ style: _ParagraphStyle,
146
+ *,
147
+ western_font: str,
148
+ east_asia_font: str,
149
+ size_pt: float,
150
+ bold: bool = False,
151
+ italic: bool = False,
152
+ color: str | None = None,
153
+ space_before_pt: float,
154
+ space_after_pt: float,
155
+ line_spacing: float,
156
+ ) -> None:
157
+ """给一个 Word 段落样式写入确定性的字体与段落节奏。"""
158
+ style.font.name = western_font
159
+ style.font.size = Pt(size_pt)
160
+ style.font.bold = bold
161
+ style.font.italic = italic
162
+ if color is not None:
163
+ style.font.color.rgb = RGBColor.from_string(color)
164
+ run_properties = style._element.get_or_add_rPr()
165
+ run_fonts = run_properties.get_or_add_rFonts()
166
+ run_fonts.set(qn("w:ascii"), western_font)
167
+ run_fonts.set(qn("w:hAnsi"), western_font)
168
+ run_fonts.set(qn("w:eastAsia"), east_asia_font)
169
+ paragraph_format = style.paragraph_format
170
+ paragraph_format.space_before = Pt(space_before_pt)
171
+ paragraph_format.space_after = Pt(space_after_pt)
172
+ paragraph_format.line_spacing = line_spacing
173
+
174
+
175
+ def _set_paragraph_shading(style: _ParagraphStyle, fill: str) -> None:
176
+ """给段落样式设置稳定的背景色。"""
177
+ paragraph_properties = style._element.get_or_add_pPr()
178
+ shading = paragraph_properties.find(qn("w:shd"))
179
+ if shading is None:
180
+ shading = OxmlElement("w:shd")
181
+ paragraph_properties.append(shading)
182
+ shading.set(qn("w:fill"), fill)
183
+
184
+
185
+ __all__ = [
186
+ "BODY_STYLE",
187
+ "CAPTION_STYLE",
188
+ "CODE_STYLE",
189
+ "FOOTNOTE_STYLE",
190
+ "FORMULA_FALLBACK_STYLE",
191
+ "SPATIAL_TABLE_STYLE",
192
+ "configure_document",
193
+ "usable_width_emu",
194
+ "usable_width_twips",
195
+ ]
@@ -0,0 +1,442 @@
1
+ """DOCX renderer 的 HTML 表格占位网格解析与原生表格物化。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from dataclasses import dataclass
7
+ from typing import Any, Protocol, TypeAlias
8
+
9
+ from bs4 import BeautifulSoup
10
+ from bs4.element import Tag
11
+ from docx.document import Document as DocxDocument
12
+ from docx.exceptions import InvalidSpanError
13
+ from docx.oxml import OxmlElement
14
+ from docx.oxml.ns import qn
15
+ from docx.shared import Twips
16
+ from docx.table import Table, _Cell
17
+
18
+
19
+ DEFAULT_TABLE_WIDTH_TWIPS = 8640
20
+ MAX_NESTED_TABLE_DEPTH = 4
21
+ _POSITIVE_INTEGER_RE = re.compile(r"[0-9]+")
22
+
23
+ HtmlTableSource: TypeAlias = str | BeautifulSoup | Tag
24
+
25
+
26
+ class DocxTableError(ValueError):
27
+ """表示 HTML 表格结构或 DOCX 表格几何无法安全物化。"""
28
+
29
+
30
+ @dataclass(frozen=True, slots=True)
31
+ class HtmlTableCell:
32
+ """保存一个 HTML 原始单元格在逻辑占位网格中的位置。"""
33
+
34
+ tag: Tag
35
+ row: int
36
+ column: int
37
+ rowspan: int
38
+ colspan: int
39
+ is_header: bool
40
+
41
+ @property
42
+ def end_row(self) -> int:
43
+ """返回单元格占用的末行下标。"""
44
+ return self.row + self.rowspan - 1
45
+
46
+ @property
47
+ def end_column(self) -> int:
48
+ """返回单元格占用的末列下标。"""
49
+ return self.column + self.colspan - 1
50
+
51
+
52
+ @dataclass(frozen=True, slots=True)
53
+ class HtmlTableGrid:
54
+ """保存一个经过完整矩形校验的 HTML 表格占位网格。"""
55
+
56
+ tag: Tag
57
+ row_count: int
58
+ column_count: int
59
+ cells: tuple[HtmlTableCell, ...]
60
+ occupancy: tuple[tuple[HtmlTableCell, ...], ...]
61
+ header_rows: tuple[int, ...]
62
+
63
+
64
+ class NestedTableWriter(Protocol):
65
+ """定义单元格回调可调用的嵌套表格写入函数。"""
66
+
67
+ def __call__(
68
+ self,
69
+ source: HtmlTableSource,
70
+ width_twips: int | None = None,
71
+ ) -> tuple[Table, ...]:
72
+ """把 source 中的直接嵌套表格写入当前 Word 单元格。"""
73
+ ...
74
+
75
+
76
+ class CellFillCallback(Protocol):
77
+ """定义由上层 renderer 填充原始单元格内容的回调。"""
78
+
79
+ def __call__(
80
+ self,
81
+ cell: _Cell,
82
+ source: Tag,
83
+ write_nested: NestedTableWriter,
84
+ ) -> None:
85
+ """使用原始 td/th 标签填充 origin cell,并按需递归写入嵌套表格。"""
86
+ ...
87
+
88
+
89
+ def parse_html_table(table: Tag) -> HtmlTableGrid:
90
+ """把单个 table 标签解析为经过重叠、越界和矩形校验的占位网格。"""
91
+ if table.name != "table":
92
+ raise DocxTableError("Expected a <table> tag")
93
+
94
+ rows = tuple(row for row in table.find_all("tr") if row.find_parent("table") is table)
95
+ if not rows:
96
+ raise DocxTableError("Table must contain at least one row")
97
+
98
+ occupied: dict[tuple[int, int], HtmlTableCell] = {}
99
+ cells: list[HtmlTableCell] = []
100
+
101
+ for row_index, row in enumerate(rows):
102
+ column_index = 0
103
+ source_cells = row.find_all(("td", "th"), recursive=False)
104
+ for source_cell in source_cells:
105
+ while (row_index, column_index) in occupied:
106
+ column_index += 1
107
+
108
+ rowspan = _parse_span(source_cell, "rowspan")
109
+ colspan = _parse_span(source_cell, "colspan")
110
+ if row_index + rowspan > len(rows):
111
+ raise DocxTableError(f"rowspan exceeds table bounds at row={row_index}, column={column_index}")
112
+
113
+ coordinates = tuple(
114
+ (target_row, target_column)
115
+ for target_row in range(row_index, row_index + rowspan)
116
+ for target_column in range(column_index, column_index + colspan)
117
+ )
118
+ overlap = next((coordinate for coordinate in coordinates if coordinate in occupied), None)
119
+ if overlap is not None:
120
+ raise DocxTableError(f"Cell span overlaps an occupied coordinate at row={overlap[0]}, column={overlap[1]}")
121
+
122
+ placement = HtmlTableCell(
123
+ tag=source_cell,
124
+ row=row_index,
125
+ column=column_index,
126
+ rowspan=rowspan,
127
+ colspan=colspan,
128
+ is_header=source_cell.name == "th",
129
+ )
130
+ cells.append(placement)
131
+ occupied.update(dict.fromkeys(coordinates, placement))
132
+ column_index += colspan
133
+
134
+ if not occupied:
135
+ raise DocxTableError("Table must contain at least one cell")
136
+
137
+ column_count = max(column for _, column in occupied) + 1
138
+ missing = next(
139
+ (
140
+ (row_index, column_index)
141
+ for row_index in range(len(rows))
142
+ for column_index in range(column_count)
143
+ if (row_index, column_index) not in occupied
144
+ ),
145
+ None,
146
+ )
147
+ if missing is not None:
148
+ raise DocxTableError(f"Table occupancy must be rectangular; missing row={missing[0]}, column={missing[1]}")
149
+
150
+ occupancy = tuple(
151
+ tuple(occupied[(row_index, column_index)] for column_index in range(column_count)) for row_index in range(len(rows))
152
+ )
153
+ header_rows = tuple(
154
+ row_index
155
+ for row_index, row in enumerate(rows)
156
+ if _row_belongs_to_thead(row, table) or all(cell.is_header for cell in occupancy[row_index])
157
+ )
158
+ return HtmlTableGrid(
159
+ tag=table,
160
+ row_count=len(rows),
161
+ column_count=column_count,
162
+ cells=tuple(cells),
163
+ occupancy=occupancy,
164
+ header_rows=header_rows,
165
+ )
166
+
167
+
168
+ def parse_html_tables(source: HtmlTableSource) -> tuple[HtmlTableGrid, ...]:
169
+ """解析 source 中相对当前上下文的一个或多个顶层 table。"""
170
+ root = BeautifulSoup(source, "html.parser") if isinstance(source, str) else source
171
+ if not isinstance(root, (BeautifulSoup, Tag)):
172
+ raise DocxTableError("HTML table source must be a string or BeautifulSoup Tag")
173
+
174
+ if isinstance(root, Tag) and root.name == "table":
175
+ table_tags = (root,)
176
+ else:
177
+ parent_table = root.find_parent("table") if isinstance(root, Tag) else None
178
+ table_tags = tuple(table for table in root.find_all("table") if table.find_parent("table") is parent_table)
179
+ if not table_tags:
180
+ raise DocxTableError("HTML does not contain a top-level table")
181
+ return tuple(parse_html_table(table) for table in table_tags)
182
+
183
+
184
+ def materialize_docx_tables(
185
+ container: Any,
186
+ source: HtmlTableSource,
187
+ *,
188
+ width_twips: int = DEFAULT_TABLE_WIDTH_TWIPS,
189
+ fill_cell: CellFillCallback,
190
+ ) -> tuple[Table, ...]:
191
+ """把 source 中的全部顶层表格物化到 Document、Header/Footer 或 Cell。"""
192
+ grids = parse_html_tables(source)
193
+ return _materialize_grids(
194
+ container,
195
+ grids,
196
+ width_twips=width_twips,
197
+ fill_cell=fill_cell,
198
+ depth=1,
199
+ )
200
+
201
+
202
+ def materialize_docx_table(
203
+ container: Any,
204
+ grid: HtmlTableGrid,
205
+ *,
206
+ width_twips: int = DEFAULT_TABLE_WIDTH_TWIPS,
207
+ fill_cell: CellFillCallback,
208
+ ) -> Table:
209
+ """把一个已解析表格网格物化到指定 python-docx 容器。"""
210
+ return _materialize_grid(
211
+ container,
212
+ grid,
213
+ width_twips=width_twips,
214
+ fill_cell=fill_cell,
215
+ depth=1,
216
+ )
217
+
218
+
219
+ def _materialize_grids(
220
+ container: Any,
221
+ grids: tuple[HtmlTableGrid, ...],
222
+ *,
223
+ width_twips: int,
224
+ fill_cell: CellFillCallback,
225
+ depth: int,
226
+ ) -> tuple[Table, ...]:
227
+ """在同一递归层级内按源码顺序物化全部表格网格。"""
228
+ if depth > MAX_NESTED_TABLE_DEPTH:
229
+ raise DocxTableError(f"Nested table depth exceeds {MAX_NESTED_TABLE_DEPTH}")
230
+ _validate_width(width_twips)
231
+ return tuple(
232
+ _materialize_grid(
233
+ container,
234
+ grid,
235
+ width_twips=width_twips,
236
+ fill_cell=fill_cell,
237
+ depth=depth,
238
+ )
239
+ for grid in grids
240
+ )
241
+
242
+
243
+ def _materialize_grid(
244
+ container: Any,
245
+ grid: HtmlTableGrid,
246
+ *,
247
+ width_twips: int,
248
+ fill_cell: CellFillCallback,
249
+ depth: int,
250
+ ) -> Table:
251
+ """创建单个 Word 表格、应用合并几何并回调填充所有 origin cell。"""
252
+ if depth > MAX_NESTED_TABLE_DEPTH:
253
+ raise DocxTableError(f"Nested table depth exceeds {MAX_NESTED_TABLE_DEPTH}")
254
+ column_widths = _split_width(width_twips, grid.column_count)
255
+ table = _add_table(container, grid.row_count, grid.column_count, width_twips)
256
+ _configure_table_geometry(table, width_twips, column_widths)
257
+ _apply_cell_merges(table, grid)
258
+ _normalize_cell_widths(table, column_widths)
259
+ _clear_fixed_row_heights(table)
260
+ _mark_header_rows(table, grid.header_rows)
261
+
262
+ for placement in grid.cells:
263
+ origin_cell = table.cell(placement.row, placement.column)
264
+ origin_width = sum(column_widths[placement.column : placement.column + placement.colspan])
265
+
266
+ def write_nested(
267
+ nested_source: HtmlTableSource,
268
+ nested_width_twips: int | None = None,
269
+ *,
270
+ _origin_cell: _Cell = origin_cell,
271
+ _origin_width: int = origin_width,
272
+ _depth: int = depth,
273
+ ) -> tuple[Table, ...]:
274
+ """在当前 origin cell 内继续物化直接嵌套表格。"""
275
+ if _depth >= MAX_NESTED_TABLE_DEPTH:
276
+ raise DocxTableError(f"Nested table depth exceeds {MAX_NESTED_TABLE_DEPTH}")
277
+ nested_grids = parse_html_tables(nested_source)
278
+ return _materialize_grids(
279
+ _origin_cell,
280
+ nested_grids,
281
+ width_twips=_origin_width if nested_width_twips is None else nested_width_twips,
282
+ fill_cell=fill_cell,
283
+ depth=_depth + 1,
284
+ )
285
+
286
+ fill_cell(origin_cell, placement.tag, write_nested)
287
+ return table
288
+
289
+
290
+ def _parse_span(cell: Tag, attribute: str) -> int:
291
+ """读取严格正整数 rowspan/colspan,缺失属性时返回一。"""
292
+ raw_value = cell.get(attribute, "1")
293
+ if isinstance(raw_value, list):
294
+ raise DocxTableError(f"Invalid {attribute}: {raw_value!r}")
295
+ value = str(raw_value).strip()
296
+ if _POSITIVE_INTEGER_RE.fullmatch(value) is None:
297
+ raise DocxTableError(f"Invalid {attribute}: {raw_value!r}")
298
+ span = int(value)
299
+ if span < 1:
300
+ raise DocxTableError(f"Invalid {attribute}: {raw_value!r}")
301
+ return span
302
+
303
+
304
+ def _row_belongs_to_thead(row: Tag, table: Tag) -> bool:
305
+ """判断当前 tr 是否位于本 table 的 thead 内。"""
306
+ parent = row.parent
307
+ while isinstance(parent, Tag) and parent is not table:
308
+ if parent.name == "thead":
309
+ return True
310
+ parent = parent.parent
311
+ return False
312
+
313
+
314
+ def _validate_width(width_twips: int) -> None:
315
+ """校验调用方传入的 DXA/twips 表格宽度。"""
316
+ if isinstance(width_twips, bool) or not isinstance(width_twips, int) or width_twips <= 0:
317
+ raise DocxTableError("width_twips must be a positive integer")
318
+
319
+
320
+ def _split_width(width_twips: int, column_count: int) -> tuple[int, ...]:
321
+ """把总宽度确定性地均分到列,并保证列宽之和严格等于总宽。"""
322
+ _validate_width(width_twips)
323
+ if column_count <= 0:
324
+ raise DocxTableError("Table must contain at least one column")
325
+ if width_twips < column_count:
326
+ raise DocxTableError("width_twips is too small for the table column count")
327
+ base_width, remainder = divmod(width_twips, column_count)
328
+ return tuple(base_width + (1 if column_index < remainder else 0) for column_index in range(column_count))
329
+
330
+
331
+ def _add_table(container: Any, row_count: int, column_count: int, width_twips: int) -> Table:
332
+ """按 python-docx 容器的不同 add_table 签名创建空表格。"""
333
+ if not hasattr(container, "add_table"):
334
+ raise DocxTableError("DOCX container must provide add_table()")
335
+ try:
336
+ if isinstance(container, (DocxDocument, _Cell)):
337
+ return container.add_table(rows=row_count, cols=column_count)
338
+ return container.add_table(
339
+ rows=row_count,
340
+ cols=column_count,
341
+ width=Twips(width_twips),
342
+ )
343
+ except (AttributeError, TypeError, ValueError) as exc:
344
+ raise DocxTableError("Failed to create DOCX table in the target container") from exc
345
+
346
+
347
+ def _configure_table_geometry(
348
+ table: Table,
349
+ width_twips: int,
350
+ column_widths: tuple[int, ...],
351
+ ) -> None:
352
+ """设置固定布局、Table Grid 样式及确定性的 tblW、tblGrid、tcW。"""
353
+ try:
354
+ table.style = "Table Grid"
355
+ except KeyError as exc:
356
+ raise DocxTableError("DOCX template does not define the 'Table Grid' style") from exc
357
+ table.autofit = False
358
+
359
+ table_width = table._tbl.tblPr.find(qn("w:tblW"))
360
+ if table_width is None:
361
+ table_width = OxmlElement("w:tblW")
362
+ table._tbl.tblPr.insert(0, table_width)
363
+ table_width.set(qn("w:type"), "dxa")
364
+ table_width.set(qn("w:w"), str(width_twips))
365
+
366
+ grid_columns = table._tbl.tblGrid.gridCol_lst
367
+ if len(grid_columns) != len(column_widths):
368
+ raise DocxTableError("DOCX table grid column count is inconsistent")
369
+ for grid_column, column_width in zip(grid_columns, column_widths):
370
+ grid_column.w = Twips(column_width)
371
+
372
+ for row in table._tbl.tr_lst:
373
+ if len(row.tc_lst) != len(column_widths):
374
+ raise DocxTableError("DOCX table row is inconsistent before cell merging")
375
+ for cell, column_width in zip(row.tc_lst, column_widths):
376
+ cell_width = cell.get_or_add_tcPr().get_or_add_tcW()
377
+ cell_width.type = "dxa"
378
+ cell_width.w = column_width
379
+
380
+
381
+ def _apply_cell_merges(table: Table, grid: HtmlTableGrid) -> None:
382
+ """按已验证 origin placement 为 Word 表格应用水平和垂直合并。"""
383
+ for placement in grid.cells:
384
+ if placement.rowspan == 1 and placement.colspan == 1:
385
+ continue
386
+ try:
387
+ table.cell(placement.row, placement.column).merge(table.cell(placement.end_row, placement.end_column))
388
+ except (IndexError, InvalidSpanError, ValueError) as exc:
389
+ raise DocxTableError(f"Failed to merge cell at row={placement.row}, column={placement.column}") from exc
390
+
391
+
392
+ def _normalize_cell_widths(table: Table, column_widths: tuple[int, ...]) -> None:
393
+ """合并后按 gridSpan 重新写入每个物理 tc 的确定性 DXA 宽度。"""
394
+ for row_index, row in enumerate(table._tbl.tr_lst):
395
+ column_index = 0
396
+ for cell in row.tc_lst:
397
+ colspan = cell.grid_span
398
+ if colspan < 1 or column_index + colspan > len(column_widths):
399
+ raise DocxTableError(f"DOCX cell geometry is invalid at row={row_index}")
400
+ width = sum(column_widths[column_index : column_index + colspan])
401
+ cell_width = cell.get_or_add_tcPr().get_or_add_tcW()
402
+ cell_width.type = "dxa"
403
+ cell_width.w = width
404
+ column_index += colspan
405
+ if column_index != len(column_widths):
406
+ raise DocxTableError(f"DOCX row width is inconsistent at row={row_index}")
407
+
408
+
409
+ def _clear_fixed_row_heights(table: Table) -> None:
410
+ """删除所有固定 trHeight,使 Word 根据单元格内容自然扩展行高。"""
411
+ for row in table._tbl.tr_lst:
412
+ row_properties = row.trPr
413
+ if row_properties is None:
414
+ continue
415
+ for height in tuple(row_properties.findall(qn("w:trHeight"))):
416
+ row_properties.remove(height)
417
+
418
+
419
+ def _mark_header_rows(table: Table, header_rows: tuple[int, ...]) -> None:
420
+ """把 thead 或全 th 行标记为可跨页重复的 Word 表头行。"""
421
+ for row_index in header_rows:
422
+ row_properties = table.rows[row_index]._tr.get_or_add_trPr()
423
+ header = row_properties.find(qn("w:tblHeader"))
424
+ if header is None:
425
+ header = OxmlElement("w:tblHeader")
426
+ row_properties.append(header)
427
+ header.set(qn("w:val"), "1")
428
+
429
+
430
+ __all__ = [
431
+ "CellFillCallback",
432
+ "DEFAULT_TABLE_WIDTH_TWIPS",
433
+ "DocxTableError",
434
+ "HtmlTableCell",
435
+ "HtmlTableGrid",
436
+ "MAX_NESTED_TABLE_DEPTH",
437
+ "NestedTableWriter",
438
+ "materialize_docx_table",
439
+ "materialize_docx_tables",
440
+ "parse_html_table",
441
+ "parse_html_tables",
442
+ ]
@@ -0,0 +1,5 @@
1
+ """EPUB 3.3 renderer 的私有实现包。"""
2
+
3
+ from .renderer import render_epub
4
+
5
+ __all__ = ["render_epub"]