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.
- docvortex/__init__.py +21 -0
- docvortex/analyzers/__init__.py +3 -0
- docvortex/analyzers/native/__init__.py +37 -0
- docvortex/analyzers/native/_shared/__init__.py +3 -0
- docvortex/analyzers/native/_shared/hyperlink.py +16 -0
- docvortex/analyzers/native/_shared/image.py +8 -0
- docvortex/analyzers/native/_shared/markup/__init__.py +47 -0
- docvortex/analyzers/native/_shared/markup/anchors.py +30 -0
- docvortex/analyzers/native/_shared/markup/formula.py +37 -0
- docvortex/analyzers/native/_shared/markup/projector.py +57 -0
- docvortex/analyzers/native/_shared/markup/styles.py +19 -0
- docvortex/analyzers/native/_shared/mathml.py +17 -0
- docvortex/analyzers/native/_shared/names.py +7 -0
- docvortex/analyzers/native/_shared/xycut.py +414 -0
- docvortex/analyzers/native/contracts.py +44 -0
- docvortex/analyzers/native/csv.py +351 -0
- docvortex/analyzers/native/epub/__init__.py +18 -0
- docvortex/analyzers/native/epub/constants.py +45 -0
- docvortex/analyzers/native/epub/converter.py +80 -0
- docvortex/analyzers/native/epub/errors.py +20 -0
- docvortex/analyzers/native/epub/metadata.py +31 -0
- docvortex/analyzers/native/epub/package.py +649 -0
- docvortex/analyzers/native/epub/xhtml.py +306 -0
- docvortex/analyzers/native/html/__init__.py +6 -0
- docvortex/analyzers/native/html/anchors.py +271 -0
- docvortex/analyzers/native/html/constants.py +25 -0
- docvortex/analyzers/native/html/contracts.py +7 -0
- docvortex/analyzers/native/html/converter.py +117 -0
- docvortex/analyzers/native/html/document.py +389 -0
- docvortex/analyzers/native/html/errors.py +12 -0
- docvortex/analyzers/native/html/resources.py +365 -0
- docvortex/analyzers/native/html/selector.py +421 -0
- docvortex/analyzers/native/models.py +209 -0
- docvortex/analyzers/native/ofd/__init__.py +14 -0
- docvortex/analyzers/native/ofd/constants.py +59 -0
- docvortex/analyzers/native/ofd/converter.py +31 -0
- docvortex/analyzers/native/ofd/errors.py +16 -0
- docvortex/analyzers/native/ofd/geometry.py +215 -0
- docvortex/analyzers/native/ofd/images.py +124 -0
- docvortex/analyzers/native/ofd/metadata.py +53 -0
- docvortex/analyzers/native/ofd/models.py +170 -0
- docvortex/analyzers/native/ofd/package.py +346 -0
- docvortex/analyzers/native/ofd/path.py +201 -0
- docvortex/analyzers/native/ofd/reading_order.py +293 -0
- docvortex/analyzers/native/ofd/resources.py +122 -0
- docvortex/analyzers/native/ofd/scene.py +347 -0
- docvortex/analyzers/native/ofd/table.py +240 -0
- docvortex/analyzers/native/ofd/text.py +521 -0
- docvortex/analyzers/native/office/__init__.py +3 -0
- docvortex/analyzers/native/office/doc/__init__.py +3 -0
- docvortex/analyzers/native/office/doc/bookmarks.py +81 -0
- docvortex/analyzers/native/office/doc/doc_converter.py +573 -0
- docvortex/analyzers/native/office/doc/fib.py +261 -0
- docvortex/analyzers/native/office/doc/fields.py +101 -0
- docvortex/analyzers/native/office/doc/formatting.py +171 -0
- docvortex/analyzers/native/office/doc/images.py +162 -0
- docvortex/analyzers/native/office/doc/lists.py +356 -0
- docvortex/analyzers/native/office/doc/models.py +164 -0
- docvortex/analyzers/native/office/doc/parser.py +851 -0
- docvortex/analyzers/native/office/doc/pieces.py +256 -0
- docvortex/analyzers/native/office/doc/records.py +40 -0
- docvortex/analyzers/native/office/doc/sprm.py +269 -0
- docvortex/analyzers/native/office/doc/styles.py +215 -0
- docvortex/analyzers/native/office/docx/__init__.py +3 -0
- docvortex/analyzers/native/office/docx/context.py +71 -0
- docvortex/analyzers/native/office/docx/docx_converter.py +614 -0
- docvortex/analyzers/native/office/docx/equationxml.py +119 -0
- docvortex/analyzers/native/office/docx/fields.py +816 -0
- docvortex/analyzers/native/office/docx/formatting_types.py +23 -0
- docvortex/analyzers/native/office/docx/main.py +50 -0
- docvortex/analyzers/native/office/docx/numbering.py +491 -0
- docvortex/analyzers/native/office/docx/office_xml.py +57 -0
- docvortex/analyzers/native/office/docx/package_normalizer.py +248 -0
- docvortex/analyzers/native/office/docx/resources.py +526 -0
- docvortex/analyzers/native/office/docx/styles.py +693 -0
- docvortex/analyzers/native/office/docx/tables.py +515 -0
- docvortex/analyzers/native/office/equation/__init__.py +3 -0
- docvortex/analyzers/native/office/equation/image.py +470 -0
- docvortex/analyzers/native/office/equation/latex_dict.py +324 -0
- docvortex/analyzers/native/office/equation/mtef.py +885 -0
- docvortex/analyzers/native/office/equation/mtef_v5.py +941 -0
- docvortex/analyzers/native/office/equation/omml.py +561 -0
- docvortex/analyzers/native/office/equation/ooxml.py +62 -0
- docvortex/analyzers/native/office/errors.py +33 -0
- docvortex/analyzers/native/office/image.py +307 -0
- docvortex/analyzers/native/office/legacy/__init__.py +3 -0
- docvortex/analyzers/native/office/legacy/binary.py +43 -0
- docvortex/analyzers/native/office/legacy/officeart.py +362 -0
- docvortex/analyzers/native/office/legacy/ole.py +110 -0
- docvortex/analyzers/native/office/limits.py +12 -0
- docvortex/analyzers/native/office/odf/__init__.py +3 -0
- docvortex/analyzers/native/office/odf/chart.py +76 -0
- docvortex/analyzers/native/office/odf/constants.py +70 -0
- docvortex/analyzers/native/office/odf/converters.py +403 -0
- docvortex/analyzers/native/office/odf/errors.py +18 -0
- docvortex/analyzers/native/office/odf/metadata.py +91 -0
- docvortex/analyzers/native/office/odf/models.py +176 -0
- docvortex/analyzers/native/office/odf/package.py +270 -0
- docvortex/analyzers/native/office/odf/styles.py +329 -0
- docvortex/analyzers/native/office/odf/table.py +469 -0
- docvortex/analyzers/native/office/odf/text.py +1002 -0
- docvortex/analyzers/native/office/ooxml_chart.py +1016 -0
- docvortex/analyzers/native/office/opc.py +38 -0
- docvortex/analyzers/native/office/ppt/__init__.py +3 -0
- docvortex/analyzers/native/office/ppt/models.py +118 -0
- docvortex/analyzers/native/office/ppt/parser.py +1895 -0
- docvortex/analyzers/native/office/ppt/ppt_converter.py +292 -0
- docvortex/analyzers/native/office/ppt/records.py +131 -0
- docvortex/analyzers/native/office/ppt/style_text.py +247 -0
- docvortex/analyzers/native/office/pptx/__init__.py +3 -0
- docvortex/analyzers/native/office/pptx/context.py +113 -0
- docvortex/analyzers/native/office/pptx/lists.py +558 -0
- docvortex/analyzers/native/office/pptx/main.py +20 -0
- docvortex/analyzers/native/office/pptx/package_normalizer.py +321 -0
- docvortex/analyzers/native/office/pptx/pptx_converter.py +323 -0
- docvortex/analyzers/native/office/pptx/resources.py +329 -0
- docvortex/analyzers/native/office/pptx/shapes.py +393 -0
- docvortex/analyzers/native/office/pptx/text_styles.py +625 -0
- docvortex/analyzers/native/office/pptx/titles.py +178 -0
- docvortex/analyzers/native/office/rich_text.py +420 -0
- docvortex/analyzers/native/office/rtf/__init__.py +3 -0
- docvortex/analyzers/native/office/rtf/converter.py +708 -0
- docvortex/analyzers/native/office/rtf/lexer.py +213 -0
- docvortex/analyzers/native/office/rtf/math.py +339 -0
- docvortex/analyzers/native/office/rtf/models.py +185 -0
- docvortex/analyzers/native/office/rtf/parser.py +1553 -0
- docvortex/analyzers/native/office/spreadsheet/__init__.py +3 -0
- docvortex/analyzers/native/office/spreadsheet/html.py +81 -0
- docvortex/analyzers/native/office/spreadsheet/models.py +79 -0
- docvortex/analyzers/native/office/spreadsheet/projector.py +928 -0
- docvortex/analyzers/native/office/streams.py +18 -0
- docvortex/analyzers/native/office/xls/__init__.py +3 -0
- docvortex/analyzers/native/office/xls/chart.py +132 -0
- docvortex/analyzers/native/office/xls/embedded_chart.py +299 -0
- docvortex/analyzers/native/office/xls/models.py +109 -0
- docvortex/analyzers/native/office/xls/number_format.py +521 -0
- docvortex/analyzers/native/office/xls/parser.py +1145 -0
- docvortex/analyzers/native/office/xls/records.py +201 -0
- docvortex/analyzers/native/office/xls/strings.py +205 -0
- docvortex/analyzers/native/office/xls/xls_converter.py +354 -0
- docvortex/analyzers/native/office/xlsx/__init__.py +3 -0
- docvortex/analyzers/native/office/xlsx/main.py +20 -0
- docvortex/analyzers/native/office/xlsx/ooxml_ole.py +522 -0
- docvortex/analyzers/native/office/xlsx/package_normalizer.py +310 -0
- docvortex/analyzers/native/office/xlsx/xlsx_converter.py +716 -0
- docvortex/analyzers/native/pdf/__init__.py +3 -0
- docvortex/analyzers/native/pdf/auxiliary_text.py +1671 -0
- docvortex/analyzers/native/pdf/char_geometry.py +1662 -0
- docvortex/analyzers/native/pdf/code_blocks.py +535 -0
- docvortex/analyzers/native/pdf/formulas.py +1985 -0
- docvortex/analyzers/native/pdf/geometry.py +281 -0
- docvortex/analyzers/native/pdf/graphics.py +1501 -0
- docvortex/analyzers/native/pdf/index_blocks.py +268 -0
- docvortex/analyzers/native/pdf/inline/__init__.py +3 -0
- docvortex/analyzers/native/pdf/inline/common.py +112 -0
- docvortex/analyzers/native/pdf/inline/detection.py +590 -0
- docvortex/analyzers/native/pdf/inline/matching.py +1185 -0
- docvortex/analyzers/native/pdf/inline/materialize.py +457 -0
- docvortex/analyzers/native/pdf/inline/scripts.py +975 -0
- docvortex/analyzers/native/pdf/inline/types.py +385 -0
- docvortex/analyzers/native/pdf/line_layout.py +1106 -0
- docvortex/analyzers/native/pdf/line_merging.py +1223 -0
- docvortex/analyzers/native/pdf/models.py +246 -0
- docvortex/analyzers/native/pdf/native_text.py +1004 -0
- docvortex/analyzers/native/pdf/pipeline.py +1390 -0
- docvortex/analyzers/native/pdf/script_geometry.py +636 -0
- docvortex/analyzers/native/pdf/shared.py +30 -0
- docvortex/analyzers/native/pdf/spatial_text.py +383 -0
- docvortex/analyzers/native/pdf/table_annotations.py +446 -0
- docvortex/analyzers/native/pdf/table_constants.py +48 -0
- docvortex/analyzers/native/pdf/table_detection.py +227 -0
- docvortex/analyzers/native/pdf/table_filled_grid.py +218 -0
- docvortex/analyzers/native/pdf/table_geometry.py +40 -0
- docvortex/analyzers/native/pdf/table_materialization.py +444 -0
- docvortex/analyzers/native/pdf/table_recovery/__init__.py +15 -0
- docvortex/analyzers/native/pdf/table_recovery/candidate.py +356 -0
- docvortex/analyzers/native/pdf/table_recovery/contracts.py +154 -0
- docvortex/analyzers/native/pdf/table_recovery/engine.py +609 -0
- docvortex/analyzers/native/pdf/table_recovery/geometry.py +164 -0
- docvortex/analyzers/native/pdf/table_recovery/sparse_common.py +85 -0
- docvortex/analyzers/native/pdf/table_recovery/sparse_hybrid.py +804 -0
- docvortex/analyzers/native/pdf/table_recovery/sparse_multiline.py +1122 -0
- docvortex/analyzers/native/pdf/table_recovery/text.py +414 -0
- docvortex/analyzers/native/pdf/table_recovery/text_grid.py +618 -0
- docvortex/analyzers/native/pdf/table_recovery/vector.py +1931 -0
- docvortex/analyzers/native/pdf/table_rows.py +34 -0
- docvortex/analyzers/native/pdf/table_rules.py +1129 -0
- docvortex/analyzers/native/pdf/table_text_styles.py +283 -0
- docvortex/analyzers/native/pdf/tables.py +147 -0
- docvortex/analyzers/native/pdf/text_assembly/__init__.py +3 -0
- docvortex/analyzers/native/pdf/text_assembly/annotations.py +581 -0
- docvortex/analyzers/native/pdf/text_assembly/assembly.py +292 -0
- docvortex/analyzers/native/pdf/text_assembly/common.py +477 -0
- docvortex/analyzers/native/pdf/text_assembly/footnotes.py +394 -0
- docvortex/analyzers/native/pdf/text_assembly/merging.py +1274 -0
- docvortex/analyzers/native/pdf/text_assembly/rows.py +692 -0
- docvortex/analyzers/native/pdf/text_blocks.py +82 -0
- docvortex/analyzers/native/pdf/text_styles.py +55 -0
- docvortex/analyzers/native/pdf/title_analysis/__init__.py +3 -0
- docvortex/analyzers/native/pdf/title_analysis/body_profile.py +215 -0
- docvortex/analyzers/native/pdf/title_analysis/common.py +117 -0
- docvortex/analyzers/native/pdf/title_analysis/document_profile.py +164 -0
- docvortex/analyzers/native/pdf/title_analysis/lane_titles.py +758 -0
- docvortex/analyzers/native/pdf/title_analysis/page_titles.py +1024 -0
- docvortex/analyzers/native/pdf/title_analysis/prototype.py +194 -0
- docvortex/analyzers/native/pdf/title_analysis/structural.py +1081 -0
- docvortex/analyzers/native/pdf/titles.py +75 -0
- docvortex/analyzers/native/pdf/typography.py +19 -0
- docvortex/analyzers/native/pdf/visual_annotations.py +1262 -0
- docvortex/api.py +180 -0
- docvortex/assets/__init__.py +5 -0
- docvortex/assets/store.py +51 -0
- docvortex/cli.py +54 -0
- docvortex/codecs/__init__.py +3 -0
- docvortex/codecs/html/__init__.py +22 -0
- docvortex/codecs/html/contracts.py +236 -0
- docvortex/codecs/html/materializer.py +331 -0
- docvortex/codecs/html/parser.py +763 -0
- docvortex/codecs/html/resources.py +34 -0
- docvortex/codecs/json.py +17 -0
- docvortex/content/__init__.py +5 -0
- docvortex/content/inline.py +248 -0
- docvortex/content/markup/__init__.py +44 -0
- docvortex/content/markup/anchors.py +188 -0
- docvortex/content/markup/formula.py +280 -0
- docvortex/content/markup/projector.py +1237 -0
- docvortex/content/markup/styles.py +327 -0
- docvortex/content/mathml.py +167 -0
- docvortex/content/normalization.py +188 -0
- docvortex/content/spans.py +183 -0
- docvortex/content/table/__init__.py +18 -0
- docvortex/content/table/blocks.py +152 -0
- docvortex/content/table/content.py +425 -0
- docvortex/content/table/document.py +104 -0
- docvortex/content/table/html.py +399 -0
- docvortex/content/table/models.py +76 -0
- docvortex/content/table/rules.py +42 -0
- docvortex/content/table/structure.py +221 -0
- docvortex/content/tree.py +5 -0
- docvortex/document/__init__.py +3 -0
- docvortex/document/contracts.py +22 -0
- docvortex/document/detection.py +389 -0
- docvortex/document/filetypes.py +175 -0
- docvortex/document/page_range.py +167 -0
- docvortex/document/pdf/__init__.py +19 -0
- docvortex/document/pdf/classify.py +1138 -0
- docvortex/document/pdf/constants.py +132 -0
- docvortex/document/pdf/diagnostics.py +350 -0
- docvortex/document/pdf/document.py +582 -0
- docvortex/document/pdf/font_runtime.py +335 -0
- docvortex/document/pdf/geometry.py +31 -0
- docvortex/document/pdf/images.py +654 -0
- docvortex/document/pdf/native_annotations.py +367 -0
- docvortex/document/pdf/native_contracts.py +169 -0
- docvortex/document/pdf/native_coordinates.py +216 -0
- docvortex/document/pdf/native_lifecycle.py +16 -0
- docvortex/document/pdf/native_objects.py +902 -0
- docvortex/document/pdf/native_text_geometry.py +315 -0
- docvortex/document/pdf/pdfium.py +325 -0
- docvortex/document/pdf/raster.py +46 -0
- docvortex/document/pdf/text/__init__.py +62 -0
- docvortex/document/pdf/text/contracts.py +211 -0
- docvortex/document/pdf/text/extract.py +165 -0
- docvortex/document/pdf/text/geometry.py +16 -0
- docvortex/document/pdf/text/groups.py +162 -0
- docvortex/document/pdf/visual_geometry.py +201 -0
- docvortex/document/pdf/visuals.py +343 -0
- docvortex/document/source.py +92 -0
- docvortex/errors.py +21 -0
- docvortex/export/__init__.py +3 -0
- docvortex/export/bundle.py +98 -0
- docvortex/export/files.py +66 -0
- docvortex/export/middle.py +208 -0
- docvortex/foundation/__init__.py +3 -0
- docvortex/foundation/geometry.py +125 -0
- docvortex/foundation/hyperlink.py +65 -0
- docvortex/foundation/image.py +48 -0
- docvortex/foundation/image_encoding.py +30 -0
- docvortex/foundation/image_payload.py +280 -0
- docvortex/foundation/language.py +92 -0
- docvortex/foundation/platform.py +38 -0
- docvortex/foundation/text.py +153 -0
- docvortex/foundation/type_identity.py +20 -0
- docvortex/foundation/xml_names.py +20 -0
- docvortex/options.py +30 -0
- docvortex/postprocess/__init__.py +3 -0
- docvortex/postprocess/content.py +53 -0
- docvortex/postprocess/document.py +19 -0
- docvortex/postprocess/lists.py +236 -0
- docvortex/postprocess/page_blocks.py +214 -0
- docvortex/postprocess/pages.py +95 -0
- docvortex/postprocess/paragraphs.py +580 -0
- docvortex/postprocess/visual.py +715 -0
- docvortex/render/__init__.py +48 -0
- docvortex/render/_internal/__init__.py +3 -0
- docvortex/render/_internal/common/__init__.py +3 -0
- docvortex/render/_internal/common/context.py +43 -0
- docvortex/render/_internal/common/html_table.py +178 -0
- docvortex/render/_internal/common/index.py +33 -0
- docvortex/render/_internal/common/list_items.py +158 -0
- docvortex/render/_internal/common/planner.py +140 -0
- docvortex/render/_internal/docx/__init__.py +3 -0
- docvortex/render/_internal/docx/assets.py +202 -0
- docvortex/render/_internal/docx/inline.py +434 -0
- docvortex/render/_internal/docx/math.py +220 -0
- docvortex/render/_internal/docx/renderer.py +905 -0
- docvortex/render/_internal/docx/styles.py +195 -0
- docvortex/render/_internal/docx/table.py +442 -0
- docvortex/render/_internal/epub/__init__.py +5 -0
- docvortex/render/_internal/epub/assets.py +173 -0
- docvortex/render/_internal/epub/package.py +249 -0
- docvortex/render/_internal/epub/renderer.py +1156 -0
- docvortex/render/_internal/html/__init__.py +3 -0
- docvortex/render/_internal/html/inline.py +346 -0
- docvortex/render/_internal/html/renderer.py +1041 -0
- docvortex/render/_internal/html/sanitizer.py +478 -0
- docvortex/render/_internal/html/table.py +121 -0
- docvortex/render/_internal/latex/__init__.py +1 -0
- docvortex/render/_internal/latex/assets.py +85 -0
- docvortex/render/_internal/latex/inline.py +145 -0
- docvortex/render/_internal/latex/renderer.py +506 -0
- docvortex/render/_internal/latex/table.py +347 -0
- docvortex/render/_internal/markdown/__init__.py +3 -0
- docvortex/render/_internal/markdown/assets.py +78 -0
- docvortex/render/_internal/markdown/blocks.py +635 -0
- docvortex/render/_internal/markdown/escaping.py +51 -0
- docvortex/render/_internal/markdown/inline.py +260 -0
- docvortex/render/_internal/markdown/renderer.py +93 -0
- docvortex/render/_internal/markdown/table.py +281 -0
- docvortex/render/_internal/pdf/__init__.py +3 -0
- docvortex/render/_internal/pdf/assets.py +197 -0
- docvortex/render/_internal/pdf/formula.py +417 -0
- docvortex/render/_internal/pdf/inline.py +343 -0
- docvortex/render/_internal/pdf/renderer.py +734 -0
- docvortex/render/_internal/pdf/styles.py +206 -0
- docvortex/render/_internal/pdf/table.py +272 -0
- docvortex/render/_internal/structured_content/__init__.py +3 -0
- docvortex/render/_internal/structured_content/renderer.py +193 -0
- docvortex/render/api.py +199 -0
- docvortex/render/contracts.py +205 -0
- docvortex/render/docx.py +38 -0
- docvortex/render/epub.py +35 -0
- docvortex/render/fragments.py +70 -0
- docvortex/render/html.py +29 -0
- docvortex/render/latex.py +24 -0
- docvortex/render/markdown.py +50 -0
- docvortex/render/pdf.py +25 -0
- docvortex/render/structured_content.py +23 -0
- docvortex/resources/epub/docvortex.css +91 -0
- docvortex/resources/fasttext-langdetect/lid.176.ftz +0 -0
- docvortex/resources/fonts/DroidSansFallbackFull.ttf +0 -0
- docvortex/resources/fonts/NOTICE +190 -0
- docvortex/resources/fonts/manifest.json +9 -0
- docvortex/resources/html/docvortex.css +601 -0
- docvortex/resources/html/docvortex.min.css +1 -0
- docvortex/result.py +91 -0
- docvortex/schema.py +1141 -0
- docvortex/version.py +3 -0
- docvortex-0.2.1.dist-info/METADATA +193 -0
- docvortex-0.2.1.dist-info/RECORD +364 -0
- docvortex-0.2.1.dist-info/WHEEL +5 -0
- docvortex-0.2.1.dist-info/entry_points.txt +2 -0
- docvortex-0.2.1.dist-info/licenses/LICENSE.md +21 -0
- 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
|
+
)
|