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,178 @@
1
+ """PPTX 幻灯片标题判定,复用当前转换器的单文档状态。"""
2
+
3
+ from collections import Counter
4
+ from typing import Optional
5
+ from .....schema import BlockType
6
+
7
+ from .context import (
8
+ _EFFECTIVE_FONT_SIZE_KEY,
9
+ _EFFECTIVE_ALL_BOLD_KEY,
10
+ _PPTX_TITLE_CANDIDATE_KEY,
11
+ _PPTX_TITLE_ROLE_KEY,
12
+ _PPTX_TITLE_ROLE_CENTER,
13
+ _PPTX_TITLE_ROLE_SUBTITLE,
14
+ )
15
+
16
+
17
+ class _PptxTitles:
18
+ """集中维护幻灯片标题判定,不改变文档生命周期和公开入口。"""
19
+
20
+ @staticmethod
21
+ def _most_common_size(font_sizes: list[float]) -> Optional[float]:
22
+ """按原有幻灯片标题判定规则执行 _most_common_size,保持输入顺序与降级行为。"""
23
+ if not font_sizes:
24
+ return None
25
+
26
+ counts = Counter(font_sizes)
27
+ return min(
28
+ counts.items(),
29
+ key=lambda item: (-item[1], item[0]),
30
+ )[0]
31
+
32
+ def _promote_slide_text_blocks_to_titles(self, slide_blocks: list[dict]) -> None:
33
+ """按原有幻灯片标题判定规则执行 _promote_slide_text_blocks_to_titles,保持输入顺序与降级行为。"""
34
+ body_font_size_pt = self._most_common_size(
35
+ [
36
+ block[_EFFECTIVE_FONT_SIZE_KEY]
37
+ for block in slide_blocks
38
+ if (
39
+ block.get("type") == BlockType.TEXT
40
+ and block.get(_PPTX_TITLE_CANDIDATE_KEY) is not True
41
+ and block.get(_EFFECTIVE_FONT_SIZE_KEY) is not None
42
+ and not block.get(_EFFECTIVE_ALL_BOLD_KEY, False)
43
+ )
44
+ ]
45
+ )
46
+
47
+ self._promote_level2_text_blocks(slide_blocks, body_font_size_pt)
48
+ self._promote_level3_text_blocks(slide_blocks, body_font_size_pt)
49
+
50
+ def _promote_level2_text_blocks(
51
+ self,
52
+ slide_blocks: list[dict],
53
+ body_font_size_pt: Optional[float],
54
+ ) -> None:
55
+ """按原有幻灯片标题判定规则执行 _promote_level2_text_blocks,保持输入顺序与降级行为。"""
56
+ bold_text_blocks = [
57
+ block
58
+ for block in slide_blocks
59
+ if (
60
+ block.get("type") == BlockType.TEXT
61
+ and block.get(_PPTX_TITLE_CANDIDATE_KEY) is not True
62
+ and block.get(_EFFECTIVE_ALL_BOLD_KEY, False)
63
+ and block.get(_EFFECTIVE_FONT_SIZE_KEY) is not None
64
+ )
65
+ ]
66
+ if not bold_text_blocks:
67
+ return
68
+
69
+ bold_font_sizes = sorted(
70
+ {block[_EFFECTIVE_FONT_SIZE_KEY] for block in bold_text_blocks},
71
+ reverse=True,
72
+ )
73
+ level2_font_size_pt = bold_font_sizes[0]
74
+ level2_candidates = [block for block in bold_text_blocks if block[_EFFECTIVE_FONT_SIZE_KEY] == level2_font_size_pt]
75
+
76
+ if len(level2_candidates) != 1:
77
+ return
78
+
79
+ if body_font_size_pt is not None and level2_font_size_pt < body_font_size_pt + 4:
80
+ return
81
+
82
+ if len(bold_font_sizes) > 1 and level2_font_size_pt < bold_font_sizes[1] + 2:
83
+ return
84
+
85
+ level2_candidates[0][_PPTX_TITLE_CANDIDATE_KEY] = True
86
+ level2_candidates[0]["level"] = 2
87
+
88
+ def _promote_level3_text_blocks(
89
+ self,
90
+ slide_blocks: list[dict],
91
+ body_font_size_pt: Optional[float],
92
+ ) -> None:
93
+ """按原有幻灯片标题判定规则执行 _promote_level3_text_blocks,保持输入顺序与降级行为。"""
94
+ if body_font_size_pt is None:
95
+ return
96
+
97
+ level2_font_sizes = sorted(
98
+ {
99
+ block[_EFFECTIVE_FONT_SIZE_KEY]
100
+ for block in slide_blocks
101
+ if (
102
+ block.get(_PPTX_TITLE_CANDIDATE_KEY) is True
103
+ and block.get("level") == 2
104
+ and block.get(_EFFECTIVE_FONT_SIZE_KEY) is not None
105
+ )
106
+ },
107
+ reverse=True,
108
+ )
109
+ if not level2_font_sizes:
110
+ return
111
+
112
+ level2_font_size_pt = level2_font_sizes[0]
113
+ level3_font_sizes = sorted(
114
+ {
115
+ block[_EFFECTIVE_FONT_SIZE_KEY]
116
+ for block in slide_blocks
117
+ if (
118
+ block.get("type") == BlockType.TEXT
119
+ and block.get(_PPTX_TITLE_CANDIDATE_KEY) is not True
120
+ and block.get(_EFFECTIVE_ALL_BOLD_KEY, False)
121
+ and block.get(_EFFECTIVE_FONT_SIZE_KEY) is not None
122
+ and block[_EFFECTIVE_FONT_SIZE_KEY] < level2_font_size_pt
123
+ )
124
+ },
125
+ reverse=True,
126
+ )
127
+ if not level3_font_sizes:
128
+ return
129
+
130
+ level3_font_size_pt = level3_font_sizes[0]
131
+ if level3_font_size_pt < body_font_size_pt + 2:
132
+ return
133
+ if level2_font_size_pt < level3_font_size_pt + 2:
134
+ return
135
+
136
+ for block in slide_blocks:
137
+ if (
138
+ block.get("type") == BlockType.TEXT
139
+ and block.get(_PPTX_TITLE_CANDIDATE_KEY) is not True
140
+ and block.get(_EFFECTIVE_ALL_BOLD_KEY, False)
141
+ and block.get(_EFFECTIVE_FONT_SIZE_KEY) == level3_font_size_pt
142
+ ):
143
+ block[_PPTX_TITLE_CANDIDATE_KEY] = True
144
+ block["level"] = 3
145
+
146
+ @staticmethod
147
+ def _finalize_slide_title_types(
148
+ slide_blocks: list[dict],
149
+ *,
150
+ is_first_visible_slide: bool,
151
+ ) -> None:
152
+ """将 PPTX 标题候选统一拆分为文档标题、段落标题或普通文本。"""
153
+ for block in slide_blocks:
154
+ is_title_candidate = block.pop(_PPTX_TITLE_CANDIDATE_KEY, False) is True
155
+ title_role = block.pop(_PPTX_TITLE_ROLE_KEY, None)
156
+ if not is_title_candidate:
157
+ continue
158
+ if title_role == _PPTX_TITLE_ROLE_SUBTITLE:
159
+ block["type"] = BlockType.TEXT
160
+ block.pop("level", None)
161
+ block.pop("is_numbered_style", None)
162
+ continue
163
+
164
+ if title_role == _PPTX_TITLE_ROLE_CENTER and is_first_visible_slide:
165
+ block["type"] = BlockType.DOC_TITLE
166
+ block["level"] = 1
167
+ block.pop("is_numbered_style", None)
168
+ else:
169
+ block["type"] = BlockType.PARAGRAPH_TITLE
170
+
171
+ @staticmethod
172
+ def _cleanup_slide_text_block_metadata(slide_blocks: list[dict]) -> None:
173
+ """按原有幻灯片标题判定规则执行 _cleanup_slide_text_block_metadata,保持输入顺序与降级行为。"""
174
+ for block in slide_blocks:
175
+ block.pop(_EFFECTIVE_FONT_SIZE_KEY, None)
176
+ block.pop(_EFFECTIVE_ALL_BOLD_KEY, None)
177
+ block.pop(_PPTX_TITLE_CANDIDATE_KEY, None)
178
+ block.pop(_PPTX_TITLE_ROLE_KEY, None)
@@ -0,0 +1,420 @@
1
+ import html
2
+ from dataclasses import dataclass
3
+ from typing import Any, Optional
4
+
5
+ from .._shared.hyperlink import OFFICE_EXTERNAL_HYPERLINK_SCHEMES, sanitize_hyperlink_target
6
+ from ....content.spans import append_hyperlink_span, append_text_span, extend_inline_spans, normalize_span_dicts
7
+
8
+ VISIBLE_SPACE_STYLES = {"underline", "emphasis", "strikethrough"}
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class OfficeRichTextSegment:
13
+ """表示 Office 行内富文本片段,用于统一样式和超链接输出。"""
14
+
15
+ text: str
16
+ style: str | list[str] | tuple[str, ...] | None = None
17
+ hyperlink: Optional[str] = None
18
+
19
+
20
+ def _style_list(style: str | list[str] | tuple[str, ...] | None) -> list[str]:
21
+ """把样式字符串或列表规范为样式列表。"""
22
+ if not style:
23
+ return []
24
+ if isinstance(style, str):
25
+ return [item.strip() for item in style.split(",") if item.strip()]
26
+ return [str(item).strip() for item in style if str(item).strip()]
27
+
28
+
29
+ def _style_str(style: str | list[str] | tuple[str, ...] | None) -> Optional[str]:
30
+ """把样式字符串或列表规范为逗号分隔字符串。"""
31
+ styles = _style_list(style)
32
+ return ",".join(styles) if styles else None
33
+
34
+
35
+ def _script_to_style_name(format_obj: Any) -> Optional[str]:
36
+ """把 DOCX 上下标脚本位置转换为 Office 内部富文本样式名。"""
37
+ script = getattr(format_obj, "script", None)
38
+ script_value = getattr(script, "value", script)
39
+ if script_value == "super":
40
+ return "superscript"
41
+ if script_value == "sub":
42
+ return "subscript"
43
+ return None
44
+
45
+
46
+ def formatting_to_style_str(format_obj: Any) -> Optional[str]:
47
+ """从 Formatting-like 对象提取 Office 内部富文本样式字符串。"""
48
+ if format_obj is None:
49
+ return None
50
+ styles = []
51
+ if getattr(format_obj, "bold", False):
52
+ styles.append("bold")
53
+ if getattr(format_obj, "italic", False):
54
+ styles.append("italic")
55
+ if getattr(format_obj, "underline", False):
56
+ styles.append("underline")
57
+ if getattr(format_obj, "emphasis", False):
58
+ styles.append("emphasis")
59
+ if getattr(format_obj, "strikethrough", False):
60
+ styles.append("strikethrough")
61
+ script_style = _script_to_style_name(format_obj)
62
+ if script_style:
63
+ styles.append(script_style)
64
+ return ",".join(styles) if styles else None
65
+
66
+
67
+ def has_visible_style(format_obj: Any) -> bool:
68
+ """判断格式是否包含让空白文本也可见的样式。"""
69
+ if format_obj is None:
70
+ return False
71
+ return bool(
72
+ getattr(format_obj, "underline", False)
73
+ or getattr(format_obj, "emphasis", False)
74
+ or getattr(format_obj, "strikethrough", False)
75
+ )
76
+
77
+
78
+ def has_non_visible_text_style(format_obj: Any) -> bool:
79
+ """判断格式是否只包含空白文本不可见的字形样式。"""
80
+ if format_obj is None:
81
+ return False
82
+ return bool(getattr(format_obj, "bold", False) or getattr(format_obj, "italic", False))
83
+
84
+
85
+ def normalize_format_for_text(
86
+ format_obj: Any,
87
+ text: str,
88
+ *,
89
+ preserve_blank_non_visible_style: bool = False,
90
+ ) -> Any:
91
+ """按文本内容规范 run 格式,避免空白 run 误把不可见样式带到输出。"""
92
+ if format_obj is None:
93
+ return None
94
+ if text.strip():
95
+ return format_obj
96
+
97
+ updates = {}
98
+ if getattr(format_obj, "underline_style", "") == "words":
99
+ updates["underline"] = False
100
+ updates["underline_style"] = ""
101
+ if has_non_visible_text_style(format_obj) and not preserve_blank_non_visible_style:
102
+ updates["bold"] = False
103
+ updates["italic"] = False
104
+
105
+ if updates and hasattr(format_obj, "model_copy"):
106
+ format_obj = format_obj.model_copy(update=updates)
107
+
108
+ if not has_visible_style(format_obj):
109
+ if preserve_blank_non_visible_style and has_non_visible_text_style(format_obj):
110
+ return format_obj
111
+ return None
112
+ return format_obj
113
+
114
+
115
+ def should_keep_group_text(
116
+ text: str,
117
+ format_obj: Any,
118
+ *,
119
+ preserve_plain_blank: bool = False,
120
+ ) -> bool:
121
+ """判断当前累积文本是否应输出,保留可见样式或被显式保留的空白。"""
122
+ if not text:
123
+ return False
124
+ if text.strip():
125
+ return True
126
+ if has_visible_style(format_obj):
127
+ return True
128
+ return preserve_plain_blank
129
+
130
+
131
+ def append_rich_text_element(
132
+ paragraph_elements: list[tuple[str, Any, Any]],
133
+ text: str,
134
+ format_obj: Any,
135
+ hyperlink: Any,
136
+ ) -> None:
137
+ """追加段落元素;相邻同 URL 且同格式的片段合并为一个元素。"""
138
+ if (
139
+ hyperlink is not None
140
+ and paragraph_elements
141
+ and paragraph_elements[-1][2] is not None
142
+ and str(paragraph_elements[-1][2]) == str(hyperlink)
143
+ and paragraph_elements[-1][1] == format_obj
144
+ ):
145
+ previous_text, previous_format, previous_hyperlink = paragraph_elements[-1]
146
+ paragraph_elements[-1] = (
147
+ f"{previous_text}{text}",
148
+ previous_format,
149
+ previous_hyperlink,
150
+ )
151
+ return
152
+ paragraph_elements.append((text, format_obj, hyperlink))
153
+
154
+
155
+ def format_text_spans(
156
+ text: str,
157
+ hyperlink: Any = None,
158
+ style: str | list[str] | tuple[str, ...] | None = None,
159
+ ) -> list[dict[str, Any]]:
160
+ """把 Office 文字、样式和安全超链接直接构造为 Span。"""
161
+ if not text:
162
+ return []
163
+ normalized_text = text.replace("\r\n", "\n").replace("\r", "\n")
164
+ children: list[dict[str, Any]] = []
165
+ append_text_span(children, normalized_text, _style_list(style))
166
+ safe_target = sanitize_hyperlink_target(
167
+ hyperlink,
168
+ allowed_schemes=OFFICE_EXTERNAL_HYPERLINK_SCHEMES,
169
+ allow_relative=True,
170
+ allow_fragment=True,
171
+ )
172
+ if safe_target is None:
173
+ return children
174
+ output: list[dict[str, Any]] = []
175
+ append_hyperlink_span(output, children, safe_target)
176
+ return output
177
+
178
+
179
+ def is_valid_hyperlink_target(hyperlink: Any) -> bool:
180
+ """判断超链接目标是否可作为真实链接输出。"""
181
+ return (
182
+ sanitize_hyperlink_target(
183
+ hyperlink,
184
+ allowed_schemes=OFFICE_EXTERNAL_HYPERLINK_SCHEMES,
185
+ allow_relative=True,
186
+ allow_fragment=True,
187
+ )
188
+ is not None
189
+ )
190
+
191
+
192
+ def _format_hyperlink_segments(group: list[OfficeRichTextSegment]) -> list[dict[str, Any]]:
193
+ """将连续同 URL 的多个片段构造成单个 HyperlinkSpan。"""
194
+ if not group:
195
+ return []
196
+ safe_target = sanitize_hyperlink_target(
197
+ group[0].hyperlink,
198
+ allowed_schemes=OFFICE_EXTERNAL_HYPERLINK_SCHEMES,
199
+ allow_relative=True,
200
+ allow_fragment=True,
201
+ )
202
+ children: list[dict[str, Any]] = []
203
+ for segment in group:
204
+ append_text_span(children, segment.text, _style_list(segment.style))
205
+ if safe_target is None:
206
+ return children
207
+ output: list[dict[str, Any]] = []
208
+ append_hyperlink_span(output, children, safe_target)
209
+ return output
210
+
211
+
212
+ def format_hyperlink_group(
213
+ group: list[tuple[str, Any, Any]],
214
+ ) -> list[dict[str, Any]]:
215
+ """将 DOCX paragraph element 分组构造成单个 HyperlinkSpan。"""
216
+ return _format_hyperlink_segments(
217
+ [
218
+ OfficeRichTextSegment(
219
+ text=text,
220
+ style=formatting_to_style_str(format_obj),
221
+ hyperlink=str(hyperlink) if hyperlink is not None else None,
222
+ )
223
+ for text, format_obj, hyperlink in group
224
+ ]
225
+ )
226
+
227
+
228
+ def _style_has_visible_space(style: str | list[str] | tuple[str, ...] | None) -> bool:
229
+ """判断样式列表是否会让空白文本在渲染结果中可见。"""
230
+ return any(style_name in VISIBLE_SPACE_STYLES for style_name in _style_list(style))
231
+
232
+
233
+ def _trim_plain_edge_spaces(
234
+ segments: list[OfficeRichTextSegment],
235
+ ) -> list[OfficeRichTextSegment]:
236
+ """只裁剪段落首尾普通空白,不裁剪带可见样式的空白。"""
237
+ trimmed_segments = [segment for segment in segments if segment.text is not None]
238
+ if not trimmed_segments:
239
+ return []
240
+
241
+ start_idx = 0
242
+ while start_idx < len(trimmed_segments):
243
+ segment = trimmed_segments[start_idx]
244
+ if segment.text.strip() or _style_has_visible_space(segment.style):
245
+ if not _style_has_visible_space(segment.style):
246
+ trimmed_segments[start_idx] = OfficeRichTextSegment(
247
+ segment.text.lstrip(),
248
+ segment.style,
249
+ segment.hyperlink,
250
+ )
251
+ break
252
+ start_idx += 1
253
+ if start_idx == len(trimmed_segments):
254
+ return []
255
+
256
+ trimmed_segments = trimmed_segments[start_idx:]
257
+ end_idx = len(trimmed_segments) - 1
258
+ while end_idx >= 0:
259
+ segment = trimmed_segments[end_idx]
260
+ if segment.text.strip() or _style_has_visible_space(segment.style):
261
+ if not _style_has_visible_space(segment.style):
262
+ trimmed_segments[end_idx] = OfficeRichTextSegment(
263
+ segment.text.rstrip(),
264
+ segment.style,
265
+ segment.hyperlink,
266
+ )
267
+ break
268
+ end_idx -= 1
269
+ if end_idx < 0:
270
+ return []
271
+ return trimmed_segments[: end_idx + 1]
272
+
273
+
274
+ def _merge_non_link_segments(
275
+ segments: list[OfficeRichTextSegment],
276
+ ) -> list[OfficeRichTextSegment]:
277
+ """合并相邻同样式的非超链接片段,避免输出碎片化样式标记。"""
278
+ merged: list[OfficeRichTextSegment] = []
279
+ for segment in segments:
280
+ if (
281
+ merged
282
+ and not is_valid_hyperlink_target(merged[-1].hyperlink)
283
+ and not is_valid_hyperlink_target(segment.hyperlink)
284
+ and _style_str(merged[-1].style) == _style_str(segment.style)
285
+ ):
286
+ previous = merged[-1]
287
+ merged[-1] = OfficeRichTextSegment(
288
+ f"{previous.text}{segment.text}",
289
+ previous.style,
290
+ previous.hyperlink,
291
+ )
292
+ continue
293
+ merged.append(segment)
294
+ return merged
295
+
296
+
297
+ def build_rich_text_from_segments(
298
+ segments: list[OfficeRichTextSegment],
299
+ *,
300
+ trim_plain_edges: bool = False,
301
+ ) -> list[dict[str, Any]]:
302
+ """从 Office 富文本片段直接构建规范化行内 Span。"""
303
+ normalized_segments = [
304
+ OfficeRichTextSegment(
305
+ segment.text,
306
+ _style_str(segment.style),
307
+ sanitize_hyperlink_target(
308
+ segment.hyperlink,
309
+ allowed_schemes=OFFICE_EXTERNAL_HYPERLINK_SCHEMES,
310
+ allow_relative=True,
311
+ allow_fragment=True,
312
+ ),
313
+ )
314
+ for segment in segments
315
+ if segment.text is not None and segment.text != ""
316
+ ]
317
+ if trim_plain_edges:
318
+ normalized_segments = _trim_plain_edge_spaces(normalized_segments)
319
+ normalized_segments = _merge_non_link_segments(normalized_segments)
320
+
321
+ rendered_spans: list[dict[str, Any]] = []
322
+ index = 0
323
+ while index < len(normalized_segments):
324
+ segment = normalized_segments[index]
325
+ if is_valid_hyperlink_target(segment.hyperlink):
326
+ group = [segment]
327
+ index += 1
328
+ while index < len(normalized_segments):
329
+ next_segment = normalized_segments[index]
330
+ if not is_valid_hyperlink_target(next_segment.hyperlink) or str(next_segment.hyperlink) != str(
331
+ segment.hyperlink
332
+ ):
333
+ break
334
+ group.append(next_segment)
335
+ index += 1
336
+ extend_inline_spans(rendered_spans, _format_hyperlink_segments(group))
337
+ continue
338
+
339
+ extend_inline_spans(
340
+ rendered_spans,
341
+ format_text_spans(
342
+ segment.text,
343
+ segment.hyperlink,
344
+ segment.style,
345
+ ),
346
+ )
347
+ index += 1
348
+
349
+ return normalize_span_dicts(rendered_spans)
350
+
351
+
352
+ def build_spans_from_elements(
353
+ paragraph_elements: list[tuple[str, Any, Any]],
354
+ ) -> list[dict[str, Any]]:
355
+ """把 DOCX paragraph element 直接构造成结构化 Span。"""
356
+ return build_rich_text_from_segments(
357
+ [
358
+ OfficeRichTextSegment(
359
+ text=text,
360
+ style=formatting_to_style_str(format_obj),
361
+ hyperlink=str(hyperlink) if hyperlink is not None else None,
362
+ )
363
+ for text, format_obj, hyperlink in paragraph_elements
364
+ if text
365
+ ]
366
+ )
367
+
368
+
369
+ def build_rich_text_html_from_segments(
370
+ segments: list[OfficeRichTextSegment],
371
+ *,
372
+ trim_plain_edges: bool = False,
373
+ ) -> str:
374
+ """把 Office 富文本片段序列化为表格单元格使用的安全 HTML。"""
375
+ normalized = _trim_plain_edge_spaces(segments) if trim_plain_edges else list(segments)
376
+ parts: list[str] = []
377
+ for segment in normalized:
378
+ if not segment.text:
379
+ continue
380
+ rendered = html.escape(segment.text, quote=False).replace("\r\n", "\n").replace("\r", "\n")
381
+ styles = _style_list(segment.style)
382
+ if "superscript" in styles:
383
+ rendered = f"<sup>{rendered}</sup>"
384
+ elif "subscript" in styles:
385
+ rendered = f"<sub>{rendered}</sub>"
386
+ if "underline" in styles:
387
+ rendered = f"<u>{rendered}</u>"
388
+ if "bold" in styles:
389
+ rendered = f"<strong>{rendered}</strong>"
390
+ if "italic" in styles:
391
+ rendered = f"<em>{rendered}</em>"
392
+ if "strikethrough" in styles:
393
+ rendered = f"<s>{rendered}</s>"
394
+ safe_target = sanitize_hyperlink_target(
395
+ segment.hyperlink,
396
+ allowed_schemes=OFFICE_EXTERNAL_HYPERLINK_SCHEMES,
397
+ allow_relative=True,
398
+ allow_fragment=True,
399
+ )
400
+ if safe_target:
401
+ rendered = f'<a href="{html.escape(safe_target, quote=True)}">{rendered}</a>'
402
+ parts.append(rendered)
403
+ return "".join(parts)
404
+
405
+
406
+ __all__ = [
407
+ "OfficeRichTextSegment",
408
+ "append_rich_text_element",
409
+ "build_rich_text_from_segments",
410
+ "build_rich_text_html_from_segments",
411
+ "build_spans_from_elements",
412
+ "format_hyperlink_group",
413
+ "format_text_spans",
414
+ "formatting_to_style_str",
415
+ "has_non_visible_text_style",
416
+ "has_visible_style",
417
+ "is_valid_hyperlink_target",
418
+ "normalize_format_for_text",
419
+ "should_keep_group_text",
420
+ ]
@@ -0,0 +1,3 @@
1
+ """纯 Python RTF 语义解析实现。"""
2
+
3
+ __all__: list[str] = []