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