edgeparse 0.2.1__tar.gz → 0.2.3__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.1 → edgeparse-0.2.3}/Cargo.lock +5 -5
- {edgeparse-0.2.1 → edgeparse-0.2.3}/Cargo.toml +1 -1
- {edgeparse-0.2.1 → edgeparse-0.2.3}/PKG-INFO +1 -1
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/api/config.rs +33 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/lib.rs +92 -21
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/output/markdown.rs +224 -0
- edgeparse-0.2.3/crates/edgeparse-core/src/pdf/raster_table_ocr.rs +5047 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pipeline/orchestrator.rs +171 -87
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pipeline/stages/cluster_table_detector.rs +75 -1
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pipeline/stages/table_detector.rs +129 -3
- edgeparse-0.2.1/crates/edgeparse-core/src/pdf/raster_table_ocr.rs +0 -1607
- {edgeparse-0.2.1 → edgeparse-0.2.3}/README.md +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/Cargo.toml +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/README.md +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/api/batch.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/api/config_loader.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/api/filter.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/api/mod.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/hybrid.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/models/bbox.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/models/chunks.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/models/content.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/models/document.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/models/enums.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/models/list.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/models/mod.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/models/semantic.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/models/table.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/models/text.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/output/csv.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/output/docx.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/output/html.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/output/json.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/output/legacy_json.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/output/mod.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/output/text.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/output/toc_builder.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pdf/annotation_enrichment.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pdf/annotation_extractor.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pdf/bookmark_extractor.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pdf/chunk_parser.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pdf/data/glyphlist.txt +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pdf/encryption.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pdf/font.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pdf/form_extractor.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pdf/graphics_state.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pdf/hyperlink_extractor.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pdf/image_extractor.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pdf/line_extractor.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pdf/loader.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pdf/metadata_writer.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pdf/mod.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pdf/page_info.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pdf/text_extractor.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pipeline/error_recovery.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pipeline/logging.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pipeline/mod.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pipeline/parallel.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pipeline/stages/boxed_heading_promoter.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pipeline/stages/caption_linker.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pipeline/stages/column_detector.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pipeline/stages/content_filter.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pipeline/stages/content_sanitizer.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pipeline/stages/cross_page_linker.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pipeline/stages/figure_detector.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pipeline/stages/footnote_detector.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pipeline/stages/footnote_linker.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pipeline/stages/header_footer.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pipeline/stages/heading_detector.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pipeline/stages/id_assignment.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pipeline/stages/list_detector.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pipeline/stages/list_pass2.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pipeline/stages/mod.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pipeline/stages/nesting_level.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pipeline/stages/output_builder.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pipeline/stages/paragraph_detector.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pipeline/stages/reading_order.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pipeline/stages/table_content_assigner.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pipeline/stages/text_block_grouper.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pipeline/stages/text_line_grouper.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pipeline/stages/toc_detector.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/pipeline/stages/watermark_detector.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/tagged/mod.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/tagged/processor.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/tagged/struct_tree.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/utils/diff.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/utils/font_metrics_cache.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/utils/image_dedup.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/utils/language_detector.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/utils/layout_analysis.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/utils/mod.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/utils/page_range.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/utils/sanitizer.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/utils/statistics.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/utils/text_normalizer.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/utils/xref_index.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/src/utils/xycut.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/tests/integration_tests.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-core/tests/perf_benchmarks.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-python/Cargo.toml +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/edgeparse-python/src/lib.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/.cargo_vcs_info.json +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/.chglog/CHANGELOG.tpl.md +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/.chglog/config.yml +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/.editorconfig +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/.gitattributes +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/.github/workflows/ci.yml +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/.gitignore +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/.vscode/launch.json +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/.vscode/settings.json +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/CHANGELOG.md +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/Cargo.toml +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/LICENSE +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/README.md +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/assets/AnnotationDemo.pdf +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/assets/Incremental.pdf +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/assets/encrypted.pdf +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/assets/example.pdf +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/assets/pdf_icon.jpg +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/assets/supported_color_type/L16.png +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/assets/supported_color_type/L8.jpg +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/assets/supported_color_type/La16.png +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/assets/supported_color_type/La8.png +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/assets/supported_color_type/Rgb16.png +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/assets/supported_color_type/Rgb8.jpg +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/assets/supported_color_type/Rgba16.png +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/assets/supported_color_type/Rgba8.png +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/assets/unicode.pdf +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/benches/datetime.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/benches/parse.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/add_barcode.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/analyze_object_streams.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/analyze_page_contents.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/analyze_pdf.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/analyze_references.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/check_raw_objstream.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/compress_existing_pdf.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/create.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/create_text_heavy_pdf.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/debug_compression_detailed.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/debug_compression_full.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/debug_object_stream.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/debug_save_with_objstreams.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/decrypt.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/encrypt.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/extract_text.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/extract_toc.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/final_compression_test.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/inspect_object_stream.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/merge.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/object_streams.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/print_annotations.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/replace_partial_text.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/rotate.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/test_better_compression.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/test_decryption.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/test_direct_objstream.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/test_filter_roundtrip.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/test_metadata.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/test_object_stream_creation.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/test_object_stream_roundtrip.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/test_real_pdf_compression_with_fix.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/test_structural_compression.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/verify_decryption.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/verify_page_compression.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/verify_pdf.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/examples/verify_trailer_compression_fix.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/rustfmt.toml +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/bookmarks.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/cmap_section.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/common_data_structures/mod.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/content.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/creator.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/datetime.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/destinations.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/document.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/encodings/cmap.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/encodings/glyphnames.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/encodings/mappings.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/encodings/mod.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/encryption/algorithms.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/encryption/crypt_filters.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/encryption/pkcs5.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/encryption/rc4.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/encryption.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/error.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/filters/mod.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/filters/png.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/font.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/incremental_document.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/lib.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/object.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/object_stream.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/outlines.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/parser/cmap_parser.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/parser/mod.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/parser_aux.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/processor.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/reader.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/save_options.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/toc.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/writer.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/xobject.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/src/xref.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/tests/annotation.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/tests/catalog_compression_integration_test.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/tests/decryption.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/tests/extract_test.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/tests/font_test.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/tests/incremental_document.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/tests/metadata_test.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/tests/modify.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/tests/object_stream_comprehensive_test.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/tests/object_stream_edge_cases_test.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/tests/object_stream_methods_test.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/tests/object_stream_performance_test.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/tests/resources/fonts/Montserrat-Regular.ttf +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/tests/simple_object_stream_test.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/tests/trailer_reference_compression_test.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/tests/unicode.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/crates/pdf-cos/tests/utils.rs +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/edgeparse/__init__.py +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/edgeparse/_types.py +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/edgeparse/cli.py +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/edgeparse/py.typed +0 -0
- {edgeparse-0.2.1 → edgeparse-0.2.3}/pyproject.toml +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.3"
|
|
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.3"
|
|
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.3"
|
|
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.3"
|
|
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.3"
|
|
699
699
|
dependencies = [
|
|
700
700
|
"console_error_panic_hook",
|
|
701
701
|
"console_log",
|
|
@@ -114,6 +114,8 @@ pub struct ProcessingConfig {
|
|
|
114
114
|
pub image_format: ImageFormat,
|
|
115
115
|
/// Directory for extracted images
|
|
116
116
|
pub image_dir: Option<String>,
|
|
117
|
+
/// Enable raster table OCR recovery on image-based tables
|
|
118
|
+
pub raster_table_ocr: bool,
|
|
117
119
|
/// Pages to extract (e.g., "1,3,5-7")
|
|
118
120
|
pub pages: Option<String>,
|
|
119
121
|
/// Include headers/footers in output
|
|
@@ -150,6 +152,7 @@ impl Default for ProcessingConfig {
|
|
|
150
152
|
image_output: ImageOutput::External,
|
|
151
153
|
image_format: ImageFormat::Png,
|
|
152
154
|
image_dir: None,
|
|
155
|
+
raster_table_ocr: true,
|
|
153
156
|
pages: None,
|
|
154
157
|
include_header_footer: false,
|
|
155
158
|
hybrid: HybridBackend::Off,
|
|
@@ -161,6 +164,21 @@ impl Default for ProcessingConfig {
|
|
|
161
164
|
}
|
|
162
165
|
}
|
|
163
166
|
|
|
167
|
+
impl ProcessingConfig {
|
|
168
|
+
/// Returns true when a hybrid backend is configured.
|
|
169
|
+
pub fn hybrid_enabled(&self) -> bool {
|
|
170
|
+
!matches!(self.hybrid, HybridBackend::Off)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/// Returns true when local OCR recovery should run.
|
|
174
|
+
///
|
|
175
|
+
/// OCR is only active in hybrid mode and can still be disabled explicitly
|
|
176
|
+
/// via `raster_table_ocr`.
|
|
177
|
+
pub fn raster_table_ocr_enabled(&self) -> bool {
|
|
178
|
+
self.raster_table_ocr && self.hybrid_enabled()
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
164
182
|
#[cfg(test)]
|
|
165
183
|
mod tests {
|
|
166
184
|
use super::*;
|
|
@@ -175,7 +193,22 @@ mod tests {
|
|
|
175
193
|
assert_eq!(config.table_method, TableMethod::Default);
|
|
176
194
|
assert_eq!(config.image_output, ImageOutput::External);
|
|
177
195
|
assert_eq!(config.image_format, ImageFormat::Png);
|
|
196
|
+
assert!(config.raster_table_ocr);
|
|
178
197
|
assert_eq!(config.hybrid, HybridBackend::Off);
|
|
179
198
|
assert_eq!(config.hybrid_timeout, 30000);
|
|
180
199
|
}
|
|
200
|
+
|
|
201
|
+
#[test]
|
|
202
|
+
fn test_raster_table_ocr_requires_hybrid_mode() {
|
|
203
|
+
let mut config = ProcessingConfig::default();
|
|
204
|
+
assert!(!config.hybrid_enabled());
|
|
205
|
+
assert!(!config.raster_table_ocr_enabled());
|
|
206
|
+
|
|
207
|
+
config.hybrid = HybridBackend::DoclingFast;
|
|
208
|
+
assert!(config.hybrid_enabled());
|
|
209
|
+
assert!(config.raster_table_ocr_enabled());
|
|
210
|
+
|
|
211
|
+
config.raster_table_ocr = false;
|
|
212
|
+
assert!(!config.raster_table_ocr_enabled());
|
|
213
|
+
}
|
|
181
214
|
}
|
|
@@ -25,10 +25,12 @@ use crate::pdf::chunk_parser::extract_page_chunks;
|
|
|
25
25
|
use crate::pdf::page_info;
|
|
26
26
|
#[cfg(not(target_arch = "wasm32"))]
|
|
27
27
|
use crate::pdf::raster_table_ocr::{
|
|
28
|
-
|
|
28
|
+
recover_dominant_image_text_chunks, recover_page_raster_table_cell_text,
|
|
29
|
+
recover_raster_table_borders,
|
|
29
30
|
};
|
|
30
31
|
use crate::pipeline::orchestrator::{run_pipeline, PipelineState};
|
|
31
32
|
use crate::tagged::struct_tree::build_mcid_map;
|
|
33
|
+
use std::time::Instant;
|
|
32
34
|
|
|
33
35
|
/// Main entry point: convert a PDF file to structured data.
|
|
34
36
|
///
|
|
@@ -46,35 +48,69 @@ pub fn convert(
|
|
|
46
48
|
input_path: &std::path::Path,
|
|
47
49
|
config: &ProcessingConfig,
|
|
48
50
|
) -> Result<PdfDocument, EdgePdfError> {
|
|
51
|
+
let timing_enabled = timing_enabled();
|
|
52
|
+
let total_start = Instant::now();
|
|
53
|
+
|
|
54
|
+
let phase_start = Instant::now();
|
|
49
55
|
let raw_doc = pdf::loader::load_pdf(input_path, config.password.as_deref())?;
|
|
56
|
+
log_phase_duration(timing_enabled, "load_pdf", phase_start);
|
|
50
57
|
|
|
51
58
|
// Extract per-page geometry (MediaBox, CropBox, rotation) for use throughout the pipeline.
|
|
59
|
+
let phase_start = Instant::now();
|
|
52
60
|
let page_info_list = page_info::extract_page_info(&raw_doc.document);
|
|
61
|
+
log_phase_duration(timing_enabled, "extract_page_info", phase_start);
|
|
53
62
|
|
|
54
63
|
// Extract text chunks from each page
|
|
55
64
|
let pages_map = raw_doc.document.get_pages();
|
|
65
|
+
// Index by 1-based page number for fast lookup during optional OCR recovery.
|
|
66
|
+
// Keep this out of the default fast path when OCR is disabled.
|
|
67
|
+
let page_info_by_number: Vec<Option<&page_info::PageInfo>> =
|
|
68
|
+
if config.raster_table_ocr_enabled() {
|
|
69
|
+
let mut index = vec![None; pages_map.len().saturating_add(1)];
|
|
70
|
+
for info in &page_info_list {
|
|
71
|
+
if let Some(slot) = index.get_mut(info.page_number as usize) {
|
|
72
|
+
*slot = Some(info);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
index
|
|
76
|
+
} else {
|
|
77
|
+
Vec::new()
|
|
78
|
+
};
|
|
56
79
|
let mut page_contents = Vec::with_capacity(pages_map.len());
|
|
57
80
|
|
|
81
|
+
let phase_start = Instant::now();
|
|
58
82
|
for (&page_num, &page_id) in &pages_map {
|
|
59
83
|
let page_chunks = extract_page_chunks(&raw_doc.document, page_num, page_id)?;
|
|
84
|
+
let mut recovered_text_chunks = Vec::new();
|
|
60
85
|
let mut recovered_tables = Vec::new();
|
|
61
|
-
if
|
|
62
|
-
.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
86
|
+
if config.raster_table_ocr_enabled() {
|
|
87
|
+
if let Some(Some(page_info)) = page_info_by_number.get(page_num as usize) {
|
|
88
|
+
recovered_text_chunks = recover_dominant_image_text_chunks(
|
|
89
|
+
input_path,
|
|
90
|
+
&page_info.crop_box,
|
|
91
|
+
page_num,
|
|
92
|
+
&page_chunks.text_chunks,
|
|
93
|
+
&page_chunks.image_chunks,
|
|
94
|
+
);
|
|
95
|
+
recovered_tables = recover_raster_table_borders(
|
|
96
|
+
input_path,
|
|
97
|
+
&page_info.crop_box,
|
|
98
|
+
page_num,
|
|
99
|
+
&page_chunks.text_chunks,
|
|
100
|
+
&page_chunks.image_chunks,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
72
103
|
}
|
|
73
104
|
let mut elements: Vec<ContentElement> = page_chunks
|
|
74
105
|
.text_chunks
|
|
75
106
|
.into_iter()
|
|
76
107
|
.map(ContentElement::TextChunk)
|
|
77
108
|
.collect();
|
|
109
|
+
elements.extend(
|
|
110
|
+
recovered_text_chunks
|
|
111
|
+
.into_iter()
|
|
112
|
+
.map(ContentElement::TextChunk),
|
|
113
|
+
);
|
|
78
114
|
|
|
79
115
|
elements.extend(
|
|
80
116
|
page_chunks
|
|
@@ -102,12 +138,15 @@ pub fn convert(
|
|
|
102
138
|
|
|
103
139
|
page_contents.push(elements);
|
|
104
140
|
}
|
|
141
|
+
log_phase_duration(timing_enabled, "extract_page_chunks", phase_start);
|
|
105
142
|
|
|
106
143
|
// Run the processing pipeline
|
|
144
|
+
let phase_start = Instant::now();
|
|
107
145
|
let mcid_map = build_mcid_map(&raw_doc.document);
|
|
108
146
|
let mut pipeline_state = PipelineState::with_mcid_map(page_contents, config.clone(), mcid_map)
|
|
109
|
-
.with_page_info(page_info_list
|
|
147
|
+
.with_page_info(page_info_list);
|
|
110
148
|
run_pipeline(&mut pipeline_state)?;
|
|
149
|
+
log_phase_duration(timing_enabled, "run_pipeline", phase_start);
|
|
111
150
|
|
|
112
151
|
// Build the output document
|
|
113
152
|
let file_name = input_path
|
|
@@ -124,21 +163,32 @@ pub fn convert(
|
|
|
124
163
|
doc.creation_date = raw_doc.metadata.creation_date;
|
|
125
164
|
doc.modification_date = raw_doc.metadata.modification_date;
|
|
126
165
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
166
|
+
let phase_start = Instant::now();
|
|
167
|
+
if config.raster_table_ocr_enabled() {
|
|
168
|
+
for (page_idx, page) in pipeline_state.pages.iter_mut().enumerate() {
|
|
169
|
+
if let Some(page_info) = pipeline_state.page_info.get(page_idx) {
|
|
170
|
+
recover_page_raster_table_cell_text(
|
|
171
|
+
input_path,
|
|
172
|
+
&page_info.crop_box,
|
|
173
|
+
page_info.page_number,
|
|
174
|
+
page,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
135
177
|
}
|
|
136
178
|
}
|
|
179
|
+
log_phase_duration(
|
|
180
|
+
timing_enabled,
|
|
181
|
+
"recover_page_raster_table_cell_text",
|
|
182
|
+
phase_start,
|
|
183
|
+
);
|
|
137
184
|
|
|
138
185
|
// Flatten pipeline output into document kids
|
|
186
|
+
let phase_start = Instant::now();
|
|
139
187
|
for page in pipeline_state.pages {
|
|
140
188
|
doc.kids.extend(page);
|
|
141
189
|
}
|
|
190
|
+
log_phase_duration(timing_enabled, "flatten_document", phase_start);
|
|
191
|
+
log_phase_duration(timing_enabled, "convert_total", total_start);
|
|
142
192
|
|
|
143
193
|
Ok(doc)
|
|
144
194
|
}
|
|
@@ -267,6 +317,27 @@ impl From<lopdf::Error> for EdgePdfError {
|
|
|
267
317
|
}
|
|
268
318
|
}
|
|
269
319
|
|
|
320
|
+
fn timing_enabled() -> bool {
|
|
321
|
+
std::env::var("EDGEPARSE_TIMING")
|
|
322
|
+
.map(|value| {
|
|
323
|
+
matches!(
|
|
324
|
+
value.to_ascii_lowercase().as_str(),
|
|
325
|
+
"1" | "true" | "yes" | "on"
|
|
326
|
+
)
|
|
327
|
+
})
|
|
328
|
+
.unwrap_or(false)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
fn log_phase_duration(enabled: bool, phase: &str, start: Instant) {
|
|
332
|
+
if enabled {
|
|
333
|
+
log::info!(
|
|
334
|
+
"Timing {}: {:.2} ms",
|
|
335
|
+
phase,
|
|
336
|
+
start.elapsed().as_secs_f64() * 1000.0
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
270
341
|
#[cfg(test)]
|
|
271
342
|
mod tests {
|
|
272
343
|
use super::*;
|
|
@@ -208,6 +208,15 @@ fn render_markdown_core(doc: &PdfDocument) -> String {
|
|
|
208
208
|
continue;
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
// Demote carried-over table header rows that were promoted to
|
|
212
|
+
// headings by the pipeline but only duplicate the table above.
|
|
213
|
+
if looks_like_table_header_duplicate_heading(doc, i, trimmed) {
|
|
214
|
+
output.push_str(&escape_md_line_start(trimmed));
|
|
215
|
+
output.push_str("\n\n");
|
|
216
|
+
i += 1;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
|
|
211
220
|
// Demote headings that sit in the bottom margin of the page
|
|
212
221
|
// (running footers misclassified as headings by the pipeline).
|
|
213
222
|
if looks_like_bottom_margin_heading(doc, i) {
|
|
@@ -7814,6 +7823,9 @@ fn should_render_paragraph_as_heading(
|
|
|
7814
7823
|
if looks_like_top_margin_running_header(doc, idx, text) {
|
|
7815
7824
|
return false;
|
|
7816
7825
|
}
|
|
7826
|
+
if looks_like_hyphenated_table_title_continuation(doc, idx, text, next) {
|
|
7827
|
+
return true;
|
|
7828
|
+
}
|
|
7817
7829
|
if should_render_element_as_heading(&doc.kids[idx], text, next) {
|
|
7818
7830
|
return true;
|
|
7819
7831
|
}
|
|
@@ -8379,6 +8391,135 @@ fn should_render_element_as_heading(
|
|
|
8379
8391
|
&& !is_attribution
|
|
8380
8392
|
}
|
|
8381
8393
|
|
|
8394
|
+
fn looks_like_hyphenated_table_title_continuation(
|
|
8395
|
+
doc: &PdfDocument,
|
|
8396
|
+
idx: usize,
|
|
8397
|
+
text: &str,
|
|
8398
|
+
next: Option<&ContentElement>,
|
|
8399
|
+
) -> bool {
|
|
8400
|
+
if !matches!(
|
|
8401
|
+
next,
|
|
8402
|
+
Some(ContentElement::Table(_)) | Some(ContentElement::TableBorder(_))
|
|
8403
|
+
) {
|
|
8404
|
+
return false;
|
|
8405
|
+
}
|
|
8406
|
+
|
|
8407
|
+
let trimmed = text.trim();
|
|
8408
|
+
if trimmed.is_empty()
|
|
8409
|
+
|| starts_with_caption_prefix(trimmed)
|
|
8410
|
+
|| looks_like_numbered_section(trimmed)
|
|
8411
|
+
|| looks_like_keyword_numbered_section(trimmed)
|
|
8412
|
+
|| !trimmed.ends_with(':')
|
|
8413
|
+
{
|
|
8414
|
+
return false;
|
|
8415
|
+
}
|
|
8416
|
+
|
|
8417
|
+
let word_count = trimmed.split_whitespace().count();
|
|
8418
|
+
if !(3..=5).contains(&word_count) || trimmed.len() > 60 {
|
|
8419
|
+
return false;
|
|
8420
|
+
}
|
|
8421
|
+
|
|
8422
|
+
let Some(first_alpha) = trimmed.chars().find(|ch| ch.is_alphabetic()) else {
|
|
8423
|
+
return false;
|
|
8424
|
+
};
|
|
8425
|
+
if first_alpha.is_lowercase() {
|
|
8426
|
+
return false;
|
|
8427
|
+
}
|
|
8428
|
+
|
|
8429
|
+
let Some(prev_idx) = idx.checked_sub(1) else {
|
|
8430
|
+
return false;
|
|
8431
|
+
};
|
|
8432
|
+
let prev_text = extract_element_text(&doc.kids[prev_idx]);
|
|
8433
|
+
let prev_trimmed = prev_text.trim();
|
|
8434
|
+
!prev_trimmed.is_empty() && prev_trimmed.ends_with('-')
|
|
8435
|
+
}
|
|
8436
|
+
|
|
8437
|
+
fn looks_like_table_header_duplicate_heading(doc: &PdfDocument, idx: usize, text: &str) -> bool {
|
|
8438
|
+
let trimmed = text.trim();
|
|
8439
|
+
if trimmed.is_empty()
|
|
8440
|
+
|| starts_with_caption_prefix(trimmed)
|
|
8441
|
+
|| looks_like_numbered_section(trimmed)
|
|
8442
|
+
|| looks_like_keyword_numbered_section(trimmed)
|
|
8443
|
+
{
|
|
8444
|
+
return false;
|
|
8445
|
+
}
|
|
8446
|
+
|
|
8447
|
+
let word_count = trimmed.split_whitespace().count();
|
|
8448
|
+
if !(3..=10).contains(&word_count) || trimmed.len() > 96 {
|
|
8449
|
+
return false;
|
|
8450
|
+
}
|
|
8451
|
+
|
|
8452
|
+
let Some(prev_idx) = idx.checked_sub(1) else {
|
|
8453
|
+
return false;
|
|
8454
|
+
};
|
|
8455
|
+
let Some(previous_table) = table_border_from_element(&doc.kids[prev_idx]) else {
|
|
8456
|
+
return false;
|
|
8457
|
+
};
|
|
8458
|
+
if previous_table.num_columns < 3 || previous_table.rows.len() < 3 {
|
|
8459
|
+
return false;
|
|
8460
|
+
}
|
|
8461
|
+
|
|
8462
|
+
let mut rendered_rows = collect_table_border_rows(previous_table);
|
|
8463
|
+
if rendered_rows.is_empty() {
|
|
8464
|
+
return false;
|
|
8465
|
+
}
|
|
8466
|
+
merge_continuation_rows(&mut rendered_rows);
|
|
8467
|
+
trim_leading_table_carryover_rows(&mut rendered_rows);
|
|
8468
|
+
|
|
8469
|
+
let Some(header_row) = rendered_rows.first() else {
|
|
8470
|
+
return false;
|
|
8471
|
+
};
|
|
8472
|
+
let header_text = header_row
|
|
8473
|
+
.iter()
|
|
8474
|
+
.map(|cell| cell.trim())
|
|
8475
|
+
.filter(|cell| !cell.is_empty())
|
|
8476
|
+
.collect::<Vec<_>>()
|
|
8477
|
+
.join(" ");
|
|
8478
|
+
if !equivalent_heading_text(trimmed, &header_text) {
|
|
8479
|
+
return false;
|
|
8480
|
+
}
|
|
8481
|
+
|
|
8482
|
+
let page_number = doc.kids[idx].page_number();
|
|
8483
|
+
let mut short_fragments = 0usize;
|
|
8484
|
+
let mut numeric_fragments = 0usize;
|
|
8485
|
+
|
|
8486
|
+
for candidate in doc.kids.iter().skip(idx + 1) {
|
|
8487
|
+
if candidate.page_number() != page_number {
|
|
8488
|
+
break;
|
|
8489
|
+
}
|
|
8490
|
+
if matches!(
|
|
8491
|
+
candidate,
|
|
8492
|
+
ContentElement::Table(_) | ContentElement::TableBorder(_)
|
|
8493
|
+
) {
|
|
8494
|
+
break;
|
|
8495
|
+
}
|
|
8496
|
+
|
|
8497
|
+
let fragment = extract_element_text(candidate);
|
|
8498
|
+
let fragment_trimmed = fragment.trim();
|
|
8499
|
+
if fragment_trimmed.is_empty()
|
|
8500
|
+
|| looks_like_margin_page_number(doc, candidate, fragment_trimmed)
|
|
8501
|
+
{
|
|
8502
|
+
continue;
|
|
8503
|
+
}
|
|
8504
|
+
|
|
8505
|
+
let fragment_words = fragment_trimmed.split_whitespace().count();
|
|
8506
|
+
if fragment_words > 6 {
|
|
8507
|
+
return false;
|
|
8508
|
+
}
|
|
8509
|
+
|
|
8510
|
+
short_fragments += 1;
|
|
8511
|
+
if fragment_trimmed.chars().any(|ch| ch.is_ascii_digit()) {
|
|
8512
|
+
numeric_fragments += 1;
|
|
8513
|
+
}
|
|
8514
|
+
|
|
8515
|
+
if short_fragments >= 3 {
|
|
8516
|
+
break;
|
|
8517
|
+
}
|
|
8518
|
+
}
|
|
8519
|
+
|
|
8520
|
+
short_fragments >= 2 && numeric_fragments >= 1
|
|
8521
|
+
}
|
|
8522
|
+
|
|
8382
8523
|
fn looks_like_top_margin_running_header(doc: &PdfDocument, idx: usize, text: &str) -> bool {
|
|
8383
8524
|
let trimmed = text.trim();
|
|
8384
8525
|
if trimmed.is_empty() || trimmed.split_whitespace().count() > 6 {
|
|
@@ -13389,6 +13530,89 @@ mod tests {
|
|
|
13389
13530
|
));
|
|
13390
13531
|
}
|
|
13391
13532
|
|
|
13533
|
+
#[test]
|
|
13534
|
+
fn test_hyphenated_table_title_continuation_renders_as_heading() {
|
|
13535
|
+
let mut doc = PdfDocument::new("hyphenated-table-title.pdf".to_string());
|
|
13536
|
+
doc.number_of_pages = 1;
|
|
13537
|
+
doc.kids.push(make_paragraph_at(
|
|
13538
|
+
72.0,
|
|
13539
|
+
724.0,
|
|
13540
|
+
520.0,
|
|
13541
|
+
738.0,
|
|
13542
|
+
"With this in mind, here we have the 7 key competence areas selected to form a part of Eco-",
|
|
13543
|
+
));
|
|
13544
|
+
doc.kids.push(make_paragraph_at(
|
|
13545
|
+
72.0,
|
|
13546
|
+
704.0,
|
|
13547
|
+
260.0,
|
|
13548
|
+
718.0,
|
|
13549
|
+
"Circle's Competence Framework:",
|
|
13550
|
+
));
|
|
13551
|
+
doc.kids.push(make_n_column_table(
|
|
13552
|
+
&[
|
|
13553
|
+
vec!["Eco-Circle Competence Framework"],
|
|
13554
|
+
vec!["#1: The 3 Rs: Recycle-Reuse-Reduce"],
|
|
13555
|
+
vec!["#2: Lifecycle of Circular Economy"],
|
|
13556
|
+
],
|
|
13557
|
+
&[(140.0, 460.0)],
|
|
13558
|
+
));
|
|
13559
|
+
|
|
13560
|
+
let md = to_markdown(&doc).unwrap();
|
|
13561
|
+
assert!(md.contains("# Circle's Competence Framework:"), "{md}");
|
|
13562
|
+
}
|
|
13563
|
+
|
|
13564
|
+
#[test]
|
|
13565
|
+
fn test_duplicate_table_header_heading_is_demoted() {
|
|
13566
|
+
let mut doc = PdfDocument::new("duplicate-table-header-heading.pdf".to_string());
|
|
13567
|
+
doc.number_of_pages = 1;
|
|
13568
|
+
doc.kids
|
|
13569
|
+
.push(make_heading("MOHAVE COMMUNITY COLLEGE BIO181"));
|
|
13570
|
+
doc.kids.push(make_n_column_table(
|
|
13571
|
+
&[
|
|
13572
|
+
vec![
|
|
13573
|
+
"",
|
|
13574
|
+
"Saccharometer",
|
|
13575
|
+
"DI Water",
|
|
13576
|
+
"Glucose Solution",
|
|
13577
|
+
"Yeast Suspension",
|
|
13578
|
+
],
|
|
13579
|
+
vec!["1", "", "8 ml", "6 ml", "0 ml"],
|
|
13580
|
+
vec!["2", "", "12 ml", "0 ml", "2 ml"],
|
|
13581
|
+
vec!["3", "", "6 ml", "6 ml", "2 ml"],
|
|
13582
|
+
],
|
|
13583
|
+
&[
|
|
13584
|
+
(72.0, 110.0),
|
|
13585
|
+
(110.0, 210.0),
|
|
13586
|
+
(210.0, 300.0),
|
|
13587
|
+
(300.0, 430.0),
|
|
13588
|
+
(430.0, 540.0),
|
|
13589
|
+
],
|
|
13590
|
+
));
|
|
13591
|
+
doc.kids.push(make_heading_at(
|
|
13592
|
+
72.0,
|
|
13593
|
+
92.0,
|
|
13594
|
+
390.0,
|
|
13595
|
+
108.0,
|
|
13596
|
+
"Saccharometer DI Water Glucose Solution Yeast Suspension",
|
|
13597
|
+
));
|
|
13598
|
+
doc.kids
|
|
13599
|
+
.push(make_paragraph_at(72.0, 72.0, 120.0, 88.0, "below"));
|
|
13600
|
+
doc.kids
|
|
13601
|
+
.push(make_paragraph_at(72.0, 56.0, 240.0, 72.0, "1 16 ml 12 ml"));
|
|
13602
|
+
doc.kids
|
|
13603
|
+
.push(make_paragraph_at(296.0, 56.0, 340.0, 72.0, "0 ml"));
|
|
13604
|
+
|
|
13605
|
+
let md = to_markdown(&doc).unwrap();
|
|
13606
|
+
assert!(
|
|
13607
|
+
md.contains("Saccharometer DI Water Glucose Solution Yeast Suspension"),
|
|
13608
|
+
"{md}"
|
|
13609
|
+
);
|
|
13610
|
+
assert!(
|
|
13611
|
+
!md.contains("# Saccharometer DI Water Glucose Solution Yeast Suspension"),
|
|
13612
|
+
"{md}"
|
|
13613
|
+
);
|
|
13614
|
+
}
|
|
13615
|
+
|
|
13392
13616
|
#[test]
|
|
13393
13617
|
fn test_geometric_panel_headers_are_promoted_into_table() {
|
|
13394
13618
|
let mut doc = PdfDocument::new("ai-pack-panel.pdf".to_string());
|