edgeparse 0.2.3__tar.gz → 0.2.5__tar.gz

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 (225) hide show
  1. {edgeparse-0.2.3 → edgeparse-0.2.5}/Cargo.lock +5 -5
  2. {edgeparse-0.2.3 → edgeparse-0.2.5}/Cargo.toml +2 -1
  3. {edgeparse-0.2.3 → edgeparse-0.2.5}/PKG-INFO +2 -2
  4. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/output/markdown.rs +45 -0
  5. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/raster_table_ocr.rs +342 -56
  6. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/table_detector.rs +157 -0
  7. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/Cargo.toml +1 -1
  8. {edgeparse-0.2.3 → edgeparse-0.2.5}/pyproject.toml +1 -1
  9. {edgeparse-0.2.3 → edgeparse-0.2.5}/README.md +0 -0
  10. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/Cargo.toml +0 -0
  11. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/README.md +0 -0
  12. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/api/batch.rs +0 -0
  13. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/api/config.rs +0 -0
  14. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/api/config_loader.rs +0 -0
  15. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/api/filter.rs +0 -0
  16. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/api/mod.rs +0 -0
  17. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/hybrid.rs +0 -0
  18. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/lib.rs +0 -0
  19. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/models/bbox.rs +0 -0
  20. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/models/chunks.rs +0 -0
  21. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/models/content.rs +0 -0
  22. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/models/document.rs +0 -0
  23. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/models/enums.rs +0 -0
  24. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/models/list.rs +0 -0
  25. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/models/mod.rs +0 -0
  26. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/models/semantic.rs +0 -0
  27. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/models/table.rs +0 -0
  28. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/models/text.rs +0 -0
  29. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/output/csv.rs +0 -0
  30. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/output/docx.rs +0 -0
  31. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/output/html.rs +0 -0
  32. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/output/json.rs +0 -0
  33. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/output/legacy_json.rs +0 -0
  34. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/output/mod.rs +0 -0
  35. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/output/text.rs +0 -0
  36. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/output/toc_builder.rs +0 -0
  37. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/annotation_enrichment.rs +0 -0
  38. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/annotation_extractor.rs +0 -0
  39. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/bookmark_extractor.rs +0 -0
  40. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/chunk_parser.rs +0 -0
  41. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/data/glyphlist.txt +0 -0
  42. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/encryption.rs +0 -0
  43. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/font.rs +0 -0
  44. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/form_extractor.rs +0 -0
  45. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/graphics_state.rs +0 -0
  46. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/hyperlink_extractor.rs +0 -0
  47. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/image_extractor.rs +0 -0
  48. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/line_extractor.rs +0 -0
  49. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/loader.rs +0 -0
  50. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/metadata_writer.rs +0 -0
  51. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/mod.rs +0 -0
  52. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/page_info.rs +0 -0
  53. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/text_extractor.rs +0 -0
  54. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/error_recovery.rs +0 -0
  55. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/logging.rs +0 -0
  56. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/mod.rs +0 -0
  57. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/orchestrator.rs +0 -0
  58. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/parallel.rs +0 -0
  59. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/boxed_heading_promoter.rs +0 -0
  60. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/caption_linker.rs +0 -0
  61. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/cluster_table_detector.rs +0 -0
  62. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/column_detector.rs +0 -0
  63. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/content_filter.rs +0 -0
  64. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/content_sanitizer.rs +0 -0
  65. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/cross_page_linker.rs +0 -0
  66. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/figure_detector.rs +0 -0
  67. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/footnote_detector.rs +0 -0
  68. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/footnote_linker.rs +0 -0
  69. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/header_footer.rs +0 -0
  70. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/heading_detector.rs +0 -0
  71. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/id_assignment.rs +0 -0
  72. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/list_detector.rs +0 -0
  73. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/list_pass2.rs +0 -0
  74. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/mod.rs +0 -0
  75. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/nesting_level.rs +0 -0
  76. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/output_builder.rs +0 -0
  77. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/paragraph_detector.rs +0 -0
  78. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/reading_order.rs +0 -0
  79. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/table_content_assigner.rs +0 -0
  80. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/text_block_grouper.rs +0 -0
  81. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/text_line_grouper.rs +0 -0
  82. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/toc_detector.rs +0 -0
  83. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/watermark_detector.rs +0 -0
  84. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/tagged/mod.rs +0 -0
  85. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/tagged/processor.rs +0 -0
  86. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/tagged/struct_tree.rs +0 -0
  87. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/utils/diff.rs +0 -0
  88. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/utils/font_metrics_cache.rs +0 -0
  89. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/utils/image_dedup.rs +0 -0
  90. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/utils/language_detector.rs +0 -0
  91. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/utils/layout_analysis.rs +0 -0
  92. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/utils/mod.rs +0 -0
  93. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/utils/page_range.rs +0 -0
  94. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/utils/sanitizer.rs +0 -0
  95. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/utils/statistics.rs +0 -0
  96. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/utils/text_normalizer.rs +0 -0
  97. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/utils/xref_index.rs +0 -0
  98. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/utils/xycut.rs +0 -0
  99. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/tests/integration_tests.rs +0 -0
  100. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/tests/perf_benchmarks.rs +0 -0
  101. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-python/Cargo.toml +0 -0
  102. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-python/src/lib.rs +0 -0
  103. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/.cargo_vcs_info.json +0 -0
  104. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/.chglog/CHANGELOG.tpl.md +0 -0
  105. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/.chglog/config.yml +0 -0
  106. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/.editorconfig +0 -0
  107. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/.gitattributes +0 -0
  108. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/.github/workflows/ci.yml +0 -0
  109. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/.gitignore +0 -0
  110. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/.vscode/launch.json +0 -0
  111. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/.vscode/settings.json +0 -0
  112. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/CHANGELOG.md +0 -0
  113. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/LICENSE +0 -0
  114. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/README.md +0 -0
  115. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/assets/AnnotationDemo.pdf +0 -0
  116. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/assets/Incremental.pdf +0 -0
  117. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/assets/encrypted.pdf +0 -0
  118. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/assets/example.pdf +0 -0
  119. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/assets/pdf_icon.jpg +0 -0
  120. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/assets/supported_color_type/L16.png +0 -0
  121. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/assets/supported_color_type/L8.jpg +0 -0
  122. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/assets/supported_color_type/La16.png +0 -0
  123. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/assets/supported_color_type/La8.png +0 -0
  124. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/assets/supported_color_type/Rgb16.png +0 -0
  125. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/assets/supported_color_type/Rgb8.jpg +0 -0
  126. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/assets/supported_color_type/Rgba16.png +0 -0
  127. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/assets/supported_color_type/Rgba8.png +0 -0
  128. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/assets/unicode.pdf +0 -0
  129. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/benches/datetime.rs +0 -0
  130. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/benches/parse.rs +0 -0
  131. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/add_barcode.rs +0 -0
  132. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/analyze_object_streams.rs +0 -0
  133. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/analyze_page_contents.rs +0 -0
  134. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/analyze_pdf.rs +0 -0
  135. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/analyze_references.rs +0 -0
  136. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/check_raw_objstream.rs +0 -0
  137. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/compress_existing_pdf.rs +0 -0
  138. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/create.rs +0 -0
  139. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/create_text_heavy_pdf.rs +0 -0
  140. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/debug_compression_detailed.rs +0 -0
  141. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/debug_compression_full.rs +0 -0
  142. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/debug_object_stream.rs +0 -0
  143. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/debug_save_with_objstreams.rs +0 -0
  144. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/decrypt.rs +0 -0
  145. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/encrypt.rs +0 -0
  146. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/extract_text.rs +0 -0
  147. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/extract_toc.rs +0 -0
  148. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/final_compression_test.rs +0 -0
  149. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/inspect_object_stream.rs +0 -0
  150. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/merge.rs +0 -0
  151. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/object_streams.rs +0 -0
  152. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/print_annotations.rs +0 -0
  153. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/replace_partial_text.rs +0 -0
  154. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/rotate.rs +0 -0
  155. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/test_better_compression.rs +0 -0
  156. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/test_decryption.rs +0 -0
  157. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/test_direct_objstream.rs +0 -0
  158. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/test_filter_roundtrip.rs +0 -0
  159. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/test_metadata.rs +0 -0
  160. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/test_object_stream_creation.rs +0 -0
  161. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/test_object_stream_roundtrip.rs +0 -0
  162. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/test_real_pdf_compression_with_fix.rs +0 -0
  163. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/test_structural_compression.rs +0 -0
  164. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/verify_decryption.rs +0 -0
  165. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/verify_page_compression.rs +0 -0
  166. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/verify_pdf.rs +0 -0
  167. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/verify_trailer_compression_fix.rs +0 -0
  168. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/rustfmt.toml +0 -0
  169. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/bookmarks.rs +0 -0
  170. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/cmap_section.rs +0 -0
  171. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/common_data_structures/mod.rs +0 -0
  172. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/content.rs +0 -0
  173. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/creator.rs +0 -0
  174. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/datetime.rs +0 -0
  175. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/destinations.rs +0 -0
  176. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/document.rs +0 -0
  177. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/encodings/cmap.rs +0 -0
  178. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/encodings/glyphnames.rs +0 -0
  179. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/encodings/mappings.rs +0 -0
  180. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/encodings/mod.rs +0 -0
  181. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/encryption/algorithms.rs +0 -0
  182. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/encryption/crypt_filters.rs +0 -0
  183. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/encryption/pkcs5.rs +0 -0
  184. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/encryption/rc4.rs +0 -0
  185. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/encryption.rs +0 -0
  186. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/error.rs +0 -0
  187. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/filters/mod.rs +0 -0
  188. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/filters/png.rs +0 -0
  189. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/font.rs +0 -0
  190. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/incremental_document.rs +0 -0
  191. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/lib.rs +0 -0
  192. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/object.rs +0 -0
  193. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/object_stream.rs +0 -0
  194. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/outlines.rs +0 -0
  195. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/parser/cmap_parser.rs +0 -0
  196. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/parser/mod.rs +0 -0
  197. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/parser_aux.rs +0 -0
  198. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/processor.rs +0 -0
  199. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/reader.rs +0 -0
  200. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/save_options.rs +0 -0
  201. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/toc.rs +0 -0
  202. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/writer.rs +0 -0
  203. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/xobject.rs +0 -0
  204. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/xref.rs +0 -0
  205. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/annotation.rs +0 -0
  206. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/catalog_compression_integration_test.rs +0 -0
  207. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/decryption.rs +0 -0
  208. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/extract_test.rs +0 -0
  209. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/font_test.rs +0 -0
  210. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/incremental_document.rs +0 -0
  211. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/metadata_test.rs +0 -0
  212. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/modify.rs +0 -0
  213. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/object_stream_comprehensive_test.rs +0 -0
  214. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/object_stream_edge_cases_test.rs +0 -0
  215. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/object_stream_methods_test.rs +0 -0
  216. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/object_stream_performance_test.rs +0 -0
  217. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/resources/fonts/Montserrat-Regular.ttf +0 -0
  218. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/simple_object_stream_test.rs +0 -0
  219. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/trailer_reference_compression_test.rs +0 -0
  220. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/unicode.rs +0 -0
  221. {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/utils.rs +0 -0
  222. {edgeparse-0.2.3 → edgeparse-0.2.5}/edgeparse/__init__.py +0 -0
  223. {edgeparse-0.2.3 → edgeparse-0.2.5}/edgeparse/_types.py +0 -0
  224. {edgeparse-0.2.3 → edgeparse-0.2.5}/edgeparse/cli.py +0 -0
  225. {edgeparse-0.2.3 → edgeparse-0.2.5}/edgeparse/py.typed +0 -0
@@ -636,7 +636,7 @@ dependencies = [
636
636
 
637
637
  [[package]]
638
638
  name = "edgeparse-cli"
639
- version = "0.2.3"
639
+ version = "0.2.5"
640
640
  dependencies = [
641
641
  "anyhow",
642
642
  "clap",
@@ -650,7 +650,7 @@ dependencies = [
650
650
 
651
651
  [[package]]
652
652
  name = "edgeparse-core"
653
- version = "0.2.3"
653
+ version = "0.2.5"
654
654
  dependencies = [
655
655
  "anyhow",
656
656
  "base64",
@@ -674,7 +674,7 @@ dependencies = [
674
674
 
675
675
  [[package]]
676
676
  name = "edgeparse-node"
677
- version = "0.2.3"
677
+ version = "0.2.5"
678
678
  dependencies = [
679
679
  "edgeparse-core",
680
680
  "napi",
@@ -686,7 +686,7 @@ dependencies = [
686
686
 
687
687
  [[package]]
688
688
  name = "edgeparse-python"
689
- version = "0.2.3"
689
+ version = "0.2.5"
690
690
  dependencies = [
691
691
  "edgeparse-core",
692
692
  "pyo3",
@@ -695,7 +695,7 @@ dependencies = [
695
695
 
696
696
  [[package]]
697
697
  name = "edgeparse-wasm"
698
- version = "0.2.3"
698
+ version = "0.2.5"
699
699
  dependencies = [
700
700
  "console_error_panic_hook",
701
701
  "console_log",
@@ -3,10 +3,11 @@ resolver = "2"
3
3
  members = ["crates/edgeparse-core", "crates/pdf-cos", "crates/edgeparse-python"]
4
4
 
5
5
  [workspace.package]
6
- version = "0.2.3"
6
+ version = "0.2.5"
7
7
  edition = "2021"
8
8
  rust-version = "1.85"
9
9
  license = "Apache-2.0"
10
+ homepage = "https://www.edgeparse.com"
10
11
  repository = "https://github.com/raphaelmansuy/edgeparse"
11
12
  description = "EdgeParse — High-performance PDF-to-structured-data extraction engine"
12
13
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: edgeparse
3
- Version: 0.2.3
3
+ Version: 0.2.5
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: Apache Software License
@@ -18,7 +18,7 @@ Keywords: pdf,extraction,markdown,rag,llm
18
18
  Requires-Python: >=3.9
19
19
  Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
20
20
  Project-URL: Documentation, https://github.com/raphaelmansuy/edgeparse/tree/main/docs
21
- Project-URL: Homepage, https://github.com/raphaelmansuy/edgeparse
21
+ Project-URL: Homepage, https://www.edgeparse.com
22
22
  Project-URL: Repository, https://github.com/raphaelmansuy/edgeparse
23
23
 
24
24
  # edgeparse
@@ -1445,6 +1445,9 @@ fn render_layout_single_caption_chart_document_cached(
1445
1445
  if doc.number_of_pages != 1 {
1446
1446
  return None;
1447
1447
  }
1448
+ if document_has_populated_table(doc) {
1449
+ return None;
1450
+ }
1448
1451
 
1449
1452
  let caption_indices = doc
1450
1453
  .kids
@@ -1555,6 +1558,22 @@ fn render_layout_single_caption_chart_document_cached(
1555
1558
  Some(output.trim_end().to_string() + "\n")
1556
1559
  }
1557
1560
 
1561
+ fn document_has_populated_table(doc: &PdfDocument) -> bool {
1562
+ doc.kids.iter().any(|element| {
1563
+ table_border_from_element(element).is_some_and(|table| {
1564
+ table.num_rows >= 2
1565
+ && table.num_columns >= 2
1566
+ && table.rows.iter().any(|row| {
1567
+ row.cells
1568
+ .iter()
1569
+ .filter(|cell| !cell_text_content(cell).trim().is_empty())
1570
+ .count()
1571
+ >= 2
1572
+ })
1573
+ })
1574
+ })
1575
+ }
1576
+
1558
1577
  fn looks_like_chart_noise_element(_element: &ContentElement, text: &str) -> bool {
1559
1578
  if text.is_empty() {
1560
1579
  return false;
@@ -13174,6 +13193,32 @@ mod tests {
13174
13193
  assert!(md.contains("| kaolinite | 10 |"));
13175
13194
  }
13176
13195
 
13196
+ #[test]
13197
+ fn test_single_caption_chart_renderer_skips_documents_with_populated_tables() {
13198
+ let mut doc = PdfDocument::new("table-with-caption.pdf".to_string());
13199
+ doc.number_of_pages = 1;
13200
+ for idx in 0..10 {
13201
+ let bottom = 720.0 - idx as f64 * 18.0;
13202
+ doc.kids.push(make_paragraph(
13203
+ "Explanatory body text that should remain outside the chart-only renderer.",
13204
+ bottom,
13205
+ bottom + 10.0,
13206
+ ));
13207
+ }
13208
+ doc.kids.push(make_paragraph(
13209
+ "Figure 7.2: Kinematic Viscosity of Water at Atmospheric Pressure.",
13210
+ 150.0,
13211
+ 162.0,
13212
+ ));
13213
+ doc.kids.push(make_two_column_table(&[
13214
+ ("Temperature", "Viscosity"),
13215
+ ("20", "1.004"),
13216
+ ("25", "0.893"),
13217
+ ]));
13218
+
13219
+ assert!(render_layout_single_caption_chart_document(&doc).is_none());
13220
+ }
13221
+
13177
13222
  #[test]
13178
13223
  fn test_blank_right_column_table_is_not_misrendered_as_toc() {
13179
13224
  let mut doc = PdfDocument::new("flocculation-table.pdf".to_string());
@@ -67,6 +67,7 @@ const MIN_NUMERIC_TABLE_MEDIAN_FILL_RATIO: f64 = 0.40;
67
67
  const MIN_BORDERED_CELL_DARK_RATIO: f64 = 0.03;
68
68
  const MIN_BORDERED_INKED_CELL_RATIO: f64 = 0.18;
69
69
  const MIN_BORDERED_ROWS_WITH_INK: usize = 2;
70
+ const MAX_BORDERED_TABLE_PER_CELL_FALLBACK_CELLS: usize = 24;
70
71
  const MIN_BRIGHT_PHOTO_MID_TONE_RATIO: f64 = 0.24;
71
72
  const MIN_BRIGHT_PHOTO_HISTOGRAM_BINS: usize = 8;
72
73
  const MIN_BRIGHT_PHOTO_ENTROPY: f64 = 1.6;
@@ -502,17 +503,22 @@ pub fn recover_page_raster_table_cell_text(
502
503
  }
503
504
 
504
505
  let native_text_chars = page_native_text_chars(elements);
505
- if native_text_chars > MAX_NATIVE_TEXT_CHARS_FOR_PAGE_RASTER_OCR {
506
- return;
507
- }
508
506
 
509
507
  let candidate_indices: Vec<usize> = elements
510
508
  .iter()
511
509
  .enumerate()
512
510
  .filter_map(|(idx, elem)| {
513
- table_candidate_ref(elem)
514
- .filter(|table| table_needs_page_raster_ocr(table))
515
- .map(|_| idx)
511
+ let table = table_candidate_ref(elem)?;
512
+ let local_text_chars = native_text_chars_in_region(elements, &table.bbox);
513
+ if !table_needs_page_raster_ocr(table) {
514
+ return None;
515
+ }
516
+ if native_text_chars > MAX_NATIVE_TEXT_CHARS_FOR_PAGE_RASTER_OCR
517
+ && local_text_chars > MAX_NATIVE_TEXT_CHARS_FOR_PAGE_RASTER_OCR
518
+ {
519
+ return None;
520
+ }
521
+ Some(idx)
516
522
  })
517
523
  .take(MAX_EMPTY_TABLES_FOR_PAGE_RASTER_OCR)
518
524
  .collect();
@@ -590,6 +596,38 @@ fn table_candidate_mut(elem: &mut ContentElement) -> Option<&mut TableBorder> {
590
596
  }
591
597
  }
592
598
 
599
+ fn page_native_text_chars(elements: &[ContentElement]) -> usize {
600
+ native_text_chars_in_region(elements, &BoundingBox::new(None, f64::MIN, f64::MIN, f64::MAX, f64::MAX))
601
+ }
602
+
603
+ fn native_text_chars_in_region(elements: &[ContentElement], region: &BoundingBox) -> usize {
604
+ elements
605
+ .iter()
606
+ .filter(|elem| region.overlaps(elem.bbox()))
607
+ .map(|elem| match elem {
608
+ ContentElement::Paragraph(p) => p.base.value().chars().count(),
609
+ ContentElement::Heading(h) => h.base.base.value().chars().count(),
610
+ ContentElement::NumberHeading(h) => h.base.base.base.value().chars().count(),
611
+ ContentElement::TextBlock(tb) => tb.value().chars().count(),
612
+ ContentElement::TextLine(tl) => tl.value().chars().count(),
613
+ ContentElement::TextChunk(tc) => tc.value.chars().count(),
614
+ ContentElement::List(list) => list
615
+ .list_items
616
+ .iter()
617
+ .flat_map(|item| item.contents.iter())
618
+ .map(|content| match content {
619
+ ContentElement::Paragraph(p) => p.base.value().chars().count(),
620
+ ContentElement::TextBlock(tb) => tb.value().chars().count(),
621
+ ContentElement::TextLine(tl) => tl.value().chars().count(),
622
+ ContentElement::TextChunk(tc) => tc.value.chars().count(),
623
+ _ => 0,
624
+ })
625
+ .sum(),
626
+ _ => 0,
627
+ })
628
+ .sum()
629
+ }
630
+
593
631
  fn recover_from_page_images(
594
632
  input_path: &Path,
595
633
  temp_dir: &Path,
@@ -660,46 +698,54 @@ fn recover_from_page_images(
660
698
  recovered
661
699
  }
662
700
 
663
- fn page_native_text_chars(elements: &[ContentElement]) -> usize {
664
- elements
701
+ fn table_needs_page_raster_ocr(table: &TableBorder) -> bool {
702
+ if table.num_rows < 1 || table.num_columns < 2 {
703
+ return false;
704
+ }
705
+
706
+ let total_cells = table.rows.iter().map(|row| row.cells.len()).sum::<usize>();
707
+ if total_cells == 0 {
708
+ return false;
709
+ }
710
+
711
+ let text_cells = table_text_cell_count(table);
712
+ let text_cell_ratio = text_cells as f64 / total_cells as f64;
713
+ text_cells == 0 || text_cell_ratio < MIN_RASTER_TABLE_TEXT_CELL_RATIO
714
+ }
715
+
716
+ fn table_text_cell_count(table: &TableBorder) -> usize {
717
+ table
718
+ .rows
665
719
  .iter()
666
- .map(|elem| match elem {
667
- ContentElement::Paragraph(p) => p.base.value().chars().count(),
668
- ContentElement::Heading(h) => h.base.base.value().chars().count(),
669
- ContentElement::NumberHeading(h) => h.base.base.base.value().chars().count(),
670
- ContentElement::TextBlock(tb) => tb.value().chars().count(),
671
- ContentElement::TextLine(tl) => tl.value().chars().count(),
672
- ContentElement::TextChunk(tc) => tc.value.chars().count(),
673
- ContentElement::List(list) => list
674
- .list_items
675
- .iter()
676
- .flat_map(|item| item.contents.iter())
677
- .map(|content| match content {
678
- ContentElement::Paragraph(p) => p.base.value().chars().count(),
679
- ContentElement::TextBlock(tb) => tb.value().chars().count(),
680
- ContentElement::TextLine(tl) => tl.value().chars().count(),
681
- ContentElement::TextChunk(tc) => tc.value.chars().count(),
682
- _ => 0,
683
- })
684
- .sum(),
685
- _ => 0,
686
- })
687
- .sum()
720
+ .flat_map(|row| row.cells.iter())
721
+ .filter(|cell| cell_has_substantive_text(cell))
722
+ .count()
688
723
  }
689
724
 
690
- fn table_needs_page_raster_ocr(table: &TableBorder) -> bool {
691
- table.num_rows >= 1
692
- && table.num_columns >= 2
693
- && table
694
- .rows
695
- .iter()
696
- .flat_map(|row| row.cells.iter())
697
- .all(|cell| {
698
- !cell
699
- .content
700
- .iter()
701
- .any(|token| matches!(token.token_type, TableTokenType::Text))
702
- })
725
+ fn cell_has_substantive_text(cell: &TableBorderCell) -> bool {
726
+ let has_token_text = cell.content.iter().any(|token| {
727
+ matches!(token.token_type, TableTokenType::Text)
728
+ && token.base.value.chars().any(|ch| ch.is_alphanumeric())
729
+ });
730
+ if has_token_text {
731
+ return true;
732
+ }
733
+
734
+ cell.contents.iter().any(|elem| match elem {
735
+ ContentElement::Paragraph(p) => p.base.value().chars().any(|ch| ch.is_alphanumeric()),
736
+ ContentElement::Heading(h) => h.base.base.value().chars().any(|ch| ch.is_alphanumeric()),
737
+ ContentElement::NumberHeading(h) => h
738
+ .base
739
+ .base
740
+ .base
741
+ .value()
742
+ .chars()
743
+ .any(|ch| ch.is_alphanumeric()),
744
+ ContentElement::TextBlock(tb) => tb.value().chars().any(|ch| ch.is_alphanumeric()),
745
+ ContentElement::TextLine(tl) => tl.value().chars().any(|ch| ch.is_alphanumeric()),
746
+ ContentElement::TextChunk(tc) => tc.value.chars().any(|ch| ch.is_alphanumeric()),
747
+ _ => false,
748
+ })
703
749
  }
704
750
 
705
751
  fn enrich_empty_table_from_page_raster(
@@ -763,10 +809,10 @@ fn enrich_empty_table_from_page_raster(
763
809
  let cropped = gray
764
810
  .view(crop_left, crop_top, crop_width, crop_height)
765
811
  .to_image();
766
- if is_obvious_bar_chart_raster(&cropped)
767
- || is_natural_photograph_raster(&cropped)
768
- || is_dark_ui_screenshot_raster(&cropped)
769
- {
812
+ let is_bar_chart = is_obvious_bar_chart_raster(&cropped);
813
+ let is_photo = is_natural_photograph_raster(&cropped);
814
+ let is_ui = is_dark_ui_screenshot_raster(&cropped);
815
+ if is_bar_chart || is_photo || is_ui {
770
816
  return;
771
817
  }
772
818
  let bordered = expand_white_border(&cropped, TABLE_RASTER_OCR_BORDER_PX);
@@ -785,7 +831,8 @@ fn enrich_empty_table_from_page_raster(
785
831
  fill_cells_with_per_cell_ocr(gray, table, &empty_cells);
786
832
  return;
787
833
  }
788
- if looks_like_chart_label_ocr(&words) {
834
+ let chart_like = looks_like_chart_label_ocr(&words);
835
+ if chart_like {
789
836
  return;
790
837
  }
791
838
 
@@ -1815,8 +1862,12 @@ fn build_numeric_table_border(words: &[OcrWord], image: &ImageChunk) -> Option<T
1815
1862
  .partial_cmp(&a.top_y)
1816
1863
  .unwrap_or(std::cmp::Ordering::Equal)
1817
1864
  });
1818
- let x_coordinates =
1819
- build_boundaries_from_centers(&centers, image.bbox.left_x, image.bbox.right_x);
1865
+ let x_coordinates = build_boundaries_from_centers(
1866
+ &centers,
1867
+ image.bbox.left_x,
1868
+ image.bbox.right_x,
1869
+ image_width,
1870
+ );
1820
1871
  let row_bounds: Vec<(f64, f64)> = built_rows
1821
1872
  .iter()
1822
1873
  .map(|row| (row.top_y, row.bottom_y))
@@ -2069,8 +2120,12 @@ fn build_structured_ocr_table_border(words: &[OcrWord], image: &ImageChunk) -> O
2069
2120
  .partial_cmp(&a.top_y)
2070
2121
  .unwrap_or(std::cmp::Ordering::Equal)
2071
2122
  });
2072
- let x_coordinates =
2073
- build_boundaries_from_centers(&centers, image.bbox.left_x, image.bbox.right_x);
2123
+ let x_coordinates = build_boundaries_from_centers(
2124
+ &centers,
2125
+ image.bbox.left_x,
2126
+ image.bbox.right_x,
2127
+ image_width,
2128
+ );
2074
2129
  let row_bounds: Vec<(f64, f64)> = built_rows
2075
2130
  .iter()
2076
2131
  .map(|row| (row.top_y, row.bottom_y))
@@ -2345,6 +2400,11 @@ fn recover_bordered_raster_table_from_gray(
2345
2400
  let mut non_empty_cells = 0usize;
2346
2401
  let mut rows_with_text = 0usize;
2347
2402
  let mut total_cells = 0usize;
2403
+ let mut whole_table_buckets =
2404
+ collect_bordered_table_ocr_buckets(gray, &grid, num_rows, num_cols)
2405
+ .unwrap_or_else(|| vec![Vec::new(); num_rows * num_cols]);
2406
+ let allow_per_cell_fallback =
2407
+ num_rows.saturating_mul(num_cols) <= MAX_BORDERED_TABLE_PER_CELL_FALLBACK_CELLS;
2348
2408
  for row_idx in 0..num_rows {
2349
2409
  let row_bbox = BoundingBox::new(
2350
2410
  image.bbox.page_number,
@@ -2368,8 +2428,27 @@ fn recover_bordered_raster_table_from_gray(
2368
2428
  x_coordinates[col_idx + 1],
2369
2429
  y_coordinates[row_idx],
2370
2430
  );
2371
- let text = extract_raster_cell_text(gray, row_idx, col_idx, x1, y1, x2, y2)
2372
- .unwrap_or_default();
2431
+ let bucket_idx = row_idx * num_cols + col_idx;
2432
+ let text = if let Some(parts) = whole_table_buckets.get_mut(bucket_idx) {
2433
+ if parts.is_empty() {
2434
+ String::new()
2435
+ } else {
2436
+ parts.sort_by(|a, b| (a.0, a.1).cmp(&(b.0, b.1)));
2437
+ let raw = parts
2438
+ .iter()
2439
+ .map(|(_, _, text)| text.as_str())
2440
+ .collect::<Vec<_>>()
2441
+ .join(" ");
2442
+ normalize_raster_cell_text(row_idx, col_idx, raw)
2443
+ }
2444
+ } else {
2445
+ String::new()
2446
+ };
2447
+ let text = if text.is_empty() && allow_per_cell_fallback {
2448
+ extract_raster_cell_text(gray, row_idx, col_idx, x1, y1, x2, y2).unwrap_or_default()
2449
+ } else {
2450
+ text
2451
+ };
2373
2452
  total_cells += 1;
2374
2453
 
2375
2454
  let mut content = Vec::new();
@@ -2456,6 +2535,69 @@ fn recover_bordered_raster_table_from_gray(
2456
2535
  })
2457
2536
  }
2458
2537
 
2538
+ fn collect_bordered_table_ocr_buckets(
2539
+ gray: &GrayImage,
2540
+ grid: &RasterTableGrid,
2541
+ num_rows: usize,
2542
+ num_cols: usize,
2543
+ ) -> Option<Vec<Vec<(u32, u32, String)>>> {
2544
+ if num_rows == 0 || num_cols == 0 {
2545
+ return None;
2546
+ }
2547
+
2548
+ let bordered = expand_white_border(gray, TABLE_RASTER_OCR_BORDER_PX);
2549
+ let scaled = image::imageops::resize(
2550
+ &bordered,
2551
+ bordered.width() * OCR_SCALE_FACTOR,
2552
+ bordered.height() * OCR_SCALE_FACTOR,
2553
+ image::imageops::FilterType::Lanczos3,
2554
+ );
2555
+ let words = run_tesseract_tsv_words_best(&scaled, &["6", "11"], |_| true)?;
2556
+ if words.is_empty() || looks_like_chart_label_ocr(&words) {
2557
+ return None;
2558
+ }
2559
+
2560
+ let mut buckets = vec![Vec::new(); num_rows * num_cols];
2561
+ let scale = f64::from(OCR_SCALE_FACTOR);
2562
+ let border = f64::from(TABLE_RASTER_OCR_BORDER_PX);
2563
+
2564
+ for word in words {
2565
+ let cx_scaled = f64::from(word.left) + f64::from(word.width) / 2.0;
2566
+ let cy_scaled = f64::from(word.top) + f64::from(word.height) / 2.0;
2567
+
2568
+ let cx = cx_scaled / scale - border;
2569
+ let cy = cy_scaled / scale - border;
2570
+ if cx < 0.0 || cy < 0.0 {
2571
+ continue;
2572
+ }
2573
+
2574
+ let cx = match u32::try_from(cx.round() as i64) {
2575
+ Ok(value) => value,
2576
+ Err(_) => continue,
2577
+ };
2578
+ let cy = match u32::try_from(cy.round() as i64) {
2579
+ Ok(value) => value,
2580
+ Err(_) => continue,
2581
+ };
2582
+
2583
+ let col_idx = grid
2584
+ .vertical_lines
2585
+ .windows(2)
2586
+ .position(|span| cx >= span[0] && cx < span[1]);
2587
+ let row_idx = grid
2588
+ .horizontal_lines
2589
+ .windows(2)
2590
+ .position(|span| cy >= span[0] && cy < span[1]);
2591
+ let (Some(row_idx), Some(col_idx)) = (row_idx, col_idx) else {
2592
+ continue;
2593
+ };
2594
+
2595
+ buckets[row_idx * num_cols + col_idx].push((cy, cx, word.text));
2596
+ }
2597
+
2598
+ Some(buckets)
2599
+ }
2600
+
2459
2601
  fn is_obvious_bar_chart_raster(gray: &GrayImage) -> bool {
2460
2602
  let width = gray.width();
2461
2603
  let height = gray.height();
@@ -2897,11 +3039,28 @@ fn merge_runs(values: impl Iterator<Item = u32>) -> Vec<(u32, u32)> {
2897
3039
  runs
2898
3040
  }
2899
3041
 
2900
- fn build_boundaries_from_centers(centers: &[f64], left_edge: f64, right_edge: f64) -> Vec<f64> {
3042
+ fn build_boundaries_from_centers(
3043
+ centers: &[f64],
3044
+ left_edge: f64,
3045
+ right_edge: f64,
3046
+ image_width: u32,
3047
+ ) -> Vec<f64> {
2901
3048
  let mut boundaries = Vec::with_capacity(centers.len() + 1);
2902
3049
  boundaries.push(left_edge);
3050
+ if centers.len() < 2 || image_width == 0 || right_edge <= left_edge {
3051
+ boundaries.push(right_edge.max(left_edge));
3052
+ return boundaries;
3053
+ }
3054
+
3055
+ let page_width = right_edge - left_edge;
3056
+ let mut previous = left_edge;
2903
3057
  for pair in centers.windows(2) {
2904
- boundaries.push((pair[0] + pair[1]) / 2.0);
3058
+ let midpoint_px = ((pair[0] + pair[1]) / 2.0).clamp(0.0, f64::from(image_width));
3059
+ let boundary =
3060
+ left_edge + midpoint_px / f64::from(image_width) * page_width;
3061
+ let boundary = boundary.clamp(previous, right_edge);
3062
+ boundaries.push(boundary);
3063
+ previous = boundary;
2905
3064
  }
2906
3065
  boundaries.push(right_edge);
2907
3066
  boundaries
@@ -4251,6 +4410,7 @@ fn parse_pdfimages_list(output: &str) -> Vec<PdfImagesListEntry> {
4251
4410
  mod tests {
4252
4411
  use super::*;
4253
4412
  use image::GrayImage;
4413
+ use crate::models::enums::{PdfLayer, TextFormat, TextType};
4254
4414
 
4255
4415
  fn image_chunk() -> ImageChunk {
4256
4416
  ImageChunk {
@@ -4284,6 +4444,28 @@ mod tests {
4284
4444
  }
4285
4445
  }
4286
4446
 
4447
+ fn text_chunk(value: &str, bbox: BoundingBox) -> TextChunk {
4448
+ TextChunk {
4449
+ value: value.to_string(),
4450
+ bbox,
4451
+ font_name: "Helvetica".to_string(),
4452
+ font_size: 12.0,
4453
+ font_weight: 400.0,
4454
+ italic_angle: 0.0,
4455
+ font_color: "#000000".to_string(),
4456
+ contrast_ratio: 21.0,
4457
+ symbol_ends: Vec::new(),
4458
+ text_format: TextFormat::Normal,
4459
+ text_type: TextType::Regular,
4460
+ pdf_layer: PdfLayer::Main,
4461
+ ocg_visible: true,
4462
+ index: None,
4463
+ page_number: Some(1),
4464
+ level: None,
4465
+ mcid: None,
4466
+ }
4467
+ }
4468
+
4287
4469
  fn test_cell_text(cell: &TableBorderCell) -> String {
4288
4470
  cell.content
4289
4471
  .iter()
@@ -4340,6 +4522,40 @@ mod tests {
4340
4522
  assert_eq!(test_cell_text(&table.rows[3].cells[2]), "control");
4341
4523
  }
4342
4524
 
4525
+ #[test]
4526
+ fn test_structured_ocr_table_border_scales_column_boundaries_to_page_bbox() {
4527
+ let image = ImageChunk {
4528
+ bbox: BoundingBox::new(Some(1), 56.6929, 163.6519, 555.3071, 442.0069),
4529
+ index: Some(1),
4530
+ level: None,
4531
+ };
4532
+ let words = vec![
4533
+ word_at((1, 1, 1), 10, 10, 110, "TempC"),
4534
+ word_at((1, 1, 1), 255, 10, 150, "KinViscA"),
4535
+ word_at((1, 1, 1), 520, 10, 110, "TempC"),
4536
+ word_at((1, 1, 1), 760, 10, 170, "KinViscB"),
4537
+ word_at((1, 1, 2), 10, 44, 24, "0"),
4538
+ word_at((1, 1, 2), 255, 44, 130, "1.793E-06"),
4539
+ word_at((1, 1, 2), 520, 44, 28, "25"),
4540
+ word_at((1, 1, 2), 760, 44, 130, "8.930E-07"),
4541
+ word_at((1, 1, 3), 10, 78, 24, "1"),
4542
+ word_at((1, 1, 3), 255, 78, 130, "1.732E-06"),
4543
+ word_at((1, 1, 3), 520, 78, 28, "26"),
4544
+ word_at((1, 1, 3), 760, 78, 130, "8.760E-07"),
4545
+ ];
4546
+
4547
+ let table = build_structured_ocr_table_border(&words, &image).expect("structured table");
4548
+
4549
+ assert_eq!(table.num_columns, 4);
4550
+ assert_eq!(table.num_rows, 3);
4551
+ assert_eq!(test_cell_text(&table.rows[1].cells[1]), "1.793E-06");
4552
+ assert!(table.x_coordinates.windows(2).all(|pair| pair[1] >= pair[0]));
4553
+ assert!(table
4554
+ .x_coordinates
4555
+ .iter()
4556
+ .all(|x| *x >= image.bbox.left_x && *x <= image.bbox.right_x));
4557
+ }
4558
+
4343
4559
  #[test]
4344
4560
  fn test_chart_label_ocr_does_not_reject_five_row_table() {
4345
4561
  let words = vec![
@@ -5012,6 +5228,76 @@ mod tests {
5012
5228
  .all(|cell| cell.content.is_empty()));
5013
5229
  }
5014
5230
 
5231
+ #[test]
5232
+ fn test_native_text_chars_in_region_ignores_distant_page_text() {
5233
+ let table_bbox = BoundingBox::new(Some(1), 40.0, 120.0, 360.0, 280.0);
5234
+ let distant_text = ContentElement::TextChunk(text_chunk(
5235
+ &"A".repeat(MAX_NATIVE_TEXT_CHARS_FOR_PAGE_RASTER_OCR + 40),
5236
+ BoundingBox::new(Some(1), 40.0, 500.0, 380.0, 560.0),
5237
+ ));
5238
+ let overlapping_text = ContentElement::TextChunk(text_chunk(
5239
+ "1234",
5240
+ BoundingBox::new(Some(1), 60.0, 160.0, 100.0, 176.0),
5241
+ ));
5242
+ let elements = vec![distant_text, overlapping_text];
5243
+
5244
+ assert!(page_native_text_chars(&elements) > MAX_NATIVE_TEXT_CHARS_FOR_PAGE_RASTER_OCR);
5245
+ assert_eq!(native_text_chars_in_region(&elements, &table_bbox), 4);
5246
+ }
5247
+
5248
+ #[test]
5249
+ fn test_table_needs_page_raster_ocr_for_sparse_partial_table() {
5250
+ let mut table = TableBorder {
5251
+ bbox: BoundingBox::new(Some(1), 0.0, 0.0, 300.0, 200.0),
5252
+ index: None,
5253
+ level: None,
5254
+ x_coordinates: vec![0.0, 60.0, 120.0, 180.0, 240.0, 300.0],
5255
+ x_widths: vec![0.0; 6],
5256
+ y_coordinates: vec![200.0, 160.0, 120.0, 80.0, 40.0, 0.0],
5257
+ y_widths: vec![0.0; 6],
5258
+ rows: Vec::new(),
5259
+ num_rows: 5,
5260
+ num_columns: 5,
5261
+ is_bad_table: false,
5262
+ is_table_transformer: true,
5263
+ previous_table: None,
5264
+ next_table: None,
5265
+ };
5266
+
5267
+ for row_idx in 0..5 {
5268
+ let mut row = TableBorderRow {
5269
+ bbox: BoundingBox::new(Some(1), 0.0, 0.0, 300.0, 200.0),
5270
+ index: None,
5271
+ level: None,
5272
+ row_number: row_idx,
5273
+ cells: Vec::new(),
5274
+ semantic_type: None,
5275
+ };
5276
+ for col_idx in 0..5 {
5277
+ row.cells.push(TableBorderCell {
5278
+ bbox: BoundingBox::new(Some(1), 0.0, 0.0, 60.0, 40.0),
5279
+ index: None,
5280
+ level: None,
5281
+ row_number: row_idx,
5282
+ col_number: col_idx,
5283
+ row_span: 1,
5284
+ col_span: 1,
5285
+ content: Vec::new(),
5286
+ contents: Vec::new(),
5287
+ semantic_type: None,
5288
+ });
5289
+ }
5290
+ table.rows.push(row);
5291
+ }
5292
+
5293
+ table.rows[0].cells[0].content.push(TableToken {
5294
+ base: text_chunk("12", BoundingBox::new(Some(1), 0.0, 0.0, 20.0, 10.0)),
5295
+ token_type: TableTokenType::Text,
5296
+ });
5297
+
5298
+ assert!(table_needs_page_raster_ocr(&table));
5299
+ }
5300
+
5015
5301
  #[test]
5016
5302
  fn test_lines_from_ocr_words_dedupes_against_native_text() {
5017
5303
  let words = vec![