From 0f9b5fa1c67067b836c16777ae78dd25885971a5 Mon Sep 17 00:00:00 2001 From: Abimael Martell <1450169+abimaelmartell@users.noreply.github.com> Date: Mon, 17 Aug 2026 09:25:56 -0700 Subject: [PATCH] feat(bindings): expose OCR in Node and Python (#405) * feat(bindings): expose selective OCR * fix(bindings): address review feedback --- .github/workflows/ci.yml | 55 + Cargo.toml | 2 +- README.md | 25 +- docs/python.md | 65 +- napi/Cargo.lock | 2283 +++++++++++++++++++++++++++++++++++++- napi/Cargo.toml | 2 +- napi/README.md | 51 +- napi/src/lib.rs | 241 ++++ napi/test.mjs | 32 + pdf_inspector.pyi | 82 +- src/python.rs | 296 +++++ tests/test_python.py | 44 + 12 files changed, 3135 insertions(+), 43 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4e7db35..b258161 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -134,10 +134,21 @@ jobs: with: toolchain: stable + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: '3.12' + + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + with: + bun-version: latest + - name: Cache cargo uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 with: key: ocr-runtime + workspaces: | + . -> target + napi -> target - name: Install PDFium shell: bash @@ -202,6 +213,50 @@ jobs: assert "layout_ms" not in result["pages"][0]["timings"] PY + - name: Build Node binding + working-directory: napi + run: | + bun install --frozen-lockfile + bunx napi build --platform --release + + - name: Run Node OCR binding + shell: bash + run: | + node --input-type=module - <<'JS' + import { readFileSync } from 'node:fs' + import { processPdfWithOcr } from './napi/index.js' + + const pdf = readFileSync('tests/fixtures/scan_with_native_header_text.pdf') + const result = await processPdfWithOcr(pdf, { offline: true }) + if (JSON.stringify(result.pagesRoutedToOcr) !== '[1]') throw new Error('unexpected OCR route') + if (result.pagesRecommendingHosted.length !== 0) throw new Error('unexpected hosted recommendation') + if (!['Ocr', 'Fused'].includes(result.pages[0].provenance.source)) throw new Error('unexpected source') + if (!result.pages[0].markdown.trim()) throw new Error('empty OCR markdown') + JS + + - name: Build and install Python binding + shell: bash + run: | + python -m pip install 'maturin>=1,<2' + maturin build --release --out "$RUNNER_TEMP/python-wheels" + python -m pip install "$RUNNER_TEMP"/python-wheels/*.whl + + - name: Run Python OCR binding + shell: bash + run: | + python - <<'PY' + import pdf_inspector + + result = pdf_inspector.process_pdf_with_ocr( + "tests/fixtures/scan_with_native_header_text.pdf", + offline=True, + ) + assert result.pages_routed_to_ocr == [1] + assert result.pages_recommending_hosted == [] + assert result.pages[0].provenance.source in {"ocr", "fused"} + assert result.pages[0].markdown.strip() + PY + wasm: name: WebAssembly runs-on: ubuntu-latest diff --git a/Cargo.toml b/Cargo.toml index 326b33b..b29c7fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,7 +80,7 @@ tempfile = "3.3" [features] default = [] -python = ["pyo3"] +python = ["pyo3", "ocr"] vision = [] model-cache = ["vision", "dep:dirs", "dep:fs2", "dep:sha2", "dep:windows-sys"] model-download = ["model-cache", "dep:ureq"] diff --git a/README.md b/README.md index ca49d31..30a8574 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,10 @@ Built by [Firecrawl](https://firecrawl.dev) to handle text-based PDFs locally in - **CID font support** — ToUnicode CMap decoding for Type0/Identity-H fonts, UTF-16BE, UTF-8, and Latin-1 encodings. - **Multi-column layout** — Automatic detection of newspaper-style columns, sequential reading order, and RTL text support. - **Encoding issue detection** — Automatically flags broken font encodings so callers can fall back to OCR. -- **Optional OCR** — An opt-in Rust and CLI feature selectively renders only pages that need OCR, runs PP-OCRv6 Small locally, and preserves per-page provenance and hosted-fallback recommendations. +- **Selective OCR** — Rust, CLI, Python, and Node can render only pages that need OCR, run PP-OCRv6 Small locally, and preserve per-page provenance and hosted-fallback recommendations. - **Single document load** — The document is parsed once and shared between detection and extraction, avoiding redundant I/O. - **Browser WebAssembly** — Run the same Rust parser locally in browsers and Web Workers, with embedded CMaps and no server round trip. -- **Lightweight by default** — The default build is pure Rust with no ML models or external services. PDFium, ONNX Runtime, and OCR models are added only when the native `ocr` feature is selected and remain external runtime artifacts. +- **Lightweight by default** — The default Rust and browser builds remain pure extraction. Native Python and Node packages include the OCR integration, but PDFium, ONNX Runtime, and model files remain external and are touched only when a page is routed to OCR. ## Benchmark @@ -58,6 +58,10 @@ import pdf_inspector result = pdf_inspector.process_pdf("document.pdf") print(result.pdf_type) # "text_based", "scanned", "image_based", "mixed" print(result.markdown) # Markdown string or None + +# Selective OCR; clean text PDFs do not load the external OCR runtime. +ocr = pdf_inspector.process_pdf_with_ocr("document.pdf") +print(ocr.pages_routed_to_ocr) ``` > Full API reference: [docs/python.md](docs/python.md) @@ -70,11 +74,15 @@ npm install @firecrawl/pdf-inspector ```javascript import { readFileSync } from 'fs'; -import { processPdf, classifyPdf } from '@firecrawl/pdf-inspector'; +import { processPdf, processPdfWithOcr } from '@firecrawl/pdf-inspector'; -const result = processPdf(readFileSync('document.pdf')); +const pdf = readFileSync('document.pdf'); +const result = processPdf(pdf); console.log(result.pdfType); // "TextBased", "Scanned", "ImageBased", "Mixed" console.log(result.markdown); // Markdown string or null + +const ocr = await processPdfWithOcr(pdf); // selective OCR, off the event loop +console.log(ocr.pagesRoutedToOcr); ``` > Full API reference: [napi/README.md](napi/README.md) @@ -161,7 +169,7 @@ detect-pdf document.pdf --json detect-pdf document.pdf --analyze --json ``` -OCR is a separate native CLI build and does not change the default package: +Rust and CLI consumers opt into OCR at build time: ```bash cargo install pdf-inspector --features ocr --bin pdf2md @@ -171,8 +179,11 @@ PDFIUM_LIB_PATH=/path/to/libpdfium ORT_DYLIB_PATH=/path/to/libonnxruntime \ The OCR JSON envelope is versioned and reports routed pages, per-page source and confidence, warnings, and pages recommended for the hosted document -pipeline. See the [Rust API guide](docs/rust-api.md#complete-ocr-api) for model -cache and offline configuration. +pipeline. Native Python and Node packages expose the same pipeline without a +source-build feature. All native entry points still require separately +installed PDFium and ONNX Runtime libraries only when OCR is routed. See the +[Rust API guide](docs/rust-api.md#complete-ocr-api) for model cache and offline +configuration. From a source checkout, use `cargo run --bin pdf2md -- document.pdf` or `cargo run --bin detect-pdf -- document.pdf` instead. diff --git a/docs/python.md b/docs/python.md index 9d20fbd..423c874 100644 --- a/docs/python.md +++ b/docs/python.md @@ -1,6 +1,6 @@ # pdf-inspector -Fast PDF classification and text extraction. Detects whether a PDF is text-based or scanned, extracts text with position awareness, and converts to clean Markdown — all without OCR. Python bindings via [PyO3](https://pyo3.rs) for the [pdf-inspector](https://github.com/firecrawl/pdf-inspector) Rust library. +Fast PDF classification, text extraction, and selective OCR. Detects whether a PDF is text-based or scanned, extracts text with position awareness, and converts clean native and OCR results to Markdown. Python bindings via [PyO3](https://pyo3.rs) for the [pdf-inspector](https://github.com/firecrawl/pdf-inspector) Rust library. Built by [Firecrawl](https://firecrawl.dev) to handle text-based PDFs locally in under 200ms, skipping expensive OCR services for the ~54% of PDFs that don't need them. @@ -10,7 +10,8 @@ Built by [Firecrawl](https://firecrawl.dev) to handle text-based PDFs locally in - **Markdown conversion** — headings, lists, code blocks, bold/italic, URL linking, and dual-mode table detection (PDF drawing ops + text-alignment heuristics). - **Layout-aware extraction** — multi-column reading order, position and font info per text item, RTL support. - **Robust text decoding** — CID/Type0 fonts via ToUnicode CMaps, plus automatic flagging of broken encodings so callers can fall back to OCR. -- **Lightweight** — native Rust core, no ML models, no external services; ships type stubs. +- **Selective OCR** — `auto` routes only pages rejected by native extraction; `force` OCRs every selected page; `off` keeps the result/provenance contract without external runtime work. +- **External artifacts** — the wheel embeds no OCR models, PDFium, or ONNX Runtime; clean `auto` requests never load or download them. ## Benchmark @@ -39,6 +40,12 @@ pip install maturin maturin develop --release ``` +OCR calls that route work require compatible PDFium and ONNX Runtime shared +libraries. Set `PDFIUM_LIB_PATH` and `ORT_DYLIB_PATH` when they are not on the +platform library search path. The pinned OCR model set is downloaded and +checksum-verified on the first routed page; use `offline=True` with a warm +cache or `model_directory` to prohibit network access. + ## Usage ```python @@ -81,6 +88,19 @@ for page in result.pages: # Restrict to specific 0-indexed pages (preserves caller order) result = pdf_inspector.extract_pages_markdown("document.pdf", pages=[0, 2]) +# One-call selective OCR. This releases the GIL while processing. +ocr = pdf_inspector.process_pdf_with_ocr("document.pdf") +for page in ocr.pages: + print(page.page_number, page.provenance.source) + +# Restrict OCR processing to 1-indexed PDF pages and prohibit downloads. +ocr = pdf_inspector.process_pdf_with_ocr( + "document.pdf", + page_numbers=[1, 3], + model_directory="/opt/models/pp-ocrv6-small", + offline=True, +) + # Structure-tree elements from tagged PDFs (empty list when untagged). # Pages are 1-indexed to match TextItem.page, so (page, mcid) joins directly # against extract_text_with_positions — e.g. to recover real heading levels: @@ -99,6 +119,8 @@ headings = [ |---|---| | `process_pdf(path, pages=None)` | Full processing (detect + extract + markdown) | | `process_pdf_bytes(data, pages=None)` | Full processing from bytes | +| `process_pdf_with_ocr(path, **options)` | Native extraction + selective OCR with provenance | +| `process_pdf_with_ocr_bytes(data, **options)` | Native extraction + selective OCR from bytes | | `detect_pdf(path)` | Fast detection only (returns PdfResult) | | `detect_pdf_bytes(data)` | Fast detection from bytes | | `classify_pdf(path)` | Lightweight classification (returns PdfClassification) | @@ -137,6 +159,45 @@ class PageOcrReasons: # per-page OCR diagnostics page: int # 1-indexed reasons: list[str] # machine-readable reason identifiers +class OcrModelIdentity: + name: str # model family/name + revision: str # immutable artifact-set revision + +class OcrTimings: # per-page processing stages + render_ms: int + ocr_ms: int + assembly_ms: int + +class OcrPageProvenance: + page_number: int # 1-indexed + source: Literal["native", "ocr", "fused"] + ocr_model: OcrModelIdentity | None + render_dpi: float | None + ocr_confidence: float | None + timings: OcrTimings + warnings: list[str] + hosted_recommended: bool + +class OcrPageResult: + page_number: int # 1-indexed + markdown: str + provenance: OcrPageProvenance + +class OcrPdfResult: # process_pdf_with_ocr / bytes + markdown: str + pages: list[OcrPageResult] + page_count: int + pages_recommended_for_ocr: list[int] + pages_routed_to_ocr: list[int] + pages_recommending_hosted: list[int] + ocr_reasons_by_page: list[PageOcrReasons] + pages_with_tables: list[int] + pages_with_columns: list[int] + is_complex: bool + processing_time_ms: int + render_time_ms: int + ocr_time_ms: int + class PdfClassification: # classify_pdf pdf_type: str page_count: int diff --git a/napi/Cargo.lock b/napi/Cargo.lock index 3f63515..9ce2767 100644 --- a/napi/Cargo.lock +++ b/napi/Cargo.lock @@ -2,6 +2,22 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "ab_glyph" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] + +[[package]] +name = "ab_glyph_rasterizer" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" + [[package]] name = "adler2" version = "2.0.1" @@ -19,6 +35,20 @@ dependencies = [ "cpufeatures 0.2.17", ] +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "serde", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.4" @@ -28,6 +58,24 @@ dependencies = [ "memchr", ] +[[package]] +name = "aligned" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" +dependencies = [ + "as-slice", +] + +[[package]] +name = "aligned-vec" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -73,7 +121,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -84,7 +132,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -93,18 +141,129 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "arg_enum_proc_macro" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "as-slice" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +dependencies = [ + "stable_deref_trait", +] + [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "av-scenechange" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394" +dependencies = [ + "aligned", + "anyhow", + "arg_enum_proc_macro", + "arrayvec", + "log", + "num-rational", + "num-traits", + "pastey 0.1.1", + "rayon", + "thiserror", + "v_frame", + "y4m", +] + +[[package]] +name = "av1-grain" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" +dependencies = [ + "anyhow", + "arrayvec", + "log", + "nom 8.0.0", + "num-rational", + "v_frame", +] + +[[package]] +name = "avif-serialize" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7178fe5f7d460b13895ebb9dcb28a3a6216d2df2574a0806cb51b555d297f38" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + [[package]] name = "bitflags" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +[[package]] +name = "bitstream-io" +version = "4.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eff00be299a18769011411c9def0d827e8f2d7bf0c3dbf53633147a8867fd1f" +dependencies = [ + "no_std_io2", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -114,6 +273,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + [[package]] name = "block-padding" version = "0.3.3" @@ -123,12 +291,45 @@ dependencies = [ "generic-array", ] +[[package]] +name = "built" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c0e531d93d39c34eef561e929e8a7f86d77a5af08aac4f6d6e39976c51858e9" + [[package]] name = "bumpalo" version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + [[package]] name = "cbc" version = "0.1.2" @@ -145,6 +346,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -162,7 +365,7 @@ checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "rand_core", + "rand_core 0.10.0", ] [[package]] @@ -182,16 +385,74 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "inout", ] +[[package]] +name = "clipper2-rust" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fd663fe209e7030c956e3be4c051dcc20cdb73da794f31466762cff12ca11bf" +dependencies = [ + "num-traits", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "colorchoice" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "compact_str" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dfdd1c2274d9aa354115b09dc9a901d6c5576818cdf70d14cae2bdb47df00ab" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "serde", + "static_assertions", +] + +[[package]] +name = "console" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fe5f465a4f6fee88fad41b85d990f84c835335e85b5d9e6e63e0d06d28cba7c" +dependencies = [ + "encode_unicode", + "libc", + "unicode-width", + "windows-sys 0.61.2", +] + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "convert_case" version = "0.11.0" @@ -201,6 +462,16 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -259,6 +530,12 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-common" version = "0.1.7" @@ -269,6 +546,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + [[package]] name = "ctor" version = "0.8.0" @@ -285,6 +571,90 @@ version = "0.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" +[[package]] +name = "daachorse" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f55d7153ba3b507595872a3874803f07a8a81d1e888abed8e5db7da0597d6e2" + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88490bf1b990d87eaaa7ac8aa887f629a08e7359765b4911faf63c3763347d23" +dependencies = [ + "darling_core 0.24.0", + "darling_macro 0.24.0", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", +] + +[[package]] +name = "darling_core" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "084e274f91c482280130e1e34e0b8d6e66776a060d7b6de7b84289ca778868c4" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 3.0.3", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core 0.20.11", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f5792fa0d41cd2325ce0ffa64f0a340eaebd4971a3a0c5e1ffd2cc488a355e" +dependencies = [ + "darling_core 0.24.0", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "dary_heap" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1e3a325bc115f096c8b77bbf027a7c2592230e70be2d985be950d3d5e60ebe" +dependencies = [ + "serde", +] + [[package]] name = "deranged" version = "0.5.8" @@ -294,14 +664,77 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.117", +] + [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "crypto-common", + "block-buffer 0.10.4", + "crypto-common 0.1.7", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "const-oid", + "crypto-common 0.2.2", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", ] [[package]] @@ -334,6 +767,12 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -366,18 +805,85 @@ dependencies = [ "log", ] +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "esaxx-rs" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d817e038c30374a4bcb22f94d0a8a0e216958d4c3dcde369b1439fec4bdda6e6" + +[[package]] +name = "exr" +version = "1.74.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711fe42c9964295e01ee3fba3f9fe0e1d24b98886950d68efe81b1c76e21adf3" +dependencies = [ + "bit_field", + "half", + "lebe", + "miniz_oxide", + "num-complex", + "pulp", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fax" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "firecrawl-pdfium" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ae09ff39bd357efb94cacf8096ec527adb05a62022691ff2d68f09336e1612" +dependencies = [ + "libloading 0.8.9", +] + [[package]] name = "flate2" version = "1.1.9" @@ -388,12 +894,28 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "futures" version = "0.3.32" @@ -450,7 +972,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -492,6 +1014,29 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + [[package]] name = "getrandom" version = "0.4.2" @@ -501,13 +1046,58 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", - "rand_core", + "r-efi 6.0.0", + "rand_core 0.10.0", "wasip2", "wasip3", "wasm-bindgen", ] +[[package]] +name = "gif" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "glam" +version = "0.30.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19fc433e8437a212d1b6f1e68c7824af3aed907da60afa994e7f542d18d12aa9" + +[[package]] +name = "glam" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556f6b2ea90b8d15a74e0e7bb41671c9bdf38cd9f78c284d750b9ce58a2b5be7" + +[[package]] +name = "glam" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f70749695b063ecbf6b62949ccccde2e733ec3ecbbd71d467dca4e5c6c97cca0" + +[[package]] +name = "glam" +version = "0.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360bd2cd76e0cd9032d42cf2922155cecea2685b0cfa4630c3246df030bcfd6" + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -529,6 +1119,31 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hybrid-array" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" +dependencies = [ + "typenum", +] + [[package]] name = "iana-time-zone" version = "0.1.65" @@ -559,6 +1174,71 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "color_quant", + "exr", + "gif", + "image-webp", + "moxcms", + "num-traits", + "png", + "qoi", + "ravif", + "rayon", + "rgb", + "tiff", + "zune-core", + "zune-jpeg", +] + +[[package]] +name = "image-webp" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" +dependencies = [ + "byteorder-lite", + "quick-error", +] + +[[package]] +name = "imageproc" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7b27bc0867dc40df08deb53d6e96342db6e0702e7ae33ed09a4eba33e594b05" +dependencies = [ + "ab_glyph", + "approx", + "getrandom 0.4.2", + "image", + "itertools 0.14.0", + "nalgebra", + "num", + "rand 0.10.0", + "rand_distr", + "rayon", + "rustdct", +] + +[[package]] +name = "imgref" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89194689a993ab15268672e99e7b0e19da2da3268ac682e8f02d29d4d1434cd7" + [[package]] name = "include_dir" version = "0.7.4" @@ -590,6 +1270,19 @@ dependencies = [ "serde_core", ] +[[package]] +name = "indicatif" +version = "0.18.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9433806cd6b4ec1aba79c021c7e4c58fb4c3b9977c085062e611ac929998fb0c" +dependencies = [ + "console", + "portable-atomic", + "unicode-width", + "unit-prefix", + "web-time", +] + [[package]] name = "inout" version = "0.1.4" @@ -600,12 +1293,41 @@ dependencies = [ "generic-array", ] +[[package]] +name = "interpolate_name" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" @@ -624,7 +1346,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde_core", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -635,7 +1357,7 @@ checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -653,6 +1375,65 @@ dependencies = [ "jiff-tzdb", ] +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.117", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.2", + "libc", +] + [[package]] name = "js-sys" version = "0.3.94" @@ -663,18 +1444,50 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "leb128fmt" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + [[package]] name = "libc" version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +[[package]] +name = "libfuzzer-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9fd2f41a1cba099f79a0b6b6c35656cf7c03351a7bae8ff0f28f25270f929d2" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + [[package]] name = "libloading" version = "0.9.0" @@ -685,12 +1498,36 @@ dependencies = [ "windows-link", ] +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a" +dependencies = [ + "libc", +] + [[package]] name = "log" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "loop9" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" +dependencies = [ + "imgref", +] + [[package]] name = "lopdf" version = "0.42.0" @@ -704,17 +1541,17 @@ dependencies = [ "ecb", "encoding_rs", "flate2", - "getrandom", + "getrandom 0.4.2", "indexmap", "itoa", "jiff", "log", "md-5", - "nom", - "rand", + "nom 8.0.0", + "rand 0.10.0", "rangemap", "rayon", - "sha2", + "sha2 0.10.9", "stringprep", "thiserror", "time", @@ -722,6 +1559,51 @@ dependencies = [ "weezl", ] +[[package]] +name = "macro_rules_attribute" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3ae8f6d608c795738406608304d30a2dfbdc8e58e44f7ba43236da5208ded3c" +dependencies = [ + "macro_rules_attribute-proc_macro", + "pastey 0.2.3", +] + +[[package]] +name = "macro_rules_attribute-proc_macro" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc04a4c58212d57930a24bf47d3fa87485264a3a054e9c10e042eb373573ad3c" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matrixmultiply" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f607c237553f086e7043417a51df26b2eb899d3caff94e6a67592ff992fedc7" +dependencies = [ + "autocfg", + "rawpointer", +] + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + [[package]] name = "md-5" version = "0.10.6" @@ -729,7 +1611,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ "cfg-if", - "digest", + "digest 0.10.7", ] [[package]] @@ -738,6 +1620,12 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.9" @@ -748,6 +1636,91 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "monostate" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3341a273f6c9d5bef1908f17b7267bbab0e95c9bf69a0d4dcf8e9e1b2c76ef67" +dependencies = [ + "monostate-impl", + "serde", + "serde_core", +] + +[[package]] +name = "monostate-impl" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4db6d5580af57bf992f59068d4ea26fd518574ff48d7639b255a36f9de6e7e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "multiversion" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edb7f0ff51249dfda9ab96b5823695e15a052dc15074c9dbf3d118afaf2c201" +dependencies = [ + "multiversion-macros", + "target-features", +] + +[[package]] +name = "multiversion-macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b093064383341eb3271f42e381cb8f10a01459478446953953c75d24bd339fc0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "target-features", +] + +[[package]] +name = "nalgebra" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc43a60c217b0c6ff46e47f26911015ad8d2e5a8be1af668c67e370d99a4346" +dependencies = [ + "approx", + "glam 0.30.10", + "glam 0.31.1", + "glam 0.32.1", + "glam 0.33.3", + "matrixmultiply", + "nalgebra-macros", + "num-complex", + "num-rational", + "num-traits", + "simba", + "typenum", +] + +[[package]] +name = "nalgebra-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "973e7178a678cfd059ccec50887658d482ce16b0aa9da3888ddeab5cd5eb4889" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "napi" version = "3.8.4" @@ -782,7 +1755,7 @@ dependencies = [ "napi-derive-backend", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -795,7 +1768,7 @@ dependencies = [ "proc-macro2", "quote", "semver", - "syn", + "syn 2.0.117", ] [[package]] @@ -804,7 +1777,37 @@ version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eb602b84d7c1edae45e50bbf1374696548f36ae179dfa667f577e384bb90c2b" dependencies = [ - "libloading", + "libloading 0.9.0", +] + +[[package]] +name = "ndarray" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520080814a7a6b4a6e9070823bb24b4531daac8c4627e08ba5de8c5ef2f2752d" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "portable-atomic", + "portable-atomic-util", + "rawpointer", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "no_std_io2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418abd1b6d34fbf6cae440dc874771b0525a604428704c76e48b29a5e67b8003" +dependencies = [ + "memchr", ] [[package]] @@ -813,6 +1816,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nom" version = "8.0.0" @@ -822,12 +1835,101 @@ dependencies = [ "memchr", ] +[[package]] +name = "noop_proc_macro" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "bytemuck", + "num-traits", +] + [[package]] name = "num-conv" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -835,6 +1937,62 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", +] + +[[package]] +name = "oar-ocr" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffeabbc8ff0c93b79da9c7b6515552ffd64114f86dc649b9baacca6616d5def8" +dependencies = [ + "image", + "imageproc", + "oar-ocr-core", + "oar-ocr-derive", + "rayon", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "oar-ocr-core" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf73d1be858b752333aabfda8208ed55f4126ca07dc7bbde1f4d35a2552057f6" +dependencies = [ + "clipper2-rust", + "image", + "imageproc", + "itertools 0.15.0", + "multiversion", + "nalgebra", + "ndarray", + "oar-ocr-derive", + "ort", + "rayon", + "regex", + "serde", + "serde_json", + "thiserror", + "tokenizers", + "tracing", + "tracing-subscriber", + "wide", +] + +[[package]] +name = "oar-ocr-derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5cc69235973da61eebc65f2385903a2a2ced84dbafc65f99935af9e61d6d86" +dependencies = [ + "darling 0.24.0", + "proc-macro2", + "quote", + "syn 3.0.3", ] [[package]] @@ -849,20 +2007,109 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "onig" +version = "6.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc3cbf698f9438986c11a880c90a6d04b9de27575afd28bbf45b154b6c709e2" +dependencies = [ + "bitflags", + "libc", + "once_cell", + "onig_sys", +] + +[[package]] +name = "onig_sys" +version = "69.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e68317604e77e53b85896388e1a803c1d21b74c899ec9e5e1112db90735edd7" +dependencies = [ + "cc", + "pkg-config", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ort" +version = "2.0.0-rc.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4336a1e2b38848325241c72889086886004e589b7c74f335e60a8e8db5138a0b" +dependencies = [ + "libloading 0.9.0", + "ndarray", + "ort-sys", + "smallvec", + "tracing", +] + +[[package]] +name = "ort-sys" +version = "2.0.0-rc.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf211e3776eea6aec988552fa118dd746d70e1b1e5e244058d1c98015f3e5872" + +[[package]] +name = "owned_ttf_parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" +dependencies = [ + "ttf-parser", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + +[[package]] +name = "pastey" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4" + [[package]] name = "pdf-inspector" version = "1.14.2" dependencies = [ + "dirs", "env_logger", + "firecrawl-pdfium", + "fs2", + "image", "include_dir", "log", "lopdf", + "oar-ocr", "once_cell", + "ort", "rayon", "regex", + "sha2 0.11.0", "thiserror", "ttf-parser", "unicode-normalization", + "ureq", + "windows-sys 0.61.2", ] [[package]] @@ -875,12 +2122,37 @@ dependencies = [ "pdf-inspector", ] +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + [[package]] name = "pin-project-lite" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "portable-atomic" version = "1.13.1" @@ -902,6 +2174,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "prettyplease" version = "0.2.37" @@ -909,7 +2190,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.117", +] + +[[package]] +name = "primal-check" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc0d895b311e3af9902528fbb8f928688abbd95872819320517cc24ca6b2bd08" +dependencies = [ + "num-integer", ] [[package]] @@ -921,6 +2211,69 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "pulp" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046aa45b989642ec2e4717c8e72d677b13edd831a4d3b6cf37d9a3e54912496a" +dependencies = [ + "bytemuck", + "cfg-if", + "libm", + "num-complex", + "paste", + "pulp-wasm-simd-flag", + "raw-cpuid", + "reborrow", + "version_check", +] + +[[package]] +name = "pulp-wasm-simd-flag" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d8f70e07b9c3962945a74e59ca1c511bba65b6419468acc217c457d93f3c740" + +[[package]] +name = "pxfm" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" + +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quote" version = "1.0.45" @@ -930,12 +2283,28 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "r-efi" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha", + "rand_core 0.9.5", +] + [[package]] name = "rand" version = "0.10.0" @@ -943,8 +2312,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" dependencies = [ "chacha20", - "getrandom", - "rand_core", + "getrandom 0.4.2", + "rand_core 0.10.0", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", ] [[package]] @@ -953,12 +2341,87 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" +[[package]] +name = "rand_distr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d431c2703ccf129de4d45253c03f49ebb22b97d6ad79ee3ecfc7e3f4862c1d8" +dependencies = [ + "num-traits", + "rand 0.10.0", +] + [[package]] name = "rangemap" version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "973443cf09a9c8656b574a866ab68dfa19f0867d0340648c7d2f6a71b8a8ea68" +[[package]] +name = "rav1e" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b" +dependencies = [ + "aligned-vec", + "arbitrary", + "arg_enum_proc_macro", + "arrayvec", + "av-scenechange", + "av1-grain", + "bitstream-io", + "built", + "cfg-if", + "interpolate_name", + "itertools 0.14.0", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "paste", + "profiling", + "rand 0.9.5", + "rand_chacha", + "simd_helpers", + "thiserror", + "v_frame", + "wasm-bindgen", +] + +[[package]] +name = "ravif" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45" +dependencies = [ + "avif-serialize", + "imgref", + "loop9", + "quick-error", + "rav1e", + "rayon", + "rgb", +] + +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "rayon" version = "1.11.0" @@ -969,6 +2432,17 @@ dependencies = [ "rayon-core", ] +[[package]] +name = "rayon-cond" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2964d0cf57a3e7a06e8183d14a8b527195c706b7983549cd5462d5aa3747438f" +dependencies = [ + "either", + "itertools 0.14.0", + "rayon", +] + [[package]] name = "rayon-core" version = "1.13.0" @@ -979,6 +2453,23 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "reborrow" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror", +] + [[package]] name = "regex" version = "1.12.3" @@ -1008,18 +2499,200 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +[[package]] +name = "rgb" +version = "0.8.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rustc-hash" version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustdct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b61555105d6a9bf98797c063c362a1d24ed8ab0431655e38f1cf51e52089551" +dependencies = [ + "rustfft", +] + +[[package]] +name = "rustfft" +version = "6.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21db5f9893e91f41798c88680037dba611ca6674703c1a18601b01a72c8adb89" +dependencies = [ + "num-complex", + "num-integer", + "num-traits", + "primal-check", + "strength_reduce", + "transpose", +] + +[[package]] +name = "rustls" +version = "0.23.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0527518605e68109d875e248ea259b6758801cf165e4b2c2733ae3b51f12535a" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "safe_arch" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42c6efa15875e6ecb39ca61fb0b0c1a40b84fac5a5ffe71eef7d1000c8eb3f5f" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" version = "1.0.27" @@ -1033,6 +2706,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", + "serde_derive", ] [[package]] @@ -1052,7 +2726,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1076,7 +2750,27 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures 0.2.17", - "digest", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", ] [[package]] @@ -1085,18 +2779,91 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "simba" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a7200d82ff1c7b4235efd12f11e2537a402c91cf83a5cb97ce80eef787fde7" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "wide", +] + [[package]] name = "simd-adler32" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simd_helpers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6" +dependencies = [ + "quote", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "slab" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "spm_precompiled" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5851699c4033c63636f7ea4cf7b7c1f1bf06d0cc03cfb42e711de5a5c46cf326" +dependencies = [ + "base64 0.13.1", + "nom 7.1.3", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strength_reduce" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" + [[package]] name = "stringprep" version = "0.1.5" @@ -1108,6 +2875,18 @@ dependencies = [ "unicode-properties", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.117" @@ -1119,6 +2898,23 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-features" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1bbb9f3c5c463a01705937a24fdabc5047929ac764b2d5b9cf681c1f5041ed5" + [[package]] name = "thiserror" version = "2.0.18" @@ -1136,7 +2932,30 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", +] + +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", ] [[package]] @@ -1185,6 +3004,111 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tokenizers" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44e5bea67576e04b6ff8564c5d9e09c2ef0cf476502245f2f120e497769d3112" +dependencies = [ + "ahash", + "compact_str", + "daachorse", + "dary_heap", + "derive_builder", + "esaxx-rs", + "getrandom 0.3.4", + "indicatif", + "itertools 0.14.0", + "log", + "macro_rules_attribute", + "monostate", + "onig", + "paste", + "rand 0.9.5", + "rayon", + "rayon-cond", + "regex", + "regex-syntax", + "serde", + "serde_json", + "spm_precompiled", + "thiserror", + "unicode-normalization-alignments", + "unicode-segmentation", + "unicode_categories", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "transpose" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e" +dependencies = [ + "num-integer", + "strength_reduce", +] + [[package]] name = "ttf-parser" version = "0.25.1" @@ -1218,6 +3142,15 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-normalization-alignments" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f613e4fa046e69818dd287fdc4bc78175ff20331479dab6e1b0f98d57062de" +dependencies = [ + "smallvec", +] + [[package]] name = "unicode-properties" version = "0.1.4" @@ -1230,24 +3163,116 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "unicode-xid" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "unit-prefix" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "972d7902c8735f2695410b8aed7df6ed12a47394aa1c8d7af49f0497b731a94d" +dependencies = [ + "base64 0.23.1", + "log", + "percent-encoding", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "ureq-proto", + "utf8-zero", + "webpki-roots", +] + +[[package]] +name = "ureq-proto" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da5f78b09e6941e1a0f2e30e695e4b120377b54d5e0aec11b594bb57b3971613" +dependencies = [ + "base64 0.23.1", + "http", + "httparse", + "log", +] + +[[package]] +name = "utf8-zero" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" + [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "v_frame" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" +dependencies = [ + "aligned-vec", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + [[package]] name = "wasip2" version = "1.0.2+wasi-0.2.9" @@ -1298,7 +3323,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -1345,12 +3370,81 @@ dependencies = [ "semver", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webpki-roots" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "weezl" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" +[[package]] +name = "wide" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de2aaf408e58689c2096682331b1f42bb2d9f2ed6b11560407d023cd0a6c634e" +dependencies = [ + "bytemuck", + "safe_arch", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.62.2" @@ -1372,7 +3466,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1383,7 +3477,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1410,6 +3504,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -1419,6 +3522,70 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "wit-bindgen" version = "0.51.0" @@ -1449,7 +3616,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -1465,7 +3632,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -1507,8 +3674,64 @@ dependencies = [ "wasmparser", ] +[[package]] +name = "y4m" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" + +[[package]] +name = "zerocopy" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + [[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zune-core" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b" + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "zune-jpeg" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" +dependencies = [ + "zune-core", +] diff --git a/napi/Cargo.toml b/napi/Cargo.toml index 28ff7e6..b496667 100644 --- a/napi/Cargo.toml +++ b/napi/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -pdf-inspector = { path = ".." } +pdf-inspector = { path = "..", features = ["ocr"] } napi = { version = "3.0.0", features = ["serde-json"] } napi-derive = "3.0.0" diff --git a/napi/README.md b/napi/README.md index 414d869..a89dac6 100644 --- a/napi/README.md +++ b/napi/README.md @@ -10,7 +10,8 @@ Built by [Firecrawl](https://firecrawl.dev) for hybrid OCR pipelines — extract - **Region-based extraction** — pull text from bounding boxes with per-region quality checks (`needsOcr`). - **Layout-aware** — multi-column reading order, position and font info per text item, RTL support. - **Robust text decoding** — CID/Type0 fonts via ToUnicode CMaps, plus automatic flagging of broken encodings so callers can fall back to OCR. -- **Lightweight** — native Rust core via napi-rs, no ML models, no external services; ~5–6 MB platform binary, TypeScript definitions included. +- **Selective OCR** — `Auto` routes only pages rejected by native extraction and returns source/model provenance plus hosted-fallback recommendations. +- **External artifacts** — the native package embeds no OCR models, PDFium, or ONNX Runtime; clean `Auto` requests never load or download them. ## Benchmark @@ -36,8 +37,40 @@ bun add @firecrawl/pdf-inspector Prebuilt binaries for **Linux x64/ARM64** (glibc and musl/Alpine), **macOS ARM64**, and **Windows x64** — npm installs only the one matching your platform. No Rust toolchain needed. +OCR calls that route work require compatible PDFium and ONNX Runtime shared +libraries. Set `PDFIUM_LIB_PATH` and `ORT_DYLIB_PATH` when they are not on the +platform library search path. The pinned OCR model set is downloaded and +checksum-verified on the first routed page; use `offline: true` with a warm +cache or `modelDirectory` to prohibit network access. + ## API +### `processPdfWithOcr(buffer: Buffer, options?: OcrOptions): Promise` + +Run native extraction first and OCR only the pages selected by its quality +signals. The default mode is `Auto`; `Off` returns the same detailed result +shape without external runtime work, and `Force` OCRs every selected page. +The work runs on the libuv thread pool and never blocks Node's event loop. + +```typescript +import { OcrMode, processPdfWithOcr } from '@firecrawl/pdf-inspector' + +const result = await processPdfWithOcr(pdf, { + mode: OcrMode.Auto, + pageNumbers: [1, 3], // 1-indexed +}) + +for (const page of result.pages) { + console.log(page.pageNumber, page.provenance.source) +} +console.log(result.pagesRoutedToOcr) +console.log(result.pagesRecommendingHosted) +``` + +For offline deployments, pass `modelDirectory` and `offline: true`. Other +controls include `dpi`, `minimumConfidence`, +`hostedRecommendationConfidence`, and `password`. + ### `classifyPdf(buffer: Buffer): PdfClassification` Classify a PDF as TextBased, Scanned, Mixed, or ImageBased (~10-50ms). Returns which pages need OCR. @@ -124,6 +157,22 @@ interface RegionText { needsOcr: boolean // true when text is unreliable ocrReason?: string // "suspected_garbled_text" when known } + +interface OcrPdfResult { + markdown: string + pages: OcrPageResult[] // 1-indexed pages + provenance + pageCount: number + pagesRecommendedForOcr: number[] + pagesRoutedToOcr: number[] + pagesRecommendingHosted: number[] + ocrReasonsByPage: PageOcrReasons[] + pagesWithTables: number[] + pagesWithColumns: number[] + isComplex: boolean + processingTimeMs: number + renderTimeMs: number + ocrTimeMs: number +} ``` ## Platforms diff --git a/napi/src/lib.rs b/napi/src/lib.rs index 652469a..7e9efda 100644 --- a/napi/src/lib.rs +++ b/napi/src/lib.rs @@ -27,6 +27,26 @@ pub enum ItemType { FormField, } +/// Selects when OCR runs. +#[napi(string_enum)] +#[derive(Clone, Copy)] +pub enum OcrMode { + /// Never run OCR; return the native extraction in the OCR result shape. + Off, + /// Run OCR only on pages selected by the native quality signals. + Auto, + /// Run OCR on every selected page. + Force, +} + +/// How final page content was sourced. +#[napi(string_enum)] +pub enum PageContentSource { + Native, + Ocr, + Fused, +} + // --------------------------------------------------------------------------- // Result types // --------------------------------------------------------------------------- @@ -128,6 +148,84 @@ pub struct VectorGridDetectionJs { pub cell_bboxes: Vec>, } +/// Options for one-call native extraction with selective OCR. +#[napi(object)] +#[derive(Clone)] +pub struct OcrOptions { + /// OCR routing behavior. Defaults to Auto. + pub mode: Option, + /// Optional 1-indexed page selection. + pub page_numbers: Option>, + /// Password for an encrypted PDF. + pub password: Option, + /// Page rasterization resolution. Defaults to 150 DPI. + pub dpi: Option, + /// Drop OCR spans below this inclusive 0-1 threshold. + pub minimum_confidence: Option, + /// Recommend hosted parsing below this inclusive 0-1 page confidence. + pub hosted_recommendation_confidence: Option, + /// Directory containing an offline OCR model set. + pub model_directory: Option, + /// Disable model downloads and require a model directory or warm cache. + pub offline: Option, +} + +/// Exact OCR model identity retained in page provenance. +#[napi(object)] +pub struct OcrModelIdentity { + pub name: String, + pub revision: String, +} + +/// Per-page OCR processing timings. +#[napi(object)] +pub struct OcrTimings { + pub render_ms: u32, + pub ocr_ms: u32, + pub assembly_ms: u32, +} + +/// Source, model, confidence, and fallback metadata for one page. +#[napi(object)] +pub struct OcrPageProvenance { + /// 1-indexed page number. + pub page_number: u32, + pub source: PageContentSource, + pub ocr_model: Option, + pub render_dpi: Option, + pub ocr_confidence: Option, + pub timings: OcrTimings, + pub warnings: Vec, + pub hosted_recommended: bool, +} + +/// Final Markdown and provenance for one page. +#[napi(object)] +pub struct OcrPageResult { + /// 1-indexed page number. + pub page_number: u32, + pub markdown: String, + pub provenance: OcrPageProvenance, +} + +/// Complete native/OCR Markdown output. +#[napi(object)] +pub struct OcrPdfResult { + pub markdown: String, + pub pages: Vec, + pub page_count: u32, + pub pages_recommended_for_ocr: Vec, + pub pages_routed_to_ocr: Vec, + pub pages_recommending_hosted: Vec, + pub ocr_reasons_by_page: Vec, + pub pages_with_tables: Vec, + pub pages_with_columns: Vec, + pub is_complex: bool, + pub processing_time_ms: u32, + pub render_time_ms: u32, + pub ocr_time_ms: u32, +} + // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- @@ -168,6 +266,103 @@ fn to_napi_page_ocr_reasons(reasons: Vec) -> Vec< .collect() } +fn to_core_ocr_options(options: Option) -> pdf_inspector::vision::OcrPdfOptions { + let mut result = pdf_inspector::vision::OcrPdfOptions::auto(); + let Some(options) = options else { + return result; + }; + + if let Some(mode) = options.mode { + result.ocr.mode = match mode { + OcrMode::Off => pdf_inspector::vision::OcrMode::Off, + OcrMode::Auto => pdf_inspector::vision::OcrMode::Auto, + OcrMode::Force => pdf_inspector::vision::OcrMode::Force, + }; + } + if let Some(pages) = options.page_numbers { + result = result.page_numbers(pages); + } + if let Some(password) = options.password { + result = result.password(password); + } + if let Some(dpi) = options.dpi { + result.render.dpi = dpi as f32; + } + if let Some(minimum_confidence) = options.minimum_confidence { + result.ocr.minimum_confidence = minimum_confidence as f32; + } + if let Some(confidence) = options.hosted_recommendation_confidence { + result.hosted_recommendation_confidence = confidence as f32; + } + if let Some(directory) = options.model_directory { + result.ocr.model_directory = Some(directory.into()); + } + if options.offline.unwrap_or(false) { + result.ocr.model_downloads = pdf_inspector::vision::ModelDownloadPolicy::Offline; + } + result +} + +fn convert_page_content_source( + source: pdf_inspector::vision::PageContentSource, +) -> PageContentSource { + match source { + pdf_inspector::vision::PageContentSource::Native => PageContentSource::Native, + pdf_inspector::vision::PageContentSource::Ocr => PageContentSource::Ocr, + pdf_inspector::vision::PageContentSource::Fused => PageContentSource::Fused, + _ => PageContentSource::Native, + } +} + +fn timing_ms(value: u64) -> u32 { + u32::try_from(value).unwrap_or(u32::MAX) +} + +fn to_napi_ocr_result(result: pdf_inspector::vision::OcrPdfResult) -> OcrPdfResult { + OcrPdfResult { + markdown: result.markdown, + pages: result + .pages + .into_iter() + .map(|page| { + let provenance = page.provenance; + OcrPageResult { + page_number: page.page_number, + markdown: page.markdown, + provenance: OcrPageProvenance { + page_number: provenance.page_number, + source: convert_page_content_source(provenance.source), + ocr_model: provenance.ocr_model.map(|model| OcrModelIdentity { + name: model.name, + revision: model.revision, + }), + render_dpi: provenance.render_dpi.map(f64::from), + ocr_confidence: provenance.ocr_confidence.map(f64::from), + timings: OcrTimings { + render_ms: timing_ms(provenance.timings.render_ms), + ocr_ms: timing_ms(provenance.timings.ocr_ms), + assembly_ms: timing_ms(provenance.timings.assembly_ms), + }, + warnings: provenance.warnings, + hosted_recommended: provenance.hosted_recommended, + }, + } + }) + .collect(), + page_count: result.page_count, + pages_recommended_for_ocr: result.pages_recommended_for_ocr, + pages_routed_to_ocr: result.pages_routed_to_ocr, + pages_recommending_hosted: result.pages_recommending_hosted, + ocr_reasons_by_page: to_napi_page_ocr_reasons(result.ocr_reasons_by_page), + pages_with_tables: result.pages_with_tables, + pages_with_columns: result.pages_with_columns, + is_complex: result.is_complex, + processing_time_ms: timing_ms(result.processing_time_ms), + render_time_ms: timing_ms(result.render_time_ms), + ocr_time_ms: timing_ms(result.ocr_time_ms), + } +} + fn convert_item_type(t: &pdf_inspector::types::ItemType) -> (ItemType, Option) { match t { pdf_inspector::types::ItemType::Text => (ItemType::Text, None), @@ -219,6 +414,13 @@ fn process_pdf_impl(bytes: &[u8], pages: Option>) -> Result Ok(to_napi_result(result)) } +fn process_pdf_with_ocr_impl(bytes: &[u8], options: Option) -> Result { + let options = to_core_ocr_options(options); + let result = pdf_inspector::vision::process_pdf_with_ocr_mem(bytes, options) + .map_err(|error| to_napi_err(error, "process_pdf_with_ocr"))?; + Ok(to_napi_ocr_result(result)) +} + fn classify_pdf_impl(bytes: &[u8]) -> Result { let result = pdf_inspector::classify_pdf_mem(bytes).map_err(|e| to_napi_err(e, "classify_pdf"))?; @@ -818,6 +1020,45 @@ pub fn process_pdf_async(buffer: Buffer, pages: Option>) -> AsyncTask

, + options: Option, +} + +impl Task for ProcessPdfWithOcrTask { + type Output = OcrPdfResult; + type JsValue = OcrPdfResult; + + fn compute(&mut self) -> Result { + let bytes = std::mem::take(&mut self.bytes); + let options = self.options.take(); + catch_panic( + "process_pdf_with_ocr", + panic::AssertUnwindSafe(move || process_pdf_with_ocr_impl(&bytes, options)), + ) + } + + fn resolve(&mut self, _env: Env, output: Self::Output) -> Result { + Ok(output) + } +} + +/// Process a PDF with selective OCR on the libuv thread pool. +/// +/// OCR defaults to Auto, which only loads PDFium, ONNX Runtime, and the OCR +/// model if native extraction routes at least one page. The input buffer is +/// copied before the promise is returned and is safe to reuse immediately. +#[napi(ts_return_type = "Promise")] +pub fn process_pdf_with_ocr( + buffer: Buffer, + options: Option, +) -> AsyncTask { + AsyncTask::new(ProcessPdfWithOcrTask { + bytes: buffer.to_vec(), + options, + }) +} + pub struct ClassifyPdfTask { bytes: Vec, } diff --git a/napi/test.mjs b/napi/test.mjs index 15585a3..c1cd0ca 100644 --- a/napi/test.mjs +++ b/napi/test.mjs @@ -3,6 +3,7 @@ import { strict as assert } from 'assert'; import { processPdf, processPdfAsync, + processPdfWithOcr, detectPdf, classifyPdf, classifyPdfAsync, @@ -220,6 +221,37 @@ const fromMutated = await inFlight; assert.equal(fromMutated.markdown, result.markdown); console.log(' processPdfAsync input copied at call time: OK'); +// --- Selective OCR --- +console.log('Testing processPdfWithOcr...'); + +// Off exercises the complete result/provenance contract without loading +// external PDFium, ONNX Runtime, or model artifacts. +const ocrOff = await processPdfWithOcr(fixture, { mode: 'Off' }); +assert.equal(ocrOff.pageCount, 3); +assert.equal(ocrOff.pages.length, 3); +assert.deepEqual(ocrOff.pagesRoutedToOcr, []); +assert.ok(ocrOff.pages.every(page => page.provenance.source === 'Native')); +assert.ok(ocrOff.pages.every(page => page.provenance.ocrModel === undefined)); +assert.ok(ocrOff.markdown.length > 0); + +// Auto must preserve the lightweight path for clean text PDFs. +const ocrAuto = await processPdfWithOcr(fixture); +assert.deepEqual(ocrAuto.pagesRoutedToOcr, []); +assert.equal(ocrAuto.renderTimeMs, 0); +assert.equal(ocrAuto.ocrTimeMs, 0); + +const ocrSelected = await processPdfWithOcr(fixture, { + mode: 'Off', + pageNumbers: [2], +}); +assert.deepEqual(ocrSelected.pages.map(page => page.pageNumber), [2]); + +await assert.rejects( + processPdfWithOcr(fixture, { mode: 'Off', pageNumbers: [0] }), + /page 0/, +); +console.log(' processPdfWithOcr: OK'); + // concurrent async calls all settle const [c1, c2, c3] = await Promise.all([ processPdfAsync(fixture), diff --git a/pdf_inspector.pyi b/pdf_inspector.pyi index f2ea3d5..96d6dcf 100644 --- a/pdf_inspector.pyi +++ b/pdf_inspector.pyi @@ -1,6 +1,6 @@ """Type stubs for pdf_inspector.""" -from typing import Optional +from typing import Literal, Optional class PdfResult: """Result of processing a PDF file.""" @@ -27,6 +27,53 @@ class PageOcrReasons: reasons: list[str] """Machine-readable OCR reason identifiers.""" +class OcrModelIdentity: + """Exact OCR model identity retained in page provenance.""" + name: str + revision: str + +class OcrTimings: + """Per-page OCR processing timings.""" + render_ms: int + ocr_ms: int + assembly_ms: int + +class OcrPageProvenance: + """Source, model, confidence, and fallback metadata for one page.""" + page_number: int + """1-indexed page number.""" + source: Literal["native", "ocr", "fused"] + """'native', 'ocr', or 'fused'.""" + ocr_model: Optional[OcrModelIdentity] + render_dpi: Optional[float] + ocr_confidence: Optional[float] + timings: OcrTimings + warnings: list[str] + hosted_recommended: bool + +class OcrPageResult: + """Final Markdown and provenance for one page.""" + page_number: int + """1-indexed page number.""" + markdown: str + provenance: OcrPageProvenance + +class OcrPdfResult: + """Complete native/OCR Markdown output.""" + markdown: str + pages: list[OcrPageResult] + page_count: int + pages_recommended_for_ocr: list[int] + pages_routed_to_ocr: list[int] + pages_recommending_hosted: list[int] + ocr_reasons_by_page: list[PageOcrReasons] + pages_with_tables: list[int] + pages_with_columns: list[int] + is_complex: bool + processing_time_ms: int + render_time_ms: int + ocr_time_ms: int + class PdfClassification: """Lightweight PDF classification result.""" pdf_type: str @@ -114,6 +161,39 @@ def process_pdf_bytes(data: bytes, pages: Optional[list[int]] = None) -> PdfResu """Process a PDF from bytes in memory.""" ... +def process_pdf_with_ocr( + path: str, + *, + mode: Literal["off", "auto", "force"] = "auto", + page_numbers: Optional[list[int]] = None, + password: Optional[str] = None, + dpi: float = 150.0, + minimum_confidence: float = 0.0, + hosted_recommendation_confidence: float = 0.5, + model_directory: Optional[str] = None, + offline: bool = False, +) -> OcrPdfResult: + """Process a PDF through native extraction and selective OCR. + + Page numbers are 1-indexed. OCR runs without holding the Python GIL. + """ + ... + +def process_pdf_with_ocr_bytes( + data: bytes, + *, + mode: Literal["off", "auto", "force"] = "auto", + page_numbers: Optional[list[int]] = None, + password: Optional[str] = None, + dpi: float = 150.0, + minimum_confidence: float = 0.0, + hosted_recommendation_confidence: float = 0.5, + model_directory: Optional[str] = None, + offline: bool = False, +) -> OcrPdfResult: + """Process PDF bytes through native extraction and selective OCR.""" + ... + def detect_pdf(path: str) -> PdfResult: """Fast detection only — no text extraction.""" ... diff --git a/src/python.rs b/src/python.rs index e0e709d..8042855 100644 --- a/src/python.rs +++ b/src/python.rs @@ -85,6 +85,107 @@ impl PyPageOcrReasons { } } +/// Exact OCR model identity retained in page provenance. +#[pyclass(name = "OcrModelIdentity")] +#[derive(Clone)] +pub struct PyOcrModelIdentity { + #[pyo3(get)] + pub name: String, + #[pyo3(get)] + pub revision: String, +} + +/// Per-page OCR processing timings. +#[pyclass(name = "OcrTimings")] +#[derive(Clone)] +pub struct PyOcrTimings { + #[pyo3(get)] + pub render_ms: u64, + #[pyo3(get)] + pub ocr_ms: u64, + #[pyo3(get)] + pub assembly_ms: u64, +} + +/// Source, model, confidence, and fallback metadata for one page. +#[pyclass(name = "OcrPageProvenance")] +#[derive(Clone)] +pub struct PyOcrPageProvenance { + /// 1-indexed page number. + #[pyo3(get)] + pub page_number: u32, + /// "native", "ocr", or "fused". + #[pyo3(get)] + pub source: String, + #[pyo3(get)] + pub ocr_model: Option, + #[pyo3(get)] + pub render_dpi: Option, + #[pyo3(get)] + pub ocr_confidence: Option, + #[pyo3(get)] + pub timings: PyOcrTimings, + #[pyo3(get)] + pub warnings: Vec, + #[pyo3(get)] + pub hosted_recommended: bool, +} + +/// Final Markdown and provenance for one page. +#[pyclass(name = "OcrPageResult")] +#[derive(Clone)] +pub struct PyOcrPageResult { + /// 1-indexed page number. + #[pyo3(get)] + pub page_number: u32, + #[pyo3(get)] + pub markdown: String, + #[pyo3(get)] + pub provenance: PyOcrPageProvenance, +} + +/// Complete native/OCR Markdown output. +#[pyclass(name = "OcrPdfResult")] +#[derive(Clone)] +pub struct PyOcrPdfResult { + #[pyo3(get)] + pub markdown: String, + #[pyo3(get)] + pub pages: Vec, + #[pyo3(get)] + pub page_count: u32, + #[pyo3(get)] + pub pages_recommended_for_ocr: Vec, + #[pyo3(get)] + pub pages_routed_to_ocr: Vec, + #[pyo3(get)] + pub pages_recommending_hosted: Vec, + #[pyo3(get)] + pub ocr_reasons_by_page: Vec, + #[pyo3(get)] + pub pages_with_tables: Vec, + #[pyo3(get)] + pub pages_with_columns: Vec, + #[pyo3(get)] + pub is_complex: bool, + #[pyo3(get)] + pub processing_time_ms: u64, + #[pyo3(get)] + pub render_time_ms: u64, + #[pyo3(get)] + pub ocr_time_ms: u64, +} + +#[pymethods] +impl PyOcrPdfResult { + fn __repr__(&self) -> String { + format!( + "OcrPdfResult(pages={}, routed_to_ocr={:?}, recommending_hosted={:?})", + self.page_count, self.pages_routed_to_ocr, self.pages_recommending_hosted + ) + } +} + // --------------------------------------------------------------------------- // Classification wrapper (lightweight) // --------------------------------------------------------------------------- @@ -362,6 +463,101 @@ fn to_py_err(e: crate::PdfError) -> PyErr { PyValueError::new_err(e.to_string()) } +struct PythonOcrOptions { + mode: String, + page_numbers: Option>, + password: Option, + dpi: f32, + minimum_confidence: f32, + hosted_recommendation_confidence: f32, + model_directory: Option, + offline: bool, +} + +fn build_ocr_options(binding: PythonOcrOptions) -> PyResult { + let mode = match binding.mode.trim().to_ascii_lowercase().as_str() { + "off" => crate::vision::OcrMode::Off, + "auto" => crate::vision::OcrMode::Auto, + "force" => crate::vision::OcrMode::Force, + _ => { + return Err(PyValueError::new_err( + "mode must be 'off', 'auto', or 'force'", + )); + } + }; + + let mut options = crate::vision::OcrPdfOptions::new().mode(mode); + options.render.dpi = binding.dpi; + options.ocr.minimum_confidence = binding.minimum_confidence; + options.hosted_recommendation_confidence = binding.hosted_recommendation_confidence; + if let Some(pages) = binding.page_numbers { + options = options.page_numbers(pages); + } + if let Some(password) = binding.password { + options = options.password(password); + } + if let Some(directory) = binding.model_directory { + options.ocr.model_directory = Some(directory.into()); + } + if binding.offline { + options.ocr.model_downloads = crate::vision::ModelDownloadPolicy::Offline; + } + Ok(options) +} + +fn page_content_source_str(source: crate::vision::PageContentSource) -> String { + match source { + crate::vision::PageContentSource::Native => "native".into(), + crate::vision::PageContentSource::Ocr => "ocr".into(), + crate::vision::PageContentSource::Fused => "fused".into(), + } +} + +fn to_py_ocr_result(result: crate::vision::OcrPdfResult) -> PyOcrPdfResult { + PyOcrPdfResult { + markdown: result.markdown, + pages: result + .pages + .into_iter() + .map(|page| { + let provenance = page.provenance; + PyOcrPageResult { + page_number: page.page_number, + markdown: page.markdown, + provenance: PyOcrPageProvenance { + page_number: provenance.page_number, + source: page_content_source_str(provenance.source), + ocr_model: provenance.ocr_model.map(|model| PyOcrModelIdentity { + name: model.name, + revision: model.revision, + }), + render_dpi: provenance.render_dpi, + ocr_confidence: provenance.ocr_confidence, + timings: PyOcrTimings { + render_ms: provenance.timings.render_ms, + ocr_ms: provenance.timings.ocr_ms, + assembly_ms: provenance.timings.assembly_ms, + }, + warnings: provenance.warnings, + hosted_recommended: provenance.hosted_recommended, + }, + } + }) + .collect(), + page_count: result.page_count, + pages_recommended_for_ocr: result.pages_recommended_for_ocr, + pages_routed_to_ocr: result.pages_routed_to_ocr, + pages_recommending_hosted: result.pages_recommending_hosted, + ocr_reasons_by_page: to_py_page_ocr_reasons(result.ocr_reasons_by_page), + pages_with_tables: result.pages_with_tables, + pages_with_columns: result.pages_with_columns, + is_complex: result.is_complex, + processing_time_ms: result.processing_time_ms, + render_time_ms: result.render_time_ms, + ocr_time_ms: result.ocr_time_ms, + } +} + fn item_type_str(t: &ItemType) -> String { match t { ItemType::Text => "text".into(), @@ -502,6 +698,99 @@ fn process_pdf_bytes(data: &[u8], pages: Option>) -> PyResult, + path: String, + mode: &str, + page_numbers: Option>, + password: Option, + dpi: f32, + minimum_confidence: f32, + hosted_recommendation_confidence: f32, + model_directory: Option, + offline: bool, +) -> PyResult { + let options = build_ocr_options(PythonOcrOptions { + mode: mode.to_string(), + page_numbers, + password, + dpi, + minimum_confidence, + hosted_recommendation_confidence, + model_directory, + offline, + })?; + let result = py + .allow_threads(move || crate::vision::process_pdf_with_ocr(path, options)) + .map_err(|error| PyValueError::new_err(error.to_string()))?; + Ok(to_py_ocr_result(result)) +} + +/// Process PDF bytes through native extraction and selective OCR. +/// +/// See [`process_pdf_with_ocr`] for options and result semantics. +#[pyfunction] +#[pyo3(signature = ( + data, + *, + mode="auto", + page_numbers=None, + password=None, + dpi=150.0, + minimum_confidence=0.0, + hosted_recommendation_confidence=0.5, + model_directory=None, + offline=false +))] +#[allow(clippy::too_many_arguments)] +fn process_pdf_with_ocr_bytes( + py: Python<'_>, + data: &[u8], + mode: &str, + page_numbers: Option>, + password: Option, + dpi: f32, + minimum_confidence: f32, + hosted_recommendation_confidence: f32, + model_directory: Option, + offline: bool, +) -> PyResult { + let options = build_ocr_options(PythonOcrOptions { + mode: mode.to_string(), + page_numbers, + password, + dpi, + minimum_confidence, + hosted_recommendation_confidence, + model_directory, + offline, + })?; + let data = data.to_vec(); + let result = py + .allow_threads(move || crate::vision::process_pdf_with_ocr_mem(&data, options)) + .map_err(|error| PyValueError::new_err(error.to_string()))?; + Ok(to_py_ocr_result(result)) +} + /// Fast detection only — no text extraction or markdown. #[pyfunction] fn detect_pdf(path: &str) -> PyResult { @@ -704,6 +993,11 @@ fn extract_structure_elements_bytes( fn pdf_inspector(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; @@ -713,6 +1007,8 @@ fn pdf_inspector(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; m.add_function(wrap_pyfunction!(process_pdf, m)?)?; m.add_function(wrap_pyfunction!(process_pdf_bytes, m)?)?; + m.add_function(wrap_pyfunction!(process_pdf_with_ocr, m)?)?; + m.add_function(wrap_pyfunction!(process_pdf_with_ocr_bytes, m)?)?; m.add_function(wrap_pyfunction!(detect_pdf, m)?)?; m.add_function(wrap_pyfunction!(detect_pdf_bytes, m)?)?; m.add_function(wrap_pyfunction!(classify_pdf, m)?)?; diff --git a/tests/test_python.py b/tests/test_python.py index ccf9752..0ea7c51 100644 --- a/tests/test_python.py +++ b/tests/test_python.py @@ -77,6 +77,50 @@ class TestProcessPdfBytes: assert result.markdown is not None +# --------------------------------------------------------------------------- +# process_pdf_with_ocr / process_pdf_with_ocr_bytes +# --------------------------------------------------------------------------- + + +class TestProcessPdfWithOcr: + def test_off_mode_has_full_provenance_without_external_runtimes(self): + result = pdf_inspector.process_pdf_with_ocr( + fixture_path("thermo-freon12.pdf"), mode="off" + ) + assert result.page_count == 3 + assert len(result.pages) == 3 + assert result.pages_routed_to_ocr == [] + assert all(page.provenance.source == "native" for page in result.pages) + assert all(page.provenance.ocr_model is None for page in result.pages) + assert result.markdown + assert "OcrPdfResult" in repr(result) + + def test_auto_mode_skips_external_runtimes_for_clean_text(self): + result = pdf_inspector.process_pdf_with_ocr_bytes( + fixture_bytes("thermo-freon12.pdf") + ) + assert result.pages_routed_to_ocr == [] + assert result.render_time_ms == 0 + assert result.ocr_time_ms == 0 + + def test_selected_pages_are_one_indexed(self): + result = pdf_inspector.process_pdf_with_ocr( + fixture_path("thermo-freon12.pdf"), mode="off", page_numbers=[2] + ) + assert [page.page_number for page in result.pages] == [2] + + def test_rejects_invalid_options(self): + with pytest.raises(ValueError, match="mode must be"): + pdf_inspector.process_pdf_with_ocr( + fixture_path("thermo-freon12.pdf"), mode="sometimes" + ) + with pytest.raises(ValueError, match="page 0"): + pdf_inspector.process_pdf_with_ocr( + fixture_path("thermo-freon12.pdf"), + mode="off", + page_numbers=[0], + ) + # --------------------------------------------------------------------------- # detect_pdf / detect_pdf_bytes # ---------------------------------------------------------------------------