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,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
+ }