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,625 @@
|
|
|
1
|
+
"""PPTX 文字样式和继承,复用当前转换器的单文档状态。"""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
from loguru import logger
|
|
5
|
+
from lxml import etree
|
|
6
|
+
from pptx.oxml.text import CT_TextLineBreak
|
|
7
|
+
from ..equation.omml import oMath2Latex
|
|
8
|
+
from .....content.spans import append_equation_span, extend_inline_spans, strip_span_dicts
|
|
9
|
+
from ..rich_text import OfficeRichTextSegment, build_rich_text_from_segments
|
|
10
|
+
|
|
11
|
+
from .context import DRAWINGML_NS, A14_DRAWING_NS, OMML_NS
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class _PptxTextStyles:
|
|
15
|
+
"""集中维护文字样式和继承,不改变文档生命周期和公开入口。"""
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def _normalize_xml_toggle_attr(value: Optional[str]) -> Optional[bool]:
|
|
19
|
+
"""按原有文字样式和继承规则执行 _normalize_xml_toggle_attr,保持输入顺序与降级行为。"""
|
|
20
|
+
if value is None:
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
normalized = str(value).strip().lower()
|
|
24
|
+
if normalized in {"1", "true", "t", "on"}:
|
|
25
|
+
return True
|
|
26
|
+
if normalized in {"0", "false", "f", "off", "none"}:
|
|
27
|
+
return False
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def _parse_toggle_attr_from_rpr(
|
|
32
|
+
cls,
|
|
33
|
+
rpr: Optional[etree._Element],
|
|
34
|
+
attr_name: str,
|
|
35
|
+
) -> Optional[bool]:
|
|
36
|
+
"""按原有文字样式和继承规则执行 _parse_toggle_attr_from_rpr,保持输入顺序与降级行为。"""
|
|
37
|
+
if rpr is None:
|
|
38
|
+
return None
|
|
39
|
+
return cls._normalize_xml_toggle_attr(rpr.get(attr_name))
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def _parse_underline_from_rpr(
|
|
43
|
+
cls,
|
|
44
|
+
rpr: Optional[etree._Element],
|
|
45
|
+
) -> Optional[bool]:
|
|
46
|
+
"""按原有文字样式和继承规则执行 _parse_underline_from_rpr,保持输入顺序与降级行为。"""
|
|
47
|
+
if rpr is None:
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
underline = rpr.get("u")
|
|
51
|
+
if underline is None:
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
normalized = str(underline).strip().lower()
|
|
55
|
+
if normalized in {"0", "false", "f", "off", "none"}:
|
|
56
|
+
return False
|
|
57
|
+
return True
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def _parse_strikethrough_from_rpr(
|
|
61
|
+
cls,
|
|
62
|
+
rpr: Optional[etree._Element],
|
|
63
|
+
) -> Optional[bool]:
|
|
64
|
+
"""按原有文字样式和继承规则执行 _parse_strikethrough_from_rpr,保持输入顺序与降级行为。"""
|
|
65
|
+
if rpr is None:
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
strike = rpr.get("strike")
|
|
69
|
+
if strike is None:
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
normalized = str(strike).strip().lower()
|
|
73
|
+
if normalized in {"0", "false", "f", "off", "none", "nostrike"}:
|
|
74
|
+
return False
|
|
75
|
+
return True
|
|
76
|
+
|
|
77
|
+
def _get_run_rpr(
|
|
78
|
+
self,
|
|
79
|
+
run,
|
|
80
|
+
) -> Optional[etree._Element]:
|
|
81
|
+
"""按原有文字样式和继承规则执行 _get_run_rpr,保持输入顺序与降级行为。"""
|
|
82
|
+
if run is None:
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
run_xml = getattr(run, "_r", None)
|
|
86
|
+
if run_xml is None:
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
return run_xml.find("a:rPr", namespaces=self.namespaces)
|
|
91
|
+
except Exception:
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
@staticmethod
|
|
95
|
+
def _get_run_raw_text(run) -> str:
|
|
96
|
+
"""从run底层XML读取文本,避免数学run触发python-pptx的to_latex诊断输出。"""
|
|
97
|
+
run_xml = getattr(run, "_r", None)
|
|
98
|
+
if run_xml is None:
|
|
99
|
+
return ""
|
|
100
|
+
|
|
101
|
+
text_parts = []
|
|
102
|
+
text_tags = {
|
|
103
|
+
f"{{{DRAWINGML_NS}}}t",
|
|
104
|
+
f"{{{OMML_NS}}}t",
|
|
105
|
+
}
|
|
106
|
+
for text_node in run_xml.iter():
|
|
107
|
+
if getattr(text_node, "tag", None) not in text_tags:
|
|
108
|
+
continue
|
|
109
|
+
if text_node.text:
|
|
110
|
+
text_parts.append(text_node.text)
|
|
111
|
+
|
|
112
|
+
return "".join(text_parts)
|
|
113
|
+
|
|
114
|
+
def _resolve_effective_run_bool(
|
|
115
|
+
self,
|
|
116
|
+
run,
|
|
117
|
+
paragraph_font_sources: list[etree._Element],
|
|
118
|
+
parser,
|
|
119
|
+
) -> bool:
|
|
120
|
+
"""按原有文字样式和继承规则执行 _resolve_effective_run_bool,保持输入顺序与降级行为。"""
|
|
121
|
+
for source in [self._get_run_rpr(run), *paragraph_font_sources]:
|
|
122
|
+
resolved = parser(source)
|
|
123
|
+
if resolved is not None:
|
|
124
|
+
return resolved
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
def _resolve_effective_run_italic(
|
|
128
|
+
self,
|
|
129
|
+
run,
|
|
130
|
+
paragraph_font_sources: list[etree._Element],
|
|
131
|
+
) -> bool:
|
|
132
|
+
"""按原有文字样式和继承规则执行 _resolve_effective_run_italic,保持输入顺序与降级行为。"""
|
|
133
|
+
return self._resolve_effective_run_bool(
|
|
134
|
+
run,
|
|
135
|
+
paragraph_font_sources,
|
|
136
|
+
self._parse_italic_from_rpr,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def _resolve_effective_run_underline(
|
|
140
|
+
self,
|
|
141
|
+
run,
|
|
142
|
+
paragraph_font_sources: list[etree._Element],
|
|
143
|
+
) -> bool:
|
|
144
|
+
"""按原有文字样式和继承规则执行 _resolve_effective_run_underline,保持输入顺序与降级行为。"""
|
|
145
|
+
return self._resolve_effective_run_bool(
|
|
146
|
+
run,
|
|
147
|
+
paragraph_font_sources,
|
|
148
|
+
self._parse_underline_from_rpr,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
def _resolve_effective_run_strikethrough(
|
|
152
|
+
self,
|
|
153
|
+
run,
|
|
154
|
+
paragraph_font_sources: list[etree._Element],
|
|
155
|
+
) -> bool:
|
|
156
|
+
"""按原有文字样式和继承规则执行 _resolve_effective_run_strikethrough,保持输入顺序与降级行为。"""
|
|
157
|
+
return self._resolve_effective_run_bool(
|
|
158
|
+
run,
|
|
159
|
+
paragraph_font_sources,
|
|
160
|
+
self._parse_strikethrough_from_rpr,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
def _get_style_str_from_run(
|
|
164
|
+
self,
|
|
165
|
+
run,
|
|
166
|
+
paragraph_font_sources: list[etree._Element],
|
|
167
|
+
) -> Optional[str]:
|
|
168
|
+
"""从PPTX run对象提取可序列化的生效字体样式字符串。"""
|
|
169
|
+
if run is None:
|
|
170
|
+
return None
|
|
171
|
+
|
|
172
|
+
styles = []
|
|
173
|
+
if self._resolve_effective_run_bold(run, paragraph_font_sources):
|
|
174
|
+
styles.append("bold")
|
|
175
|
+
if self._resolve_effective_run_italic(run, paragraph_font_sources):
|
|
176
|
+
styles.append("italic")
|
|
177
|
+
if self._resolve_effective_run_underline(run, paragraph_font_sources):
|
|
178
|
+
styles.append("underline")
|
|
179
|
+
if self._resolve_effective_run_strikethrough(run, paragraph_font_sources):
|
|
180
|
+
styles.append("strikethrough")
|
|
181
|
+
|
|
182
|
+
return ",".join(styles) if styles else None
|
|
183
|
+
|
|
184
|
+
def _resolve_hyperlink_from_run(self, run, shape) -> Optional[str]:
|
|
185
|
+
"""解析 run 对应的超链接,优先公开 API,回退到 XML + rels。"""
|
|
186
|
+
try:
|
|
187
|
+
if hasattr(run, "hyperlink") and run.hyperlink is not None:
|
|
188
|
+
address = run.hyperlink.address
|
|
189
|
+
if address and str(address).strip():
|
|
190
|
+
return str(address).strip()
|
|
191
|
+
except Exception:
|
|
192
|
+
pass
|
|
193
|
+
|
|
194
|
+
try:
|
|
195
|
+
rPr = run._r.find("a:rPr", namespaces=self.namespaces)
|
|
196
|
+
if rPr is None:
|
|
197
|
+
return None
|
|
198
|
+
|
|
199
|
+
hlink_click = rPr.find("a:hlinkClick", namespaces=self.namespaces)
|
|
200
|
+
if hlink_click is None:
|
|
201
|
+
return None
|
|
202
|
+
|
|
203
|
+
rid = hlink_click.get("{http://schemas.openxmlformats.org/officeDocument/2006/relationships}id")
|
|
204
|
+
if not rid:
|
|
205
|
+
return None
|
|
206
|
+
|
|
207
|
+
rels = shape.part.rels
|
|
208
|
+
if rid not in rels:
|
|
209
|
+
return None
|
|
210
|
+
|
|
211
|
+
rel = rels[rid]
|
|
212
|
+
target_ref = getattr(rel, "target_ref", None)
|
|
213
|
+
if target_ref and str(target_ref).strip():
|
|
214
|
+
return str(target_ref).strip()
|
|
215
|
+
|
|
216
|
+
target_part = getattr(rel, "target_part", None)
|
|
217
|
+
if target_part is not None:
|
|
218
|
+
partname = getattr(target_part, "partname", None)
|
|
219
|
+
if partname and str(partname).strip():
|
|
220
|
+
return str(partname).strip()
|
|
221
|
+
except Exception:
|
|
222
|
+
return None
|
|
223
|
+
|
|
224
|
+
return None
|
|
225
|
+
|
|
226
|
+
def _build_paragraph_plain_text(self, paragraph) -> str:
|
|
227
|
+
"""构建段落纯文本(保留软换行为空格)。"""
|
|
228
|
+
p = paragraph._element
|
|
229
|
+
text_parts = []
|
|
230
|
+
for node in p.content_children:
|
|
231
|
+
if isinstance(node, CT_TextLineBreak):
|
|
232
|
+
text_parts.append(" ")
|
|
233
|
+
continue
|
|
234
|
+
|
|
235
|
+
node_text = getattr(node, "text", None)
|
|
236
|
+
if node_text is not None:
|
|
237
|
+
text_parts.append(node_text)
|
|
238
|
+
|
|
239
|
+
return "".join(text_parts)
|
|
240
|
+
|
|
241
|
+
@staticmethod
|
|
242
|
+
def _is_math_content_node(node) -> bool:
|
|
243
|
+
"""按原有文字样式和继承规则执行 _is_math_content_node,保持输入顺序与降级行为。"""
|
|
244
|
+
tag = getattr(node, "tag", None)
|
|
245
|
+
return tag in {
|
|
246
|
+
f"{{{A14_DRAWING_NS}}}m",
|
|
247
|
+
f"{{{OMML_NS}}}oMath",
|
|
248
|
+
f"{{{OMML_NS}}}oMathPara",
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
@staticmethod
|
|
252
|
+
def _strip_math_delimiters(math_text: str) -> str:
|
|
253
|
+
"""按原有文字样式和继承规则执行 _strip_math_delimiters,保持输入顺序与降级行为。"""
|
|
254
|
+
stripped = math_text.strip()
|
|
255
|
+
if stripped.startswith("$$") and stripped.endswith("$$") and len(stripped) >= 4:
|
|
256
|
+
return stripped[2:-2].strip()
|
|
257
|
+
if stripped.startswith("$") and stripped.endswith("$") and len(stripped) >= 2:
|
|
258
|
+
return stripped[1:-1].strip()
|
|
259
|
+
return stripped
|
|
260
|
+
|
|
261
|
+
def _convert_math_node_to_latex(self, node) -> Optional[str]:
|
|
262
|
+
"""按原有文字样式和继承规则执行 _convert_math_node_to_latex,保持输入顺序与降级行为。"""
|
|
263
|
+
omath = None
|
|
264
|
+
if getattr(node, "tag", None) == f"{{{OMML_NS}}}oMath":
|
|
265
|
+
omath = node
|
|
266
|
+
else:
|
|
267
|
+
omath = node.find(".//m:oMath", namespaces=self.namespaces)
|
|
268
|
+
|
|
269
|
+
if omath is not None:
|
|
270
|
+
try:
|
|
271
|
+
latex = str(oMath2Latex(omath)).strip()
|
|
272
|
+
except Exception as exc:
|
|
273
|
+
logger.debug(f"Failed to convert PPTX OMML equation to LaTeX: {exc}")
|
|
274
|
+
else:
|
|
275
|
+
if latex:
|
|
276
|
+
return latex
|
|
277
|
+
|
|
278
|
+
fallback_text = getattr(node, "text", None)
|
|
279
|
+
if isinstance(fallback_text, str):
|
|
280
|
+
latex = self._strip_math_delimiters(fallback_text)
|
|
281
|
+
if latex:
|
|
282
|
+
return latex
|
|
283
|
+
|
|
284
|
+
return None
|
|
285
|
+
|
|
286
|
+
def _build_paragraph_rich_text(self, paragraph, shape) -> list[dict[str, Any]]:
|
|
287
|
+
"""按 run 维度构建段落 Span,支持样式、公式与超链接。"""
|
|
288
|
+
paragraph_font_sources = self._get_paragraph_font_sources(shape, paragraph)
|
|
289
|
+
run_map = {}
|
|
290
|
+
for run in paragraph.runs:
|
|
291
|
+
try:
|
|
292
|
+
run_map[id(run._r)] = run
|
|
293
|
+
except Exception:
|
|
294
|
+
continue
|
|
295
|
+
|
|
296
|
+
output: list[dict[str, Any]] = []
|
|
297
|
+
segments: list[OfficeRichTextSegment] = []
|
|
298
|
+
|
|
299
|
+
def flush_segments() -> None:
|
|
300
|
+
"""在公式边界输出累计普通富文本 Span。"""
|
|
301
|
+
if not segments:
|
|
302
|
+
return
|
|
303
|
+
extend_inline_spans(output, build_rich_text_from_segments(list(segments)))
|
|
304
|
+
segments.clear()
|
|
305
|
+
|
|
306
|
+
for node in paragraph._element.content_children:
|
|
307
|
+
if isinstance(node, CT_TextLineBreak):
|
|
308
|
+
segments.append(OfficeRichTextSegment(" "))
|
|
309
|
+
continue
|
|
310
|
+
|
|
311
|
+
if self._is_math_content_node(node):
|
|
312
|
+
latex = self._convert_math_node_to_latex(node)
|
|
313
|
+
if latex:
|
|
314
|
+
flush_segments()
|
|
315
|
+
append_equation_span(output, latex)
|
|
316
|
+
continue
|
|
317
|
+
|
|
318
|
+
node_text = getattr(node, "text", None)
|
|
319
|
+
if node_text is None:
|
|
320
|
+
continue
|
|
321
|
+
if node_text == "":
|
|
322
|
+
continue
|
|
323
|
+
|
|
324
|
+
run = run_map.get(id(node))
|
|
325
|
+
if run is None:
|
|
326
|
+
segments.append(OfficeRichTextSegment(node_text))
|
|
327
|
+
continue
|
|
328
|
+
|
|
329
|
+
segments.append(
|
|
330
|
+
OfficeRichTextSegment(
|
|
331
|
+
node_text,
|
|
332
|
+
self._get_style_str_from_run(
|
|
333
|
+
run,
|
|
334
|
+
paragraph_font_sources,
|
|
335
|
+
),
|
|
336
|
+
self._resolve_hyperlink_from_run(run, shape),
|
|
337
|
+
)
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
flush_segments()
|
|
341
|
+
return strip_span_dicts(output)
|
|
342
|
+
|
|
343
|
+
@staticmethod
|
|
344
|
+
def _trim_rich_text_segments(segments: list[dict]) -> list[dict]:
|
|
345
|
+
"""按原有文字样式和继承规则执行 _trim_rich_text_segments,保持输入顺序与降级行为。"""
|
|
346
|
+
trimmed_segments = [dict(segment) for segment in segments if segment.get("text") is not None]
|
|
347
|
+
if not trimmed_segments:
|
|
348
|
+
return []
|
|
349
|
+
|
|
350
|
+
start_idx = 0
|
|
351
|
+
while start_idx < len(trimmed_segments):
|
|
352
|
+
normalized_text = trimmed_segments[start_idx]["text"].lstrip()
|
|
353
|
+
if normalized_text:
|
|
354
|
+
trimmed_segments[start_idx]["text"] = normalized_text
|
|
355
|
+
break
|
|
356
|
+
start_idx += 1
|
|
357
|
+
|
|
358
|
+
if start_idx == len(trimmed_segments):
|
|
359
|
+
return []
|
|
360
|
+
|
|
361
|
+
trimmed_segments = trimmed_segments[start_idx:]
|
|
362
|
+
end_idx = len(trimmed_segments) - 1
|
|
363
|
+
while end_idx >= 0:
|
|
364
|
+
normalized_text = trimmed_segments[end_idx]["text"].rstrip()
|
|
365
|
+
if normalized_text:
|
|
366
|
+
trimmed_segments[end_idx]["text"] = normalized_text
|
|
367
|
+
break
|
|
368
|
+
end_idx -= 1
|
|
369
|
+
|
|
370
|
+
if end_idx < 0:
|
|
371
|
+
return []
|
|
372
|
+
|
|
373
|
+
return trimmed_segments[: end_idx + 1]
|
|
374
|
+
|
|
375
|
+
@staticmethod
|
|
376
|
+
def _normalize_text_block_content(content: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
377
|
+
"""裁剪提取文本首尾空白,同时保留 Span 样式和链接。"""
|
|
378
|
+
return strip_span_dicts(content)
|
|
379
|
+
|
|
380
|
+
@staticmethod
|
|
381
|
+
def _parse_font_size_pt_from_rpr(
|
|
382
|
+
rpr: Optional[etree._Element],
|
|
383
|
+
) -> Optional[float]:
|
|
384
|
+
"""按原有文字样式和继承规则执行 _parse_font_size_pt_from_rpr,保持输入顺序与降级行为。"""
|
|
385
|
+
if rpr is None:
|
|
386
|
+
return None
|
|
387
|
+
|
|
388
|
+
size = rpr.get("sz")
|
|
389
|
+
if size is None:
|
|
390
|
+
return None
|
|
391
|
+
|
|
392
|
+
try:
|
|
393
|
+
return round(int(size) / 100, 1)
|
|
394
|
+
except (TypeError, ValueError):
|
|
395
|
+
return None
|
|
396
|
+
|
|
397
|
+
@staticmethod
|
|
398
|
+
def _parse_bold_from_rpr(
|
|
399
|
+
rpr: Optional[etree._Element],
|
|
400
|
+
) -> Optional[bool]:
|
|
401
|
+
"""按原有文字样式和继承规则执行 _parse_bold_from_rpr,保持输入顺序与降级行为。"""
|
|
402
|
+
return _PptxTextStyles._parse_toggle_attr_from_rpr(rpr, "b")
|
|
403
|
+
|
|
404
|
+
@staticmethod
|
|
405
|
+
def _parse_italic_from_rpr(
|
|
406
|
+
rpr: Optional[etree._Element],
|
|
407
|
+
) -> Optional[bool]:
|
|
408
|
+
"""按原有文字样式和继承规则执行 _parse_italic_from_rpr,保持输入顺序与降级行为。"""
|
|
409
|
+
return _PptxTextStyles._parse_toggle_attr_from_rpr(rpr, "i")
|
|
410
|
+
|
|
411
|
+
def _find_def_rpr(
|
|
412
|
+
self,
|
|
413
|
+
paragraph_properties: Optional[etree._Element],
|
|
414
|
+
) -> Optional[etree._Element]:
|
|
415
|
+
"""按原有文字样式和继承规则执行 _find_def_rpr,保持输入顺序与降级行为。"""
|
|
416
|
+
if paragraph_properties is None:
|
|
417
|
+
return None
|
|
418
|
+
return paragraph_properties.find("a:defRPr", namespaces=self.namespaces)
|
|
419
|
+
|
|
420
|
+
def _find_end_para_rpr(
|
|
421
|
+
self,
|
|
422
|
+
paragraph: Optional[etree._Element],
|
|
423
|
+
) -> Optional[etree._Element]:
|
|
424
|
+
"""按原有文字样式和继承规则执行 _find_end_para_rpr,保持输入顺序与降级行为。"""
|
|
425
|
+
if paragraph is None:
|
|
426
|
+
return None
|
|
427
|
+
return paragraph.find("a:endParaRPr", namespaces=self.namespaces)
|
|
428
|
+
|
|
429
|
+
def _get_font_sources_from_paragraph(
|
|
430
|
+
self,
|
|
431
|
+
paragraph: Optional[etree._Element],
|
|
432
|
+
) -> list[etree._Element]:
|
|
433
|
+
"""按原有文字样式和继承规则执行 _get_font_sources_from_paragraph,保持输入顺序与降级行为。"""
|
|
434
|
+
if paragraph is None:
|
|
435
|
+
return []
|
|
436
|
+
|
|
437
|
+
sources = []
|
|
438
|
+
paragraph_properties = paragraph.find("a:pPr", namespaces=self.namespaces)
|
|
439
|
+
paragraph_def_rpr = self._find_def_rpr(paragraph_properties)
|
|
440
|
+
if paragraph_def_rpr is not None:
|
|
441
|
+
sources.append(paragraph_def_rpr)
|
|
442
|
+
|
|
443
|
+
end_para_rpr = self._find_end_para_rpr(paragraph)
|
|
444
|
+
if end_para_rpr is not None:
|
|
445
|
+
sources.append(end_para_rpr)
|
|
446
|
+
|
|
447
|
+
return sources
|
|
448
|
+
|
|
449
|
+
def _get_font_sources_from_text_body(
|
|
450
|
+
self,
|
|
451
|
+
tx_body: Optional[etree._Element],
|
|
452
|
+
level: int,
|
|
453
|
+
) -> list[etree._Element]:
|
|
454
|
+
"""按原有文字样式和继承规则执行 _get_font_sources_from_text_body,保持输入顺序与降级行为。"""
|
|
455
|
+
if tx_body is None:
|
|
456
|
+
return []
|
|
457
|
+
|
|
458
|
+
lst_style = tx_body.find("a:lstStyle", namespaces=self.namespaces)
|
|
459
|
+
if lst_style is None:
|
|
460
|
+
return []
|
|
461
|
+
|
|
462
|
+
sources = []
|
|
463
|
+
level_properties = self._find_level_properties_in_list_style(
|
|
464
|
+
lst_style,
|
|
465
|
+
level,
|
|
466
|
+
)
|
|
467
|
+
level_def_rpr = self._find_def_rpr(level_properties)
|
|
468
|
+
if level_def_rpr is not None:
|
|
469
|
+
sources.append(level_def_rpr)
|
|
470
|
+
|
|
471
|
+
default_properties = lst_style.find("a:defPPr", namespaces=self.namespaces)
|
|
472
|
+
default_def_rpr = self._find_def_rpr(default_properties)
|
|
473
|
+
if default_def_rpr is not None:
|
|
474
|
+
sources.append(default_def_rpr)
|
|
475
|
+
|
|
476
|
+
return sources
|
|
477
|
+
|
|
478
|
+
def _get_font_sources_from_text_style_bucket(
|
|
479
|
+
self,
|
|
480
|
+
style_bucket: Optional[etree._Element],
|
|
481
|
+
level: int,
|
|
482
|
+
) -> list[etree._Element]:
|
|
483
|
+
"""按原有文字样式和继承规则执行 _get_font_sources_from_text_style_bucket,保持输入顺序与降级行为。"""
|
|
484
|
+
if style_bucket is None:
|
|
485
|
+
return []
|
|
486
|
+
|
|
487
|
+
sources = []
|
|
488
|
+
level_properties = style_bucket.find(
|
|
489
|
+
f"a:lvl{level + 1}pPr",
|
|
490
|
+
namespaces=self.namespaces,
|
|
491
|
+
)
|
|
492
|
+
level_def_rpr = self._find_def_rpr(level_properties)
|
|
493
|
+
if level_def_rpr is not None:
|
|
494
|
+
sources.append(level_def_rpr)
|
|
495
|
+
|
|
496
|
+
default_properties = style_bucket.find(
|
|
497
|
+
"a:defPPr",
|
|
498
|
+
namespaces=self.namespaces,
|
|
499
|
+
)
|
|
500
|
+
default_def_rpr = self._find_def_rpr(default_properties)
|
|
501
|
+
if default_def_rpr is not None:
|
|
502
|
+
sources.append(default_def_rpr)
|
|
503
|
+
|
|
504
|
+
return sources
|
|
505
|
+
|
|
506
|
+
def _resolve_layout_placeholder(self, shape):
|
|
507
|
+
"""按原有文字样式和继承规则执行 _resolve_layout_placeholder,保持输入顺序与降级行为。"""
|
|
508
|
+
if not getattr(shape, "is_placeholder", False):
|
|
509
|
+
return None
|
|
510
|
+
|
|
511
|
+
try:
|
|
512
|
+
idx = shape.placeholder_format.idx
|
|
513
|
+
layout = shape.part.slide.slide_layout
|
|
514
|
+
layout_ph = layout.placeholders.get(idx)
|
|
515
|
+
except Exception:
|
|
516
|
+
layout_ph = None
|
|
517
|
+
|
|
518
|
+
if layout_ph is not None:
|
|
519
|
+
return layout_ph
|
|
520
|
+
|
|
521
|
+
try:
|
|
522
|
+
placeholder_type = shape.placeholder_format.type
|
|
523
|
+
layout = shape.part.slide.slide_layout
|
|
524
|
+
for candidate in layout.placeholders:
|
|
525
|
+
if candidate.placeholder_format.type == placeholder_type:
|
|
526
|
+
return candidate
|
|
527
|
+
except Exception:
|
|
528
|
+
return None
|
|
529
|
+
|
|
530
|
+
return None
|
|
531
|
+
|
|
532
|
+
def _get_paragraph_font_sources(self, shape, paragraph) -> list[etree._Element]:
|
|
533
|
+
"""按原有文字样式和继承规则执行 _get_paragraph_font_sources,保持输入顺序与降级行为。"""
|
|
534
|
+
level = self._get_paragraph_level(paragraph._element)
|
|
535
|
+
sources = self._get_font_sources_from_paragraph(paragraph._element)
|
|
536
|
+
|
|
537
|
+
tx_body = shape._element.find(".//p:txBody", namespaces=self.namespaces)
|
|
538
|
+
sources.extend(self._get_font_sources_from_text_body(tx_body, level))
|
|
539
|
+
|
|
540
|
+
if getattr(shape, "is_placeholder", False):
|
|
541
|
+
layout_placeholder = self._resolve_layout_placeholder(shape)
|
|
542
|
+
if layout_placeholder is not None:
|
|
543
|
+
layout_tx_body = layout_placeholder._element.find(
|
|
544
|
+
".//p:txBody",
|
|
545
|
+
namespaces=self.namespaces,
|
|
546
|
+
)
|
|
547
|
+
sources.extend(self._get_font_sources_from_text_body(layout_tx_body, level))
|
|
548
|
+
|
|
549
|
+
try:
|
|
550
|
+
placeholder_type = shape.placeholder_format.type
|
|
551
|
+
slide_master = shape.part.slide.slide_layout.slide_master
|
|
552
|
+
except Exception:
|
|
553
|
+
return sources
|
|
554
|
+
|
|
555
|
+
style_bucket = self._get_master_text_style_node(
|
|
556
|
+
slide_master,
|
|
557
|
+
placeholder_type,
|
|
558
|
+
)
|
|
559
|
+
sources.extend(
|
|
560
|
+
self._get_font_sources_from_text_style_bucket(
|
|
561
|
+
style_bucket,
|
|
562
|
+
level,
|
|
563
|
+
)
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
return sources
|
|
567
|
+
|
|
568
|
+
def _resolve_effective_run_font_size_pt(
|
|
569
|
+
self,
|
|
570
|
+
run,
|
|
571
|
+
paragraph_font_sources: list[etree._Element],
|
|
572
|
+
) -> Optional[float]:
|
|
573
|
+
"""按原有文字样式和继承规则执行 _resolve_effective_run_font_size_pt,保持输入顺序与降级行为。"""
|
|
574
|
+
for source in [self._get_run_rpr(run), *paragraph_font_sources]:
|
|
575
|
+
font_size_pt = self._parse_font_size_pt_from_rpr(source)
|
|
576
|
+
if font_size_pt is not None:
|
|
577
|
+
return font_size_pt
|
|
578
|
+
return None
|
|
579
|
+
|
|
580
|
+
def _resolve_effective_run_bold(
|
|
581
|
+
self,
|
|
582
|
+
run,
|
|
583
|
+
paragraph_font_sources: list[etree._Element],
|
|
584
|
+
) -> bool:
|
|
585
|
+
"""按原有文字样式和继承规则执行 _resolve_effective_run_bold,保持输入顺序与降级行为。"""
|
|
586
|
+
return self._resolve_effective_run_bool(
|
|
587
|
+
run,
|
|
588
|
+
paragraph_font_sources,
|
|
589
|
+
self._parse_bold_from_rpr,
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
def _build_paragraph_style_profile(self, shape, paragraph) -> dict[str, Optional[float] | bool]:
|
|
593
|
+
"""按原有文字样式和继承规则执行 _build_paragraph_style_profile,保持输入顺序与降级行为。"""
|
|
594
|
+
paragraph_font_sources = self._get_paragraph_font_sources(shape, paragraph)
|
|
595
|
+
effective_font_size_pt = None
|
|
596
|
+
all_bold = True
|
|
597
|
+
has_non_whitespace_run = False
|
|
598
|
+
|
|
599
|
+
for run in paragraph.runs:
|
|
600
|
+
run_text = self._get_run_raw_text(run)
|
|
601
|
+
if not run_text.strip():
|
|
602
|
+
continue
|
|
603
|
+
|
|
604
|
+
has_non_whitespace_run = True
|
|
605
|
+
|
|
606
|
+
run_font_size_pt = self._resolve_effective_run_font_size_pt(
|
|
607
|
+
run,
|
|
608
|
+
paragraph_font_sources,
|
|
609
|
+
)
|
|
610
|
+
if run_font_size_pt is not None:
|
|
611
|
+
if effective_font_size_pt is None:
|
|
612
|
+
effective_font_size_pt = run_font_size_pt
|
|
613
|
+
else:
|
|
614
|
+
effective_font_size_pt = max(
|
|
615
|
+
effective_font_size_pt,
|
|
616
|
+
run_font_size_pt,
|
|
617
|
+
)
|
|
618
|
+
|
|
619
|
+
if self._resolve_effective_run_bold(run, paragraph_font_sources) is not True:
|
|
620
|
+
all_bold = False
|
|
621
|
+
|
|
622
|
+
return {
|
|
623
|
+
"font_size_pt": effective_font_size_pt,
|
|
624
|
+
"all_bold": has_non_whitespace_run and all_bold,
|
|
625
|
+
}
|