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,470 @@
1
+ """从 WMF/GIF 图片 comment 中安全恢复 MathType MTEF 公式。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ import hashlib
7
+ from pathlib import PurePosixPath
8
+ import struct
9
+
10
+ from loguru import logger
11
+
12
+ from ..errors import LegacyOfficeResourceLimitError
13
+ from ..limits import MAX_ASSET_TOTAL_BYTES, MAX_ENTRY_BYTES, MAX_PICTURE_RECORDS
14
+ from .mtef import decode_mtef
15
+
16
+ _PLACEABLE_WMF_MAGIC = b"\xd7\xcd\xc6\x9a"
17
+ _META_ESCAPE = 0x0626
18
+ _MFCOMMENT = 0x000F
19
+ _APPS_MFCC_ID = b"AppsMFCC"
20
+ _GIF_HEADERS = {b"GIF87a", b"GIF89a"}
21
+ _HISTORICAL_APPS_SIGNATURES = {
22
+ "design science",
23
+ "design science, inc.",
24
+ "wiris",
25
+ }
26
+
27
+
28
+ class _ImageEquationError(ValueError):
29
+ """图片 comment 结构损坏、冲突或不受支持。"""
30
+
31
+
32
+ @dataclass(frozen=True, slots=True)
33
+ class _AppsChunk:
34
+ """一段已验证边界和 signature 的 AppsMFCC 数据。"""
35
+
36
+ key: tuple[str, ...]
37
+ total_length: int
38
+ data: bytes
39
+
40
+
41
+ def _charge_picture_record(counter: list[int]) -> None:
42
+ """累计 WMF/GIF record 数并在超限时抛稳定资源错误。"""
43
+
44
+ counter[0] += 1
45
+ if counter[0] > MAX_PICTURE_RECORDS:
46
+ raise LegacyOfficeResourceLimitError(f"image equation records exceed max_picture_records={MAX_PICTURE_RECORDS}")
47
+
48
+
49
+ def _signature_components(value: str) -> tuple[str, ...] | None:
50
+ """按 AppsMFCC 转义规则拆分 slash 分隔的 signature。"""
51
+
52
+ components: list[str] = []
53
+ current: list[str] = []
54
+ escaped = False
55
+ for character in value:
56
+ if escaped:
57
+ current.append(character)
58
+ escaped = False
59
+ elif character == "\\":
60
+ escaped = True
61
+ elif character == "/":
62
+ components.append("".join(current))
63
+ current = []
64
+ else:
65
+ current.append(character)
66
+ if escaped:
67
+ return None
68
+ components.append("".join(current))
69
+ if any(not component for component in components):
70
+ return None
71
+ return tuple(components)
72
+
73
+
74
+ def _parse_apps_chunk(comment: bytes) -> _AppsChunk | None:
75
+ """解析一个 AppsMFCC comment,并仅接受规范或历史 MTEF signature。"""
76
+
77
+ if not comment.startswith(_APPS_MFCC_ID):
78
+ return None
79
+ if len(comment) > 0x7FFE:
80
+ raise _ImageEquationError("AppsMFCC comment exceeds WMF size limit")
81
+ if len(comment) < 19:
82
+ raise _ImageEquationError("AppsMFCC header is truncated")
83
+ version, total_length, data_length = struct.unpack_from("<HII", comment, 8)
84
+ if version != 1:
85
+ raise _ImageEquationError("AppsMFCC version is unsupported")
86
+ if total_length <= 0 or data_length <= 0 or data_length > total_length or total_length > MAX_ENTRY_BYTES:
87
+ raise _ImageEquationError("AppsMFCC lengths are invalid")
88
+
89
+ signature_start = 18
90
+ signature_limit = min(len(comment), signature_start + 4097)
91
+ signature_end = comment.find(b"\x00", signature_start, signature_limit)
92
+ if signature_end < 0:
93
+ raise _ImageEquationError("AppsMFCC signature is not null-terminated")
94
+ try:
95
+ signature = comment[signature_start:signature_end].decode("ascii")
96
+ except UnicodeDecodeError as exc:
97
+ raise _ImageEquationError("AppsMFCC signature is not ASCII") from exc
98
+ components = _signature_components(signature)
99
+ if components is None:
100
+ raise _ImageEquationError("AppsMFCC signature is malformed")
101
+ normalized = tuple(component.strip().casefold() for component in components)
102
+ is_mtef = (len(normalized) >= 2 and normalized[1] == "mtef") or (
103
+ len(normalized) == 1 and normalized[0] in _HISTORICAL_APPS_SIGNATURES
104
+ )
105
+ if not is_mtef:
106
+ return None
107
+
108
+ data_start = signature_end + 1
109
+ data_end = data_start + int(data_length)
110
+ if data_end < data_start or data_end > len(comment):
111
+ raise _ImageEquationError("AppsMFCC chunk is truncated")
112
+ if any(comment[data_end:]):
113
+ raise _ImageEquationError("AppsMFCC comment has non-padding tail bytes")
114
+ key = (str(version), *normalized[:2])
115
+ return _AppsChunk(
116
+ key=key,
117
+ total_length=int(total_length),
118
+ data=comment[data_start:data_end],
119
+ )
120
+
121
+
122
+ def _pre6_mtef_comment(comment: bytes) -> bytes | None:
123
+ """从 MathType 6.0b 前的单 comment 头提取 MTEF。"""
124
+
125
+ if not comment.startswith(b"MathType"):
126
+ return None
127
+ if len(comment) > 0x7FFE:
128
+ raise _ImageEquationError("pre-6 MathType comment exceeds WMF size limit")
129
+ if len(comment) < 12:
130
+ raise _ImageEquationError("pre-6 MathType comment is truncated")
131
+ magic, data_length = struct.unpack_from("<HH", comment, 8)
132
+ if magic != 0x5555:
133
+ # type=0 是 baseline comment,其他类型也不是 MTEF。
134
+ return None
135
+ data_start = 12
136
+ data_end = data_start + int(data_length)
137
+ if data_end < data_start or data_end > len(comment):
138
+ raise _ImageEquationError("pre-6 MathType comment length is invalid")
139
+ if any(comment[data_end:]):
140
+ raise _ImageEquationError("pre-6 MathType comment has non-padding tail bytes")
141
+ return comment[data_start:data_end]
142
+
143
+
144
+ def _wmf_comments(image_data: bytes) -> tuple[list[bytes], bool]:
145
+ """有界遍历 WMF META_ESCAPE/MFCOMMENT 并返回 comment payloads。"""
146
+
147
+ cursor = 22 if image_data.startswith(_PLACEABLE_WMF_MAGIC) else 0
148
+ if cursor + 18 > len(image_data):
149
+ return [], False
150
+ metafile_type, header_words, _version, file_words = struct.unpack_from(
151
+ "<HHHI",
152
+ image_data,
153
+ cursor,
154
+ )
155
+ if metafile_type not in {1, 2} or header_words != 9:
156
+ return [], False
157
+ declared_end = cursor + int(file_words) * 2
158
+ if declared_end < cursor + 18 or declared_end > len(image_data):
159
+ raise _ImageEquationError("WMF declared file size is invalid")
160
+
161
+ comments: list[bytes] = []
162
+ counter = [0]
163
+ record_cursor = cursor + 18
164
+ saw_eof = False
165
+ while record_cursor < declared_end:
166
+ _charge_picture_record(counter)
167
+ if record_cursor + 6 > declared_end:
168
+ raise _ImageEquationError("WMF record header is truncated")
169
+ record_words, record_function = struct.unpack_from(
170
+ "<IH",
171
+ image_data,
172
+ record_cursor,
173
+ )
174
+ if record_words < 3:
175
+ raise _ImageEquationError("WMF record size is invalid")
176
+ record_end = record_cursor + int(record_words) * 2
177
+ if record_end <= record_cursor or record_end > declared_end:
178
+ raise _ImageEquationError("WMF record exceeds declared file size")
179
+ if record_function == 0:
180
+ saw_eof = True
181
+ record_cursor = record_end
182
+ break
183
+ if record_function == _META_ESCAPE:
184
+ if record_cursor + 10 > record_end:
185
+ raise _ImageEquationError("WMF META_ESCAPE record is truncated")
186
+ escape_function, byte_count = struct.unpack_from(
187
+ "<HH",
188
+ image_data,
189
+ record_cursor + 6,
190
+ )
191
+ data_start = record_cursor + 10
192
+ data_end = data_start + int(byte_count)
193
+ if data_end < data_start or data_end > record_end:
194
+ raise _ImageEquationError("WMF META_ESCAPE byte count is invalid")
195
+ if escape_function == _MFCOMMENT:
196
+ comments.append(image_data[data_start:data_end])
197
+ record_cursor = record_end
198
+
199
+ if not saw_eof or record_cursor != declared_end:
200
+ raise _ImageEquationError("WMF EOF record is missing or misplaced")
201
+ if any(image_data[declared_end:]):
202
+ raise _ImageEquationError("WMF contains non-padding bytes after declared end")
203
+ return comments, True
204
+
205
+
206
+ def _wmf_mtef_candidates(image_data: bytes) -> tuple[list[bytes], bool]:
207
+ """从 WMF comments 提取 pre-6 与 AppsMFCC MTEF candidates。"""
208
+
209
+ comments, is_wmf = _wmf_comments(image_data)
210
+ if not is_wmf:
211
+ return [], False
212
+ candidates: list[bytes] = []
213
+ recognized = False
214
+ pending: _AppsChunk | None = None
215
+ pending_data = bytearray()
216
+
217
+ for comment in comments:
218
+ pre6 = _pre6_mtef_comment(comment)
219
+ if pre6 is not None:
220
+ recognized = True
221
+ candidates.append(pre6)
222
+ continue
223
+
224
+ is_apps = comment.startswith(_APPS_MFCC_ID)
225
+ try:
226
+ chunk = _parse_apps_chunk(comment)
227
+ except _ImageEquationError:
228
+ recognized = True
229
+ pending = None
230
+ pending_data.clear()
231
+ continue
232
+ if chunk is None:
233
+ if is_apps and pending is not None:
234
+ recognized = True
235
+ pending = None
236
+ pending_data.clear()
237
+ continue
238
+ recognized = True
239
+
240
+ if pending is None:
241
+ if len(chunk.data) == chunk.total_length:
242
+ candidates.append(chunk.data)
243
+ else:
244
+ pending = chunk
245
+ pending_data = bytearray(chunk.data)
246
+ continue
247
+
248
+ if chunk.key != pending.key or chunk.total_length != pending.total_length:
249
+ pending = None
250
+ pending_data.clear()
251
+ if len(chunk.data) == chunk.total_length:
252
+ candidates.append(chunk.data)
253
+ elif len(chunk.data) < chunk.total_length:
254
+ pending = chunk
255
+ pending_data = bytearray(chunk.data)
256
+ continue
257
+ pending_data.extend(chunk.data)
258
+ if len(pending_data) == pending.total_length:
259
+ candidates.append(bytes(pending_data))
260
+ pending = None
261
+ pending_data.clear()
262
+ elif len(pending_data) > pending.total_length:
263
+ pending = None
264
+ pending_data.clear()
265
+
266
+ if pending is not None:
267
+ recognized = True
268
+ return candidates, recognized
269
+
270
+
271
+ def _gif_subblocks(
272
+ image_data: bytes,
273
+ cursor: int,
274
+ counter: list[int],
275
+ ) -> tuple[bytes, int]:
276
+ """读取以零长度块终止的 GIF sub-block 序列。"""
277
+
278
+ chunks: list[bytes] = []
279
+ total = 0
280
+ while True:
281
+ _charge_picture_record(counter)
282
+ if cursor >= len(image_data):
283
+ raise _ImageEquationError("GIF sub-block length is truncated")
284
+ size = image_data[cursor]
285
+ cursor += 1
286
+ if size == 0:
287
+ return b"".join(chunks), cursor
288
+ end = cursor + size
289
+ if end < cursor or end > len(image_data):
290
+ raise _ImageEquationError("GIF sub-block data is truncated")
291
+ total += size
292
+ if total > MAX_ENTRY_BYTES:
293
+ raise LegacyOfficeResourceLimitError(f"GIF extension exceeds max_entry_bytes={MAX_ENTRY_BYTES}")
294
+ chunks.append(image_data[cursor:end])
295
+ cursor = end
296
+
297
+
298
+ def _gif_mtef_candidates(image_data: bytes) -> tuple[list[bytes], bool]:
299
+ """完整遍历 GIF blocks 并提取 MathType/001 Application Extension。"""
300
+
301
+ if len(image_data) < 13 or image_data[:6] not in _GIF_HEADERS:
302
+ return [], False
303
+ packed = image_data[10]
304
+ cursor = 13
305
+ if packed & 0x80:
306
+ cursor += 3 * (1 << ((packed & 0x07) + 1))
307
+ if cursor > len(image_data):
308
+ raise _ImageEquationError("GIF global color table is truncated")
309
+
310
+ candidates: list[bytes] = []
311
+ recognized = False
312
+ counter = [0]
313
+ saw_trailer = False
314
+ while cursor < len(image_data):
315
+ _charge_picture_record(counter)
316
+ marker = image_data[cursor]
317
+ cursor += 1
318
+ if marker == 0x3B:
319
+ saw_trailer = True
320
+ break
321
+ if marker == 0x2C:
322
+ if cursor + 9 > len(image_data):
323
+ raise _ImageEquationError("GIF image descriptor is truncated")
324
+ image_packed = image_data[cursor + 8]
325
+ cursor += 9
326
+ if image_packed & 0x80:
327
+ cursor += 3 * (1 << ((image_packed & 0x07) + 1))
328
+ if cursor >= len(image_data):
329
+ raise _ImageEquationError("GIF image data is truncated")
330
+ cursor += 1 # LZW minimum code size
331
+ _image_payload, cursor = _gif_subblocks(image_data, cursor, counter)
332
+ continue
333
+ if marker != 0x21:
334
+ raise _ImageEquationError("GIF block marker is invalid")
335
+ if cursor >= len(image_data):
336
+ raise _ImageEquationError("GIF extension label is truncated")
337
+ extension_label = image_data[cursor]
338
+ cursor += 1
339
+ if extension_label != 0xFF:
340
+ _payload, cursor = _gif_subblocks(image_data, cursor, counter)
341
+ continue
342
+ if cursor >= len(image_data):
343
+ raise _ImageEquationError("GIF application block size is truncated")
344
+ application_size = image_data[cursor]
345
+ cursor += 1
346
+ application_end = cursor + application_size
347
+ if application_end > len(image_data):
348
+ raise _ImageEquationError("GIF application identifier is truncated")
349
+ application = image_data[cursor:application_end]
350
+ cursor = application_end
351
+ payload, cursor = _gif_subblocks(image_data, cursor, counter)
352
+ if application_size != 11:
353
+ continue
354
+ application_id = application[:8]
355
+ authentication = application[8:11]
356
+ if application_id == b"MathType" and authentication == b"001":
357
+ recognized = True
358
+ candidates.append(payload)
359
+ # MathType/002 是 baseline,必须明确忽略。
360
+
361
+ if not saw_trailer:
362
+ raise _ImageEquationError("GIF trailer is missing")
363
+ if any(image_data[cursor:]):
364
+ raise _ImageEquationError("GIF contains non-padding bytes after trailer")
365
+ return candidates, recognized
366
+
367
+
368
+ def _select_candidate_latex(candidates: list[bytes]) -> str | None:
369
+ """解码全部 candidates;仅返回唯一且完整一致的 LaTeX。"""
370
+
371
+ decoded = {latex for candidate in candidates if (latex := decode_mtef(candidate)) is not None}
372
+ return next(iter(decoded)) if len(decoded) == 1 else None
373
+
374
+
375
+ def _format_hint(
376
+ image_data: bytes,
377
+ part_name: object | None,
378
+ content_type: str | None,
379
+ ) -> str | None:
380
+ """结合 magic、扩展名和内容类型确定 WMF/GIF decoder。"""
381
+
382
+ if image_data[:6] in _GIF_HEADERS:
383
+ return "gif"
384
+ if image_data.startswith(_PLACEABLE_WMF_MAGIC):
385
+ return "wmf"
386
+ if len(image_data) >= 4 and image_data[:2] in {b"\x01\x00", b"\x02\x00"} and image_data[2:4] == b"\x09\x00":
387
+ return "wmf"
388
+ suffix = PurePosixPath(str(part_name or "")).suffix.casefold()
389
+ normalized_content_type = (content_type or "").split(";", 1)[0].strip().casefold()
390
+ if suffix == ".gif" or normalized_content_type == "image/gif":
391
+ return "gif"
392
+ if suffix == ".wmf" or normalized_content_type in {
393
+ "image/wmf",
394
+ "image/x-wmf",
395
+ "application/x-msmetafile",
396
+ }:
397
+ return "wmf"
398
+ return None
399
+
400
+
401
+ @dataclass(slots=True)
402
+ class OfficeImageEquationDecoder:
403
+ """按共享资源上限缓存并解码 WMF/GIF 图片中的 MTEF。"""
404
+
405
+ total_bytes: int = 0
406
+ _cache: dict[tuple[str, bytes], str | None] = field(default_factory=dict)
407
+ _warned: set[tuple[str, bytes]] = field(default_factory=set)
408
+
409
+ def decode(
410
+ self,
411
+ image_data: object | None,
412
+ *,
413
+ part_name: object | None = None,
414
+ content_type: str | None = None,
415
+ ) -> str | None:
416
+ """识别图片格式、执行有界 comment 解包并返回完整 LaTeX。"""
417
+
418
+ if not isinstance(image_data, bytes):
419
+ return None
420
+ image_format = _format_hint(image_data, part_name, content_type)
421
+ if image_format is None:
422
+ return None
423
+ if len(image_data) > MAX_ENTRY_BYTES:
424
+ raise LegacyOfficeResourceLimitError(f"image equation payload exceeds max_entry_bytes={MAX_ENTRY_BYTES}")
425
+ digest = hashlib.sha256(image_data).digest()
426
+ cache_key = (image_format, digest)
427
+ if cache_key in self._cache:
428
+ return self._cache[cache_key]
429
+ if self.total_bytes + len(image_data) > MAX_ASSET_TOTAL_BYTES:
430
+ raise LegacyOfficeResourceLimitError(
431
+ f"image equation payloads exceed max_asset_total_bytes={MAX_ASSET_TOTAL_BYTES}"
432
+ )
433
+ self.total_bytes += len(image_data)
434
+
435
+ recognized = False
436
+ try:
437
+ if image_format == "wmf":
438
+ candidates, recognized = _wmf_mtef_candidates(image_data)
439
+ else:
440
+ candidates, recognized = _gif_mtef_candidates(image_data)
441
+ latex = _select_candidate_latex(candidates)
442
+ except LegacyOfficeResourceLimitError:
443
+ raise
444
+ except (_ImageEquationError, ArithmeticError, IndexError, struct.error):
445
+ latex = None
446
+ recognized = b"MathType" in image_data or _APPS_MFCC_ID in image_data
447
+ self._cache[cache_key] = latex
448
+ if recognized and latex is None and cache_key not in self._warned:
449
+ self._warned.add(cache_key)
450
+ logger.warning(
451
+ "OFFICE_IMAGE_MTEF_FALLBACK: format={}, payload_sha256={} is malformed, conflicting, or unsupported",
452
+ image_format,
453
+ digest.hex()[:16],
454
+ )
455
+ return latex
456
+
457
+
458
+ def decode_image_embedded_equation(
459
+ image_data: bytes,
460
+ *,
461
+ part_name: object | None = None,
462
+ content_type: str | None = None,
463
+ ) -> str | None:
464
+ """使用一次性有界 decoder 从单张 WMF/GIF 中恢复 MTEF。"""
465
+
466
+ return OfficeImageEquationDecoder().decode(
467
+ image_data,
468
+ part_name=part_name,
469
+ content_type=content_type,
470
+ )