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,580 @@
1
+ """PDF 段落延续关系的 raw model-list 后处理。"""
2
+
3
+ import math
4
+ from typing import Any, TypeAlias
5
+
6
+ from ..content.spans import inline_span_plain_text
7
+ from ..schema import MERGE_TRANSPARENT_BLOCK_TYPES, BlockType
8
+
9
+ LINE_STOP_FLAG = (".", "!", "?", "。", "!", "?", ")", ")", '"', "”", ":", ":", ";", ";")
10
+ SECTION_MERGE_BARRIER_TYPES = {
11
+ BlockType.DOC_TITLE,
12
+ BlockType.PARAGRAPH_TITLE,
13
+ BlockType.EQUATION,
14
+ }
15
+ TEXT_MERGE_BARRIER_TYPES = {
16
+ *SECTION_MERGE_BARRIER_TYPES,
17
+ BlockType.LIST,
18
+ }
19
+ # 文本段落合并允许跨过视觉根块、页面脚注和页面装饰块,其他语义块仍会阻断候选查找。
20
+ TEXT_MERGE_TRANSPARENT_TYPES = {
21
+ BlockType.IMAGE,
22
+ BlockType.TABLE,
23
+ BlockType.CHART,
24
+ BlockType.CODE,
25
+ *MERGE_TRANSPARENT_BLOCK_TYPES,
26
+ }
27
+ CONTINUABLE_TEXT_BLOCK_TYPES = {BlockType.TEXT, BlockType.REF_TEXT}
28
+ VERTICAL_LINE_HEIGHT_TO_WIDTH_RATIO_THRESHOLD = 2
29
+ VERTICAL_LINE_IN_BLOCK_THRESHOLD = 0.8
30
+ SINGLE_LINE_LOOKAHEAD_LIMIT = 5
31
+ SINGLE_LINE_MIN_ALIGNED_LOOKAHEAD = 3
32
+ SINGLE_LINE_THICKNESS_RATIO_MAX = 1.5
33
+ BlockDict: TypeAlias = dict[str, Any]
34
+ CalculationBBox: TypeAlias = tuple[int, int, int, int]
35
+ OrderedBlock: TypeAlias = tuple[int, int, BlockDict]
36
+
37
+
38
+ def merge_para_text_blocks(pages: list[dict[str, Any]]) -> None:
39
+ """按页面阅读顺序给可延续的文本或参考文献列表写入 continues_prev 标记。"""
40
+ ordered_blocks: list[OrderedBlock] = []
41
+ for page_info in pages:
42
+ blocks = page_info.get("blocks")
43
+ if not isinstance(blocks, list):
44
+ continue
45
+
46
+ for block in blocks:
47
+ if isinstance(block, dict):
48
+ _clear_nested_continues_prev(block)
49
+ if block.get("type") not in CONTINUABLE_TEXT_BLOCK_TYPES:
50
+ block.pop("continues_prev", None)
51
+
52
+ page_idx = page_info.get("page_idx")
53
+ if not isinstance(page_idx, int):
54
+ continue
55
+ for order_idx, block in enumerate(blocks):
56
+ if isinstance(block, dict):
57
+ ordered_blocks.append((page_idx, order_idx, block))
58
+
59
+ for current_index in range(len(ordered_blocks) - 1, -1, -1):
60
+ current_page_idx, _, current_block = ordered_blocks[current_index]
61
+ current_type = current_block.get("type")
62
+ if current_type in CONTINUABLE_TEXT_BLOCK_TYPES:
63
+ # 已清理过 lines 的结果视为 finalize 完成,保留其既有标记以支持幂等调用。
64
+ if "lines" not in current_block:
65
+ continue
66
+ current_block.pop("continues_prev", None)
67
+ is_ref_text = current_type == BlockType.REF_TEXT
68
+ previous_block = (
69
+ _find_previous_ref_text_block(ordered_blocks, current_index)
70
+ if is_ref_text
71
+ else _find_previous_text_block(ordered_blocks, current_index)
72
+ )
73
+ if previous_block is None:
74
+ continue
75
+ previous_page_idx, _, previous_text_block = previous_block
76
+ if not _is_same_or_consecutive_page(current_page_idx, previous_page_idx):
77
+ continue
78
+ can_merge = (
79
+ can_auto_merge_ref_text_blocks(current_block, previous_text_block)
80
+ if is_ref_text
81
+ else can_auto_merge_text_blocks(current_block, previous_text_block)
82
+ )
83
+ if can_merge or _can_auto_merge_multiline_to_single_line(
84
+ current_block,
85
+ previous_text_block,
86
+ ordered_blocks=ordered_blocks,
87
+ current_index=current_index,
88
+ current_page_idx=current_page_idx,
89
+ current_type=current_type,
90
+ require_current_leading_edge=not is_ref_text,
91
+ reject_digit_or_uppercase_start=not is_ref_text,
92
+ ):
93
+ current_block["continues_prev"] = True
94
+ elif current_type == BlockType.LIST:
95
+ previous_block = _find_previous_ref_text_list_block(
96
+ ordered_blocks,
97
+ current_index,
98
+ current_block,
99
+ )
100
+ if previous_block is None:
101
+ continue
102
+ previous_page_idx, _, _ = previous_block
103
+ if _is_same_or_consecutive_page(current_page_idx, previous_page_idx):
104
+ current_block["continues_prev"] = True
105
+
106
+ for page_info in pages:
107
+ blocks = page_info.get("blocks")
108
+ if not isinstance(blocks, list):
109
+ continue
110
+ for block in blocks:
111
+ if isinstance(block, dict):
112
+ _remove_line_metadata(block)
113
+
114
+
115
+ def can_auto_merge_text_blocks(current_block: BlockDict, previous_block: BlockDict) -> bool:
116
+ """按文本首尾、行方向和几何关系判断两个 dict text block 是否可连续。"""
117
+ return _can_auto_merge_continuable_text_blocks(
118
+ current_block,
119
+ previous_block,
120
+ require_current_leading_edge=True,
121
+ reject_digit_or_uppercase_start=True,
122
+ )
123
+
124
+
125
+ def can_auto_merge_ref_text_blocks(current_block: BlockDict, previous_block: BlockDict) -> bool:
126
+ """按正文规则判断 ref_text,放宽当前起始边界及数字或大写字符开头限制。"""
127
+ return _can_auto_merge_continuable_text_blocks(
128
+ current_block,
129
+ previous_block,
130
+ require_current_leading_edge=False,
131
+ reject_digit_or_uppercase_start=False,
132
+ )
133
+
134
+
135
+ def _can_auto_merge_continuable_text_blocks(
136
+ current_block: BlockDict,
137
+ previous_block: BlockDict,
138
+ *,
139
+ require_current_leading_edge: bool,
140
+ reject_digit_or_uppercase_start: bool,
141
+ ) -> bool:
142
+ """复用正文与参考文献的公共文本边界、方向及几何续接规则。"""
143
+ current_metric_lines = _metric_line_bboxes(current_block)
144
+ previous_metric_lines = _metric_line_bboxes(previous_block)
145
+ if not current_metric_lines or not previous_metric_lines:
146
+ return False
147
+
148
+ current_bbox = _bbox_for_calculation(current_block.get("bbox"))
149
+ previous_bbox = _bbox_for_calculation(previous_block.get("bbox"))
150
+ if current_bbox is None or previous_bbox is None:
151
+ return False
152
+
153
+ current_content = _normalized_text_content(current_block)
154
+ previous_content = _normalized_text_content(previous_block)
155
+ if not current_content or not previous_content:
156
+ return False
157
+ if not _has_mergeable_text_boundary(
158
+ current_content,
159
+ previous_content,
160
+ reject_digit_or_uppercase_start=reject_digit_or_uppercase_start,
161
+ ):
162
+ return False
163
+
164
+ current_is_vertical = _is_vertical_text_block_by_lines(current_metric_lines)
165
+ previous_is_vertical = _is_vertical_text_block_by_lines(previous_metric_lines)
166
+ if current_is_vertical != previous_is_vertical:
167
+ return False
168
+ if current_is_vertical:
169
+ return _can_auto_merge_vertical_text_blocks(
170
+ current_content,
171
+ previous_content,
172
+ current_bbox,
173
+ previous_bbox,
174
+ current_metric_lines,
175
+ previous_metric_lines,
176
+ require_current_leading_edge=require_current_leading_edge,
177
+ )
178
+ return _can_auto_merge_horizontal_text_blocks(
179
+ current_content,
180
+ previous_content,
181
+ current_bbox,
182
+ previous_bbox,
183
+ current_metric_lines,
184
+ previous_metric_lines,
185
+ require_current_leading_edge=require_current_leading_edge,
186
+ )
187
+
188
+
189
+ def _find_previous_text_block(
190
+ ordered_blocks: list[OrderedBlock],
191
+ current_index: int,
192
+ ) -> OrderedBlock | None:
193
+ """向前查找 text,视觉根块和合并透明块可跨过,其他语义块会阻断查找。"""
194
+ for previous_index in range(current_index - 1, -1, -1):
195
+ previous_block = ordered_blocks[previous_index][2]
196
+ previous_type = previous_block.get("type")
197
+ if previous_type in TEXT_MERGE_BARRIER_TYPES:
198
+ return None
199
+ if previous_type != BlockType.TEXT:
200
+ if previous_type not in TEXT_MERGE_TRANSPARENT_TYPES:
201
+ return None
202
+ continue
203
+ return ordered_blocks[previous_index]
204
+ return None
205
+
206
+
207
+ def _find_previous_ref_text_block(
208
+ ordered_blocks: list[OrderedBlock],
209
+ current_index: int,
210
+ ) -> OrderedBlock | None:
211
+ """跳过页面脚注与辅助块查找前一个 ref_text,其他语义块保持阻断。"""
212
+ for previous_index in range(current_index - 1, -1, -1):
213
+ previous_block = ordered_blocks[previous_index][2]
214
+ previous_type = previous_block.get("type")
215
+ if previous_type in MERGE_TRANSPARENT_BLOCK_TYPES:
216
+ continue
217
+ if previous_type == BlockType.REF_TEXT:
218
+ return ordered_blocks[previous_index]
219
+ return None
220
+ return None
221
+
222
+
223
+ def _find_previous_ref_text_list_block(
224
+ ordered_blocks: list[OrderedBlock],
225
+ current_index: int,
226
+ current_block: BlockDict,
227
+ ) -> OrderedBlock | None:
228
+ """跳过页面脚注与辅助块查找前一个 ref_text list,其他语义块保持阻断。"""
229
+ if not _is_ref_text_list_block(current_block):
230
+ return None
231
+ for previous_index in range(current_index - 1, -1, -1):
232
+ previous_block = ordered_blocks[previous_index][2]
233
+ if previous_block.get("type") in MERGE_TRANSPARENT_BLOCK_TYPES:
234
+ continue
235
+ if _is_ref_text_list_block(previous_block):
236
+ return ordered_blocks[previous_index]
237
+ return None
238
+ return None
239
+
240
+
241
+ def _is_ref_text_list_block(block: BlockDict) -> bool:
242
+ """判断当前 dict block 是否为参考文献列表。"""
243
+ return block.get("type") == BlockType.LIST and block.get("sub_type") == BlockType.REF_TEXT
244
+
245
+
246
+ def _is_same_or_consecutive_page(current_page_idx: int, previous_page_idx: int) -> bool:
247
+ """只允许同页或页码严格连续的前后页建立延续关系。"""
248
+ return current_page_idx == previous_page_idx or current_page_idx == previous_page_idx + 1
249
+
250
+
251
+ def _positive_values_have_max_ratio(first: float, second: float, max_ratio: float) -> bool:
252
+ """判断两个正值的较大较小比是否不超过给定上限。"""
253
+ if first <= 0 or second <= 0:
254
+ return False
255
+ return max(first, second) / min(first, second) <= max_ratio
256
+
257
+
258
+ def _bbox_for_calculation(bbox: Any) -> CalculationBBox | None:
259
+ """复制并将 0~1 bbox 放大为千分位整数,原始 bbox 保持不变。"""
260
+ if not isinstance(bbox, (list, tuple)) or len(bbox) != 4:
261
+ return None
262
+ try:
263
+ values = tuple(float(value) for value in bbox)
264
+ except (TypeError, ValueError):
265
+ return None
266
+ if not all(math.isfinite(value) and 0 <= value <= 1 for value in values):
267
+ return None
268
+
269
+ x0, y0, x1, y1 = (int(round(value * 1000)) for value in values)
270
+ if x1 <= x0 or y1 <= y0:
271
+ return None
272
+ return x0, y0, x1, y1
273
+
274
+
275
+ def _metric_line_bboxes(block: BlockDict) -> list[CalculationBBox]:
276
+ """读取 block.lines 的全部合法行框,任一行非法时整块按不可合并处理。"""
277
+ lines = block.get("lines")
278
+ if not isinstance(lines, list) or not lines:
279
+ return []
280
+
281
+ line_bboxes: list[CalculationBBox] = []
282
+ for line in lines:
283
+ if not isinstance(line, dict):
284
+ return []
285
+ line_bbox = _bbox_for_calculation(line.get("bbox"))
286
+ if line_bbox is None:
287
+ return []
288
+ line_bboxes.append(line_bbox)
289
+ return line_bboxes
290
+
291
+
292
+ def _normalized_text_content(block: BlockDict) -> str:
293
+ """读取 text block 的结构化 Span 可见文本。"""
294
+
295
+ content = block.get("content")
296
+ if not isinstance(content, list):
297
+ return ""
298
+ return inline_span_plain_text(item for item in content if isinstance(item, dict)).strip()
299
+
300
+
301
+ def _bbox_union(line_bboxes: list[CalculationBBox]) -> CalculationBBox:
302
+ """聚合全部行框,得到只用于几何判断的文本覆盖范围。"""
303
+ return (
304
+ min(bbox[0] for bbox in line_bboxes),
305
+ min(bbox[1] for bbox in line_bboxes),
306
+ max(bbox[2] for bbox in line_bboxes),
307
+ max(bbox[3] for bbox in line_bboxes),
308
+ )
309
+
310
+
311
+ def _line_height(line_bbox: CalculationBBox) -> int:
312
+ """计算千分位行框高度。"""
313
+ return line_bbox[3] - line_bbox[1]
314
+
315
+
316
+ def _line_width(line_bbox: CalculationBBox) -> int:
317
+ """计算千分位行框宽度。"""
318
+ return line_bbox[2] - line_bbox[0]
319
+
320
+
321
+ def _is_vertical_text_block_by_lines(line_bboxes: list[CalculationBBox]) -> bool:
322
+ """按行框高宽比判断 block 是否为竖排文本。"""
323
+ vertical_line_count = sum(
324
+ _line_height(line_bbox) / _line_width(line_bbox) > VERTICAL_LINE_HEIGHT_TO_WIDTH_RATIO_THRESHOLD
325
+ for line_bbox in line_bboxes
326
+ )
327
+ return vertical_line_count / len(line_bboxes) > VERTICAL_LINE_IN_BLOCK_THRESHOLD
328
+
329
+
330
+ def _has_mergeable_text_boundary(
331
+ current_content: str,
332
+ previous_content: str,
333
+ *,
334
+ reject_digit_or_uppercase_start: bool,
335
+ ) -> bool:
336
+ """使用前块结尾和后块开头字符排除明显的新段落边界。"""
337
+ if previous_content.endswith(LINE_STOP_FLAG):
338
+ return False
339
+ if not reject_digit_or_uppercase_start:
340
+ return True
341
+ first_char = current_content[0]
342
+ return not first_char.isdigit() and not first_char.isupper()
343
+
344
+
345
+ def _collect_following_same_orientation_lines(
346
+ ordered_blocks: list[OrderedBlock],
347
+ current_index: int,
348
+ *,
349
+ current_page_idx: int,
350
+ current_type: str,
351
+ is_vertical: bool,
352
+ ) -> list[CalculationBBox]:
353
+ """在当前页向后读取至多五条同类型同方向行,语义屏障或非法文本会终止读取。"""
354
+ following_lines: list[CalculationBBox] = []
355
+ transparent_types = TEXT_MERGE_TRANSPARENT_TYPES if current_type == BlockType.TEXT else MERGE_TRANSPARENT_BLOCK_TYPES
356
+ for page_idx, _, block in ordered_blocks[current_index + 1 :]:
357
+ if page_idx != current_page_idx:
358
+ break
359
+ block_type = block.get("type")
360
+ if block_type != current_type:
361
+ if block_type in transparent_types:
362
+ continue
363
+ break
364
+
365
+ block_lines = _metric_line_bboxes(block)
366
+ if not block_lines or _is_vertical_text_block_by_lines(block_lines) != is_vertical:
367
+ break
368
+ for line_bbox in block_lines:
369
+ following_lines.append(line_bbox)
370
+ if len(following_lines) >= SINGLE_LINE_LOOKAHEAD_LIMIT:
371
+ return following_lines
372
+ return following_lines
373
+
374
+
375
+ def _aligned_following_lines(
376
+ current_line: CalculationBBox,
377
+ following_lines: list[CalculationBBox],
378
+ *,
379
+ is_vertical: bool,
380
+ ) -> list[CalculationBBox]:
381
+ """按横排左边界或竖排上边界筛选同一虚拟栏内的后续行列。"""
382
+ current_start = current_line[1] if is_vertical else current_line[0]
383
+ current_thickness = _line_width(current_line) if is_vertical else _line_height(current_line)
384
+ aligned_lines: list[CalculationBBox] = []
385
+ for line_bbox in following_lines:
386
+ line_start = line_bbox[1] if is_vertical else line_bbox[0]
387
+ line_thickness = _line_width(line_bbox) if is_vertical else _line_height(line_bbox)
388
+ if not _positive_values_have_max_ratio(
389
+ current_thickness,
390
+ line_thickness,
391
+ SINGLE_LINE_THICKNESS_RATIO_MAX,
392
+ ):
393
+ continue
394
+ if abs(line_start - current_start) <= max(current_thickness, line_thickness):
395
+ aligned_lines.append(line_bbox)
396
+ return aligned_lines
397
+
398
+
399
+ def _virtual_single_line_bbox(
400
+ current_line: CalculationBBox,
401
+ aligned_lines: list[CalculationBBox],
402
+ *,
403
+ is_vertical: bool,
404
+ ) -> CalculationBBox:
405
+ """仅沿文本主轴扩展单行计算框,原始 line 和 block bbox 保持不变。"""
406
+ if is_vertical:
407
+ return (
408
+ current_line[0],
409
+ current_line[1],
410
+ current_line[2],
411
+ max(current_line[3], *(line_bbox[3] for line_bbox in aligned_lines)),
412
+ )
413
+ return (
414
+ current_line[0],
415
+ current_line[1],
416
+ max(current_line[2], *(line_bbox[2] for line_bbox in aligned_lines)),
417
+ current_line[3],
418
+ )
419
+
420
+
421
+ def _can_auto_merge_multiline_to_single_line(
422
+ current_block: BlockDict,
423
+ previous_block: BlockDict,
424
+ *,
425
+ ordered_blocks: list[OrderedBlock],
426
+ current_index: int,
427
+ current_page_idx: int,
428
+ current_type: str,
429
+ require_current_leading_edge: bool,
430
+ reject_digit_or_uppercase_start: bool,
431
+ ) -> bool:
432
+ """用后续五行或列补足单行主轴尺寸,再复用原横排或竖排连接规则。"""
433
+ current_lines = _metric_line_bboxes(current_block)
434
+ previous_lines = _metric_line_bboxes(previous_block)
435
+ if len(current_lines) != 1 or len(previous_lines) <= 1:
436
+ return False
437
+
438
+ current_is_vertical = _is_vertical_text_block_by_lines(current_lines)
439
+ if _is_vertical_text_block_by_lines(previous_lines) != current_is_vertical:
440
+ return False
441
+ following_lines = _collect_following_same_orientation_lines(
442
+ ordered_blocks,
443
+ current_index,
444
+ current_page_idx=current_page_idx,
445
+ current_type=current_type,
446
+ is_vertical=current_is_vertical,
447
+ )
448
+ aligned_lines = _aligned_following_lines(
449
+ current_lines[0],
450
+ following_lines,
451
+ is_vertical=current_is_vertical,
452
+ )
453
+ if len(aligned_lines) < SINGLE_LINE_MIN_ALIGNED_LOOKAHEAD:
454
+ return False
455
+
456
+ current_bbox = _bbox_for_calculation(current_block.get("bbox"))
457
+ previous_bbox = _bbox_for_calculation(previous_block.get("bbox"))
458
+ current_content = _normalized_text_content(current_block)
459
+ previous_content = _normalized_text_content(previous_block)
460
+ if current_bbox is None or previous_bbox is None or not current_content or not previous_content:
461
+ return False
462
+ if not _has_mergeable_text_boundary(
463
+ current_content,
464
+ previous_content,
465
+ reject_digit_or_uppercase_start=reject_digit_or_uppercase_start,
466
+ ):
467
+ return False
468
+
469
+ virtual_current_line = _virtual_single_line_bbox(
470
+ current_lines[0],
471
+ aligned_lines,
472
+ is_vertical=current_is_vertical,
473
+ )
474
+ if current_is_vertical:
475
+ return _can_auto_merge_vertical_text_blocks(
476
+ current_content,
477
+ previous_content,
478
+ current_bbox,
479
+ previous_bbox,
480
+ [virtual_current_line],
481
+ previous_lines,
482
+ require_current_leading_edge=require_current_leading_edge,
483
+ )
484
+ return _can_auto_merge_horizontal_text_blocks(
485
+ current_content,
486
+ previous_content,
487
+ current_bbox,
488
+ previous_bbox,
489
+ [virtual_current_line],
490
+ previous_lines,
491
+ require_current_leading_edge=require_current_leading_edge,
492
+ )
493
+
494
+
495
+ def _can_auto_merge_horizontal_text_blocks(
496
+ current_content: str,
497
+ previous_content: str,
498
+ current_bbox: CalculationBBox,
499
+ previous_bbox: CalculationBBox,
500
+ current_lines: list[CalculationBBox],
501
+ previous_lines: list[CalculationBBox],
502
+ *,
503
+ require_current_leading_edge: bool,
504
+ ) -> bool:
505
+ """使用横排段落的首行、末行、宽度和 block 相交规则判断是否连续。"""
506
+ first_line = current_lines[0]
507
+ last_line = previous_lines[-1]
508
+ first_line_height = _line_height(first_line)
509
+ last_line_height = _line_height(last_line)
510
+ if first_line_height <= 0 or last_line_height <= 0:
511
+ return False
512
+
513
+ current_lines_bbox = _bbox_union(current_lines)
514
+ previous_lines_bbox = _bbox_union(previous_lines)
515
+ if require_current_leading_edge and abs(current_lines_bbox[0] - first_line[0]) >= first_line_height / 2:
516
+ return False
517
+ if abs(previous_lines_bbox[2] - last_line[2]) >= last_line_height:
518
+ return False
519
+ current_width = current_lines_bbox[2] - current_lines_bbox[0]
520
+ previous_width = previous_lines_bbox[2] - previous_lines_bbox[0]
521
+ min_width = min(current_width, previous_width)
522
+ if min_width <= 0 or abs(current_width - previous_width) >= min_width:
523
+ return False
524
+ if len(current_lines) <= 1 and len(previous_lines) <= 1:
525
+ return False
526
+ return current_bbox[1] < previous_bbox[3]
527
+
528
+
529
+ def _can_auto_merge_vertical_text_blocks(
530
+ current_content: str,
531
+ previous_content: str,
532
+ current_bbox: CalculationBBox,
533
+ previous_bbox: CalculationBBox,
534
+ current_lines: list[CalculationBBox],
535
+ previous_lines: list[CalculationBBox],
536
+ *,
537
+ require_current_leading_edge: bool,
538
+ ) -> bool:
539
+ """使用竖排段落的首列、末列、高度和 block 相交规则判断是否连续。"""
540
+ first_line = current_lines[0]
541
+ last_line = previous_lines[-1]
542
+ first_line_width = _line_width(first_line)
543
+ last_line_width = _line_width(last_line)
544
+ if first_line_width <= 0 or last_line_width <= 0:
545
+ return False
546
+
547
+ current_lines_bbox = _bbox_union(current_lines)
548
+ previous_lines_bbox = _bbox_union(previous_lines)
549
+ if require_current_leading_edge and abs(current_lines_bbox[1] - first_line[1]) >= first_line_width / 2:
550
+ return False
551
+ if abs(previous_lines_bbox[3] - last_line[3]) >= last_line_width:
552
+ return False
553
+ current_height = current_lines_bbox[3] - current_lines_bbox[1]
554
+ previous_height = previous_lines_bbox[3] - previous_lines_bbox[1]
555
+ min_height = min(current_height, previous_height)
556
+ if min_height <= 0 or abs(current_height - previous_height) >= min_height:
557
+ return False
558
+ return current_bbox[2] > previous_bbox[0]
559
+
560
+
561
+ def _clear_nested_continues_prev(block: BlockDict) -> None:
562
+ """递归清理子块旧标记,顶层 text/ref_text 标记由是否仍有 lines 决定是否重算。"""
563
+ content = block.get("content")
564
+ if not isinstance(content, list):
565
+ return
566
+ for child_block in content:
567
+ if isinstance(child_block, dict):
568
+ child_block.pop("continues_prev", None)
569
+ _clear_nested_continues_prev(child_block)
570
+
571
+
572
+ def _remove_line_metadata(block: BlockDict) -> None:
573
+ """递归删除顶层及嵌套 block 的临时 lines 字段。"""
574
+ block.pop("lines", None)
575
+ content = block.get("content")
576
+ if not isinstance(content, list):
577
+ return
578
+ for child_block in content:
579
+ if isinstance(child_block, dict):
580
+ _remove_line_metadata(child_block)