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,582 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import os
5
+ import time
6
+ from contextlib import contextmanager
7
+ from io import BytesIO
8
+ from typing import Iterator, Literal, cast
9
+
10
+ import pypdfium2 as pdfium
11
+ from .native_annotations import pdfium_c as pdfium_c
12
+ from .text import extract as _text_extract
13
+ from .text.contracts import Char, Line
14
+ from PIL import Image, ImageOps
15
+
16
+ from ...schema import BBox, PageInfo
17
+ from ...foundation.image import crop_pil_image
18
+ from docvortex.foundation.image_encoding import image_to_bytes
19
+ from .classify import classify
20
+ from .pdfium import _pdfium_lock, pdfium_guard
21
+ from .text import get_lines_from_chars as get_lines_from_chars
22
+
23
+
24
+ # See: pdfium.PdfDocument.METADATA_KEYS
25
+
26
+
27
+ # 原有符号由单一实现显式重导出,宿主导入路径与类型身份保持不变。
28
+ from .native_annotations import (
29
+ _extract_page_link_annotations as _extract_page_link_annotations,
30
+ _extract_page_signature_bboxes as _extract_page_signature_bboxes,
31
+ _get_annotation_string as _get_annotation_string,
32
+ _get_pdfium_uri_path as _get_pdfium_uri_path,
33
+ _pdf_link_annotation_is_visible as _pdf_link_annotation_is_visible,
34
+ _pdf_link_region_bboxes as _pdf_link_region_bboxes,
35
+ _signature_bbox_from_annotation as _signature_bbox_from_annotation,
36
+ _validate_pdf_external_link_target as _validate_pdf_external_link_target,
37
+ _visual_bbox_from_pdf_points as _visual_bbox_from_pdf_points,
38
+ )
39
+ from .native_contracts import (
40
+ DEFAULT_RENDER_DPI as DEFAULT_RENDER_DPI,
41
+ DEFAULT_RENDER_MAX_EDGE as DEFAULT_RENDER_MAX_EDGE,
42
+ DEFAULT_RENDER_SCALE as DEFAULT_RENDER_SCALE,
43
+ DRAWING_FORM_MAX_DEPTH as DRAWING_FORM_MAX_DEPTH,
44
+ DRAWING_LINE_AXIS_ABSOLUTE_TOLERANCE as DRAWING_LINE_AXIS_ABSOLUTE_TOLERANCE,
45
+ DRAWING_LINE_AXIS_RATIO_TOLERANCE as DRAWING_LINE_AXIS_RATIO_TOLERANCE,
46
+ DRAWING_LINE_MERGE_TOLERANCE as DRAWING_LINE_MERGE_TOLERANCE,
47
+ DRAWING_LINE_MIN_LENGTH as DRAWING_LINE_MIN_LENGTH,
48
+ DRAWING_THIN_RECT_MAX_THICKNESS as DRAWING_THIN_RECT_MAX_THICKNESS,
49
+ DRAWING_THIN_RECT_MIN_ASPECT_RATIO as DRAWING_THIN_RECT_MIN_ASPECT_RATIO,
50
+ NEAR_IDENTICAL_CHAR_BBOX_TOLERANCE as NEAR_IDENTICAL_CHAR_BBOX_TOLERANCE,
51
+ OFFSET_DUPLICATE_CHAR_BBOX_TOLERANCE as OFFSET_DUPLICATE_CHAR_BBOX_TOLERANCE,
52
+ OFFSET_DUPLICATE_MIN_BBOX_OVERLAP_RATIO as OFFSET_DUPLICATE_MIN_BBOX_OVERLAP_RATIO,
53
+ OFFSET_DUPLICATE_TRANSLATION_TOLERANCE as OFFSET_DUPLICATE_TRANSLATION_TOLERANCE,
54
+ PDFDrawingLine as PDFDrawingLine,
55
+ PDFImageInfo as PDFImageInfo,
56
+ PDFLinkAnnotation as PDFLinkAnnotation,
57
+ PDFMetadataKey as PDFMetadataKey,
58
+ PDFPageImage as PDFPageImage,
59
+ PDFPageTextGeometry as PDFPageTextGeometry,
60
+ PDFPathInfo as PDFPathInfo,
61
+ PDF_IMAGE_FINGERPRINT_MAX_RAW_BYTES as PDF_IMAGE_FINGERPRINT_MAX_RAW_BYTES,
62
+ POINTS_PER_INCH as POINTS_PER_INCH,
63
+ _PDFPageSnapshot as _PDFPageSnapshot,
64
+ _PDF_EXTERNAL_LINK_SCHEMES as _PDF_EXTERNAL_LINK_SCHEMES,
65
+ _PathSubpath as _PathSubpath,
66
+ )
67
+ from .native_coordinates import (
68
+ _apply_pdf_matrix as _apply_pdf_matrix,
69
+ _char_visual_bbox_from_pdfium as _char_visual_bbox_from_pdfium,
70
+ _drawing_page_size as _drawing_page_size,
71
+ _extract_page_char_extended_geometry as _extract_page_char_extended_geometry,
72
+ _get_raw_object_matrix as _get_raw_object_matrix,
73
+ _multiply_pdf_matrices as _multiply_pdf_matrices,
74
+ _normalize_pdf_page_bbox as _normalize_pdf_page_bbox,
75
+ _transform_drawing_point as _transform_drawing_point,
76
+ )
77
+ from .native_lifecycle import (
78
+ _try_close as _try_close,
79
+ )
80
+ from .native_objects import (
81
+ _combine_collinear_line_group as _combine_collinear_line_group,
82
+ _extract_page_drawing_lines as _extract_page_drawing_lines,
83
+ _extract_page_form_bboxes as _extract_page_form_bboxes,
84
+ _extract_page_image_bboxes as _extract_page_image_bboxes,
85
+ _extract_page_image_infos as _extract_page_image_infos,
86
+ _extract_page_path_infos as _extract_page_path_infos,
87
+ _extract_page_paths_and_lines as _extract_page_paths_and_lines,
88
+ _extract_path_drawing_lines as _extract_path_drawing_lines,
89
+ _form_bbox_from_object as _form_bbox_from_object,
90
+ _get_path_visibility as _get_path_visibility,
91
+ _get_raw_image_fingerprint as _get_raw_image_fingerprint,
92
+ _get_raw_object_alpha as _get_raw_object_alpha,
93
+ _get_raw_object_rgba as _get_raw_object_rgba,
94
+ _get_raw_stroke_width as _get_raw_stroke_width,
95
+ _get_segment_stroke_width as _get_segment_stroke_width,
96
+ _get_thin_filled_subpath_line as _get_thin_filled_subpath_line,
97
+ _image_bbox_from_matrix as _image_bbox_from_matrix,
98
+ _iter_raw_image_objects as _iter_raw_image_objects,
99
+ _iter_raw_path_objects as _iter_raw_path_objects,
100
+ _iter_raw_path_objects_with_depth as _iter_raw_path_objects_with_depth,
101
+ _iter_raw_root_form_objects as _iter_raw_root_form_objects,
102
+ _line_axis_coordinate as _line_axis_coordinate,
103
+ _line_main_interval as _line_main_interval,
104
+ _make_axis_drawing_line as _make_axis_drawing_line,
105
+ _merge_collinear_drawing_lines as _merge_collinear_drawing_lines,
106
+ _merge_orientation_lines as _merge_orientation_lines,
107
+ _path_info_from_object as _path_info_from_object,
108
+ _read_raw_path_subpaths as _read_raw_path_subpaths,
109
+ _transform_path_subpath as _transform_path_subpath,
110
+ _walk_raw_page_objects as _walk_raw_page_objects,
111
+ _walk_raw_page_objects_with_depth as _walk_raw_page_objects_with_depth,
112
+ _walk_raw_path_objects as _walk_raw_path_objects,
113
+ )
114
+ from .native_text_geometry import (
115
+ _calculate_bbox_overlap_in_smaller_area as _calculate_bbox_overlap_in_smaller_area,
116
+ _deduplicate_near_identical_chars as _deduplicate_near_identical_chars,
117
+ _extract_page_text_geometry as _extract_page_text_geometry,
118
+ _get_near_identical_bbox_bucket_key as _get_near_identical_bbox_bucket_key,
119
+ _get_visible_char_signature as _get_visible_char_signature,
120
+ _is_adjacent_offset_duplicate_char as _is_adjacent_offset_duplicate_char,
121
+ _is_near_identical_bbox as _is_near_identical_bbox,
122
+ _iter_neighbor_bbox_bucket_keys as _iter_neighbor_bbox_bucket_keys,
123
+ _page_to_image as _page_to_image,
124
+ _restore_pdfium_surrogate_pairs as _restore_pdfium_surrogate_pairs,
125
+ )
126
+
127
+
128
+ logger = logging.getLogger(__name__)
129
+ get_chars = _text_extract.get_chars
130
+ deduplicate_chars = _text_extract.deduplicate_chars
131
+
132
+
133
+ class PDFPage:
134
+ def __init__(self, pdf_doc: "PDFDocument", idx: int) -> None:
135
+ self.pdf_doc = pdf_doc
136
+ self._idx = idx
137
+
138
+ @property
139
+ def size(self) -> tuple[float, float]:
140
+ return self.pdf_doc.page_size(self._idx)
141
+
142
+ @property
143
+ def rotation(self) -> Literal[0, 90, 180, 270]:
144
+ """只读返回当前页声明的标准旋转角度。"""
145
+
146
+ return self.pdf_doc.page_rotation(self._idx)
147
+
148
+ def get_char_count(self) -> int:
149
+ return self.pdf_doc.page_char_count(self._idx)
150
+
151
+ def get_chars(self) -> list[Char]:
152
+ return self.pdf_doc.get_page_chars(self._idx)
153
+
154
+ def get_chars_with_geometry(self) -> PDFPageTextGeometry:
155
+ """一次读取字符及 Hybrid TXT 匹配所需的额外几何。"""
156
+ return self.pdf_doc.get_page_chars_with_geometry(self._idx)
157
+
158
+ def get_drawing_lines(self) -> list[PDFDrawingLine]:
159
+ """读取当前页已规范到视觉页面坐标的横竖 drawing。"""
160
+
161
+ return self.pdf_doc.get_page_drawing_lines(self._idx)
162
+
163
+ def get_path_infos(self) -> list[PDFPathInfo]:
164
+ """读取当前页及嵌套 Form 中已规范到视觉页面坐标的 Path 摘要。"""
165
+
166
+ return self.pdf_doc.get_page_path_infos(self._idx)
167
+
168
+ def get_link_annotations(self) -> list[PDFLinkAnnotation]:
169
+ """读取当前页已规范到视觉页面坐标的外部 URI Link 注解。"""
170
+
171
+ return self.pdf_doc.get_page_link_annotations(self._idx)
172
+
173
+
174
+ class PDFDocument:
175
+ """A PDF file loaded in memory, with lazy pypdfium2 access.
176
+
177
+ This object is responsible for all access to, and lifecycle management of,
178
+ the associated PDFium document/page objects.
179
+
180
+ All pypdfium2 operations are serialized under a module-level lock for
181
+ thread safety. Call ``close()`` when done, or use as a context manager.
182
+
183
+ The class does not expose raw PDFium objects or methods without wrapping
184
+ them first. Callers may use this class to read or operate on the
185
+ underlying PDFium state, but they must not directly access PDFium objects
186
+ through this API.
187
+ """
188
+
189
+ def __init__(
190
+ self,
191
+ pdf_bytes_or_path: bytes | str,
192
+ render_scale: float = DEFAULT_RENDER_SCALE,
193
+ render_max_edge: int = DEFAULT_RENDER_MAX_EDGE,
194
+ ) -> None:
195
+ self._classification: Literal["ocr", "txt"] | None = None
196
+ if isinstance(pdf_bytes_or_path, bytes):
197
+ self._pdf_bytes: bytes = pdf_bytes_or_path
198
+ else:
199
+ assert isinstance(pdf_bytes_or_path, str)
200
+ with open(pdf_bytes_or_path, "rb") as f:
201
+ self._pdf_bytes = f.read()
202
+
203
+ self._pdf_doc_opened: pdfium.PdfDocument | None = None
204
+ self._page_count: int | None = None
205
+ self.render_scale = render_scale
206
+ self.render_max_edge = render_max_edge
207
+
208
+ # ------------------------------------------------------------------ #
209
+ # Factory
210
+ # ------------------------------------------------------------------ #
211
+
212
+ @staticmethod
213
+ def from_image(
214
+ image_bytes: bytes,
215
+ render_scale: float = DEFAULT_RENDER_SCALE,
216
+ render_max_edge: int = DEFAULT_RENDER_MAX_EDGE,
217
+ ) -> "PDFDocument":
218
+ open_started_at = time.perf_counter()
219
+ logger.debug("Pillow image open started input_bytes=%d", len(image_bytes))
220
+ image = Image.open(BytesIO(image_bytes))
221
+ logger.debug(
222
+ "Pillow image open completed format=%s mode=%s size=%sx%s elapsed_ms=%d",
223
+ image.format,
224
+ image.mode,
225
+ image.width,
226
+ image.height,
227
+ round((time.perf_counter() - open_started_at) * 1000),
228
+ )
229
+
230
+ # 根据 EXIF 信息自动转正(处理手机拍摄的带 Orientation 标记的图片)
231
+ transpose_started_at = time.perf_counter()
232
+ logger.debug("Pillow EXIF transpose started")
233
+ image = ImageOps.exif_transpose(image) or image
234
+ logger.debug(
235
+ "Pillow EXIF transpose completed mode=%s size=%sx%s elapsed_ms=%d",
236
+ image.mode,
237
+ image.width,
238
+ image.height,
239
+ round((time.perf_counter() - transpose_started_at) * 1000),
240
+ )
241
+
242
+ # 只在必要时转换
243
+ if image.mode != "RGB":
244
+ source_mode = image.mode
245
+ conversion_started_at = time.perf_counter()
246
+ logger.debug("Pillow RGB conversion started source_mode=%s", source_mode)
247
+ image = image.convert("RGB")
248
+ logger.debug(
249
+ "Pillow RGB conversion completed source_mode=%s elapsed_ms=%d",
250
+ source_mode,
251
+ round((time.perf_counter() - conversion_started_at) * 1000),
252
+ )
253
+
254
+ render_dpi = max(1, int(round(render_scale * POINTS_PER_INCH)))
255
+
256
+ with BytesIO() as pdf_buffer:
257
+ # 第一张图保存为 PDF,其余追加
258
+ save_started_at = time.perf_counter()
259
+ logger.debug(
260
+ "Pillow PDF encoding started mode=%s size=%sx%s dpi=%d",
261
+ image.mode,
262
+ image.width,
263
+ image.height,
264
+ render_dpi,
265
+ )
266
+ image.save(
267
+ pdf_buffer,
268
+ format="PDF",
269
+ resolution=render_dpi,
270
+ quality=95,
271
+ subsampling=0,
272
+ )
273
+ pdf_bytes = pdf_buffer.getvalue()
274
+ logger.debug(
275
+ "Pillow PDF encoding completed output_bytes=%d elapsed_ms=%d",
276
+ len(pdf_bytes),
277
+ round((time.perf_counter() - save_started_at) * 1000),
278
+ )
279
+
280
+ return PDFDocument(
281
+ pdf_bytes,
282
+ render_scale=render_scale,
283
+ render_max_edge=render_max_edge,
284
+ )
285
+
286
+ # ------------------------------------------------------------------ #
287
+ # Lifecycle
288
+ # ------------------------------------------------------------------ #
289
+
290
+ def close(self) -> None:
291
+ """关闭原生文档;清理路径不触发字体初始化。"""
292
+ if self._pdf_doc_opened is not None:
293
+ with _pdfium_lock:
294
+ _try_close(self._pdf_doc_opened)
295
+ self._pdf_doc_opened = None
296
+
297
+ def __enter__(self) -> "PDFDocument":
298
+ return self
299
+
300
+ def __exit__(self, *args: object) -> None:
301
+ self.close()
302
+
303
+ def __del__(self) -> None:
304
+ self.close()
305
+
306
+ # ------------------------------------------------------------------ #
307
+ # Properties
308
+ # ------------------------------------------------------------------ #
309
+
310
+ def __len__(self) -> int:
311
+ return self.page_count
312
+
313
+ def __getitem__(self, idx: int) -> PDFPage:
314
+ return PDFPage(self, idx)
315
+
316
+ @property
317
+ def page_count(self) -> int:
318
+ with pdfium_guard():
319
+ return len(self._pdf_doc)
320
+
321
+ @property
322
+ def metadata(self) -> dict[PDFMetadataKey, str]:
323
+ with pdfium_guard():
324
+ metadata = self._pdf_doc.get_metadata_dict()
325
+ return cast(dict[PDFMetadataKey, str], metadata)
326
+
327
+ @property
328
+ def bytes(self) -> bytes:
329
+ # TODO: some invoker expected PDF bytes even if input bytes is an Image.
330
+ return self._pdf_bytes
331
+
332
+ # ------------------------------------------------------------------ #
333
+ # Metadata
334
+ # ------------------------------------------------------------------ #
335
+
336
+ def page_size(self, page_idx: int) -> tuple[float, float]:
337
+ with self._open_page(page_idx) as page:
338
+ # rect: (left, bottom, right, top)
339
+ rect: tuple[float, float, float, float] = page.get_bbox()
340
+ try:
341
+ page_rotation = int(page.get_rotation()) % 360
342
+ except Exception:
343
+ page_rotation = 0
344
+ width = abs(rect[2] - rect[0])
345
+ height = abs(rect[1] - rect[3])
346
+ # PDFium 文本与渲染坐标已经应用页面旋转,页面尺寸必须使用相同视觉方向。
347
+ return (height, width) if page_rotation in {90, 270} else (width, height)
348
+
349
+ def page_rotation(self, page_idx: int) -> Literal[0, 90, 180, 270]:
350
+ """在线程锁保护下返回 PDF 页面字典声明的标准旋转角度。"""
351
+
352
+ with self._open_page(page_idx) as page:
353
+ try:
354
+ rotation = int(page.get_rotation()) % 360
355
+ except Exception:
356
+ rotation = 0
357
+ return rotation if rotation in {0, 90, 180, 270} else 0
358
+
359
+ # ------------------------------------------------------------------ #
360
+ # Rendering
361
+ # ------------------------------------------------------------------ #
362
+
363
+ def render_page(self, page_idx: int, *, scale: float | None = None) -> PDFPageImage:
364
+ if scale is None:
365
+ scale = self.render_scale
366
+ if scale <= 0:
367
+ raise ValueError("scale must be greater than 0")
368
+ with self._open_page(page_idx) as page:
369
+ return _page_to_image(page, scale, self.render_max_edge)
370
+
371
+ # TODO: move
372
+ def crop_image(self, bbox: BBox, page_idx: int) -> bytes:
373
+ image = self.render_page(page_idx)
374
+ crop = None
375
+ try:
376
+ crop = crop_pil_image(bbox, image.pil_image)
377
+ return image_to_bytes(crop, image_format="JPEG")
378
+ finally:
379
+ if crop is not None:
380
+ crop.close()
381
+ image.pil_image.close()
382
+
383
+ # ------------------------------------------------------------------ #
384
+ # Text
385
+ # ------------------------------------------------------------------ #
386
+
387
+ def page_char_count(self, page_idx: int) -> int:
388
+ with self._open_page(page_idx) as page:
389
+ textpage = None
390
+ try:
391
+ textpage = page.get_textpage()
392
+ n_chars = textpage.count_chars()
393
+ finally:
394
+ _try_close(textpage)
395
+ return cast(int, n_chars)
396
+
397
+ def get_page_chars(self, page_idx: int) -> list[Char]:
398
+ return self._get_page_text_geometry(page_idx, include_extended_geometry=False).chars
399
+
400
+ def get_page_chars_with_geometry(self, page_idx: int) -> PDFPageTextGeometry:
401
+ """一次读取字符、tight bbox 和字符原点,避免 Hybrid 重复打开 textpage。"""
402
+ return self._get_page_text_geometry(page_idx, include_extended_geometry=True)
403
+
404
+ def _extract_native_page(self, page_idx: int) -> _PDFPageSnapshot:
405
+ """在一次加锁打开中收集 Flash 页面证据,并共享 Path 子路径解码。"""
406
+
407
+ with self._open_page(page_idx) as page:
408
+ page_bbox = _normalize_pdf_page_bbox(page.get_bbox())
409
+ try:
410
+ raw_rotation = int(page.get_rotation()) % 360
411
+ except Exception:
412
+ raw_rotation = 0
413
+ rotation = raw_rotation if raw_rotation in {0, 90, 180, 270} else 0
414
+ drawings, paths = _extract_page_paths_and_lines(page, page_bbox, raw_rotation)
415
+ return _PDFPageSnapshot(
416
+ page_size=_drawing_page_size(page_bbox, raw_rotation),
417
+ rotation=cast(Literal[0, 90, 180, 270], rotation),
418
+ text_geometry=_extract_page_text_geometry(page, include_extended_geometry=True),
419
+ drawing_lines=drawings,
420
+ path_infos=paths,
421
+ image_infos=_extract_page_image_infos(page, page_bbox, raw_rotation),
422
+ form_bboxes=_extract_page_form_bboxes(page, page_bbox, raw_rotation),
423
+ signature_bboxes=_extract_page_signature_bboxes(
424
+ page,
425
+ page_bbox,
426
+ raw_rotation,
427
+ form_handle=getattr(self._pdf_doc, "formenv", None),
428
+ ),
429
+ link_annotations=_extract_page_link_annotations(page, self._pdf_doc.raw, page_bbox, raw_rotation),
430
+ )
431
+
432
+ def _get_page_text_geometry(
433
+ self,
434
+ page_idx: int,
435
+ *,
436
+ include_extended_geometry: bool,
437
+ ) -> PDFPageTextGeometry:
438
+ """在同一 PDFium textpage 生命周期内物化字符及可选扩展几何。"""
439
+ with self._open_page(page_idx) as page:
440
+ return _extract_page_text_geometry(page, include_extended_geometry=include_extended_geometry)
441
+
442
+ def get_page_lines(self, page_idx: int) -> list[Line]:
443
+ chars = self.get_page_chars(page_idx)
444
+ return get_lines_from_chars(chars)
445
+
446
+ # ------------------------------------------------------------------ #
447
+ # Drawing geometry
448
+ # ------------------------------------------------------------------ #
449
+
450
+ def get_page_drawing_lines(self, page_idx: int) -> list[PDFDrawingLine]:
451
+ """提取页面中可见的水平、竖直绘图线,并转换为页面左上原点坐标。"""
452
+ with self._open_page(page_idx) as page:
453
+ page_bbox = _normalize_pdf_page_bbox(page.get_bbox())
454
+ try:
455
+ page_rotation = int(page.get_rotation()) % 360
456
+ except Exception:
457
+ page_rotation = 0
458
+ return _extract_page_drawing_lines(page, page_bbox, page_rotation)
459
+
460
+ def get_page_path_infos(self, page_idx: int) -> list[PDFPathInfo]:
461
+ """提取页面及嵌套 Form 中的 Path 几何和绘制特征。"""
462
+ with self._open_page(page_idx) as page:
463
+ page_bbox = _normalize_pdf_page_bbox(page.get_bbox())
464
+ try:
465
+ page_rotation = int(page.get_rotation()) % 360
466
+ except Exception:
467
+ page_rotation = 0
468
+ return _extract_page_path_infos(page, page_bbox, page_rotation)
469
+
470
+ def get_page_image_bboxes(self, page_idx: int) -> list[BBox]:
471
+ """提取页面及嵌套 Form 中的点阵图 bbox,并转换为页面左上原点坐标。"""
472
+ with self._open_page(page_idx) as page:
473
+ page_bbox = _normalize_pdf_page_bbox(page.get_bbox())
474
+ try:
475
+ page_rotation = int(page.get_rotation()) % 360
476
+ except Exception:
477
+ page_rotation = 0
478
+ return _extract_page_image_bboxes(page, page_bbox, page_rotation)
479
+
480
+ def get_page_image_infos(self, page_idx: int) -> list[PDFImageInfo]:
481
+ """提取点阵图 bbox 与内容指纹,供 Flash 在认领正文前识别跨页重复图。"""
482
+
483
+ with self._open_page(page_idx) as page:
484
+ page_bbox = _normalize_pdf_page_bbox(page.get_bbox())
485
+ try:
486
+ page_rotation = int(page.get_rotation()) % 360
487
+ except Exception:
488
+ page_rotation = 0
489
+ return _extract_page_image_infos(page, page_bbox, page_rotation)
490
+
491
+ def get_page_form_bboxes(self, page_idx: int) -> list[BBox]:
492
+ """提取页面顶层 Form XObject bbox,并转换为页面左上原点坐标。"""
493
+ with self._open_page(page_idx) as page:
494
+ page_bbox = _normalize_pdf_page_bbox(page.get_bbox())
495
+ try:
496
+ page_rotation = int(page.get_rotation()) % 360
497
+ except Exception:
498
+ page_rotation = 0
499
+ return _extract_page_form_bboxes(page, page_bbox, page_rotation)
500
+
501
+ def get_page_signature_bboxes(self, page_idx: int) -> list[BBox]:
502
+ """提取带可见正常外观的数字签名控件,并转换为页面左上原点坐标。"""
503
+
504
+ with self._open_page(page_idx) as page:
505
+ page_bbox = _normalize_pdf_page_bbox(page.get_bbox())
506
+ try:
507
+ page_rotation = int(page.get_rotation()) % 360
508
+ except Exception:
509
+ page_rotation = 0
510
+ return _extract_page_signature_bboxes(
511
+ page,
512
+ page_bbox,
513
+ page_rotation,
514
+ form_handle=getattr(self._pdf_doc, "formenv", None),
515
+ )
516
+
517
+ def get_page_link_annotations(self, page_idx: int) -> list[PDFLinkAnnotation]:
518
+ """提取可见且目标安全的外部 URI Link 注解。"""
519
+
520
+ with self._open_page(page_idx) as page:
521
+ page_bbox = _normalize_pdf_page_bbox(page.get_bbox())
522
+ try:
523
+ page_rotation = int(page.get_rotation()) % 360
524
+ except Exception:
525
+ page_rotation = 0
526
+ return _extract_page_link_annotations(
527
+ page,
528
+ self._pdf_doc.raw,
529
+ page_bbox,
530
+ page_rotation,
531
+ )
532
+
533
+ # ------------------------------------------------------------------ #
534
+ # Classification
535
+ # ------------------------------------------------------------------ #
536
+
537
+ def classify(self) -> Literal["ocr", "txt"]:
538
+ """显式分类并在同一不可变文档实例内复用结果,不由文本提取隐式调用。"""
539
+ if self._classification is None:
540
+ self._classification = cast(Literal["ocr", "txt"], classify(self._pdf_doc, self.bytes))
541
+ return self._classification
542
+
543
+ # ------------------------------------------------------------------ #
544
+ # Visualization
545
+ # ------------------------------------------------------------------ #
546
+
547
+ def draw_layout_bbox(self, pages: list[PageInfo], output_path: str) -> None:
548
+ from .diagnostics import draw_layout_bbox
549
+
550
+ out_dir = os.path.dirname(output_path) or "."
551
+ filename = os.path.basename(output_path)
552
+ draw_layout_bbox(pages, self._pdf_bytes, out_dir, filename)
553
+
554
+ # ------------------------------------------------------------------ #
555
+ # Internal
556
+ # ------------------------------------------------------------------ #
557
+
558
+ @property
559
+ def _pdf_doc(self) -> pdfium.PdfDocument:
560
+ if self._pdf_doc_opened is None:
561
+ with pdfium_guard():
562
+ if self._pdf_doc_opened is None:
563
+ pdf_doc = pdfium.PdfDocument(self._pdf_bytes)
564
+ try:
565
+ # 表单环境必须在打开页面前初始化,签名字段类型才能沿 Parent 正确继承。
566
+ pdf_doc.init_forms()
567
+ except Exception:
568
+ # 异常表单不能阻断普通文本、绘图和渲染接口。
569
+ pass
570
+ self._pdf_doc_opened = pdf_doc
571
+ return self._pdf_doc_opened
572
+
573
+ @contextmanager
574
+ def _open_page(self, page_idx: int) -> Iterator[pdfium.PdfPage]:
575
+ """在统一字体运行时与共享锁内打开页面,离开时释放原生句柄。"""
576
+ with pdfium_guard():
577
+ page = None
578
+ try:
579
+ page = self._pdf_doc[page_idx]
580
+ yield page
581
+ finally:
582
+ _try_close(page)