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,581 @@
1
+ """组装跨行标题、图片注释、页眉及首页信息块。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import statistics
6
+ from typing import Any
7
+
8
+ from .....schema import BBox
9
+ from ..geometry import _bbox_axis_overlap_ratio, _bbox_center_x, _bbox_center_y, _bbox_union_many
10
+ from ..line_layout import _is_structural_typography_gap
11
+ from .common import _FIGURE_CAPTION_MARKER_RE, _components_share_lane_role, _merge_internal_text_block_group
12
+
13
+
14
+ def _merge_image_caption_text_blocks(
15
+ blocks: list[dict[str, Any]],
16
+ image_bboxes: list[BBox],
17
+ ) -> list[dict[str, Any]]:
18
+ """在图像邻接已成立后,用通用图注标记确认锚点并吸收同字体续行。"""
19
+
20
+ if not image_bboxes:
21
+ return blocks
22
+ text_indices = [
23
+ index
24
+ for index, block in enumerate(blocks)
25
+ if block.get("type") == "text"
26
+ and isinstance(block.get("content"), str)
27
+ and isinstance(block.get("bbox"), (list, tuple))
28
+ ]
29
+ all_heights = [
30
+ float(height)
31
+ for index in text_indices
32
+ for height in blocks[index].get("_line_heights", [])
33
+ if isinstance(height, (int, float)) and height > 0
34
+ ]
35
+ median_height = statistics.median(all_heights) if all_heights else 1.0
36
+ caption_image_bboxes = _caption_image_group_bboxes(
37
+ image_bboxes,
38
+ median_height,
39
+ )
40
+ seed_indices = {
41
+ index
42
+ for index in text_indices
43
+ if _FIGURE_CAPTION_MARKER_RE.match(str(blocks[index]["content"]).strip())
44
+ and any(
45
+ _caption_seed_matches_image(
46
+ blocks[index],
47
+ image_bbox,
48
+ median_height,
49
+ )
50
+ for image_bbox in caption_image_bboxes
51
+ )
52
+ }
53
+ if not seed_indices:
54
+ return blocks
55
+
56
+ assignments: dict[int, list[int]] = {index: [] for index in seed_indices}
57
+ for candidate_index in text_indices:
58
+ if candidate_index in seed_indices:
59
+ continue
60
+ candidate = blocks[candidate_index]
61
+ matches: list[tuple[float, float, int]] = []
62
+ for seed_index in seed_indices:
63
+ seed = blocks[seed_index]
64
+ if not _caption_tail_matches_seed(
65
+ seed,
66
+ candidate,
67
+ median_height,
68
+ ):
69
+ continue
70
+ seed_bbox = seed["bbox"]
71
+ candidate_bbox = candidate["bbox"]
72
+ matches.append(
73
+ (
74
+ _bbox_center_y(candidate_bbox) - _bbox_center_y(seed_bbox),
75
+ abs(_bbox_center_x(candidate_bbox) - _bbox_center_x(seed_bbox)),
76
+ seed_index,
77
+ )
78
+ )
79
+ if matches:
80
+ assignments[min(matches)[2]].append(candidate_index)
81
+
82
+ merged_indices: set[int] = set()
83
+ replacements: dict[int, dict[str, Any]] = {}
84
+ for seed_index, tail_indices in assignments.items():
85
+ if not tail_indices:
86
+ continue
87
+ group_indices = [seed_index, *tail_indices]
88
+ replacements[seed_index] = _merge_internal_text_block_group(
89
+ blocks,
90
+ group_indices,
91
+ )
92
+ merged_indices.update(tail_indices)
93
+ return [replacements.get(index, block) for index, block in enumerate(blocks) if index not in merged_indices]
94
+
95
+
96
+ def _caption_image_group_bboxes(
97
+ image_bboxes: list[BBox],
98
+ median_height: float,
99
+ ) -> list[BBox]:
100
+ """合并同一视觉行的并排图片 bbox,使跨多图的统一图注也能建立邻接。"""
101
+
102
+ remaining = list(image_bboxes)
103
+ grouped_bboxes = list(image_bboxes)
104
+ while remaining:
105
+ group = [remaining.pop(0)]
106
+ changed = True
107
+ while changed:
108
+ changed = False
109
+ for candidate in list(remaining):
110
+ aligned = False
111
+ for member in group:
112
+ overlap = max(
113
+ 0.0,
114
+ min(candidate[3], member[3]) - max(candidate[1], member[1]),
115
+ )
116
+ minimum_height = max(
117
+ 0.1,
118
+ min(
119
+ candidate[3] - candidate[1],
120
+ member[3] - member[1],
121
+ ),
122
+ )
123
+ horizontal_gap = max(
124
+ 0.0,
125
+ max(candidate[0], member[0]) - min(candidate[2], member[2]),
126
+ )
127
+ if overlap / minimum_height >= 0.7 and horizontal_gap <= 2.0 * median_height:
128
+ aligned = True
129
+ break
130
+ if aligned:
131
+ group.append(candidate)
132
+ remaining.remove(candidate)
133
+ changed = True
134
+ if len(group) >= 2:
135
+ grouped_bboxes.append(_bbox_union_many(group))
136
+ return grouped_bboxes
137
+
138
+
139
+ def _caption_seed_matches_image(
140
+ block: dict[str, Any],
141
+ image_bbox: BBox,
142
+ median_height: float,
143
+ ) -> bool:
144
+ """用上下位置、水平投影和居中关系确认图像下方的图注空间候选。"""
145
+
146
+ bbox = block["bbox"]
147
+ image_width = max(0.1, image_bbox[2] - image_bbox[0])
148
+ block_width = max(0.1, bbox[2] - bbox[0])
149
+ vertical_gap = max(0.0, bbox[1] - image_bbox[3])
150
+ return (
151
+ _bbox_center_y(bbox) >= image_bbox[3] - 0.25 * median_height
152
+ and vertical_gap <= 2.5 * median_height
153
+ and _bbox_axis_overlap_ratio(bbox, image_bbox, axis="x") >= 0.35
154
+ and abs(_bbox_center_x(bbox) - _bbox_center_x(image_bbox)) <= 0.35 * max(image_width, block_width)
155
+ and block_width <= 1.75 * image_width
156
+ )
157
+
158
+
159
+ def _caption_body_has_structural_gap(
160
+ seed: dict[str, Any],
161
+ candidate: dict[str, Any],
162
+ ) -> bool:
163
+ """用图注末行、候选首行和图注内部行距阻止跨排版层级回并。"""
164
+
165
+ seed_bboxes = seed.get("_local_line_bboxes")
166
+ seed_heights = seed.get("_line_heights")
167
+ candidate_bboxes = candidate.get("_local_line_bboxes")
168
+ candidate_heights = candidate.get("_line_heights")
169
+ if not (
170
+ isinstance(seed_bboxes, list)
171
+ and isinstance(seed_heights, list)
172
+ and len(seed_bboxes) == len(seed_heights)
173
+ and seed_bboxes
174
+ and isinstance(candidate_bboxes, list)
175
+ and isinstance(candidate_heights, list)
176
+ and len(candidate_bboxes) == len(candidate_heights)
177
+ and candidate_bboxes
178
+ ):
179
+ return False
180
+
181
+ seed_rows = sorted(
182
+ zip(seed_bboxes, seed_heights, strict=True),
183
+ key=lambda item: (item[0][1], item[0][0]),
184
+ )
185
+ candidate_rows = sorted(
186
+ zip(candidate_bboxes, candidate_heights, strict=True),
187
+ key=lambda item: (item[0][1], item[0][0]),
188
+ )
189
+ previous_bbox, previous_height = seed_rows[-1]
190
+ current_bbox, current_height = candidate_rows[0]
191
+ internal_gaps = [
192
+ max(0.0, current[0][1] - (previous[0][1] + float(previous[1]))) for previous, current in zip(seed_rows, seed_rows[1:])
193
+ ]
194
+ regular_gap = statistics.median(internal_gaps) if internal_gaps else 0.0
195
+ gap_mad = statistics.median(abs(gap - regular_gap) for gap in internal_gaps) if internal_gaps else 0.0
196
+ seed_fonts = seed.get("_font_signatures")
197
+ candidate_fonts = candidate.get("_font_signatures")
198
+ reliable_style_change = (
199
+ isinstance(seed_fonts, set)
200
+ and bool(seed_fonts)
201
+ and isinstance(candidate_fonts, set)
202
+ and bool(candidate_fonts)
203
+ and seed_fonts.isdisjoint(candidate_fonts)
204
+ )
205
+ return _is_structural_typography_gap(
206
+ float(previous_height),
207
+ float(current_height),
208
+ current_bbox[1] - (previous_bbox[1] + float(previous_height)),
209
+ regular_gap,
210
+ gap_mad,
211
+ reliable_style_change=reliable_style_change,
212
+ )
213
+
214
+
215
+ def _caption_tail_matches_seed(
216
+ seed: dict[str, Any],
217
+ candidate: dict[str, Any],
218
+ median_height: float,
219
+ ) -> bool:
220
+ """只用同栏角色、字体、邻接和投影把无标记的图注续行接回锚点。"""
221
+
222
+ seed_bbox = seed["bbox"]
223
+ candidate_bbox = candidate["bbox"]
224
+ if not _components_share_lane_role(seed, candidate, median_height) and (
225
+ _bbox_axis_overlap_ratio(seed_bbox, candidate_bbox, axis="x") < 0.75
226
+ or abs(seed_bbox[0] - candidate_bbox[0]) > median_height
227
+ ):
228
+ return False
229
+ if _bbox_center_y(candidate_bbox) <= _bbox_center_y(seed_bbox):
230
+ return False
231
+ if _caption_body_has_structural_gap(seed, candidate):
232
+ return False
233
+ vertical_gap = max(0.0, candidate_bbox[1] - seed_bbox[3])
234
+ if vertical_gap > 0.5 * median_height or _bbox_axis_overlap_ratio(seed_bbox, candidate_bbox, axis="x") < 0.35:
235
+ return False
236
+ seed_fonts = seed.get("_font_signatures")
237
+ candidate_fonts = candidate.get("_font_signatures")
238
+ return not (
239
+ isinstance(seed_fonts, set)
240
+ and seed_fonts
241
+ and isinstance(candidate_fonts, set)
242
+ and candidate_fonts
243
+ and seed_fonts.isdisjoint(candidate_fonts)
244
+ )
245
+
246
+
247
+ def _merge_multiline_title_blocks(
248
+ blocks: list[dict[str, Any]],
249
+ ) -> list[dict[str, Any]]:
250
+ """跨错误栏带合并紧贴且字体兼容的多行文档标题和段落标题。"""
251
+
252
+ replacements: dict[int, dict[str, Any]] = {}
253
+ consumed: set[int] = set()
254
+ for block_type in ("doc_title", "paragraph_title"):
255
+ indices = [
256
+ index
257
+ for index, block in enumerate(blocks)
258
+ if block.get("type") == block_type and isinstance(block.get("bbox"), (list, tuple))
259
+ ]
260
+ indices.sort(
261
+ key=lambda index: (
262
+ blocks[index]["bbox"][1],
263
+ blocks[index]["bbox"][0],
264
+ )
265
+ )
266
+ groups: list[list[int]] = []
267
+ for index in indices:
268
+ if not groups:
269
+ groups.append([index])
270
+ continue
271
+ previous_index = groups[-1][-1]
272
+ previous = blocks[previous_index]
273
+ current = blocks[index]
274
+ previous_bbox = previous["bbox"]
275
+ current_bbox = current["bbox"]
276
+ previous_heights = previous.get("_line_heights", [])
277
+ current_heights = current.get("_line_heights", [])
278
+ previous_height = (
279
+ statistics.median(previous_heights)
280
+ if isinstance(previous_heights, list) and previous_heights
281
+ else previous_bbox[3] - previous_bbox[1]
282
+ )
283
+ current_height = (
284
+ statistics.median(current_heights)
285
+ if isinstance(current_heights, list) and current_heights
286
+ else current_bbox[3] - current_bbox[1]
287
+ )
288
+ vertical_gap = current_bbox[1] - previous_bbox[3]
289
+ previous_fonts = previous.get("_font_signatures")
290
+ current_fonts = current.get("_font_signatures")
291
+ fonts_conflict = (
292
+ isinstance(previous_fonts, set)
293
+ and previous_fonts
294
+ and isinstance(current_fonts, set)
295
+ and current_fonts
296
+ and previous_fonts.isdisjoint(current_fonts)
297
+ )
298
+ if (
299
+ -0.2 * max(previous_height, current_height) <= vertical_gap <= 0.4 * max(previous_height, current_height)
300
+ and _bbox_axis_overlap_ratio(
301
+ previous_bbox,
302
+ current_bbox,
303
+ axis="x",
304
+ )
305
+ >= 0.2
306
+ and not fonts_conflict
307
+ ):
308
+ groups[-1].append(index)
309
+ else:
310
+ groups.append([index])
311
+ for group in groups:
312
+ if len(group) < 2:
313
+ continue
314
+ replacements[group[0]] = _merge_internal_text_block_group(
315
+ blocks,
316
+ group,
317
+ )
318
+ consumed.update(group[1:])
319
+ return [replacements.get(index, block) for index, block in enumerate(blocks) if index not in consumed]
320
+
321
+
322
+ def _merge_fragmented_header_blocks(
323
+ blocks: list[dict[str, Any]],
324
+ ) -> list[dict[str, Any]]:
325
+ """聚合同一视觉行中等距分散的页眉页脚片段。"""
326
+
327
+ grouped: dict[tuple[int, int], list[int]] = {}
328
+ for index, block in enumerate(blocks):
329
+ row_id = block.get("_single_run_row_id")
330
+ angle = int(block.get("angle", 0) or 0) % 360
331
+ if block.get("type") in {"header", "footer"} and isinstance(row_id, int):
332
+ grouped.setdefault((angle, row_id), []).append(index)
333
+
334
+ replacements: dict[int, dict[str, Any]] = {}
335
+ consumed: set[int] = set()
336
+ for indices in grouped.values():
337
+ ordered = sorted(indices, key=lambda index: blocks[index]["bbox"][0])
338
+ components: list[list[int]] = []
339
+ for index in ordered:
340
+ bbox = blocks[index]["bbox"]
341
+ heights = blocks[index].get("_line_heights", [])
342
+ effective_height = (
343
+ statistics.median(heights) if isinstance(heights, list) and heights else max(0.1, bbox[3] - bbox[1])
344
+ )
345
+ if blocks[index].get("type") == "header" and bbox[2] - bbox[0] > 1.25 * effective_height:
346
+ continue
347
+ if not components:
348
+ components.append([index])
349
+ continue
350
+ previous_index = components[-1][-1]
351
+ previous_bbox = blocks[previous_index]["bbox"]
352
+ previous_heights = blocks[previous_index].get("_line_heights", [])
353
+ previous_height = (
354
+ statistics.median(previous_heights)
355
+ if isinstance(previous_heights, list) and previous_heights
356
+ else max(0.1, previous_bbox[3] - previous_bbox[1])
357
+ )
358
+ if (
359
+ bbox[0] - previous_bbox[2] <= 4.0 * max(effective_height, previous_height)
360
+ and _bbox_axis_overlap_ratio(previous_bbox, bbox, axis="y") >= 0.5
361
+ ):
362
+ components[-1].append(index)
363
+ else:
364
+ components.append([index])
365
+ for component in components:
366
+ if len(component) < 2:
367
+ continue
368
+ replacement = _merge_internal_text_block_group(
369
+ blocks,
370
+ component,
371
+ preserve_visual_spaces=True,
372
+ )
373
+ replacement["_single_run_row_id"] = None
374
+ replacements[component[0]] = replacement
375
+ consumed.update(component[1:])
376
+ return [replacements.get(index, block) for index, block in enumerate(blocks) if index not in consumed]
377
+
378
+
379
+ def _merge_front_matter_column_blocks(
380
+ blocks: list[dict[str, Any]],
381
+ page_size: tuple[float, float],
382
+ *,
383
+ page_index: int,
384
+ ) -> list[dict[str, Any]]:
385
+ """把首页标题下方规则排列的多列作者信息按列聚合。"""
386
+
387
+ if page_index != 0:
388
+ return blocks
389
+ page_width, page_height = page_size
390
+ if page_width <= 0 or page_height <= 0:
391
+ return blocks
392
+ title_blocks = [
393
+ block for block in blocks if block.get("type") == "doc_title" and isinstance(block.get("bbox"), (list, tuple))
394
+ ]
395
+ if not title_blocks:
396
+ return blocks
397
+ title_bottom = max(block["bbox"][3] for block in title_blocks)
398
+ candidates = [
399
+ index
400
+ for index, block in enumerate(blocks)
401
+ if block.get("type") == "text"
402
+ and isinstance(block.get("bbox"), (list, tuple))
403
+ and title_bottom < block["bbox"][1]
404
+ and block["bbox"][3]
405
+ <= min(
406
+ 0.4 * page_height,
407
+ title_bottom + 0.22 * page_height,
408
+ )
409
+ and block["bbox"][2] - block["bbox"][0] <= 0.32 * page_width
410
+ and block["bbox"][3] - block["bbox"][1] <= 0.035 * page_height
411
+ ]
412
+ if len(candidates) < 9:
413
+ return blocks
414
+ median_height = statistics.median(blocks[index]["bbox"][3] - blocks[index]["bbox"][1] for index in candidates)
415
+ row_groups: list[list[int]] = []
416
+ for index in sorted(
417
+ candidates,
418
+ key=lambda item: (
419
+ _bbox_center_y(blocks[item]["bbox"]),
420
+ blocks[item]["bbox"][0],
421
+ ),
422
+ ):
423
+ center_y = _bbox_center_y(blocks[index]["bbox"])
424
+ target = next(
425
+ (
426
+ row
427
+ for row in row_groups
428
+ if abs(center_y - statistics.median(_bbox_center_y(blocks[member]["bbox"]) for member in row))
429
+ <= 0.6 * median_height
430
+ ),
431
+ None,
432
+ )
433
+ if target is None:
434
+ row_groups.append([index])
435
+ else:
436
+ target.append(index)
437
+ dense_rows = [
438
+ row
439
+ for row in row_groups
440
+ if 3 <= len(row) <= 6
441
+ and (
442
+ max(blocks[index]["bbox"][2] for index in row) - min(blocks[index]["bbox"][0] for index in row) >= 0.55 * page_width
443
+ )
444
+ ]
445
+ if len(dense_rows) < 2:
446
+ return blocks
447
+ anchor_row = min(
448
+ dense_rows,
449
+ key=lambda row: (
450
+ -len(row),
451
+ statistics.median(_bbox_center_y(blocks[index]["bbox"]) for index in row),
452
+ ),
453
+ )
454
+ anchor_centers = sorted(_bbox_center_x(blocks[index]["bbox"]) for index in anchor_row)
455
+ if len(anchor_centers) != 4:
456
+ return blocks
457
+ boundaries = [0.5 * (left + right) for left, right in zip(anchor_centers, anchor_centers[1:])]
458
+ band_top = min(min(blocks[index]["bbox"][1] for index in row) for row in dense_rows) - median_height
459
+ band_bottom = max(max(blocks[index]["bbox"][3] for index in row) for row in dense_rows) + median_height
460
+ column_groups: list[list[int]] = [[] for _center in anchor_centers]
461
+ for index in candidates:
462
+ bbox = blocks[index]["bbox"]
463
+ if not band_top <= _bbox_center_y(bbox) <= band_bottom:
464
+ continue
465
+ center_x = _bbox_center_x(bbox)
466
+ column_index = sum(center_x > boundary for boundary in boundaries)
467
+ if column_index >= len(column_groups):
468
+ continue
469
+ column_groups[column_index].append(index)
470
+ if any(len(group) < 3 for group in column_groups):
471
+ return blocks
472
+
473
+ replacements: dict[int, dict[str, Any]] = {}
474
+ consumed: set[int] = set()
475
+ for group in column_groups:
476
+ ordered = sorted(
477
+ group,
478
+ key=lambda index: (
479
+ blocks[index]["bbox"][1],
480
+ blocks[index]["bbox"][0],
481
+ ),
482
+ )
483
+ replacements[ordered[0]] = _merge_internal_text_block_group(
484
+ blocks,
485
+ ordered,
486
+ )
487
+ consumed.update(ordered[1:])
488
+ return [replacements.get(index, block) for index, block in enumerate(blocks) if index not in consumed]
489
+
490
+
491
+ def _merge_repeated_compact_title_continuations(
492
+ blocks: list[dict[str, Any]],
493
+ page_size: tuple[float, float],
494
+ ) -> list[dict[str, Any]]:
495
+ """把重复出现的两行弱标题与紧邻异字体续行恢复为普通文本块。"""
496
+
497
+ candidate_pairs: list[tuple[int, int, float]] = []
498
+ for title_index, title in enumerate(blocks):
499
+ title_lines = title.get("_local_line_bboxes")
500
+ title_fonts = title.get("_font_signatures")
501
+ title_bbox = title.get("bbox")
502
+ if (
503
+ title.get("type") != "paragraph_title"
504
+ or not isinstance(title_bbox, (list, tuple))
505
+ or not isinstance(title_lines, list)
506
+ or len(title_lines) < 2
507
+ or not isinstance(title_fonts, set)
508
+ or not title_fonts
509
+ ):
510
+ continue
511
+ angle = int(title.get("angle", 0) or 0) % 360
512
+ local_page_width = page_size[1] if angle in {90, 270} else page_size[0]
513
+ line_heights = [
514
+ float(height) for height in title.get("_line_heights", []) if isinstance(height, (int, float)) and height > 0
515
+ ]
516
+ title_height = statistics.median(line_heights) if line_heights else 0.0
517
+ if title_height <= 0 or title_bbox[2] - title_bbox[0] > 0.55 * local_page_width:
518
+ continue
519
+
520
+ continuations: list[tuple[float, int]] = []
521
+ for text_index, text_block in enumerate(blocks):
522
+ text_bbox = text_block.get("bbox")
523
+ text_fonts = text_block.get("_font_signatures")
524
+ if (
525
+ text_block.get("type") != "text"
526
+ or int(text_block.get("angle", 0) or 0) % 360 != angle
527
+ or not isinstance(text_bbox, (list, tuple))
528
+ or not isinstance(text_fonts, set)
529
+ or not text_fonts
530
+ or not title_fonts.isdisjoint(text_fonts)
531
+ or text_bbox[2] - text_bbox[0] > 0.6 * local_page_width
532
+ ):
533
+ continue
534
+ gap = text_bbox[1] - title_bbox[3]
535
+ if -0.25 * title_height <= gap <= 0.6 * title_height and abs(text_bbox[0] - title_bbox[0]) <= 0.75 * title_height:
536
+ continuations.append((max(0.0, gap), text_index))
537
+ if continuations:
538
+ _gap, text_index = min(continuations)
539
+ candidate_pairs.append((title_index, text_index, title_height))
540
+
541
+ supported_pairs: list[tuple[int, int]] = []
542
+ for title_index, text_index, title_height in candidate_pairs:
543
+ title_bbox = blocks[title_index]["bbox"]
544
+ support_count = sum(
545
+ abs(blocks[other_title]["bbox"][0] - title_bbox[0]) <= max(title_height, other_height)
546
+ and 0.75 <= other_height / title_height <= 1.25
547
+ for other_title, _other_text, other_height in candidate_pairs
548
+ )
549
+ if support_count >= 2:
550
+ supported_pairs.append((title_index, text_index))
551
+ if not supported_pairs:
552
+ return blocks
553
+
554
+ replacements: dict[int, dict[str, Any]] = {}
555
+ consumed: set[int] = set()
556
+ for title_index, text_index in supported_pairs:
557
+ if title_index in consumed or text_index in consumed:
558
+ continue
559
+ merged = _merge_internal_text_block_group(
560
+ blocks,
561
+ [title_index, text_index],
562
+ )
563
+ merged["type"] = "text"
564
+ replacements[min(title_index, text_index)] = merged
565
+ consumed.update({title_index, text_index})
566
+ return [
567
+ replacements.get(index, block) for index, block in enumerate(blocks) if index not in consumed or index in replacements
568
+ ]
569
+
570
+
571
+ __all__ = [
572
+ "_merge_image_caption_text_blocks",
573
+ "_caption_image_group_bboxes",
574
+ "_caption_seed_matches_image",
575
+ "_caption_body_has_structural_gap",
576
+ "_caption_tail_matches_seed",
577
+ "_merge_multiline_title_blocks",
578
+ "_merge_fragmented_header_blocks",
579
+ "_merge_front_matter_column_blocks",
580
+ "_merge_repeated_compact_title_continuations",
581
+ ]