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.
- {edgeparse-0.2.3 → edgeparse-0.2.5}/Cargo.lock +5 -5
- {edgeparse-0.2.3 → edgeparse-0.2.5}/Cargo.toml +2 -1
- {edgeparse-0.2.3 → edgeparse-0.2.5}/PKG-INFO +2 -2
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/output/markdown.rs +45 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/raster_table_ocr.rs +342 -56
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/table_detector.rs +157 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/Cargo.toml +1 -1
- {edgeparse-0.2.3 → edgeparse-0.2.5}/pyproject.toml +1 -1
- {edgeparse-0.2.3 → edgeparse-0.2.5}/README.md +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/Cargo.toml +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/README.md +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/api/batch.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/api/config.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/api/config_loader.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/api/filter.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/api/mod.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/hybrid.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/lib.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/models/bbox.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/models/chunks.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/models/content.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/models/document.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/models/enums.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/models/list.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/models/mod.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/models/semantic.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/models/table.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/models/text.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/output/csv.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/output/docx.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/output/html.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/output/json.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/output/legacy_json.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/output/mod.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/output/text.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/output/toc_builder.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/annotation_enrichment.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/annotation_extractor.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/bookmark_extractor.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/chunk_parser.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/data/glyphlist.txt +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/encryption.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/font.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/form_extractor.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/graphics_state.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/hyperlink_extractor.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/image_extractor.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/line_extractor.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/loader.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/metadata_writer.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/mod.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/page_info.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pdf/text_extractor.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/error_recovery.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/logging.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/mod.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/orchestrator.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/parallel.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/boxed_heading_promoter.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/caption_linker.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/cluster_table_detector.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/column_detector.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/content_filter.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/content_sanitizer.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/cross_page_linker.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/figure_detector.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/footnote_detector.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/footnote_linker.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/header_footer.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/heading_detector.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/id_assignment.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/list_detector.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/list_pass2.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/mod.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/nesting_level.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/output_builder.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/paragraph_detector.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/reading_order.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/table_content_assigner.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/text_block_grouper.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/text_line_grouper.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/toc_detector.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/pipeline/stages/watermark_detector.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/tagged/mod.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/tagged/processor.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/tagged/struct_tree.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/utils/diff.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/utils/font_metrics_cache.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/utils/image_dedup.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/utils/language_detector.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/utils/layout_analysis.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/utils/mod.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/utils/page_range.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/utils/sanitizer.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/utils/statistics.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/utils/text_normalizer.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/utils/xref_index.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/src/utils/xycut.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/tests/integration_tests.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-core/tests/perf_benchmarks.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-python/Cargo.toml +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/edgeparse-python/src/lib.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/.cargo_vcs_info.json +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/.chglog/CHANGELOG.tpl.md +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/.chglog/config.yml +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/.editorconfig +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/.gitattributes +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/.github/workflows/ci.yml +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/.gitignore +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/.vscode/launch.json +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/.vscode/settings.json +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/CHANGELOG.md +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/LICENSE +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/README.md +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/assets/AnnotationDemo.pdf +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/assets/Incremental.pdf +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/assets/encrypted.pdf +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/assets/example.pdf +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/assets/pdf_icon.jpg +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/assets/supported_color_type/L16.png +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/assets/supported_color_type/L8.jpg +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/assets/supported_color_type/La16.png +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/assets/supported_color_type/La8.png +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/assets/supported_color_type/Rgb16.png +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/assets/supported_color_type/Rgb8.jpg +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/assets/supported_color_type/Rgba16.png +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/assets/supported_color_type/Rgba8.png +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/assets/unicode.pdf +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/benches/datetime.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/benches/parse.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/add_barcode.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/analyze_object_streams.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/analyze_page_contents.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/analyze_pdf.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/analyze_references.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/check_raw_objstream.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/compress_existing_pdf.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/create.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/create_text_heavy_pdf.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/debug_compression_detailed.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/debug_compression_full.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/debug_object_stream.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/debug_save_with_objstreams.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/decrypt.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/encrypt.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/extract_text.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/extract_toc.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/final_compression_test.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/inspect_object_stream.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/merge.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/object_streams.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/print_annotations.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/replace_partial_text.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/rotate.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/test_better_compression.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/test_decryption.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/test_direct_objstream.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/test_filter_roundtrip.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/test_metadata.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/test_object_stream_creation.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/test_object_stream_roundtrip.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/test_real_pdf_compression_with_fix.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/test_structural_compression.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/verify_decryption.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/verify_page_compression.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/verify_pdf.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/examples/verify_trailer_compression_fix.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/rustfmt.toml +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/bookmarks.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/cmap_section.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/common_data_structures/mod.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/content.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/creator.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/datetime.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/destinations.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/document.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/encodings/cmap.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/encodings/glyphnames.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/encodings/mappings.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/encodings/mod.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/encryption/algorithms.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/encryption/crypt_filters.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/encryption/pkcs5.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/encryption/rc4.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/encryption.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/error.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/filters/mod.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/filters/png.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/font.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/incremental_document.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/lib.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/object.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/object_stream.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/outlines.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/parser/cmap_parser.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/parser/mod.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/parser_aux.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/processor.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/reader.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/save_options.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/toc.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/writer.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/xobject.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/src/xref.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/annotation.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/catalog_compression_integration_test.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/decryption.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/extract_test.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/font_test.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/incremental_document.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/metadata_test.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/modify.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/object_stream_comprehensive_test.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/object_stream_edge_cases_test.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/object_stream_methods_test.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/object_stream_performance_test.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/resources/fonts/Montserrat-Regular.ttf +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/simple_object_stream_test.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/trailer_reference_compression_test.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/unicode.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/crates/pdf-cos/tests/utils.rs +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/edgeparse/__init__.py +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/edgeparse/_types.py +0 -0
- {edgeparse-0.2.3 → edgeparse-0.2.5}/edgeparse/cli.py +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
+
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://
|
|
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
|
-
|
|
515
|
-
|
|
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
|
|
664
|
-
|
|
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
|
-
.
|
|
667
|
-
|
|
668
|
-
|
|
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
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
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
|
-
|
|
767
|
-
|
|
768
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1865
|
+
let x_coordinates = build_boundaries_from_centers(
|
|
1866
|
+
¢ers,
|
|
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
|
-
|
|
2123
|
+
let x_coordinates = build_boundaries_from_centers(
|
|
2124
|
+
¢ers,
|
|
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
|
|
2372
|
-
|
|
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(
|
|
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
|
-
|
|
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![
|