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,941 @@
1
+ """按照 WIRIS 规范安全读取 MathType MTEF v5 record tree。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ import struct
7
+
8
+ from ..errors import LegacyOfficeResourceLimitError
9
+ from ..limits import MAX_RECORD_DEPTH, MAX_RECORDS
10
+ from .mtef import _MtefError, _Node, _render_node
11
+
12
+ _OPT_NUDGE = 0x08
13
+ _CHAR_EMBELL = 0x01
14
+ _CHAR_FUNC_START = 0x02
15
+ _CHAR_ENC_8 = 0x04
16
+ _CHAR_ENC_16 = 0x10
17
+ _CHAR_NO_MTCODE = 0x20
18
+ _LINE_NULL = 0x01
19
+ _LINE_RULER = 0x02
20
+ _LINE_LSPACE = 0x04
21
+ _PILE_RULER = 0x02
22
+ _BUILTIN_TYPEFACES = frozenset({*range(1, 13), 22, 23, 24})
23
+
24
+ _PREDEFINED_ENCODINGS = {
25
+ 1: "MTCode",
26
+ 2: "Unknown",
27
+ 3: "Symbol",
28
+ 4: "MTExtra",
29
+ }
30
+
31
+ # Adobe Symbol/MathType Symbol 编码中常见且语义稳定的字符。
32
+ _SYMBOL_FONT_POSITION_TO_UNICODE = {
33
+ 0x41: 0x0391,
34
+ 0x42: 0x0392,
35
+ 0x43: 0x03A7,
36
+ 0x44: 0x0394,
37
+ 0x45: 0x0395,
38
+ 0x46: 0x03A6,
39
+ 0x47: 0x0393,
40
+ 0x48: 0x0397,
41
+ 0x49: 0x0399,
42
+ 0x4A: 0x03D1,
43
+ 0x4B: 0x039A,
44
+ 0x4C: 0x039B,
45
+ 0x4D: 0x039C,
46
+ 0x4E: 0x039D,
47
+ 0x4F: 0x039F,
48
+ 0x50: 0x03A0,
49
+ 0x51: 0x0398,
50
+ 0x52: 0x03A1,
51
+ 0x53: 0x03A3,
52
+ 0x54: 0x03A4,
53
+ 0x55: 0x03A5,
54
+ 0x56: 0x03C2,
55
+ 0x57: 0x03A9,
56
+ 0x58: 0x039E,
57
+ 0x59: 0x03A8,
58
+ 0x5A: 0x0396,
59
+ 0x61: 0x03B1,
60
+ 0x62: 0x03B2,
61
+ 0x63: 0x03C7,
62
+ 0x64: 0x03B4,
63
+ 0x65: 0x03B5,
64
+ 0x66: 0x03C6,
65
+ 0x67: 0x03B3,
66
+ 0x68: 0x03B7,
67
+ 0x69: 0x03B9,
68
+ 0x6A: 0x03D5,
69
+ 0x6B: 0x03BA,
70
+ 0x6C: 0x03BB,
71
+ 0x6D: 0x03BC,
72
+ 0x6E: 0x03BD,
73
+ 0x6F: 0x03BF,
74
+ 0x70: 0x03C0,
75
+ 0x71: 0x03B8,
76
+ 0x72: 0x03C1,
77
+ 0x73: 0x03C3,
78
+ 0x74: 0x03C4,
79
+ 0x75: 0x03C5,
80
+ 0x76: 0x03D6,
81
+ 0x77: 0x03C9,
82
+ 0x78: 0x03BE,
83
+ 0x79: 0x03C8,
84
+ 0x7A: 0x03B6,
85
+ 0xA3: 0x2264,
86
+ 0xA5: 0x221E,
87
+ 0xAB: 0x2194,
88
+ 0xAC: 0x2190,
89
+ 0xAE: 0x2192,
90
+ 0xB1: 0x00B1,
91
+ 0xB3: 0x2265,
92
+ 0xB9: 0x2260,
93
+ 0xC7: 0x2229,
94
+ 0xC8: 0x222A,
95
+ 0xD5: 0x220F,
96
+ 0xE5: 0x2211,
97
+ 0xF2: 0x222B,
98
+ }
99
+
100
+ # MathType v5 常见私有 MTCode;未列出的 PUA 继续整体回退,避免猜测字符。
101
+ _MTCODE_PUA_TO_LATEX = {
102
+ 0xE90B: r"\supseteqq ",
103
+ 0xE90C: r"\subseteqq ",
104
+ 0xE922: r"\lesseqqgtr ",
105
+ 0xE92D: r"\gtreqqless ",
106
+ 0xE92E: r"\shortmid ",
107
+ 0xE92F: r"\shortparallel ",
108
+ 0xE930: r"\leqslant ",
109
+ 0xE931: r"\geqslant ",
110
+ 0xE932: r"\lessapprox ",
111
+ 0xE933: r"\gtrapprox ",
112
+ 0xE938: r"\preceq ",
113
+ 0xE939: r"\succeq ",
114
+ 0xE981: r"\circleddash ",
115
+ 0xE98F: r"\centerdot ",
116
+ 0xEA06: r"\nleq ",
117
+ 0xEA07: r"\ngeq ",
118
+ 0xEA11: r"\nsim ",
119
+ 0xEF02: r"\,",
120
+ 0xEF03: r"\;",
121
+ 0xEF04: r"\ ",
122
+ 0xEF05: r"\quad ",
123
+ 0xEF06: r"\qquad ",
124
+ 0xEF22: r"\!",
125
+ }
126
+
127
+ _MTEXTRA_FONT_POSITION_TO_UNICODE = {
128
+ 0x20: 0x0020,
129
+ 0x3C: 0x25C3,
130
+ 0x3E: 0x25B9,
131
+ 0x43: 0x2210,
132
+ 0x44: 0x019B,
133
+ 0x49: 0x2229,
134
+ 0x4B: 0x2026,
135
+ 0x4C: 0x22EF,
136
+ 0x4D: 0x22EE,
137
+ 0x4E: 0x22F0,
138
+ 0x4F: 0x22F1,
139
+ 0x51: 0x2235,
140
+ 0x55: 0x222A,
141
+ 0x60: 0x2035,
142
+ 0x61: 0x21A6,
143
+ 0x62: 0x2195,
144
+ 0x63: 0x21D5,
145
+ 0x66: 0x227B,
146
+ 0x68: 0x210F,
147
+ 0x6C: 0x2113,
148
+ 0x6D: 0x2213,
149
+ 0x6F: 0x2218,
150
+ 0x70: 0x227A,
151
+ }
152
+
153
+
154
+ @dataclass(frozen=True, slots=True)
155
+ class _FontDefinition:
156
+ """记录一个 FONT_DEF 的 encoding 索引和字体名称。"""
157
+
158
+ encoding_index: int
159
+ name: str
160
+
161
+
162
+ @dataclass(frozen=True, slots=True)
163
+ class _FontStyleDefinition:
164
+ """记录一个 FONT_STYLE_DEF 的字体引用和粗斜体位。"""
165
+
166
+ font_definition_index: int
167
+ style_bits: int
168
+
169
+
170
+ class _MtefV5Reader:
171
+ """有界读取 MTEF v5 header、定义记录和公式对象树。"""
172
+
173
+ def __init__(self, data: bytes) -> None:
174
+ """校验 v5 header 并初始化 definition tables 与安全计数器。"""
175
+
176
+ if len(data) < 7 or data[0] != 5:
177
+ raise _MtefError("Equation Native does not contain MTEF v5")
178
+ if data[1] not in {0, 1} or data[2] not in {0, 1} or data[3] < 4:
179
+ raise _MtefError("MTEF v5 generator header is invalid")
180
+ self.data = data
181
+ self.pos = 5
182
+ self.depth = 0
183
+ self.records = 0
184
+ self.encoding_definitions: dict[int, str] = dict(_PREDEFINED_ENCODINGS)
185
+ self.font_definitions: list[_FontDefinition | None] = [None]
186
+ self.font_style_definitions: list[_FontStyleDefinition | None] = [None]
187
+ self.equation_styles: list[_FontStyleDefinition | None] = []
188
+ self.color_definition_count = 0
189
+ application_key = self._cstring(max_bytes=4096)
190
+ if not application_key:
191
+ raise _MtefError("MTEF v5 application key is empty")
192
+ equation_options = self._u8()
193
+ if equation_options & ~0x01:
194
+ raise _MtefError("MTEF v5 equation options are invalid")
195
+
196
+ def _charge(self) -> None:
197
+ """计入一条 record,超过共享上限时抛稳定资源错误。"""
198
+
199
+ self.records += 1
200
+ if self.records > MAX_RECORDS:
201
+ raise LegacyOfficeResourceLimitError(f"MTEF record count exceeds max_records={MAX_RECORDS}")
202
+
203
+ def _enter(self) -> None:
204
+ """进入嵌套 object list 并限制共享深度。"""
205
+
206
+ self.depth += 1
207
+ if self.depth > MAX_RECORD_DEPTH:
208
+ raise LegacyOfficeResourceLimitError(f"MTEF nesting exceeds max_record_depth={MAX_RECORD_DEPTH}")
209
+
210
+ def _leave(self) -> None:
211
+ """离开当前 object list。"""
212
+
213
+ self.depth -= 1
214
+
215
+ def _u8(self) -> int:
216
+ """有界读取一个无符号字节。"""
217
+
218
+ if self.pos >= len(self.data):
219
+ raise _MtefError("MTEF v5 record is truncated")
220
+ value = self.data[self.pos]
221
+ self.pos += 1
222
+ return value
223
+
224
+ def _u16(self) -> int:
225
+ """有界读取一个小端 u16。"""
226
+
227
+ if self.pos + 2 > len(self.data):
228
+ raise _MtefError("MTEF v5 u16 is truncated")
229
+ value = int(struct.unpack_from("<H", self.data, self.pos)[0])
230
+ self.pos += 2
231
+ return value
232
+
233
+ def _bytes(self, size: int) -> bytes:
234
+ """按显式长度有界读取 payload。"""
235
+
236
+ if size < 0 or self.pos + size < self.pos or self.pos + size > len(self.data):
237
+ raise _MtefError("MTEF v5 payload length is invalid")
238
+ value = self.data[self.pos : self.pos + size]
239
+ self.pos += size
240
+ return value
241
+
242
+ def _cstring(self, *, max_bytes: int) -> str:
243
+ """读取有长度上限的零结尾单字节字符串。"""
244
+
245
+ end_limit = min(len(self.data), self.pos + max_bytes + 1)
246
+ end = self.data.find(b"\x00", self.pos, end_limit)
247
+ if end < 0:
248
+ raise _MtefError("MTEF v5 string is not null-terminated")
249
+ raw = self.data[self.pos : end]
250
+ self.pos = end + 1
251
+ try:
252
+ return raw.decode("ascii")
253
+ except UnicodeDecodeError:
254
+ return raw.decode("latin-1")
255
+
256
+ def _signed(self) -> int:
257
+ """读取 MTEF v5 紧凑有符号整数。"""
258
+
259
+ first = self._u8()
260
+ if first == 0xFF:
261
+ return self._u16() - 0x8000
262
+ return first - 0x80
263
+
264
+ def _unsigned(self) -> int:
265
+ """读取 MTEF v5 紧凑无符号整数。"""
266
+
267
+ first = self._u8()
268
+ return self._u16() if first == 0xFF else first
269
+
270
+ def _variation(self) -> int:
271
+ """读取一或两字节 template variation。"""
272
+
273
+ first = self._u8()
274
+ if first & 0x80:
275
+ return (first & 0x7F) | (self._u8() << 8)
276
+ return first
277
+
278
+ def _skip_nudge(self) -> None:
279
+ """跳过短或长格式 nudge。"""
280
+
281
+ dx = self._u8()
282
+ dy = self._u8()
283
+ if dx == 0x80 and dy == 0x80:
284
+ self._u16()
285
+ self._u16()
286
+
287
+ def _skip_ruler(self) -> None:
288
+ """解析并跳过 RULER record 的 tab stops。"""
289
+
290
+ count = self._u8()
291
+ for _ in range(count):
292
+ tab_type = self._u8()
293
+ if tab_type > 4:
294
+ raise _MtefError("MTEF v5 ruler tab type is invalid")
295
+ self._u16()
296
+
297
+ def _skip_size(self) -> None:
298
+ """解析并跳过 SIZE record 的三种编码。"""
299
+
300
+ first = self._u8()
301
+ if first == 100:
302
+ logical_size = self._u8()
303
+ if logical_size > 7:
304
+ raise _MtefError("MTEF v5 logical size is invalid")
305
+ self._u16()
306
+ elif first == 101:
307
+ self._u16()
308
+ else:
309
+ if first > 7:
310
+ raise _MtefError("MTEF v5 logical size is invalid")
311
+ self._u8()
312
+
313
+ def _dimension_array(self) -> None:
314
+ """有界消费 EQN_PREFS 中按 nibble 编码的 dimension array。"""
315
+
316
+ count = self._u8()
317
+ current = 0
318
+ use_low = False
319
+
320
+ def next_nibble() -> int:
321
+ """按高四位优先顺序读取一个 nibble。"""
322
+
323
+ nonlocal current, use_low
324
+ if not use_low:
325
+ current = self._u8()
326
+ use_low = True
327
+ return current >> 4
328
+ use_low = False
329
+ return current & 0x0F
330
+
331
+ for _ in range(count):
332
+ units = next_nibble()
333
+ if units > 4:
334
+ raise _MtefError("MTEF v5 dimension units are invalid")
335
+ while True:
336
+ value = next_nibble()
337
+ if value == 0x0F:
338
+ break
339
+ if value > 0x0B:
340
+ raise _MtefError("MTEF v5 dimension digit is invalid")
341
+ if use_low and current & 0x0F:
342
+ raise _MtefError("MTEF v5 dimension padding nibble is invalid")
343
+
344
+ def _parse_font_style_definition(self) -> _Node:
345
+ """读取 FONT_STYLE_DEF 并验证先前 FONT_DEF 引用。"""
346
+
347
+ font_index = self._unsigned()
348
+ style_bits = self._u8()
349
+ if style_bits & ~0x03:
350
+ raise _MtefError("MTEF v5 font style bits are invalid")
351
+ if font_index <= 0 or font_index >= len(self.font_definitions):
352
+ raise _MtefError("MTEF v5 FONT_STYLE_DEF reference is invalid")
353
+ self.font_style_definitions.append(_FontStyleDefinition(font_index, style_bits))
354
+ return _Node("metadata")
355
+
356
+ def _parse_color_definition(self) -> _Node:
357
+ """读取 COLOR_DEF,颜色只校验结构而不进入现有 schema。"""
358
+
359
+ options = self._u8()
360
+ if options & ~0x07:
361
+ raise _MtefError("MTEF v5 color options are invalid")
362
+ component_count = 4 if options & 0x01 else 3
363
+ for _ in range(component_count):
364
+ if self._u16() > 1000:
365
+ raise _MtefError("MTEF v5 color component is invalid")
366
+ if options & 0x04:
367
+ self._cstring(max_bytes=4096)
368
+ self.color_definition_count += 1
369
+ return _Node("metadata")
370
+
371
+ def _parse_font_definition(self) -> _Node:
372
+ """读取 FONT_DEF 并绑定已声明 encoding。"""
373
+
374
+ encoding_index = self._unsigned()
375
+ if encoding_index not in self.encoding_definitions:
376
+ raise _MtefError("MTEF v5 FONT_DEF encoding reference is invalid")
377
+ name = self._cstring(max_bytes=4096)
378
+ if not name:
379
+ raise _MtefError("MTEF v5 font name is empty")
380
+ self.font_definitions.append(_FontDefinition(encoding_index, name))
381
+ return _Node("metadata")
382
+
383
+ def _parse_equation_preferences(self) -> _Node:
384
+ """读取 EQN_PREFS 的尺寸、间距和 style definition 数组。"""
385
+
386
+ if self._u8() != 0:
387
+ raise _MtefError("MTEF v5 equation preference options are invalid")
388
+ self._dimension_array()
389
+ self._dimension_array()
390
+ style_count = self._u8()
391
+ styles: list[_FontStyleDefinition | None] = []
392
+ for _ in range(style_count):
393
+ font_index = self._unsigned()
394
+ if font_index == 0:
395
+ styles.append(None)
396
+ continue
397
+ if font_index >= len(self.font_definitions):
398
+ raise _MtefError("MTEF v5 equation style reference is invalid")
399
+ style_bits = self._u8()
400
+ if style_bits & ~0x03:
401
+ raise _MtefError("MTEF v5 equation style bits are invalid")
402
+ styles.append(_FontStyleDefinition(font_index, style_bits))
403
+ self.equation_styles = styles
404
+ return _Node("metadata")
405
+
406
+ def _parse_encoding_definition(self) -> _Node:
407
+ """读取自定义 ENCODING_DEF 并按出现顺序从索引 5 编号。"""
408
+
409
+ name = self._cstring(max_bytes=4096)
410
+ if not name:
411
+ raise _MtefError("MTEF v5 encoding name is empty")
412
+ index = max(self.encoding_definitions) + 1
413
+ self.encoding_definitions[index] = name
414
+ return _Node("metadata")
415
+
416
+ def _character_style(self, typeface: int) -> int:
417
+ """从显式字体或 EQN_PREFS style 解析粗斜体位。"""
418
+
419
+ definition: _FontStyleDefinition | None = None
420
+ if typeface < 0:
421
+ index = -typeface
422
+ if 0 < index < len(self.font_style_definitions):
423
+ definition = self.font_style_definitions[index]
424
+ elif 0 < typeface <= len(self.equation_styles):
425
+ definition = self.equation_styles[typeface - 1]
426
+ return definition.style_bits if definition is not None else 0
427
+
428
+ def _character_encoding(self, typeface: int) -> str:
429
+ """解析无 MTCode 字符所引用的字体 encoding。"""
430
+
431
+ definition: _FontStyleDefinition | None = None
432
+ if typeface < 0:
433
+ index = -typeface
434
+ if 0 < index < len(self.font_style_definitions):
435
+ definition = self.font_style_definitions[index]
436
+ elif 0 < typeface <= len(self.equation_styles):
437
+ definition = self.equation_styles[typeface - 1]
438
+ if definition is not None:
439
+ font_definition = self.font_definitions[definition.font_definition_index]
440
+ if font_definition is not None:
441
+ return self.encoding_definitions[font_definition.encoding_index]
442
+ if typeface in {4, 5, 6}:
443
+ return "Symbol"
444
+ if typeface == 11:
445
+ return "MTExtra"
446
+ return "WinAllBasicCodePages"
447
+
448
+ @staticmethod
449
+ def _mtcode_character(code: int) -> int | str:
450
+ """把可验证的 MTCode 转为 Unicode/LaTeX,未知 PUA 整体回退。"""
451
+
452
+ if 0xD800 <= code <= 0xDFFF or code < 0x20:
453
+ raise _MtefError("MTEF v5 MTCode character is invalid")
454
+ if 0xE000 <= code <= 0xF8FF:
455
+ mapped = _MTCODE_PUA_TO_LATEX.get(code)
456
+ if mapped is not None:
457
+ return mapped
458
+ if 0xF000 <= code <= 0xF019:
459
+ return rf"\mathfrak{{{chr(ord('A') + code - 0xF000)}}}"
460
+ if 0xF01A <= code <= 0xF033:
461
+ return rf"\mathfrak{{{chr(ord('a') + code - 0xF01A)}}}"
462
+ if 0xF080 <= code <= 0xF099:
463
+ return rf"\mathbb{{{chr(ord('A') + code - 0xF080)}}}"
464
+ if 0xF100 <= code <= 0xF119:
465
+ return rf"\mathcal{{{chr(ord('A') + code - 0xF100)}}}"
466
+ raise _MtefError("MTEF v5 private MTCode is unsupported")
467
+ return code
468
+
469
+ @staticmethod
470
+ def _font_position_character(encoding: str, position: int) -> int | str:
471
+ """从已知 encoding 的 font position 恢复 Unicode/LaTeX。"""
472
+
473
+ normalized = encoding.strip().casefold()
474
+ if normalized == "mtcode":
475
+ return _MtefV5Reader._mtcode_character(position)
476
+ if normalized == "symbol":
477
+ character = _SYMBOL_FONT_POSITION_TO_UNICODE.get(position)
478
+ if character is None:
479
+ raise _MtefError("MTEF v5 Symbol font position is unsupported")
480
+ return character
481
+ if normalized == "mtextra":
482
+ character = _MTEXTRA_FONT_POSITION_TO_UNICODE.get(position)
483
+ if character is None:
484
+ raise _MtefError("MTEF v5 MTExtra font position is unsupported")
485
+ return character
486
+ if normalized in {
487
+ "winallbasiccodepages",
488
+ "windowsansi",
489
+ "windows-1252",
490
+ "cp1252",
491
+ "unicode",
492
+ }:
493
+ if position <= 0xFF:
494
+ value = bytes([position]).decode("cp1252")
495
+ return ord(value)
496
+ return _MtefV5Reader._mtcode_character(position)
497
+ raise _MtefError(f"unsupported MTEF v5 font encoding: {encoding}")
498
+
499
+ def _parse_embellishments(self) -> tuple[int, ...]:
500
+ """读取 CHAR 后以 END 终止的 EMBELL 列表。"""
501
+
502
+ values: list[int] = []
503
+ while True:
504
+ record_type = self._u8()
505
+ self._charge()
506
+ if record_type == 0:
507
+ return tuple(values)
508
+ if record_type != 6:
509
+ raise _MtefError("MTEF v5 embellishment list is invalid")
510
+ options = self._u8()
511
+ if options & ~_OPT_NUDGE:
512
+ raise _MtefError("MTEF v5 embellishment options are invalid")
513
+ if options & _OPT_NUDGE:
514
+ self._skip_nudge()
515
+ embellishment = self._u8()
516
+ if not 2 <= embellishment <= 37:
517
+ raise _MtefError("MTEF v5 embellishment type is invalid")
518
+ values.append(embellishment)
519
+
520
+ def _parse_character(self) -> _Node:
521
+ """读取 CHAR 的 typeface、MTCode/font position 和 embellishments。"""
522
+
523
+ options = self._u8()
524
+ allowed = _OPT_NUDGE | _CHAR_EMBELL | _CHAR_FUNC_START | _CHAR_ENC_8 | _CHAR_ENC_16 | _CHAR_NO_MTCODE
525
+ if options & ~allowed or options & _CHAR_ENC_8 and options & _CHAR_ENC_16:
526
+ raise _MtefError("MTEF v5 CHAR options are invalid")
527
+ if options & _OPT_NUDGE:
528
+ self._skip_nudge()
529
+ typeface = self._signed()
530
+ if typeface < 0:
531
+ definition_index = -typeface
532
+ if not (
533
+ 0 < definition_index < len(self.font_style_definitions)
534
+ and self.font_style_definitions[definition_index] is not None
535
+ ):
536
+ raise _MtefError("MTEF v5 explicit typeface reference is invalid")
537
+ elif typeface not in _BUILTIN_TYPEFACES:
538
+ raise _MtefError("MTEF v5 typeface value is invalid")
539
+ mtcode = None if options & _CHAR_NO_MTCODE else self._u16()
540
+ font_position: int | None = None
541
+ if options & _CHAR_ENC_8:
542
+ font_position = self._u8()
543
+ elif options & _CHAR_ENC_16:
544
+ font_position = self._u16()
545
+ if mtcode is None and font_position is None:
546
+ raise _MtefError("MTEF v5 CHAR has no character value")
547
+ if mtcode is not None:
548
+ character = self._mtcode_character(mtcode)
549
+ else:
550
+ if font_position is None:
551
+ raise _MtefError("MTEF v5 CHAR font position is missing")
552
+ character = self._font_position_character(
553
+ self._character_encoding(typeface),
554
+ font_position,
555
+ )
556
+ embellishments = self._parse_embellishments() if options & _CHAR_EMBELL else ()
557
+ return _Node(
558
+ "character_v5",
559
+ (
560
+ character,
561
+ embellishments,
562
+ self._character_style(typeface),
563
+ typeface,
564
+ bool(options & _CHAR_FUNC_START),
565
+ ),
566
+ )
567
+
568
+ def _parse_line(self) -> _Node:
569
+ """读取 LINE options、可选 ruler 和内部 object list。"""
570
+
571
+ options = self._u8()
572
+ if options & ~(_OPT_NUDGE | _LINE_NULL | _LINE_RULER | _LINE_LSPACE):
573
+ raise _MtefError("MTEF v5 LINE options are invalid")
574
+ if options & _OPT_NUDGE:
575
+ self._skip_nudge()
576
+ if options & _LINE_LSPACE:
577
+ self._u16()
578
+ if options & _LINE_RULER:
579
+ if self._u8() != 7:
580
+ raise _MtefError("MTEF v5 LINE ruler tag is invalid")
581
+ self._charge()
582
+ self._skip_ruler()
583
+ if options & _LINE_NULL:
584
+ return _Node("sequence")
585
+ self._enter()
586
+ children = self._parse_list()
587
+ self._leave()
588
+ return _Node("sequence", children=children)
589
+
590
+ def _parse_pile(self) -> _Node:
591
+ """读取 PILE alignment、ruler 和逐行 object list。"""
592
+
593
+ options = self._u8()
594
+ if options & ~(_OPT_NUDGE | _PILE_RULER):
595
+ raise _MtefError("MTEF v5 PILE options are invalid")
596
+ if options & _OPT_NUDGE:
597
+ self._skip_nudge()
598
+ horizontal_alignment = self._u8()
599
+ vertical_alignment = self._u8()
600
+ if horizontal_alignment not in {1, 2, 3, 4, 5} or vertical_alignment > 4:
601
+ raise _MtefError("MTEF v5 PILE alignment is invalid")
602
+ if options & _PILE_RULER:
603
+ if self._u8() != 7:
604
+ raise _MtefError("MTEF v5 PILE ruler tag is invalid")
605
+ self._charge()
606
+ self._skip_ruler()
607
+ self._enter()
608
+ children = self._parse_list()
609
+ self._leave()
610
+ if any(child.kind != "sequence" for child in children):
611
+ raise _MtefError("MTEF v5 PILE contains a non-LINE object")
612
+ return _Node("pile", children=children)
613
+
614
+ def _parse_matrix(self) -> _Node:
615
+ """读取 MATRIX 维度、partition bits 和逐格 LINE。"""
616
+
617
+ options = self._u8()
618
+ if options & ~_OPT_NUDGE:
619
+ raise _MtefError("MTEF v5 MATRIX options are invalid")
620
+ if options & _OPT_NUDGE:
621
+ self._skip_nudge()
622
+ vertical_alignment = self._u8()
623
+ horizontal_justification = self._u8()
624
+ vertical_justification = self._u8()
625
+ if vertical_alignment > 4 or horizontal_justification not in {1, 2, 3, 4, 5} or vertical_justification > 4:
626
+ raise _MtefError("MTEF v5 MATRIX alignment is invalid")
627
+ rows = self._u8()
628
+ cols = self._u8()
629
+ if rows == 0 or cols == 0 or rows * cols > 4096:
630
+ raise _MtefError("MTEF v5 matrix dimensions are invalid")
631
+ self._bytes((2 * (rows + 1) + 7) // 8)
632
+ self._bytes((2 * (cols + 1) + 7) // 8)
633
+ self._enter()
634
+ children = self._parse_list()
635
+ self._leave()
636
+ cells = tuple(child for child in children if child.kind == "sequence")
637
+ if len(cells) != rows * cols or len(cells) != len(children):
638
+ raise _MtefError("MTEF v5 matrix cell list is invalid")
639
+ return _Node("matrix", (rows, cols), cells)
640
+
641
+ def _parse_template(self) -> _Node:
642
+ """读取 TMPL selector、variation、options 并转换为语义 AST。"""
643
+
644
+ record_options = self._u8()
645
+ if record_options & ~_OPT_NUDGE:
646
+ raise _MtefError("MTEF v5 TMPL options are invalid")
647
+ if record_options & _OPT_NUDGE:
648
+ self._skip_nudge()
649
+ selector = self._u8()
650
+ variation = self._variation()
651
+ template_options = self._u8()
652
+ self._enter()
653
+ slots = self._parse_list()
654
+ self._leave()
655
+ return _semantic_template(selector, variation, template_options, slots)
656
+
657
+ def _parse_record(self, record_type: int) -> _Node:
658
+ """解析一条已读取 type 的 v5 record。"""
659
+
660
+ if record_type == 1:
661
+ return self._parse_line()
662
+ if record_type == 2:
663
+ return self._parse_character()
664
+ if record_type == 3:
665
+ return self._parse_template()
666
+ if record_type == 4:
667
+ return self._parse_pile()
668
+ if record_type == 5:
669
+ return self._parse_matrix()
670
+ if record_type == 6:
671
+ raise _MtefError("MTEF v5 EMBELL appears outside a CHAR list")
672
+ if record_type == 7:
673
+ self._skip_ruler()
674
+ return _Node("metadata")
675
+ if record_type == 8:
676
+ return self._parse_font_style_definition()
677
+ if record_type == 9:
678
+ self._skip_size()
679
+ return _Node("metadata")
680
+ if 10 <= record_type <= 14:
681
+ return _Node("metadata")
682
+ if record_type == 15:
683
+ color_index = self._unsigned()
684
+ if not 0 < color_index <= self.color_definition_count:
685
+ raise _MtefError("MTEF v5 COLOR reference is invalid")
686
+ return _Node("metadata")
687
+ if record_type == 16:
688
+ return self._parse_color_definition()
689
+ if record_type == 17:
690
+ return self._parse_font_definition()
691
+ if record_type == 18:
692
+ return self._parse_equation_preferences()
693
+ if record_type == 19:
694
+ return self._parse_encoding_definition()
695
+ if record_type >= 100:
696
+ self._bytes(self._unsigned())
697
+ return _Node("metadata")
698
+ raise _MtefError(f"unsupported MTEF v5 record type: {record_type}")
699
+
700
+ @staticmethod
701
+ def _group_character_runs(nodes: list[_Node]) -> tuple[_Node, ...]:
702
+ """把连续 TEXT 字符和 FUNC_START 函数字符折叠为语义节点。"""
703
+
704
+ grouped: list[_Node] = []
705
+ index = 0
706
+ while index < len(nodes):
707
+ node = nodes[index]
708
+ if node.kind != "character_v5":
709
+ grouped.append(node)
710
+ index += 1
711
+ continue
712
+ character, embellishments, style_bits, typeface, function_start = node.value # type: ignore[misc]
713
+ if isinstance(character, int) and int(typeface) in {1, 12} and not embellishments and not style_bits:
714
+ characters = [chr(character)]
715
+ cursor = index + 1
716
+ while cursor < len(nodes):
717
+ candidate = nodes[cursor]
718
+ if candidate.kind != "character_v5":
719
+ break
720
+ c_char, c_embell, c_style, c_typeface, _c_start = candidate.value # type: ignore[misc]
721
+ if not isinstance(c_char, int) or c_embell or c_style or int(c_typeface) != int(typeface):
722
+ break
723
+ characters.append(chr(c_char))
724
+ cursor += 1
725
+ grouped.append(_Node("text", "".join(characters)))
726
+ index = cursor
727
+ continue
728
+ if not isinstance(character, int) or not function_start or int(typeface) != 2 or embellishments or style_bits:
729
+ grouped.append(node)
730
+ index += 1
731
+ continue
732
+ characters = [chr(int(character))]
733
+ cursor = index + 1
734
+ while cursor < len(nodes):
735
+ candidate = nodes[cursor]
736
+ if candidate.kind != "character_v5":
737
+ break
738
+ c_char, c_embell, c_style, c_typeface, _c_start = candidate.value # type: ignore[misc]
739
+ if not isinstance(c_char, int) or c_embell or c_style or int(c_typeface) != 2:
740
+ break
741
+ characters.append(chr(int(c_char)))
742
+ cursor += 1
743
+ grouped.append(_Node("function", "".join(characters)))
744
+ index = cursor
745
+ return tuple(grouped)
746
+
747
+ def _parse_list(self) -> tuple[_Node, ...]:
748
+ """解析以 END 终止的 v5 object list。"""
749
+
750
+ nodes: list[_Node] = []
751
+ while True:
752
+ record_type = self._u8()
753
+ self._charge()
754
+ if record_type == 0:
755
+ return self._group_character_runs(nodes)
756
+ node = self._parse_record(record_type)
757
+ if node.kind != "metadata":
758
+ nodes.append(node)
759
+
760
+ def parse(self) -> _Node:
761
+ """解析根 object list,要求完整消费且产生可见公式。"""
762
+
763
+ children = self._parse_list()
764
+ if self.pos != len(self.data):
765
+ raise _MtefError("MTEF v5 contains trailing bytes")
766
+ root = _Node("sequence", children=children)
767
+ if not _render_node(root).strip():
768
+ raise _MtefError("MTEF v5 formula contains no visible content")
769
+ return root
770
+
771
+
772
+ def _slot_node(slots: tuple[_Node, ...], index: int) -> _Node:
773
+ """返回一个 template slot,不存在时补空 sequence。"""
774
+
775
+ return slots[index] if index < len(slots) else _Node("sequence")
776
+
777
+
778
+ def _semantic_template(
779
+ selector: int,
780
+ variation: int,
781
+ options: int,
782
+ slots: tuple[_Node, ...],
783
+ ) -> _Node:
784
+ """把 MTEF v5 selector/variation 转换为版本无关语义节点。"""
785
+
786
+ if 0 <= selector <= 8:
787
+ if variation & ~0x03 or options not in {0, 1, 2}:
788
+ raise _MtefError("unsupported MTEF v5 fence variation")
789
+ pairs = {
790
+ 0: (r"\langle", r"\rangle"),
791
+ 1: ("(", ")"),
792
+ 2: (r"\{", r"\}"),
793
+ 3: ("[", "]"),
794
+ 4: ("|", "|"),
795
+ 5: (r"\|", r"\|"),
796
+ 6: (r"\lfloor", r"\rfloor"),
797
+ 7: (r"\lceil", r"\rceil"),
798
+ 8: (r"\llbracket", r"\rrbracket"),
799
+ }
800
+ default_left, default_right = pairs[selector]
801
+ left = default_left if variation & 0x01 else "."
802
+ right = default_right if variation & 0x02 else "."
803
+ return _Node(
804
+ "fence_semantic",
805
+ (left, right),
806
+ (_slot_node(slots, 0), _slot_node(slots, 1), _slot_node(slots, 2)),
807
+ )
808
+ if selector == 9:
809
+ if variation & ~0x33 or options not in {0, 1, 2}:
810
+ raise _MtefError("unsupported MTEF v5 interval variation")
811
+ left = {0: "(", 1: ")", 2: "[", 3: "]"}[variation & 0x03]
812
+ right = {0: "(", 1: ")", 2: "[", 3: "]"}[(variation >> 4) & 0x03]
813
+ return _Node("fence_semantic", (left, right), (_slot_node(slots, 0),))
814
+ if selector == 10:
815
+ if variation not in {0, 1} or options != 0:
816
+ raise _MtefError("unsupported MTEF v5 root variation")
817
+ return _Node(
818
+ "root_semantic",
819
+ variation == 1,
820
+ (_slot_node(slots, 0), _slot_node(slots, 1)),
821
+ )
822
+ if selector == 11:
823
+ if variation & ~0x07 or options != 0:
824
+ raise _MtefError("unsupported MTEF v5 fraction variation")
825
+ return _Node(
826
+ "fraction_semantic",
827
+ bool(variation & 0x02),
828
+ (_slot_node(slots, 0), _slot_node(slots, 1)),
829
+ )
830
+ if selector in {12, 13}:
831
+ if variation & ~0x01 or options != 0:
832
+ raise _MtefError("unsupported MTEF v5 bar variation")
833
+ return _Node(
834
+ "bar_semantic",
835
+ (selector == 12, bool(variation & 0x01)),
836
+ (_slot_node(slots, 0),),
837
+ )
838
+ if selector == 14:
839
+ if variation & ~0x3F or options != 0:
840
+ raise _MtefError("unsupported MTEF v5 arrow variation")
841
+ return _Node("arrow_semantic", variation, slots)
842
+ if 15 <= selector <= 22:
843
+ if options not in {0, 1} or variation & ~0x01FF:
844
+ raise _MtefError("unsupported MTEF v5 big-operator variation")
845
+ defaults = {
846
+ 15: r"\int",
847
+ 16: r"\sum",
848
+ 17: r"\prod",
849
+ 18: r"\coprod",
850
+ 19: r"\bigcup",
851
+ 20: r"\bigcap",
852
+ 21: "",
853
+ 22: "",
854
+ }
855
+ return _Node(
856
+ "big_operator_semantic",
857
+ defaults[selector],
858
+ tuple(_slot_node(slots, index) for index in range(4)),
859
+ )
860
+ if selector == 23:
861
+ if variation not in {0, 1} or options != 0:
862
+ raise _MtefError("unsupported MTEF v5 limit variation")
863
+ return _Node(
864
+ "limit_semantic",
865
+ None,
866
+ tuple(_slot_node(slots, index) for index in range(3)),
867
+ )
868
+ if selector in {24, 25}:
869
+ if variation & ~0x01 or options != 0:
870
+ raise _MtefError("unsupported MTEF v5 horizontal-fence variation")
871
+ return _Node(
872
+ "horizontal_fence_semantic",
873
+ (selector == 24, bool(variation & 0x01)),
874
+ tuple(_slot_node(slots, index) for index in range(3)),
875
+ )
876
+ if selector == 26:
877
+ if variation & ~0x01 or options != 0:
878
+ raise _MtefError("unsupported MTEF v5 long-division variation")
879
+ return _Node(
880
+ "long_division_semantic",
881
+ bool(variation & 0x01),
882
+ (_slot_node(slots, 0), _slot_node(slots, 1)),
883
+ )
884
+ if selector in {27, 28, 29}:
885
+ if variation & ~0x01 or options != 0:
886
+ raise _MtefError("unsupported MTEF v5 script variation")
887
+ subscript = _slot_node(slots, 0) if selector in {27, 29} else _Node("sequence")
888
+ superscript = _slot_node(slots, 1) if selector in {28, 29} else _Node("sequence")
889
+ return _Node(
890
+ "scripts_semantic",
891
+ bool(variation & 0x01),
892
+ (subscript, superscript),
893
+ )
894
+ if selector == 30:
895
+ if variation & ~0x03 or options != 0:
896
+ raise _MtefError("unsupported MTEF v5 Dirac variation")
897
+ return _Node(
898
+ "dirac_semantic",
899
+ variation,
900
+ tuple(_slot_node(slots, index) for index in range(5)),
901
+ )
902
+ if selector == 31:
903
+ if variation & ~0x0F or options != 0 or variation & 0x08 and variation & 0x03 == 0x03:
904
+ raise _MtefError("unsupported MTEF v5 vector variation")
905
+ return _Node("vector_semantic", variation, (_slot_node(slots, 0),))
906
+ if selector in {32, 33, 34}:
907
+ if variation != 0 or options != 0:
908
+ raise _MtefError("unsupported MTEF v5 accent variation")
909
+ command = {32: "widetilde", 33: "widehat", 34: "overparen"}[selector]
910
+ return _Node("accent_semantic", command, (_slot_node(slots, 0),))
911
+ if selector == 35:
912
+ raise _MtefError("unsupported MTEF v5 joint-status template")
913
+ if selector == 36:
914
+ if variation & ~0x07 or variation == 0 or variation & 0x01 or options != 0:
915
+ raise _MtefError("unsupported MTEF v5 strike variation")
916
+ command = "xcancel" if variation & 0x06 == 0x06 else "cancel" if variation & 0x02 else "bcancel"
917
+ return _Node("strike_semantic", command, (_slot_node(slots, 0),))
918
+ if selector == 37:
919
+ if options != 0 or variation not in {0, 0x1E}:
920
+ raise _MtefError("unsupported MTEF v5 box variation")
921
+ return _Node("box_semantic", None, (_slot_node(slots, 0),))
922
+ raise _MtefError(f"unsupported MTEF v5 template selector: {selector}")
923
+
924
+
925
+ def decode_mtef_v5(data: bytes) -> str | None:
926
+ """把 MTEF v5 字节流转换为 LaTeX,损坏或不支持时整体返回空。"""
927
+
928
+ try:
929
+ latex = _render_node(_MtefV5Reader(data).parse()).strip()
930
+ except LegacyOfficeResourceLimitError:
931
+ raise
932
+ except (
933
+ ArithmeticError,
934
+ IndexError,
935
+ struct.error,
936
+ UnicodeError,
937
+ _MtefError,
938
+ ValueError,
939
+ ):
940
+ return None
941
+ return latex or None