edgeparse 0.2.2__tar.gz → 0.2.4__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.2 → edgeparse-0.2.4}/Cargo.lock +5 -5
- {edgeparse-0.2.2 → edgeparse-0.2.4}/Cargo.toml +1 -1
- {edgeparse-0.2.2 → edgeparse-0.2.4}/PKG-INFO +1 -1
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/api/config.rs +29 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/lib.rs +76 -9
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/output/markdown.rs +269 -0
- edgeparse-0.2.4/crates/edgeparse-core/src/pdf/raster_table_ocr.rs +5333 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pipeline/orchestrator.rs +171 -87
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pipeline/stages/cluster_table_detector.rs +75 -1
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pipeline/stages/table_detector.rs +286 -3
- edgeparse-0.2.2/crates/edgeparse-core/src/pdf/raster_table_ocr.rs +0 -1607
- {edgeparse-0.2.2 → edgeparse-0.2.4}/README.md +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/Cargo.toml +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/README.md +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/api/batch.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/api/config_loader.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/api/filter.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/api/mod.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/hybrid.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/models/bbox.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/models/chunks.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/models/content.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/models/document.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/models/enums.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/models/list.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/models/mod.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/models/semantic.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/models/table.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/models/text.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/output/csv.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/output/docx.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/output/html.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/output/json.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/output/legacy_json.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/output/mod.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/output/text.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/output/toc_builder.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pdf/annotation_enrichment.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pdf/annotation_extractor.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pdf/bookmark_extractor.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pdf/chunk_parser.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pdf/data/glyphlist.txt +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pdf/encryption.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pdf/font.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pdf/form_extractor.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pdf/graphics_state.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pdf/hyperlink_extractor.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pdf/image_extractor.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pdf/line_extractor.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pdf/loader.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pdf/metadata_writer.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pdf/mod.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pdf/page_info.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pdf/text_extractor.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pipeline/error_recovery.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pipeline/logging.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pipeline/mod.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pipeline/parallel.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pipeline/stages/boxed_heading_promoter.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pipeline/stages/caption_linker.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pipeline/stages/column_detector.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pipeline/stages/content_filter.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pipeline/stages/content_sanitizer.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pipeline/stages/cross_page_linker.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pipeline/stages/figure_detector.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pipeline/stages/footnote_detector.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pipeline/stages/footnote_linker.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pipeline/stages/header_footer.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pipeline/stages/heading_detector.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pipeline/stages/id_assignment.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pipeline/stages/list_detector.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pipeline/stages/list_pass2.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pipeline/stages/mod.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pipeline/stages/nesting_level.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pipeline/stages/output_builder.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pipeline/stages/paragraph_detector.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pipeline/stages/reading_order.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pipeline/stages/table_content_assigner.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pipeline/stages/text_block_grouper.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pipeline/stages/text_line_grouper.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pipeline/stages/toc_detector.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/pipeline/stages/watermark_detector.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/tagged/mod.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/tagged/processor.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/tagged/struct_tree.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/utils/diff.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/utils/font_metrics_cache.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/utils/image_dedup.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/utils/language_detector.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/utils/layout_analysis.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/utils/mod.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/utils/page_range.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/utils/sanitizer.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/utils/statistics.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/utils/text_normalizer.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/utils/xref_index.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/src/utils/xycut.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/tests/integration_tests.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-core/tests/perf_benchmarks.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-python/Cargo.toml +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/edgeparse-python/src/lib.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/.cargo_vcs_info.json +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/.chglog/CHANGELOG.tpl.md +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/.chglog/config.yml +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/.editorconfig +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/.gitattributes +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/.github/workflows/ci.yml +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/.gitignore +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/.vscode/launch.json +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/.vscode/settings.json +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/CHANGELOG.md +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/Cargo.toml +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/LICENSE +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/README.md +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/assets/AnnotationDemo.pdf +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/assets/Incremental.pdf +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/assets/encrypted.pdf +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/assets/example.pdf +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/assets/pdf_icon.jpg +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/assets/supported_color_type/L16.png +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/assets/supported_color_type/L8.jpg +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/assets/supported_color_type/La16.png +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/assets/supported_color_type/La8.png +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/assets/supported_color_type/Rgb16.png +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/assets/supported_color_type/Rgb8.jpg +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/assets/supported_color_type/Rgba16.png +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/assets/supported_color_type/Rgba8.png +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/assets/unicode.pdf +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/benches/datetime.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/benches/parse.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/add_barcode.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/analyze_object_streams.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/analyze_page_contents.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/analyze_pdf.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/analyze_references.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/check_raw_objstream.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/compress_existing_pdf.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/create.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/create_text_heavy_pdf.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/debug_compression_detailed.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/debug_compression_full.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/debug_object_stream.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/debug_save_with_objstreams.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/decrypt.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/encrypt.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/extract_text.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/extract_toc.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/final_compression_test.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/inspect_object_stream.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/merge.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/object_streams.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/print_annotations.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/replace_partial_text.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/rotate.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/test_better_compression.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/test_decryption.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/test_direct_objstream.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/test_filter_roundtrip.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/test_metadata.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/test_object_stream_creation.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/test_object_stream_roundtrip.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/test_real_pdf_compression_with_fix.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/test_structural_compression.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/verify_decryption.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/verify_page_compression.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/verify_pdf.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/examples/verify_trailer_compression_fix.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/rustfmt.toml +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/bookmarks.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/cmap_section.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/common_data_structures/mod.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/content.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/creator.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/datetime.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/destinations.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/document.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/encodings/cmap.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/encodings/glyphnames.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/encodings/mappings.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/encodings/mod.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/encryption/algorithms.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/encryption/crypt_filters.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/encryption/pkcs5.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/encryption/rc4.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/encryption.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/error.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/filters/mod.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/filters/png.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/font.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/incremental_document.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/lib.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/object.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/object_stream.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/outlines.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/parser/cmap_parser.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/parser/mod.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/parser_aux.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/processor.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/reader.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/save_options.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/toc.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/writer.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/xobject.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/src/xref.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/tests/annotation.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/tests/catalog_compression_integration_test.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/tests/decryption.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/tests/extract_test.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/tests/font_test.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/tests/incremental_document.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/tests/metadata_test.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/tests/modify.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/tests/object_stream_comprehensive_test.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/tests/object_stream_edge_cases_test.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/tests/object_stream_methods_test.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/tests/object_stream_performance_test.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/tests/resources/fonts/Montserrat-Regular.ttf +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/tests/simple_object_stream_test.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/tests/trailer_reference_compression_test.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/tests/unicode.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/crates/pdf-cos/tests/utils.rs +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/edgeparse/__init__.py +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/edgeparse/_types.py +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/edgeparse/cli.py +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/edgeparse/py.typed +0 -0
- {edgeparse-0.2.2 → edgeparse-0.2.4}/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.4"
|
|
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.4"
|
|
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.4"
|
|
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.4"
|
|
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.4"
|
|
699
699
|
dependencies = [
|
|
700
700
|
"console_error_panic_hook",
|
|
701
701
|
"console_log",
|
|
@@ -164,6 +164,21 @@ impl Default for ProcessingConfig {
|
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
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
|
+
|
|
167
182
|
#[cfg(test)]
|
|
168
183
|
mod tests {
|
|
169
184
|
use super::*;
|
|
@@ -182,4 +197,18 @@ mod tests {
|
|
|
182
197
|
assert_eq!(config.hybrid, HybridBackend::Off);
|
|
183
198
|
assert_eq!(config.hybrid_timeout, 30000);
|
|
184
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
|
+
}
|
|
185
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,23 +48,50 @@ 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 config.
|
|
62
|
-
if let Some(page_info) =
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
+
);
|
|
66
95
|
recovered_tables = recover_raster_table_borders(
|
|
67
96
|
input_path,
|
|
68
97
|
&page_info.crop_box,
|
|
@@ -77,6 +106,11 @@ pub fn convert(
|
|
|
77
106
|
.into_iter()
|
|
78
107
|
.map(ContentElement::TextChunk)
|
|
79
108
|
.collect();
|
|
109
|
+
elements.extend(
|
|
110
|
+
recovered_text_chunks
|
|
111
|
+
.into_iter()
|
|
112
|
+
.map(ContentElement::TextChunk),
|
|
113
|
+
);
|
|
80
114
|
|
|
81
115
|
elements.extend(
|
|
82
116
|
page_chunks
|
|
@@ -104,12 +138,15 @@ pub fn convert(
|
|
|
104
138
|
|
|
105
139
|
page_contents.push(elements);
|
|
106
140
|
}
|
|
141
|
+
log_phase_duration(timing_enabled, "extract_page_chunks", phase_start);
|
|
107
142
|
|
|
108
143
|
// Run the processing pipeline
|
|
144
|
+
let phase_start = Instant::now();
|
|
109
145
|
let mcid_map = build_mcid_map(&raw_doc.document);
|
|
110
146
|
let mut pipeline_state = PipelineState::with_mcid_map(page_contents, config.clone(), mcid_map)
|
|
111
|
-
.with_page_info(page_info_list
|
|
147
|
+
.with_page_info(page_info_list);
|
|
112
148
|
run_pipeline(&mut pipeline_state)?;
|
|
149
|
+
log_phase_duration(timing_enabled, "run_pipeline", phase_start);
|
|
113
150
|
|
|
114
151
|
// Build the output document
|
|
115
152
|
let file_name = input_path
|
|
@@ -126,9 +163,10 @@ pub fn convert(
|
|
|
126
163
|
doc.creation_date = raw_doc.metadata.creation_date;
|
|
127
164
|
doc.modification_date = raw_doc.metadata.modification_date;
|
|
128
165
|
|
|
129
|
-
|
|
166
|
+
let phase_start = Instant::now();
|
|
167
|
+
if config.raster_table_ocr_enabled() {
|
|
130
168
|
for (page_idx, page) in pipeline_state.pages.iter_mut().enumerate() {
|
|
131
|
-
if let Some(page_info) =
|
|
169
|
+
if let Some(page_info) = pipeline_state.page_info.get(page_idx) {
|
|
132
170
|
recover_page_raster_table_cell_text(
|
|
133
171
|
input_path,
|
|
134
172
|
&page_info.crop_box,
|
|
@@ -138,11 +176,19 @@ pub fn convert(
|
|
|
138
176
|
}
|
|
139
177
|
}
|
|
140
178
|
}
|
|
179
|
+
log_phase_duration(
|
|
180
|
+
timing_enabled,
|
|
181
|
+
"recover_page_raster_table_cell_text",
|
|
182
|
+
phase_start,
|
|
183
|
+
);
|
|
141
184
|
|
|
142
185
|
// Flatten pipeline output into document kids
|
|
186
|
+
let phase_start = Instant::now();
|
|
143
187
|
for page in pipeline_state.pages {
|
|
144
188
|
doc.kids.extend(page);
|
|
145
189
|
}
|
|
190
|
+
log_phase_duration(timing_enabled, "flatten_document", phase_start);
|
|
191
|
+
log_phase_duration(timing_enabled, "convert_total", total_start);
|
|
146
192
|
|
|
147
193
|
Ok(doc)
|
|
148
194
|
}
|
|
@@ -271,6 +317,27 @@ impl From<lopdf::Error> for EdgePdfError {
|
|
|
271
317
|
}
|
|
272
318
|
}
|
|
273
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
|
+
|
|
274
341
|
#[cfg(test)]
|
|
275
342
|
mod tests {
|
|
276
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) {
|
|
@@ -1436,6 +1445,9 @@ fn render_layout_single_caption_chart_document_cached(
|
|
|
1436
1445
|
if doc.number_of_pages != 1 {
|
|
1437
1446
|
return None;
|
|
1438
1447
|
}
|
|
1448
|
+
if document_has_populated_table(doc) {
|
|
1449
|
+
return None;
|
|
1450
|
+
}
|
|
1439
1451
|
|
|
1440
1452
|
let caption_indices = doc
|
|
1441
1453
|
.kids
|
|
@@ -1546,6 +1558,22 @@ fn render_layout_single_caption_chart_document_cached(
|
|
|
1546
1558
|
Some(output.trim_end().to_string() + "\n")
|
|
1547
1559
|
}
|
|
1548
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
|
+
|
|
1549
1577
|
fn looks_like_chart_noise_element(_element: &ContentElement, text: &str) -> bool {
|
|
1550
1578
|
if text.is_empty() {
|
|
1551
1579
|
return false;
|
|
@@ -7814,6 +7842,9 @@ fn should_render_paragraph_as_heading(
|
|
|
7814
7842
|
if looks_like_top_margin_running_header(doc, idx, text) {
|
|
7815
7843
|
return false;
|
|
7816
7844
|
}
|
|
7845
|
+
if looks_like_hyphenated_table_title_continuation(doc, idx, text, next) {
|
|
7846
|
+
return true;
|
|
7847
|
+
}
|
|
7817
7848
|
if should_render_element_as_heading(&doc.kids[idx], text, next) {
|
|
7818
7849
|
return true;
|
|
7819
7850
|
}
|
|
@@ -8379,6 +8410,135 @@ fn should_render_element_as_heading(
|
|
|
8379
8410
|
&& !is_attribution
|
|
8380
8411
|
}
|
|
8381
8412
|
|
|
8413
|
+
fn looks_like_hyphenated_table_title_continuation(
|
|
8414
|
+
doc: &PdfDocument,
|
|
8415
|
+
idx: usize,
|
|
8416
|
+
text: &str,
|
|
8417
|
+
next: Option<&ContentElement>,
|
|
8418
|
+
) -> bool {
|
|
8419
|
+
if !matches!(
|
|
8420
|
+
next,
|
|
8421
|
+
Some(ContentElement::Table(_)) | Some(ContentElement::TableBorder(_))
|
|
8422
|
+
) {
|
|
8423
|
+
return false;
|
|
8424
|
+
}
|
|
8425
|
+
|
|
8426
|
+
let trimmed = text.trim();
|
|
8427
|
+
if trimmed.is_empty()
|
|
8428
|
+
|| starts_with_caption_prefix(trimmed)
|
|
8429
|
+
|| looks_like_numbered_section(trimmed)
|
|
8430
|
+
|| looks_like_keyword_numbered_section(trimmed)
|
|
8431
|
+
|| !trimmed.ends_with(':')
|
|
8432
|
+
{
|
|
8433
|
+
return false;
|
|
8434
|
+
}
|
|
8435
|
+
|
|
8436
|
+
let word_count = trimmed.split_whitespace().count();
|
|
8437
|
+
if !(3..=5).contains(&word_count) || trimmed.len() > 60 {
|
|
8438
|
+
return false;
|
|
8439
|
+
}
|
|
8440
|
+
|
|
8441
|
+
let Some(first_alpha) = trimmed.chars().find(|ch| ch.is_alphabetic()) else {
|
|
8442
|
+
return false;
|
|
8443
|
+
};
|
|
8444
|
+
if first_alpha.is_lowercase() {
|
|
8445
|
+
return false;
|
|
8446
|
+
}
|
|
8447
|
+
|
|
8448
|
+
let Some(prev_idx) = idx.checked_sub(1) else {
|
|
8449
|
+
return false;
|
|
8450
|
+
};
|
|
8451
|
+
let prev_text = extract_element_text(&doc.kids[prev_idx]);
|
|
8452
|
+
let prev_trimmed = prev_text.trim();
|
|
8453
|
+
!prev_trimmed.is_empty() && prev_trimmed.ends_with('-')
|
|
8454
|
+
}
|
|
8455
|
+
|
|
8456
|
+
fn looks_like_table_header_duplicate_heading(doc: &PdfDocument, idx: usize, text: &str) -> bool {
|
|
8457
|
+
let trimmed = text.trim();
|
|
8458
|
+
if trimmed.is_empty()
|
|
8459
|
+
|| starts_with_caption_prefix(trimmed)
|
|
8460
|
+
|| looks_like_numbered_section(trimmed)
|
|
8461
|
+
|| looks_like_keyword_numbered_section(trimmed)
|
|
8462
|
+
{
|
|
8463
|
+
return false;
|
|
8464
|
+
}
|
|
8465
|
+
|
|
8466
|
+
let word_count = trimmed.split_whitespace().count();
|
|
8467
|
+
if !(3..=10).contains(&word_count) || trimmed.len() > 96 {
|
|
8468
|
+
return false;
|
|
8469
|
+
}
|
|
8470
|
+
|
|
8471
|
+
let Some(prev_idx) = idx.checked_sub(1) else {
|
|
8472
|
+
return false;
|
|
8473
|
+
};
|
|
8474
|
+
let Some(previous_table) = table_border_from_element(&doc.kids[prev_idx]) else {
|
|
8475
|
+
return false;
|
|
8476
|
+
};
|
|
8477
|
+
if previous_table.num_columns < 3 || previous_table.rows.len() < 3 {
|
|
8478
|
+
return false;
|
|
8479
|
+
}
|
|
8480
|
+
|
|
8481
|
+
let mut rendered_rows = collect_table_border_rows(previous_table);
|
|
8482
|
+
if rendered_rows.is_empty() {
|
|
8483
|
+
return false;
|
|
8484
|
+
}
|
|
8485
|
+
merge_continuation_rows(&mut rendered_rows);
|
|
8486
|
+
trim_leading_table_carryover_rows(&mut rendered_rows);
|
|
8487
|
+
|
|
8488
|
+
let Some(header_row) = rendered_rows.first() else {
|
|
8489
|
+
return false;
|
|
8490
|
+
};
|
|
8491
|
+
let header_text = header_row
|
|
8492
|
+
.iter()
|
|
8493
|
+
.map(|cell| cell.trim())
|
|
8494
|
+
.filter(|cell| !cell.is_empty())
|
|
8495
|
+
.collect::<Vec<_>>()
|
|
8496
|
+
.join(" ");
|
|
8497
|
+
if !equivalent_heading_text(trimmed, &header_text) {
|
|
8498
|
+
return false;
|
|
8499
|
+
}
|
|
8500
|
+
|
|
8501
|
+
let page_number = doc.kids[idx].page_number();
|
|
8502
|
+
let mut short_fragments = 0usize;
|
|
8503
|
+
let mut numeric_fragments = 0usize;
|
|
8504
|
+
|
|
8505
|
+
for candidate in doc.kids.iter().skip(idx + 1) {
|
|
8506
|
+
if candidate.page_number() != page_number {
|
|
8507
|
+
break;
|
|
8508
|
+
}
|
|
8509
|
+
if matches!(
|
|
8510
|
+
candidate,
|
|
8511
|
+
ContentElement::Table(_) | ContentElement::TableBorder(_)
|
|
8512
|
+
) {
|
|
8513
|
+
break;
|
|
8514
|
+
}
|
|
8515
|
+
|
|
8516
|
+
let fragment = extract_element_text(candidate);
|
|
8517
|
+
let fragment_trimmed = fragment.trim();
|
|
8518
|
+
if fragment_trimmed.is_empty()
|
|
8519
|
+
|| looks_like_margin_page_number(doc, candidate, fragment_trimmed)
|
|
8520
|
+
{
|
|
8521
|
+
continue;
|
|
8522
|
+
}
|
|
8523
|
+
|
|
8524
|
+
let fragment_words = fragment_trimmed.split_whitespace().count();
|
|
8525
|
+
if fragment_words > 6 {
|
|
8526
|
+
return false;
|
|
8527
|
+
}
|
|
8528
|
+
|
|
8529
|
+
short_fragments += 1;
|
|
8530
|
+
if fragment_trimmed.chars().any(|ch| ch.is_ascii_digit()) {
|
|
8531
|
+
numeric_fragments += 1;
|
|
8532
|
+
}
|
|
8533
|
+
|
|
8534
|
+
if short_fragments >= 3 {
|
|
8535
|
+
break;
|
|
8536
|
+
}
|
|
8537
|
+
}
|
|
8538
|
+
|
|
8539
|
+
short_fragments >= 2 && numeric_fragments >= 1
|
|
8540
|
+
}
|
|
8541
|
+
|
|
8382
8542
|
fn looks_like_top_margin_running_header(doc: &PdfDocument, idx: usize, text: &str) -> bool {
|
|
8383
8543
|
let trimmed = text.trim();
|
|
8384
8544
|
if trimmed.is_empty() || trimmed.split_whitespace().count() > 6 {
|
|
@@ -13033,6 +13193,32 @@ mod tests {
|
|
|
13033
13193
|
assert!(md.contains("| kaolinite | 10 |"));
|
|
13034
13194
|
}
|
|
13035
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
|
+
|
|
13036
13222
|
#[test]
|
|
13037
13223
|
fn test_blank_right_column_table_is_not_misrendered_as_toc() {
|
|
13038
13224
|
let mut doc = PdfDocument::new("flocculation-table.pdf".to_string());
|
|
@@ -13389,6 +13575,89 @@ mod tests {
|
|
|
13389
13575
|
));
|
|
13390
13576
|
}
|
|
13391
13577
|
|
|
13578
|
+
#[test]
|
|
13579
|
+
fn test_hyphenated_table_title_continuation_renders_as_heading() {
|
|
13580
|
+
let mut doc = PdfDocument::new("hyphenated-table-title.pdf".to_string());
|
|
13581
|
+
doc.number_of_pages = 1;
|
|
13582
|
+
doc.kids.push(make_paragraph_at(
|
|
13583
|
+
72.0,
|
|
13584
|
+
724.0,
|
|
13585
|
+
520.0,
|
|
13586
|
+
738.0,
|
|
13587
|
+
"With this in mind, here we have the 7 key competence areas selected to form a part of Eco-",
|
|
13588
|
+
));
|
|
13589
|
+
doc.kids.push(make_paragraph_at(
|
|
13590
|
+
72.0,
|
|
13591
|
+
704.0,
|
|
13592
|
+
260.0,
|
|
13593
|
+
718.0,
|
|
13594
|
+
"Circle's Competence Framework:",
|
|
13595
|
+
));
|
|
13596
|
+
doc.kids.push(make_n_column_table(
|
|
13597
|
+
&[
|
|
13598
|
+
vec!["Eco-Circle Competence Framework"],
|
|
13599
|
+
vec!["#1: The 3 Rs: Recycle-Reuse-Reduce"],
|
|
13600
|
+
vec!["#2: Lifecycle of Circular Economy"],
|
|
13601
|
+
],
|
|
13602
|
+
&[(140.0, 460.0)],
|
|
13603
|
+
));
|
|
13604
|
+
|
|
13605
|
+
let md = to_markdown(&doc).unwrap();
|
|
13606
|
+
assert!(md.contains("# Circle's Competence Framework:"), "{md}");
|
|
13607
|
+
}
|
|
13608
|
+
|
|
13609
|
+
#[test]
|
|
13610
|
+
fn test_duplicate_table_header_heading_is_demoted() {
|
|
13611
|
+
let mut doc = PdfDocument::new("duplicate-table-header-heading.pdf".to_string());
|
|
13612
|
+
doc.number_of_pages = 1;
|
|
13613
|
+
doc.kids
|
|
13614
|
+
.push(make_heading("MOHAVE COMMUNITY COLLEGE BIO181"));
|
|
13615
|
+
doc.kids.push(make_n_column_table(
|
|
13616
|
+
&[
|
|
13617
|
+
vec![
|
|
13618
|
+
"",
|
|
13619
|
+
"Saccharometer",
|
|
13620
|
+
"DI Water",
|
|
13621
|
+
"Glucose Solution",
|
|
13622
|
+
"Yeast Suspension",
|
|
13623
|
+
],
|
|
13624
|
+
vec!["1", "", "8 ml", "6 ml", "0 ml"],
|
|
13625
|
+
vec!["2", "", "12 ml", "0 ml", "2 ml"],
|
|
13626
|
+
vec!["3", "", "6 ml", "6 ml", "2 ml"],
|
|
13627
|
+
],
|
|
13628
|
+
&[
|
|
13629
|
+
(72.0, 110.0),
|
|
13630
|
+
(110.0, 210.0),
|
|
13631
|
+
(210.0, 300.0),
|
|
13632
|
+
(300.0, 430.0),
|
|
13633
|
+
(430.0, 540.0),
|
|
13634
|
+
],
|
|
13635
|
+
));
|
|
13636
|
+
doc.kids.push(make_heading_at(
|
|
13637
|
+
72.0,
|
|
13638
|
+
92.0,
|
|
13639
|
+
390.0,
|
|
13640
|
+
108.0,
|
|
13641
|
+
"Saccharometer DI Water Glucose Solution Yeast Suspension",
|
|
13642
|
+
));
|
|
13643
|
+
doc.kids
|
|
13644
|
+
.push(make_paragraph_at(72.0, 72.0, 120.0, 88.0, "below"));
|
|
13645
|
+
doc.kids
|
|
13646
|
+
.push(make_paragraph_at(72.0, 56.0, 240.0, 72.0, "1 16 ml 12 ml"));
|
|
13647
|
+
doc.kids
|
|
13648
|
+
.push(make_paragraph_at(296.0, 56.0, 340.0, 72.0, "0 ml"));
|
|
13649
|
+
|
|
13650
|
+
let md = to_markdown(&doc).unwrap();
|
|
13651
|
+
assert!(
|
|
13652
|
+
md.contains("Saccharometer DI Water Glucose Solution Yeast Suspension"),
|
|
13653
|
+
"{md}"
|
|
13654
|
+
);
|
|
13655
|
+
assert!(
|
|
13656
|
+
!md.contains("# Saccharometer DI Water Glucose Solution Yeast Suspension"),
|
|
13657
|
+
"{md}"
|
|
13658
|
+
);
|
|
13659
|
+
}
|
|
13660
|
+
|
|
13392
13661
|
#[test]
|
|
13393
13662
|
fn test_geometric_panel_headers_are_promoted_into_table() {
|
|
13394
13663
|
let mut doc = PdfDocument::new("ai-pack-panel.pdf".to_string());
|