From b1a9901118a7e6ee62d7f238ddea3c84a350d23e Mon Sep 17 00:00:00 2001 From: Anthony Ronning <101225832+AnthonyRonning@users.noreply.github.com> Date: Tue, 21 Jul 2026 05:57:37 +0000 Subject: [PATCH] feat: allow caller-controlled ORT loading --- Cargo.toml | 14 ++++++-- docs/OCR_GUIDE.md | 46 +++++++++++++++++------- docs/getting-started-rust.md | 9 +++-- examples/ocr_scanned_pdf.rs | 12 +++---- src/document.rs | 28 +++++++-------- src/error.rs | 6 ++-- src/extractors/auto.rs | 46 ++++++++++++------------ src/ffi.rs | 16 ++++----- src/lib.rs | 7 ++-- src/ocr/backend.rs | 28 +++++++-------- src/ocr/error.rs | 4 +-- src/python.rs | 20 +++++------ tests/test_alice_wonderland.rs | 4 +-- tests/test_auto_hybrid_text_image_520.rs | 2 +- tests/test_auto_ocr_image_519.rs | 2 +- tests/test_auto_ocr_multilang_519.rs | 2 +- tests/test_ccitt_decoder_diagnosis.rs | 2 +- tests/test_ccitt_edge_cases.rs | 2 +- tests/test_ccitt_params_extraction.rs | 2 +- tests/test_full_extraction.rs | 2 +- tests/test_image_filters.rs | 2 +- tests/test_ocr.rs | 2 +- tests/test_ocr_inference.rs | 4 +-- tests/test_ocr_integration.rs | 4 +-- tests/test_ocr_module.rs | 4 +-- tests/test_ocr_page_detection.rs | 8 ++--- tests/test_ocr_scanned_document.rs | 4 +-- tests/test_ocr_with_models.rs | 4 +-- tests/test_pdf_ccitt_params.rs | 4 +-- tests/test_prefetch_models_519.rs | 2 +- tests/test_pride_prejudice.rs | 4 +-- tests/test_stream_filters.rs | 2 +- 32 files changed, 166 insertions(+), 132 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 480f9539c..11258c1d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -243,7 +243,7 @@ tokenizers = { version = "0.23", optional = true, default-features = false, feat # OCR - PaddleOCR via ONNX Runtime (optional) ort = { version = "=2.0.0-rc.11", optional = true, default-features = false, features = [ "ndarray", - "load-dynamic", + "std", ] } imageproc = { version = "0.27.0", optional = true } @@ -414,8 +414,16 @@ python = [ ocr-tract = ["dep:tract-onnx", "dep:ndarray"] ml = ["ocr-tract", "dep:linfa", "dep:linfa-clustering", "dep:tokenizers"] table-ml = ["ml", "dep:pdfium-render"] -ocr = ["dep:ort", "dep:imageproc", "dep:ndarray", "dep:ureq"] -gpu = ["dep:ort", "ml"] +# Native ONNX Runtime OCR backend with loader/linker policy left to the final +# application. Static targets such as iOS can use their existing ORT linkage +# without Cargo feature unification forcing `disable-linking`. +ocr-ort = ["dep:ort", "dep:imageproc", "dep:ndarray", "dep:ureq"] +# Backwards-compatible OCR feature: existing consumers keep loading an ONNX +# Runtime shared library at runtime (for example via `ORT_DYLIB_PATH`). +ocr = ["ocr-ort", "ort/load-dynamic"] +# Preserve the existing GPU feature's dynamic-loader behavior. Only `ocr-ort` +# is intentionally loader-neutral for embedding applications. +gpu = ["dep:ort", "ort/load-dynamic", "ml"] wasm = [ "dep:wasm-bindgen", "dep:web-sys", diff --git a/docs/OCR_GUIDE.md b/docs/OCR_GUIDE.md index 20af8c61e..a4971f67c 100644 --- a/docs/OCR_GUIDE.md +++ b/docs/OCR_GUIDE.md @@ -18,7 +18,7 @@ PDFOxide automatically detects whether a page is scanned or has native text, so # 1. Download recommended models (~12.5 MB total) ./scripts/setup_ocr_models.sh -# 2. Run (Rust) +# 2. Run (Rust, loading an installed ONNX Runtime at runtime) cargo run --features ocr --example ocr_scanned_pdf -- \ --pdf scanned.pdf \ --det .models/det.onnx \ @@ -28,15 +28,17 @@ cargo run --features ocr --example ocr_scanned_pdf -- \ ## OCR Support by Binding -OCR *recognition* needs the native `ocr` feature compiled in **plus** an -ONNX Runtime shared library and provisioned models at runtime. +OCR *recognition* needs the native ONNX Runtime backend compiled in **plus** +an ONNX Runtime library and provisioned models. The backwards-compatible +`ocr` feature loads the runtime dynamically. Applications that already +manage ONNX Runtime linkage can use the loader-neutral `ocr-ort` feature. **Auto mode works in every binding regardless**: when OCR is unavailable it degrades gracefully to native text with a typed `ocr_requested_but_unavailable` reason — never a crash or silent empty. | Binding | OCR recognition | How | |---|---|---| -| Rust | yes | build with `--features ocr` | +| Rust | yes | use `ocr` for runtime loading, or `ocr-ort` for caller-managed linking | | Python | yes | the published wheel ships `ocr`; supply ONNX Runtime + models | | Node.js / TypeScript | yes (v0.3.52+) | the published prebuilt ships `ocr`; `npm i onnxruntime-node` + models | | Go (cgo + purego) | yes (v0.3.52+) | the published native lib ships `ocr`; supply ONNX Runtime + models | @@ -356,7 +358,19 @@ The `setup_ocr_models.sh` script handles this automatically. ## ONNX Runtime Setup -The OCR feature requires ONNX Runtime v1.23+ at runtime. +The OCR feature requires ONNX Runtime v1.23+. PDFOxide offers two feature +levels so the final application can own loader/linker policy when needed: + +- `ocr` is the backwards-compatible feature. It includes `ocr-ort` and enables + `ort/load-dynamic`. Use `ORT_DYLIB_PATH` at runtime or put the shared library + on the platform loader search path. +- `ocr-ort` enables the same ONNX Runtime OCR backend without forcing a loading + strategy. Configure normal `ort-sys` linking for the target, such as with + `ORT_LIB_LOCATION` or `ORT_IOS_XCFWK_LOCATION` at build time. + +Cargo features are additive. If any crate in the final dependency graph +enables `ocr` (or `ort/load-dynamic` directly), dynamic loading +applies to the shared `ort` dependency for the whole application. ### Option 1: System Install @@ -369,12 +383,12 @@ wget https://github.com/microsoft/onnxruntime/releases/download/v1.23.0/onnxrunt tar xzf onnxruntime-linux-x64-1.23.0.tgz ``` -### Option 2: Point to an installed ONNX Runtime (runtime) +### Option 2: Point to an installed ONNX Runtime (runtime loading) -This crate loads ONNX Runtime **dynamically at runtime** (the `ort` `load-dynamic` -feature), so you point it at an installed library — no rebuild needed. Note that -`ORT_LIB_LOCATION` is a *build-time* variable for `ort`'s download/static-link -strategies and has **no effect** here; use one of: +With `ocr`, PDFOxide loads ONNX Runtime **dynamically at runtime**, +so you point it at an installed library — no rebuild needed. Note that +`ORT_LIB_LOCATION` is a *build-time* variable for linked builds and has **no +effect** in this mode; use one of: ```bash # Either give ort the full path to the shared-library FILE: @@ -386,6 +400,12 @@ export LD_LIBRARY_PATH=/path/to/onnxruntime/lib:$LD_LIBRARY_PATH # DYLD_LIBRA cargo run --features ocr --example ocr_scanned_pdf -- ... ``` +For a linked build, enable `ocr-ort` instead and configure `ort-sys` at build time. +For example, `ORT_LIB_LOCATION` points to the ONNX Runtime library directory; +`ORT_PREFER_DYNAMIC_LINK=1` requests ordinary loader-linked shared-library +linking rather than static linking. iOS applications can supply an existing +XCFramework with `ORT_IOS_XCFWK_LOCATION`. + ### macOS ```bash @@ -402,8 +422,8 @@ export ORT_DYLIB_PATH="$(brew --prefix onnxruntime)/lib/libonnxruntime.dylib" The **default** `pdf-oxide-wasm` package ships **without** OCR — its `WasmOcrEngine` / `extractTextOcr` throw an error directing you to the -`wasm-ocr` build. (The native `ort` OCR backend links a native ONNX -Runtime shared library and does not target `wasm32`.) Auto mode still +`wasm-ocr` build. (The native `ort` OCR backend requires a native ONNX +Runtime library and does not target `wasm32`.) Auto mode still works there, falling back to native text with a typed reason. The **`wasm-ocr` build** (issue #524, *experimental*) runs OCR entirely @@ -510,7 +530,7 @@ Make sure you're using `OcrConfig::v5()` (Rust) or `OcrConfig(use_v5=True)` (Pyt ### Build error: `no method named tls_config` -This is a known bug in `ort-sys` 2.0.0-rc.11 when using the `download-binaries` feature. This crate uses the `load-dynamic` feature instead, so install ONNX Runtime manually and point to it at runtime — `ORT_DYLIB_PATH=/path/to/libonnxruntime.`, or add its directory to `LD_LIBRARY_PATH` (`DYLD_LIBRARY_PATH` on macOS). See "Point to an installed ONNX Runtime" above. +This is a known bug in `ort-sys` 2.0.0-rc.11 when using the `download-binaries` feature. Use `ocr`, install ONNX Runtime manually, and point to it at runtime — `ORT_DYLIB_PATH=/path/to/libonnxruntime.`, or add its directory to `LD_LIBRARY_PATH` (`DYLD_LIBRARY_PATH` on macOS). See "Point to an installed ONNX Runtime" above. ### Python segfault (exit code 139) diff --git a/docs/getting-started-rust.md b/docs/getting-started-rust.md index 6df4fc1bf..7293f536f 100644 --- a/docs/getting-started-rust.md +++ b/docs/getting-started-rust.md @@ -359,13 +359,18 @@ converter.convert("input.pdf", "archive.pdf")?; > For a comprehensive guide covering model selection, configuration reference, resize strategies, and troubleshooting, see the [OCR Guide](OCR_GUIDE.md). -PDFOxide can extract text from scanned PDFs using PaddleOCR models via ONNX Runtime. Enable the `ocr` feature: +PDFOxide can extract text from scanned PDFs using PaddleOCR models via ONNX +Runtime. Enable `ocr` to load an installed runtime dynamically: ```toml [dependencies] pdf_oxide = { version = "0.3", features = ["ocr"] } ``` +Applications that already manage ONNX Runtime linkage can enable `ocr-ort` +instead. That enables the same OCR backend without forcing +`ort/load-dynamic`; configure `ort-sys` for the target at build time. + ### Model Setup PDFOxide supports PaddleOCR v3, v4, and v5 models. You can mix detection and recognition models from different versions. @@ -444,7 +449,7 @@ let config = OcrConfig::v5(); let engine = OcrEngine::new("v5_det.onnx", "v5_rec.onnx", "v5_dict.txt", config)?; ``` -> **Note:** ONNX Runtime (`libonnxruntime` v1.23+) is loaded dynamically at runtime. Install it (system package, `brew install onnxruntime`, or a manual download) and either set `ORT_DYLIB_PATH` to the shared-library **file** (`libonnxruntime.so` / `.dylib` / `onnxruntime.dll`), or add its directory to `LD_LIBRARY_PATH` (`DYLD_LIBRARY_PATH` on macOS). `ORT_LIB_LOCATION` is a build-time variable and has no effect with the dynamic backend this crate uses. +> **Note:** With `ocr`, ONNX Runtime (`libonnxruntime` v1.23+) is loaded dynamically at runtime. Install it (system package, `brew install onnxruntime`, or a manual download) and either set `ORT_DYLIB_PATH` to the shared-library **file** (`libonnxruntime.so` / `.dylib` / `onnxruntime.dll`), or add its directory to `LD_LIBRARY_PATH` (`DYLD_LIBRARY_PATH` on macOS). With `ocr-ort`, loader/linker policy remains caller-controlled and `ORT_LIB_LOCATION` or `ORT_IOS_XCFWK_LOCATION` can be supplied at build time. ## Lower-Level APIs diff --git a/examples/ocr_scanned_pdf.rs b/examples/ocr_scanned_pdf.rs index a80c22062..3a9285c23 100644 --- a/examples/ocr_scanned_pdf.rs +++ b/examples/ocr_scanned_pdf.rs @@ -33,28 +33,28 @@ //! --v5 //! ``` -#[cfg(feature = "ocr")] +#[cfg(feature = "ocr-ort")] use pdf_oxide::document::PdfDocument; -#[cfg(feature = "ocr")] +#[cfg(feature = "ocr-ort")] use pdf_oxide::ocr::{self, OcrConfig, OcrEngine, OcrExtractOptions}; -#[cfg(feature = "ocr")] +#[cfg(feature = "ocr-ort")] use std::env; fn main() -> Result<(), Box> { - #[cfg(not(feature = "ocr"))] + #[cfg(not(feature = "ocr-ort"))] { eprintln!("This example requires the 'ocr' feature to be enabled."); eprintln!("Run with: cargo run --features ocr --example ocr_scanned_pdf"); Err("OCR feature not enabled".into()) } - #[cfg(feature = "ocr")] + #[cfg(feature = "ocr-ort")] { run_ocr() } } -#[cfg(feature = "ocr")] +#[cfg(feature = "ocr-ort")] fn run_ocr() -> Result<(), Box> { env_logger::init(); diff --git a/src/document.rs b/src/document.rs index b3f670bd8..96d997e03 100644 --- a/src/document.rs +++ b/src/document.rs @@ -5428,10 +5428,10 @@ impl PdfDocument { /// assembled exactly as [`extract_text_with_options`](Self::extract_text_with_options) /// would, so the native text is byte-for-byte preserved; the extra spans /// only add content, sorted in by their bounding box. - // Only the Auto extractor (behind the `ocr` feature) and the unit test that + // Only the Auto extractor (behind the `ocr-ort` feature) and the unit test that // pins span placement call this, so it is compiled only in those configs — - // a plain non-`ocr` `--lib` build omits it entirely (no dead code). - #[cfg(any(feature = "ocr", test))] + // a build without the OCR backend omits it entirely (no dead code). + #[cfg(any(feature = "ocr-ort", test))] pub(crate) fn extract_text_with_extra_spans( &self, page_index: usize, @@ -6032,7 +6032,7 @@ impl PdfDocument { spans.extend(marginalia_trailing); // OCR fallback for scanned PDFs - #[cfg(feature = "ocr")] + #[cfg(feature = "ocr-ort")] if spans.is_empty() || spans.iter().map(|s| s.text.len()).sum::() < 50 { if let Ok(true) = crate::ocr::needs_ocr(self, page_index) { log::debug!( @@ -6740,7 +6740,7 @@ impl PdfDocument { /// // Automatically uses native text or OCR as needed /// let text = doc.extract_text_with_ocr(0, Some(&engine), OcrExtractOptions::default())?; /// ``` - #[cfg(feature = "ocr")] + #[cfg(feature = "ocr-ort")] pub fn extract_text_with_ocr( &self, page_index: usize, @@ -6776,7 +6776,7 @@ impl PdfDocument { /// when the OCR backend fails to initialise (e.g. missing /// `libonnxruntime.so`) — the [`catch_unwind`](std::panic::catch_unwind) /// in `OrtBackend::from_bytes` keeps that path panic-free. - #[cfg(feature = "ocr")] + #[cfg(feature = "ocr-ort")] pub fn extract_text_ocr_only( &self, page_index: usize, @@ -6808,7 +6808,7 @@ impl PdfDocument { /// # Returns /// /// Vector of TextSpans, either from native PDF or OCR. - #[cfg(feature = "ocr")] + #[cfg(feature = "ocr-ort")] pub fn extract_spans_with_ocr( &self, page_index: usize, @@ -19717,9 +19717,9 @@ impl PdfDocument { /// [`extract_text_with_extra_spans`](Self::extract_text_with_extra_spans). /// The Auto extractor uses this to drop OCR'd image text into its figure's /// reading-order slot for Markdown, so auto markdown is a superset of native. - // Only the (ocr-gated) Auto extractor calls this, so compile it only with - // the `ocr` feature — a non-`ocr` build omits it (no dead code). - #[cfg(feature = "ocr")] + // Only the (`ocr-ort`-gated) Auto extractor calls this, so a build without + // the OCR backend omits it (no dead code). + #[cfg(feature = "ocr-ort")] pub(crate) fn to_markdown_with_extra_spans( &self, page_index: usize, @@ -20194,7 +20194,7 @@ impl PdfDocument { /// &OcrExtractOptions::default() /// )?; /// ``` - #[cfg(feature = "ocr")] + #[cfg(feature = "ocr-ort")] pub fn to_markdown_with_ocr( &self, page_index: usize, @@ -20305,9 +20305,9 @@ impl PdfDocument { /// Convert a page to HTML with caller-supplied extra spans merged into the /// converter's reading-order pass — the HTML companion to /// [`to_markdown_with_extra_spans`](Self::to_markdown_with_extra_spans). - // Only the (ocr-gated) Auto extractor calls this, so compile it only with - // the `ocr` feature — a non-`ocr` build omits it (no dead code). - #[cfg(feature = "ocr")] + // Only the (`ocr-ort`-gated) Auto extractor calls this, so a build without + // the OCR backend omits it (no dead code). + #[cfg(feature = "ocr-ort")] pub(crate) fn to_html_with_extra_spans( &self, page_index: usize, diff --git a/src/error.rs b/src/error.rs index cc52c06f0..fb3715eeb 100644 --- a/src/error.rs +++ b/src/error.rs @@ -101,9 +101,9 @@ pub enum Error { Ml(String), /// OCR error. Available whenever the OCR module is compiled — - /// `ocr` (native ONNX Runtime) or `ocr-tract` (pure-Rust tract / - /// wasm, which `ml` implies — issue #524). - #[cfg(any(feature = "ocr", feature = "ocr-tract"))] + /// `ocr-ort` (native ONNX Runtime; implied by legacy `ocr`) or + /// `ocr-tract` (pure-Rust tract / wasm, which `ml` implies — issue #524). + #[cfg(any(feature = "ocr-ort", feature = "ocr-tract"))] #[error("OCR error: {0}")] Ocr(String), diff --git a/src/extractors/auto.rs b/src/extractors/auto.rs index c2f0742e3..79b162700 100644 --- a/src/extractors/auto.rs +++ b/src/extractors/auto.rs @@ -6,7 +6,7 @@ //! machine-readable [`ReasonCode`] for every degraded result. //! //! This module is the **dependency root** and is *pure PDF inspection*: -//! it carries **no `#[cfg(feature = "ocr")]`** gate and is fully +//! it carries **no `#[cfg(feature = "ocr-ort")]`** gate and is fully //! testable on the no-`ocr` build (00-common-foundation §5/§9). The //! classification signal model (T2/T3) and the [`AutoExtractor`] //! pipeline (T4–T8) build on these types. @@ -610,7 +610,7 @@ pub enum DocumentSummary { // `PdfDocument` (00-common-foundation §8 — the `sanitize_catalog` // injected-resolver precedent). `PdfDocument::classify_page` / // `classify_document` (in `document.rs`) gather the internal signals and -// delegate here. No `#[cfg(feature = "ocr")]`. +// delegate here. No `#[cfg(feature = "ocr-ort")]`. /// Dominant raster codec on a page — a strong scan-vs-pictorial prior. #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -1024,7 +1024,7 @@ impl AutoExtractor { pub fn prefetch_models(langs: &[OcrLanguage]) -> crate::Result { let dir = Self::model_cache_dir(); std::fs::create_dir_all(&dir).map_err(crate::error::Error::Io)?; - #[cfg(feature = "ocr")] + #[cfg(feature = "ocr-ort")] { let mut want: Vec = langs.to_vec(); if want.is_empty() { @@ -1046,9 +1046,9 @@ impl AutoExtractor { } } } - #[cfg(not(feature = "ocr"))] + #[cfg(not(feature = "ocr-ort"))] { - let _ = langs; // OCR models are only usable with the `ocr` feature + let _ = langs; // OCR models are only usable with the `ocr-ort` feature } Ok(dir) } @@ -1066,7 +1066,7 @@ impl AutoExtractor { /// bindings) should warn instead of reporting a misleading success. #[must_use] pub fn prefetch_available() -> bool { - cfg!(feature = "ocr") + cfg!(feature = "ocr-ort") } /// Provision exactly the models **this** extractor's configured @@ -1097,9 +1097,9 @@ impl AutoExtractor { } /// Idempotent HTTP GET → file (atomic via a `.part` temp). Skips - /// when the destination already exists. `#[cfg(feature = "ocr")]` - /// (download needs the `ureq` dep the `ocr` feature pulls in). - #[cfg(feature = "ocr")] + /// when the destination already exists. `#[cfg(feature = "ocr-ort")]` + /// (download needs the `ureq` dep the `ocr-ort` feature pulls in). + #[cfg(feature = "ocr-ort")] fn http_fetch(url: &str, dest: &std::path::Path) -> crate::Result<()> { use std::io::Read; if dest.is_file() { @@ -1209,7 +1209,7 @@ impl AutoExtractor { /// `rec_.onnx` / `_dict.txt` — the layout /// `scripts/setup_ocr_models.sh …` produces. Aliases are /// normalised; unknown codes pass through verbatim. - #[cfg(feature = "ocr")] + #[cfg(feature = "ocr-ort")] fn ocr_lang_files(lang: &str) -> (String, String) { // Single source of truth: the same [`OcrLanguage`] spec that // `prefetch_models` / `model_manifest` / the setup script use, @@ -1250,7 +1250,7 @@ impl AutoExtractor { /// the right model instead of the English default. `None` → English /// fallback (no clear non-Latin signal). Deliberately cheap and /// conservative — a hint, not a language classifier. - #[cfg(feature = "ocr")] + #[cfg(feature = "ocr-ort")] #[must_use] fn detect_ocr_language(doc: &PdfDocument, page: usize) -> Option { let mut s = doc.extract_text(page).unwrap_or_default(); @@ -1290,7 +1290,7 @@ impl AutoExtractor { /// fallback). The single source of truth for the engine the /// Auto/ForceOcr router AND `extract_page`'s per-region split use /// (DRY — no divergent language selection). - #[cfg(feature = "ocr")] + #[cfg(feature = "ocr-ort")] #[must_use] fn build_ocr_engine(&self, doc: &PdfDocument, page: usize) -> Option { let req: Vec = if !self.opts.ocr_languages.is_empty() { @@ -1303,7 +1303,7 @@ impl AutoExtractor { Self::load_ocr_engine(&req) } - #[cfg(feature = "ocr")] + #[cfg(feature = "ocr-ort")] #[must_use] fn load_ocr_engine(langs: &[String]) -> Option { let dir = Self::model_cache_dir(); @@ -1362,7 +1362,7 @@ impl AutoExtractor { // `OcrRequestedButUnavailable` even when OCR was attempted). #[allow(unused_mut)] let mut ocr_attempted = false; - #[cfg(feature = "ocr")] + #[cfg(feature = "ocr-ort")] { // The page needs OCR. Build an engine from the documented // model cache dir (`AutoExtractor::model_cache_dir()` — @@ -1436,10 +1436,10 @@ impl AutoExtractor { ), } } - #[cfg(not(feature = "ocr"))] + #[cfg(not(feature = "ocr-ort"))] { log::warn!( - "auto-extract: OCR unavailable (ocr feature not enabled) \ + "auto-extract: OCR unavailable (ocr-ort feature not enabled) \ for page {page} (kind={:?}); falling back to native \ text (reason OcrRequestedButUnavailable)", cls.kind @@ -1482,7 +1482,7 @@ impl AutoExtractor { /// span's y-centre alone places it (the borrowed MCID is `None` and /// harmless). Shared by the text, Markdown and HTML auto paths so all three /// position recovered image text identically. - #[cfg(feature = "ocr")] + #[cfg(feature = "ocr-ort")] fn build_image_ocr_span( &self, doc: &PdfDocument, @@ -1551,7 +1551,7 @@ impl AutoExtractor { /// the image's **spatial** position in the page's reading order. Degrades to /// the plain append-merge when the text cannot be positioned (no image bbox), /// so the recovered text is never lost. - #[cfg(feature = "ocr")] + #[cfg(feature = "ocr-ort")] fn place_image_ocr_in_reading_order( &self, doc: &PdfDocument, @@ -1583,7 +1583,7 @@ impl AutoExtractor { /// unavailable/empty, or when nothing new is recovered — callers then emit /// pure native output. Gives the Markdown/HTML auto paths the same /// native-plus-positioned-image-text behaviour as the text path. - #[cfg(feature = "ocr")] + #[cfg(feature = "ocr-ort")] fn hybrid_image_ocr_span( &self, doc: &PdfDocument, @@ -1616,7 +1616,7 @@ impl AutoExtractor { // Auto mode: same contract as text — native Markdown PLUS text recovered // from image regions, positioned in reading order (never replacing the // native layer). Non-hybrid pages and the no-`ocr` build emit pure native. - #[cfg(feature = "ocr")] + #[cfg(feature = "ocr-ort")] if let Some(span) = self.hybrid_image_ocr_span(doc, page) { return doc.to_markdown_with_extra_spans(page, &[span], &ConversionOptions::default()); } @@ -1627,7 +1627,7 @@ impl AutoExtractor { pub fn extract_html(&self, doc: &PdfDocument, page: usize) -> crate::Result { // Auto mode: native HTML PLUS positioned image-region OCR (see // extract_markdown). Non-hybrid pages and the no-`ocr` build emit native. - #[cfg(feature = "ocr")] + #[cfg(feature = "ocr-ort")] if let Some(span) = self.hybrid_image_ocr_span(doc, page) { return doc.to_html_with_extra_spans(page, &[span], &ConversionOptions::default()); } @@ -1681,8 +1681,8 @@ impl AutoExtractor { // `cls.kind`*, which mislabelled the native text as `Ocr` and // (via the old HybridPage either/or in `extract_text_with_ocr`) // silently dropped the in-image text — you could not "extract - // both". Fully `ocr`-gated; non-hybrid pages are unchanged. - #[cfg(feature = "ocr")] + // both". Fully `ocr-ort`-gated; non-hybrid pages are unchanged. + #[cfg(feature = "ocr-ort")] { if matches!(cls.kind, PageKind::ImageText | PageKind::Mixed) && !matches!(self.opts.mode, ExtractMode::TextOnly) diff --git a/src/ffi.rs b/src/ffi.rs index f8060e77a..4e87ae2ea 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -9040,7 +9040,7 @@ pub extern "C" fn pdf_ocr_engine_create( dict_path: *const c_char, error_code: *mut i32, ) -> *mut std::ffi::c_void { - #[cfg(feature = "ocr")] + #[cfg(feature = "ocr-ort")] { use crate::ocr::{OcrConfig, OcrEngine}; @@ -9080,7 +9080,7 @@ pub extern "C" fn pdf_ocr_engine_create( }, } } - #[cfg(not(feature = "ocr"))] + #[cfg(not(feature = "ocr-ort"))] { let _ = (det_model_path, rec_model_path, dict_path); set_error(error_code, _ERR_UNSUPPORTED); @@ -9091,7 +9091,7 @@ pub extern "C" fn pdf_ocr_engine_create( /// Free an OCR engine handle created by `pdf_ocr_engine_create`. #[no_mangle] pub extern "C" fn pdf_ocr_engine_free(engine: *mut std::ffi::c_void) { - #[cfg(feature = "ocr")] + #[cfg(feature = "ocr-ort")] { use crate::ocr::OcrEngine; if !engine.is_null() { @@ -9100,7 +9100,7 @@ pub extern "C" fn pdf_ocr_engine_free(engine: *mut std::ffi::c_void) { } } } - #[cfg(not(feature = "ocr"))] + #[cfg(not(feature = "ocr-ort"))] { let _ = engine; } @@ -9115,7 +9115,7 @@ pub extern "C" fn pdf_ocr_page_needs_ocr( page_index: i32, error_code: *mut i32, ) -> bool { - #[cfg(feature = "ocr")] + #[cfg(feature = "ocr-ort")] { if doc.is_null() { set_error(error_code, ERR_INVALID_ARG); @@ -9133,7 +9133,7 @@ pub extern "C" fn pdf_ocr_page_needs_ocr( }, } } - #[cfg(not(feature = "ocr"))] + #[cfg(not(feature = "ocr-ort"))] { let _ = (doc, page_index); set_error(error_code, _ERR_UNSUPPORTED); @@ -9150,7 +9150,7 @@ pub extern "C" fn pdf_ocr_extract_text( engine: *const std::ffi::c_void, error_code: *mut i32, ) -> *mut c_char { - #[cfg(feature = "ocr")] + #[cfg(feature = "ocr-ort")] { use crate::ocr::{OcrEngine, OcrExtractOptions}; @@ -9180,7 +9180,7 @@ pub extern "C" fn pdf_ocr_extract_text( }, } } - #[cfg(not(feature = "ocr"))] + #[cfg(not(feature = "ocr-ort"))] { let _ = (doc, page_index, engine); set_error(error_code, _ERR_UNSUPPORTED); diff --git a/src/lib.rs b/src/lib.rs index b00bdd2d6..f6cdab862 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -288,13 +288,14 @@ pub mod config; pub mod hybrid; // OCR - PaddleOCR via a pluggable inference backend (optional). -// Native ONNX Runtime when `ocr` is on; otherwise the pure-Rust +// Native ONNX Runtime when `ocr-ort` is on (the legacy `ocr` feature +// implies it); otherwise the pure-Rust // `tract` backend (`ocr-tract`, which `ml` implies and the // browser/Deno/edge `wasm-ocr` build uses — issue #524). Exposing OCR // wherever the tract backend is available costs only the small OCR // module itself and keeps it host-testable without a native dylib. -#[cfg(any(feature = "ocr", feature = "ocr-tract"))] -#[cfg_attr(docsrs, doc(cfg(any(feature = "ocr", feature = "ocr-tract"))))] +#[cfg(any(feature = "ocr-ort", feature = "ocr-tract"))] +#[cfg_attr(docsrs, doc(cfg(any(feature = "ocr-ort", feature = "ocr-tract"))))] pub mod ocr; // C FFI for Go, Node.js, C# bindings (not available on wasm32) diff --git a/src/ocr/backend.rs b/src/ocr/backend.rs index 764ab90bf..13bee338f 100644 --- a/src/ocr/backend.rs +++ b/src/ocr/backend.rs @@ -5,8 +5,8 @@ //! the single "run an ONNX graph" operation behind [`InferenceBackend`] //! so the same detector/recognizer + pre/post-processing drive either: //! -//! * [`OrtBackend`] — native ONNX Runtime (`ocr` feature), the -//! default everywhere it is available; unchanged behaviour. +//! * [`OrtBackend`] — native ONNX Runtime (`ocr-ort` feature; the legacy +//! `ocr` feature implies it), the default everywhere it is available. //! * [`TractBackend`] — pure-Rust `tract` (`ocr-tract` feature, which //! `ml` implies), the path the browser/Deno/edge `wasm32` build uses //! since it needs no native library and no JS bridge. Validated to @@ -30,7 +30,7 @@ pub(crate) trait InferenceBackend: Send + Sync { } /// Build the backend appropriate for the current build: native ONNX -/// Runtime when the `ocr` feature is on, otherwise the pure-Rust +/// Runtime when the `ocr-ort` feature is on, otherwise the pure-Rust /// `tract` backend (`ocr-tract`, which `ml` implies and `wasm-ocr` /// uses). `num_threads` is honoured only by the native backend. #[allow(unused_variables)] @@ -40,15 +40,15 @@ pub(crate) fn build_backend( ) -> OcrResult> { // Exactly one of these cfg blocks is compiled, and it is the // function's tail expression — no `return` needed (clippy-clean). - #[cfg(feature = "ocr")] + #[cfg(feature = "ocr-ort")] { Ok(Box::new(OrtBackend::from_bytes(model_bytes, num_threads)?)) } - #[cfg(all(not(feature = "ocr"), feature = "ocr-tract"))] + #[cfg(all(not(feature = "ocr-ort"), feature = "ocr-tract"))] { Ok(Box::new(TractBackend::from_bytes(model_bytes)?)) } - #[cfg(all(not(feature = "ocr"), not(feature = "ocr-tract")))] + #[cfg(all(not(feature = "ocr-ort"), not(feature = "ocr-tract")))] { Err(OcrError::ModelLoadError( "no OCR inference backend compiled in (enable `ocr` or `ocr-tract`)".to_string(), @@ -60,7 +60,7 @@ pub(crate) fn build_backend( // Native ONNX Runtime backend (`ort`). // --------------------------------------------------------------------------- -#[cfg(feature = "ocr")] +#[cfg(feature = "ocr-ort")] pub(crate) struct OrtBackend { // `Mutex` because `ort::Session::run` needs `&mut` while the // detector/recognizer are shared `&self` across threads — exactly @@ -68,7 +68,7 @@ pub(crate) struct OrtBackend { session: std::sync::Mutex, } -#[cfg(feature = "ocr")] +#[cfg(feature = "ocr-ort")] impl OrtBackend { pub(crate) fn from_bytes(model_bytes: &[u8], num_threads: usize) -> OcrResult { // wrap `ort::Session::builder()` (and the @@ -131,7 +131,7 @@ impl OrtBackend { } } -#[cfg(feature = "ocr")] +#[cfg(feature = "ocr-ort")] impl InferenceBackend for OrtBackend { fn run(&self, input: &ndarray::Array4) -> OcrResult> { use ort::value::TensorRef; @@ -167,13 +167,13 @@ impl InferenceBackend for OrtBackend { // Pure-Rust `tract` backend — the wasm32 path. // --------------------------------------------------------------------------- -// When both `ocr` and `ocr-tract` are on (e.g. `--features ocr,ml`), +// When both `ocr-ort` and `ocr-tract` are on (e.g. `--features ocr,ml`), // the native `ort` backend wins in `build_backend`, so this type is // compiled but unconstructed — intentional, not dead code. In a real // `wasm-ocr` build (`ocr` off) it *is* constructed, so the allow is // scoped to the combined-feature case only. #[cfg(feature = "ocr-tract")] -#[cfg_attr(feature = "ocr", allow(dead_code))] +#[cfg_attr(feature = "ocr-ort", allow(dead_code))] pub(crate) struct TractBackend { // The unoptimized inference graph. PaddleOCR det/rec have dynamic // H/W, so a plan is specialised + cached per concrete input shape @@ -184,11 +184,11 @@ pub(crate) struct TractBackend { } #[cfg(feature = "ocr-tract")] -#[cfg_attr(feature = "ocr", allow(dead_code))] +#[cfg_attr(feature = "ocr-ort", allow(dead_code))] type TractPlan = tract_onnx::prelude::TypedRunnableModel; #[cfg(feature = "ocr-tract")] -#[cfg_attr(feature = "ocr", allow(dead_code))] +#[cfg_attr(feature = "ocr-ort", allow(dead_code))] impl TractBackend { pub(crate) fn from_bytes(model_bytes: &[u8]) -> OcrResult { use tract_onnx::prelude::*; @@ -278,7 +278,7 @@ impl InferenceBackend for TractBackend { // ORT_DYLIB_PATH=/path/libonnxruntime.so \ // cargo test --features ocr,ml --lib backend::parity -- --ignored --nocapture // --------------------------------------------------------------------------- -#[cfg(all(test, feature = "ocr", feature = "ocr-tract"))] +#[cfg(all(test, feature = "ocr-ort", feature = "ocr-tract"))] mod parity { use super::*; diff --git a/src/ocr/error.rs b/src/ocr/error.rs index f00e8c529..0a3f31cdf 100644 --- a/src/ocr/error.rs +++ b/src/ocr/error.rs @@ -67,9 +67,9 @@ impl From for OcrError { } } -// Available wherever the OCR module is (`ocr` or `ocr-tract` — #524), +// Available wherever the OCR module is (`ocr-ort` or `ocr-tract` — #524), // so the `?` operator works on the tract/wasm path too. -#[cfg(any(feature = "ocr", feature = "ocr-tract"))] +#[cfg(any(feature = "ocr-ort", feature = "ocr-tract"))] impl From for crate::Error { fn from(err: OcrError) -> Self { crate::Error::Ocr(err.to_string()) diff --git a/src/python.rs b/src/python.rs index 6e83d9e3f..cc0727c08 100644 --- a/src/python.rs +++ b/src/python.rs @@ -2348,7 +2348,7 @@ impl PyPdfDocument { page: usize, engine: Option>, ) -> PyResult { - #[cfg(feature = "ocr")] + #[cfg(feature = "ocr-ort")] { let ocr_engine = if let Some(eng) = engine { Some(eng.extract::>()?) @@ -2361,7 +2361,7 @@ impl PyPdfDocument { .extract_text_with_ocr(page, engine_inner, options) .map_err(|e| PyRuntimeError::new_err(e.to_string())) } - #[cfg(not(feature = "ocr"))] + #[cfg(not(feature = "ocr-ort"))] { let _ = (engine, page); // #513: actionable, not opaque. The bare "OCR feature not @@ -4445,15 +4445,15 @@ fn outline_items_to_py( Ok(list.into()) } -#[cfg(feature = "ocr")] +#[cfg(feature = "ocr-ort")] #[pyclass(module = "pdf_oxide.pdf_oxide", name = "OcrEngine", unsendable)] pub struct PyOcrEngine { inner: crate::ocr::OcrEngine, } -#[cfg(not(feature = "ocr"))] +#[cfg(not(feature = "ocr-ort"))] #[pyclass(module = "pdf_oxide.pdf_oxide", name = "OcrEngine", unsendable)] pub struct PyOcrEngine {} -#[cfg(not(feature = "ocr"))] +#[cfg(not(feature = "ocr-ort"))] #[pymethods] impl PyOcrEngine { #[new] @@ -4465,7 +4465,7 @@ impl PyOcrEngine { Err(PyRuntimeError::new_err("OCR not enabled.")) } } -#[cfg(feature = "ocr")] +#[cfg(feature = "ocr-ort")] #[pymethods] impl PyOcrEngine { #[new] @@ -4483,7 +4483,7 @@ impl PyOcrEngine { } } -#[cfg(feature = "ocr")] +#[cfg(feature = "ocr-ort")] #[pyclass( module = "pdf_oxide.pdf_oxide", name = "OcrConfig", @@ -4493,7 +4493,7 @@ impl PyOcrEngine { pub struct PyOcrConfig { inner: crate::ocr::OcrConfig, } -#[cfg(not(feature = "ocr"))] +#[cfg(not(feature = "ocr-ort"))] #[pyclass( module = "pdf_oxide.pdf_oxide", name = "OcrConfig", @@ -4501,7 +4501,7 @@ pub struct PyOcrConfig { )] #[derive(Clone)] pub struct PyOcrConfig {} -#[cfg(not(feature = "ocr"))] +#[cfg(not(feature = "ocr-ort"))] #[pymethods] impl PyOcrConfig { #[new] @@ -4510,7 +4510,7 @@ impl PyOcrConfig { Err(PyRuntimeError::new_err("OCR not enabled.")) } } -#[cfg(feature = "ocr")] +#[cfg(feature = "ocr-ort")] #[pymethods] impl PyOcrConfig { #[new] diff --git a/tests/test_alice_wonderland.rs b/tests/test_alice_wonderland.rs index 6dd91edeb..b628bfbd2 100644 --- a/tests/test_alice_wonderland.rs +++ b/tests/test_alice_wonderland.rs @@ -6,7 +6,7 @@ //! - Size: 7.5 MB //! - Format: Scanned with OCR -#[cfg(feature = "ocr")] +#[cfg(feature = "ocr-ort")] mod alice_tests { use pdf_oxide::PdfDocument; use std::path::Path; @@ -148,7 +148,7 @@ mod alice_tests { } } -#[cfg(not(feature = "ocr"))] +#[cfg(not(feature = "ocr-ort"))] mod alice_tests_disabled { #[test] fn test_alice_feature_disabled() { diff --git a/tests/test_auto_hybrid_text_image_520.rs b/tests/test_auto_hybrid_text_image_520.rs index 1fd31d41b..756f38d70 100644 --- a/tests/test_auto_hybrid_text_image_520.rs +++ b/tests/test_auto_hybrid_text_image_520.rs @@ -26,7 +26,7 @@ //! Model-gated: skips cleanly when models are not provisioned (the //! no-model default lane stays green); the CI OCR lane provisions them //! and runs it for real. Requires the `ocr` feature. -#![cfg(feature = "ocr")] +#![cfg(feature = "ocr-ort")] use pdf_oxide::document::PdfDocument; use pdf_oxide::extractors::auto::{AutoExtractor, ExtractSource, PageKind}; diff --git a/tests/test_auto_ocr_image_519.rs b/tests/test_auto_ocr_image_519.rs index f2f86609a..40ba3c692 100644 --- a/tests/test_auto_ocr_image_519.rs +++ b/tests/test_auto_ocr_image_519.rs @@ -12,7 +12,7 @@ //! no-model default lane stays green); the CI OCR lane provisions them //! via `setup_ocr_models.sh` + `PDF_OXIDE_MODEL_DIR`, where this runs //! for real. Requires the `ocr` feature. -#![cfg(feature = "ocr")] +#![cfg(feature = "ocr-ort")] use pdf_oxide::document::PdfDocument; use pdf_oxide::extractors::auto::{AutoExtractor, ExtractSource, PageKind}; diff --git a/tests/test_auto_ocr_multilang_519.rs b/tests/test_auto_ocr_multilang_519.rs index dcd62eeca..3c81384e8 100644 --- a/tests/test_auto_ocr_multilang_519.rs +++ b/tests/test_auto_ocr_multilang_519.rs @@ -17,7 +17,7 @@ //! ready the instant such a pair is dropped in, but it cannot be //! fetched — a provisioning limit, not a code defect). Requires the //! `ocr` feature. -#![cfg(feature = "ocr")] +#![cfg(feature = "ocr-ort")] use pdf_oxide::document::PdfDocument; use pdf_oxide::extractors::auto::{AutoExtractOptions, AutoExtractor, ExtractSource}; diff --git a/tests/test_ccitt_decoder_diagnosis.rs b/tests/test_ccitt_decoder_diagnosis.rs index b8f8d99d4..f32284c52 100644 --- a/tests/test_ccitt_decoder_diagnosis.rs +++ b/tests/test_ccitt_decoder_diagnosis.rs @@ -1,5 +1,5 @@ #![allow(warnings)] -#[cfg(feature = "ocr")] +#[cfg(feature = "ocr-ort")] mod ccitt_decoder_diagnosis { use pdf_oxide::decoders::CcittParams; use pdf_oxide::document::PdfDocument; diff --git a/tests/test_ccitt_edge_cases.rs b/tests/test_ccitt_edge_cases.rs index 5d84c997e..3e0c04cbf 100644 --- a/tests/test_ccitt_edge_cases.rs +++ b/tests/test_ccitt_edge_cases.rs @@ -1,4 +1,4 @@ -#[cfg(feature = "ocr")] +#[cfg(feature = "ocr-ort")] mod ccitt_edge_cases { use pdf_oxide::document::PdfDocument; diff --git a/tests/test_ccitt_params_extraction.rs b/tests/test_ccitt_params_extraction.rs index 36f5c5609..fc0c1a6b4 100644 --- a/tests/test_ccitt_params_extraction.rs +++ b/tests/test_ccitt_params_extraction.rs @@ -1,4 +1,4 @@ -#[cfg(feature = "ocr")] +#[cfg(feature = "ocr-ort")] mod ccitt_extraction_tests { #![allow(clippy::bool_assert_comparison, clippy::manual_div_ceil)] use pdf_oxide::document::PdfDocument; diff --git a/tests/test_full_extraction.rs b/tests/test_full_extraction.rs index 6c5095f57..73a4df132 100644 --- a/tests/test_full_extraction.rs +++ b/tests/test_full_extraction.rs @@ -4,7 +4,7 @@ clippy::len_zero, clippy::redundant_pattern_matching )] -#[cfg(feature = "ocr")] +#[cfg(feature = "ocr-ort")] #[test] fn test_full_document_extraction() { use pdf_oxide::converters::ConversionOptions; diff --git a/tests/test_image_filters.rs b/tests/test_image_filters.rs index ba3b5331e..805a83981 100644 --- a/tests/test_image_filters.rs +++ b/tests/test_image_filters.rs @@ -3,7 +3,7 @@ // which are internal to PdfDocument. For image filter analysis, use the public document APIs. /* -#[cfg(feature = "ocr")] +#[cfg(feature = "ocr-ort")] mod image_filter_tests { use pdf_oxide::document::PdfDocument; diff --git a/tests/test_ocr.rs b/tests/test_ocr.rs index 4b86b916d..b88b0f164 100644 --- a/tests/test_ocr.rs +++ b/tests/test_ocr.rs @@ -7,7 +7,7 @@ //! and should be run with `cargo test --features ocr -- --ignored` //! after placing model files in the appropriate location. -#![cfg(feature = "ocr")] +#![cfg(feature = "ocr-ort")] use image::{DynamicImage, GenericImageView, RgbImage}; use pdf_oxide::ocr::{ diff --git a/tests/test_ocr_inference.rs b/tests/test_ocr_inference.rs index ebcb8ab41..8b38ba116 100644 --- a/tests/test_ocr_inference.rs +++ b/tests/test_ocr_inference.rs @@ -9,7 +9,7 @@ //! PDF: Pride and Prejudice (424 pages, 8.3 MB) //! Source: Archive.org (Public Domain) -#[cfg(feature = "ocr")] +#[cfg(feature = "ocr-ort")] mod ocr_inference_tests { use pdf_oxide::ocr::{OcrConfig, OcrEngine}; use pdf_oxide::PdfDocument; @@ -425,7 +425,7 @@ mod ocr_inference_tests { // TESTS FOR WHEN OCR FEATURE IS NOT ENABLED // ============================================================================ -#[cfg(not(feature = "ocr"))] +#[cfg(not(feature = "ocr-ort"))] mod ocr_inference_not_enabled_tests { #[test] fn test_ocr_inference_feature_disabled() { diff --git a/tests/test_ocr_integration.rs b/tests/test_ocr_integration.rs index 3f53abfc1..90ed014cc 100644 --- a/tests/test_ocr_integration.rs +++ b/tests/test_ocr_integration.rs @@ -7,7 +7,7 @@ //! - Module API compatibility //! - Error handling -#[cfg(feature = "ocr")] +#[cfg(feature = "ocr-ort")] mod ocr_integration_tests { use pdf_oxide::ocr::{OcrConfig, OcrConfigBuilder, OcrExtractOptions}; @@ -366,7 +366,7 @@ mod ocr_integration_tests { // TESTS FOR WHEN OCR FEATURE IS NOT ENABLED // ============================================================================ -#[cfg(not(feature = "ocr"))] +#[cfg(not(feature = "ocr-ort"))] mod ocr_integration_not_enabled_tests { #[test] fn test_ocr_integration_feature_disabled() { diff --git a/tests/test_ocr_module.rs b/tests/test_ocr_module.rs index f0fa76323..493c7e4a5 100644 --- a/tests/test_ocr_module.rs +++ b/tests/test_ocr_module.rs @@ -6,7 +6,7 @@ //! - Module compilation with feature flags //! - Basic API compatibility -#[cfg(feature = "ocr")] +#[cfg(feature = "ocr-ort")] mod ocr_tests { use pdf_oxide::ocr::{OcrConfig, OcrConfigBuilder, OcrExtractOptions}; @@ -358,7 +358,7 @@ mod ocr_tests { // TESTS FOR WHEN OCR FEATURE IS NOT ENABLED // ============================================================================ -#[cfg(not(feature = "ocr"))] +#[cfg(not(feature = "ocr-ort"))] mod ocr_not_enabled_tests { #[test] fn test_ocr_feature_disabled() { diff --git a/tests/test_ocr_page_detection.rs b/tests/test_ocr_page_detection.rs index e3be9df1e..26704eeb0 100644 --- a/tests/test_ocr_page_detection.rs +++ b/tests/test_ocr_page_detection.rs @@ -1,7 +1,7 @@ -#[cfg(feature = "ocr")] +#[cfg(feature = "ocr-ort")] use pdf_oxide::document::PdfDocument; -#[cfg(feature = "ocr")] +#[cfg(feature = "ocr-ort")] #[test] fn test_detect_page_type_text_page() { use pdf_oxide::ocr::{detect_page_type, PageType}; @@ -10,7 +10,7 @@ fn test_detect_page_type_text_page() { assert_eq!(page_type, PageType::NativeText); } -#[cfg(feature = "ocr")] +#[cfg(feature = "ocr-ort")] #[test] fn test_needs_ocr_text_page_false() { use pdf_oxide::ocr::needs_ocr; @@ -22,7 +22,7 @@ fn test_needs_ocr_text_page_false() { /// detect_page_type uses extract_spans (not extract_text) to avoid infinite /// recursion: extract_text -> needs_ocr -> detect_page_type -> extract_text. /// If we reach the end without stack overflow, the guard works. -#[cfg(feature = "ocr")] +#[cfg(feature = "ocr-ort")] #[test] fn test_detect_page_type_no_infinite_recursion() { use pdf_oxide::ocr::{detect_page_type, needs_ocr}; diff --git a/tests/test_ocr_scanned_document.rs b/tests/test_ocr_scanned_document.rs index f205ffb9d..c67e985a8 100644 --- a/tests/test_ocr_scanned_document.rs +++ b/tests/test_ocr_scanned_document.rs @@ -8,7 +8,7 @@ //! - Pages: 400+ pages of historical text //! - Quality: High-quality scan -#[cfg(feature = "ocr")] +#[cfg(feature = "ocr-ort")] mod ocr_scanned_tests { use pdf_oxide::PdfDocument; use std::path::Path; @@ -342,7 +342,7 @@ mod ocr_scanned_tests { // TESTS FOR WHEN OCR FEATURE IS NOT ENABLED // ============================================================================ -#[cfg(not(feature = "ocr"))] +#[cfg(not(feature = "ocr-ort"))] mod ocr_scanned_not_enabled_tests { #[test] fn test_ocr_scanned_feature_disabled() { diff --git a/tests/test_ocr_with_models.rs b/tests/test_ocr_with_models.rs index bc83b0e43..a15e915bf 100644 --- a/tests/test_ocr_with_models.rs +++ b/tests/test_ocr_with_models.rs @@ -4,7 +4,7 @@ //! These tests verify that the OCR module can load and initialize //! with actual ONNX/Paddle models. -#[cfg(feature = "ocr")] +#[cfg(feature = "ocr-ort")] mod ocr_model_tests { use pdf_oxide::ocr::{OcrConfig, TextDetector, TextRecognizer}; use std::path::Path; @@ -312,7 +312,7 @@ mod ocr_model_tests { // TESTS FOR WHEN OCR FEATURE IS NOT ENABLED // ============================================================================ -#[cfg(not(feature = "ocr"))] +#[cfg(not(feature = "ocr-ort"))] mod ocr_models_not_enabled_tests { #[test] fn test_ocr_models_feature_disabled() { diff --git a/tests/test_pdf_ccitt_params.rs b/tests/test_pdf_ccitt_params.rs index 6e47c4d07..3fc749901 100644 --- a/tests/test_pdf_ccitt_params.rs +++ b/tests/test_pdf_ccitt_params.rs @@ -1,4 +1,4 @@ -#[cfg(feature = "ocr")] +#[cfg(feature = "ocr-ort")] mod ccitt_params { #![allow(dead_code, clippy::manual_div_ceil)] use pdf_oxide::document::PdfDocument; @@ -143,7 +143,7 @@ mod ccitt_params { } } -#[cfg(feature = "ocr")] +#[cfg(feature = "ocr-ort")] mod compression_analysis { #[test] fn analyze_ccitt_compression_ratios() { diff --git a/tests/test_prefetch_models_519.rs b/tests/test_prefetch_models_519.rs index 696faae9d..ac4a3dc1b 100644 --- a/tests/test_prefetch_models_519.rs +++ b/tests/test_prefetch_models_519.rs @@ -3,7 +3,7 @@ //! requires the `ocr` feature (the `ureq` downloader) + outbound HTTPS; //! runs in the CI `ocr` lane (which already has network for the model //! provisioning). Skips cleanly when offline. -#![cfg(feature = "ocr")] +#![cfg(feature = "ocr-ort")] use pdf_oxide::extractors::{AutoExtractor, OcrLanguage}; diff --git a/tests/test_pride_prejudice.rs b/tests/test_pride_prejudice.rs index 5a6a4aa98..da35a3199 100644 --- a/tests/test_pride_prejudice.rs +++ b/tests/test_pride_prejudice.rs @@ -6,7 +6,7 @@ //! - Size: 8.3 MB //! - Format: Scanned with OCR -#[cfg(feature = "ocr")] +#[cfg(feature = "ocr-ort")] mod pride_tests { use pdf_oxide::PdfDocument; use std::path::Path; @@ -136,7 +136,7 @@ mod pride_tests { } } -#[cfg(not(feature = "ocr"))] +#[cfg(not(feature = "ocr-ort"))] mod pride_tests_disabled { #[test] fn test_pride_feature_disabled() { diff --git a/tests/test_stream_filters.rs b/tests/test_stream_filters.rs index e2a252289..d3435b61a 100644 --- a/tests/test_stream_filters.rs +++ b/tests/test_stream_filters.rs @@ -1,5 +1,5 @@ #![allow(unused_imports)] -#[cfg(feature = "ocr")] +#[cfg(feature = "ocr-ort")] mod stream_filters_test { use pdf_oxide::document::PdfDocument; use pdf_oxide::object::Object;