diff --git a/.github/workflows/mobile-build.yml b/.github/workflows/mobile-build.yml index 7c461593..0fd254c7 100644 --- a/.github/workflows/mobile-build.yml +++ b/.github/workflows/mobile-build.yml @@ -61,9 +61,9 @@ jobs: with: path: | frontend/src-tauri/onnxruntime-ios - key: release-onnxruntime-ios-artifact-1.22.2-xcode26.5-${{ steps.select_xcode.outputs.build }}-${{ hashFiles('flake.lock', 'flake.nix', 'scripts/ci/_common.sh', 'scripts/ci/ios-onnxruntime.sh', 'frontend/src-tauri/scripts/build-ios-onnxruntime-all.sh', 'frontend/src-tauri/scripts/canonicalize-static-archive.py', 'frontend/src-tauri/scripts/onnxruntime-pins.sh') }} + key: release-onnxruntime-ios-artifact-1.23.2-xcode26.5-${{ steps.select_xcode.outputs.build }}-${{ hashFiles('flake.lock', 'flake.nix', 'scripts/ci/_common.sh', 'scripts/ci/ios-onnxruntime.sh', 'frontend/src-tauri/scripts/build-ios-onnxruntime-all.sh', 'frontend/src-tauri/scripts/canonicalize-static-archive.py', 'frontend/src-tauri/scripts/onnxruntime-pins.sh') }} restore-keys: | - release-onnxruntime-ios-artifact-1.22.2-xcode26.5-${{ steps.select_xcode.outputs.build }}- + release-onnxruntime-ios-artifact-1.23.2-xcode26.5-${{ steps.select_xcode.outputs.build }}- - name: Prepare ONNX Runtime iOS artifact run: nix develop .#apple -c ./scripts/ci/ios-onnxruntime.sh @@ -237,9 +237,9 @@ jobs: with: path: | frontend/src-tauri/onnxruntime-ios - key: pr-onnxruntime-ios-artifact-1.22.2-xcode26.5-${{ steps.select_xcode.outputs.build }}-${{ hashFiles('flake.lock', 'flake.nix', 'scripts/ci/_common.sh', 'scripts/ci/ios-onnxruntime.sh', 'frontend/src-tauri/scripts/build-ios-onnxruntime-all.sh', 'frontend/src-tauri/scripts/canonicalize-static-archive.py', 'frontend/src-tauri/scripts/onnxruntime-pins.sh') }} + key: pr-onnxruntime-ios-artifact-1.23.2-xcode26.5-${{ steps.select_xcode.outputs.build }}-${{ hashFiles('flake.lock', 'flake.nix', 'scripts/ci/_common.sh', 'scripts/ci/ios-onnxruntime.sh', 'frontend/src-tauri/scripts/build-ios-onnxruntime-all.sh', 'frontend/src-tauri/scripts/canonicalize-static-archive.py', 'frontend/src-tauri/scripts/onnxruntime-pins.sh') }} restore-keys: | - pr-onnxruntime-ios-artifact-1.22.2-xcode26.5-${{ steps.select_xcode.outputs.build }}- + pr-onnxruntime-ios-artifact-1.23.2-xcode26.5-${{ steps.select_xcode.outputs.build }}- - name: Prepare ONNX Runtime iOS artifact run: nix develop .#apple -c ./scripts/ci/ios-onnxruntime.sh diff --git a/.github/workflows/mobile-pr-build.yml b/.github/workflows/mobile-pr-build.yml index 0b59b9cf..87183e91 100644 --- a/.github/workflows/mobile-pr-build.yml +++ b/.github/workflows/mobile-pr-build.yml @@ -56,9 +56,9 @@ jobs: with: path: | frontend/src-tauri/onnxruntime-ios - key: pr-onnxruntime-ios-artifact-1.22.2-xcode26.5-${{ steps.select_xcode.outputs.build }}-${{ hashFiles('flake.lock', 'flake.nix', 'scripts/ci/_common.sh', 'scripts/ci/ios-onnxruntime.sh', 'frontend/src-tauri/scripts/build-ios-onnxruntime-all.sh', 'frontend/src-tauri/scripts/canonicalize-static-archive.py', 'frontend/src-tauri/scripts/onnxruntime-pins.sh') }} + key: pr-onnxruntime-ios-artifact-1.23.2-xcode26.5-${{ steps.select_xcode.outputs.build }}-${{ hashFiles('flake.lock', 'flake.nix', 'scripts/ci/_common.sh', 'scripts/ci/ios-onnxruntime.sh', 'frontend/src-tauri/scripts/build-ios-onnxruntime-all.sh', 'frontend/src-tauri/scripts/canonicalize-static-archive.py', 'frontend/src-tauri/scripts/onnxruntime-pins.sh') }} restore-keys: | - pr-onnxruntime-ios-artifact-1.22.2-xcode26.5-${{ steps.select_xcode.outputs.build }}- + pr-onnxruntime-ios-artifact-1.23.2-xcode26.5-${{ steps.select_xcode.outputs.build }}- - name: Prepare ONNX Runtime iOS artifact run: nix develop .#apple -c ./scripts/ci/ios-onnxruntime.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d509e7bd..91bde02f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -446,9 +446,9 @@ jobs: with: path: | frontend/src-tauri/onnxruntime-ios - key: release-onnxruntime-ios-artifact-1.22.2-xcode26.5-${{ steps.select_xcode.outputs.build }}-${{ hashFiles('flake.lock', 'flake.nix', 'scripts/ci/_common.sh', 'scripts/ci/ios-onnxruntime.sh', 'frontend/src-tauri/scripts/build-ios-onnxruntime-all.sh', 'frontend/src-tauri/scripts/canonicalize-static-archive.py', 'frontend/src-tauri/scripts/onnxruntime-pins.sh') }} + key: release-onnxruntime-ios-artifact-1.23.2-xcode26.5-${{ steps.select_xcode.outputs.build }}-${{ hashFiles('flake.lock', 'flake.nix', 'scripts/ci/_common.sh', 'scripts/ci/ios-onnxruntime.sh', 'frontend/src-tauri/scripts/build-ios-onnxruntime-all.sh', 'frontend/src-tauri/scripts/canonicalize-static-archive.py', 'frontend/src-tauri/scripts/onnxruntime-pins.sh') }} restore-keys: | - release-onnxruntime-ios-artifact-1.22.2-xcode26.5-${{ steps.select_xcode.outputs.build }}- + release-onnxruntime-ios-artifact-1.23.2-xcode26.5-${{ steps.select_xcode.outputs.build }}- - name: Prepare ONNX Runtime iOS artifact run: nix develop .#apple -c ./scripts/ci/ios-onnxruntime.sh diff --git a/README.md b/README.md index 2d2772c6..b7cd09f4 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ bun tauri build --target universal-apple-darwin #### Linux: ONNX Runtime Setup -Linux builds bundle `libonnxruntime.so` for local TTS (Supertonic). Before building on Linux, you must download the ONNX Runtime shared library: +Linux builds bundle ONNX Runtime 1.23.2 for local TTS and PDF OCR. `just desktop-build` provisions it automatically. Before invoking `bun tauri build` directly, download the pinned shared library: ```bash cd frontend/src-tauri @@ -152,7 +152,7 @@ cd frontend/src-tauri This downloads the pinned ONNX Runtime release, verifies its SHA-256 checksum, and extracts it to `frontend/src-tauri/onnxruntime-linux/` (which is gitignored). The script is idempotent — it skips the download if the library already exists. -> **Note:** CI workflows call this script automatically. You only need to run it manually for local builds. +> **Note:** CI workflows and the desktop `just` recipes call this script automatically. Run it manually only when invoking the lower-level Tauri commands directly. #### Linux: Running in Headless/Virtual Display Environments diff --git a/docs/ios-tts-local-development.md b/docs/ios-tts-local-development.md index 274167a6..d18bd752 100644 --- a/docs/ios-tts-local-development.md +++ b/docs/ios-tts-local-development.md @@ -1,10 +1,10 @@ -# iOS TTS Local Development Guide +# iOS ONNX Runtime Local Development Guide -This guide explains how to build and test the iOS TTS (Text-to-Speech) feature locally using the iOS Simulator or a physical device. +This guide explains how to build and test the iOS ONNX Runtime used by local TTS and PDF OCR on the iOS Simulator or a physical device. ## Overview -The iOS TTS feature uses ONNX Runtime to run Kokoro TTS models. ONNX Runtime must be built from source for iOS because: +Maple pins ONNX Runtime 1.23.2 and links it statically on iOS for both TTS and PDF OCR. ONNX Runtime is built from source because: 1. Pre-built binaries from HuggingFace are missing Abseil symbols 2. We need both device (arm64) and simulator (arm64) builds 3. The simulator build requires a workaround for a libiconv linking bug @@ -25,7 +25,7 @@ The iOS TTS feature uses ONNX Runtime to run Kokoro TTS models. ONNX Runtime mus just ios-build-onnxruntime ``` -This builds ONNX Runtime for both device and simulator (~5-10 minutes) and automatically generates the cargo config. +This builds and hash-verifies ONNX Runtime for both device and simulator, then generates the Cargo config. A valid cached artifact is reused; a stale or differently built artifact fails verification and is rebuilt. The output will be in `frontend/src-tauri/onnxruntime-ios/onnxruntime.xcframework/`. @@ -93,7 +93,7 @@ Undefined symbols for architecture arm64: "_AbslInternalSpinLockDelay_lts_20240722" ``` -This means the ONNX Runtime library wasn't built from source. Pre-built binaries are missing these symbols. Run `./scripts/build-ios-onnxruntime-all.sh` to build from source. +This means the ONNX Runtime library wasn't built through Maple's verified source pipeline. Run `just ios-build-onnxruntime`. ### Simulator Build Fails with libiconv Error diff --git a/docs/pdf-ocr.md b/docs/pdf-ocr.md new file mode 100644 index 00000000..cafd5086 --- /dev/null +++ b/docs/pdf-ocr.md @@ -0,0 +1,72 @@ +# PDF extraction and OCR + +Maple uses [`pdf_oxide` 0.3.74](https://crates.io/crates/pdf_oxide/0.3.74) for native PDF text extraction, page classification, rendering, and PaddleOCR preprocessing. PDF bytes and rendered pages stay on the device. + +Maple pins [OpenSecretCloud's `pdf_oxide` maintenance commit](https://github.com/OpenSecretCloud/pdf_oxide/commit/f24b43ba997dd91ce60839640a8ce3ac92a87a5d) based directly on the published 0.3.74 release. The fork adds only a caller-controlled ONNX Runtime loader feature; it contains no Maple-specific parser or inference changes. This keeps iOS static linkage compatible without importing unpublished upstream work. + +## One inference runtime + +PDF OCR and desktop/iOS TTS use one Rust binding (`ort = 2.0.0-rc.11`) and one Microsoft ONNX Runtime version (1.23.2). Maple does not ship tract. + +The runtime is part of the application package; users do not download it when opening a PDF: + +| Platform | ONNX Runtime packaging | +| --- | --- | +| macOS | Microsoft's official universal2 dylib, loaded from the app Frameworks directory; both Apple silicon and Intel are retained. Minimum macOS is 13.4. | +| Linux | Microsoft's x64 or aarch64 shared library, installed under Maple's private library directory. | +| Windows | Microsoft's x64 DLL, installed next to `maple.exe` and loaded by its explicit path. | +| Android | Microsoft's official Android AAR, with `libonnxruntime.so` staged for arm64-v8a, armeabi-v7a, x86, and x86_64. | +| iOS | A deterministic device/simulator XCFramework built from the pinned Microsoft source commit and linked statically. | + +Maple owns loader policy through PDFOxide's loader-neutral `ocr-ort` feature. Dynamic targets initialize the exact packaged library before either OCR or TTS creates a session. iOS initializes the same Rust API against its static library. Cargo resolves exactly one `ort` version per target. + +The application constructs and reuses one OCR engine. Native-only PDFs bypass model setup. OCR-routed pages are rendered once in full, bounded to a 2,000 by 2,000-pixel box, so tiled scans, inline images, and vector content do not depend on selecting one embedded image. Mixed pages retain native text and append OCR spans not already represented in the native layer. OCR remains optional enrichment for native-readable hybrid pages: if OCR is unavailable, Maple keeps the native text. + +## Model download and cache + +The first PDF that actually needs OCR downloads 12,577,821 bytes of PaddleOCR model data on macOS, Windows, Linux, iOS, or Android. The files are stored under the application cache at `ocr/models/paddleocr-en-v1`. An operating system may purge that cache, in which case Maple downloads and verifies the pack again. + +Maple does not use PDFOxide's mutable model downloader. Every artifact is pinned to an immutable repository revision and checked for exact length and SHA-256 before loading: + +| File | Immutable source | Bytes | SHA-256 | +| --- | --- | ---: | --- | +| `det.onnx` | [SWHL RapidOCR PaddleOCR detector](https://huggingface.co/SWHL/RapidOCR/blob/1cfba2e90fc938db55889873735088de210cc173/PP-OCRv4/ch_PP-OCRv4_det_infer.onnx) | 4,745,517 | `d2a7720d45a54257208b1e13e36a8479894cb74155a5efe29462512d42f49da9` | +| `rec.onnx` | [monkt English recognizer](https://huggingface.co/monkt/paddleocr-onnx/blob/7b02d0a30a07ba2b92ad1ff5a8941ae2c633de65/languages/english/rec.onnx) | 7,830,888 | `4e16deb22c4da6468bdca539b2cd3c8687825538b67109177c47d359ab994cd7` | +| `en_dict.txt` | [monkt English dictionary](https://huggingface.co/monkt/paddleocr-onnx/blob/7b02d0a30a07ba2b92ad1ff5a8941ae2c633de65/languages/english/dict.txt) | 1,416 | `e025a66d31f327ba0c232e03f407ae8d105e1e709e7ccb3f408aa778c24e70d6` | + +Downloads stream to a temporary file, enforce the expected size, verify SHA-256, sync, and then rename into place. Concurrent first-use requests share one setup lock. Existing cached files are re-verified before engine construction. The backend emits download progress while the current UI keeps the attachment in its processing state. + +PaddleOCR, SWHL RapidOCR, and the monkt model repository declare Apache-2.0. On Android, the model-only HTTP client uses rustls with Mozilla WebPKI roots; other platforms retain their normal trust-store integration. + +## Panic and error boundary + +All PDF parsing, classification, rendering, and inference work runs in an isolated blocking task. An ordinary Rust unwind becomes a normal Tauri command error, so the frontend clears its busy state and remains usable. Process aborts and native stack exhaustion cannot be recovered by a Rust unwind boundary. + +The backend serializes PDF jobs, bounds OCR renders to four megapixels, and checks source-image count and pixel budgets. It independently enforces 10 MiB limits on both the input document and extracted text, rejects locked PDFs with a specific message, and treats a document with no recognized text as an error rather than silently attaching an empty document. + +If OCR is required for a scanned page and that page cannot be processed, the upload fails with the page-specific error rather than attaching an incomplete subset of the document. OCR failures remain optional only for hybrid pages whose native text is already readable. + +## Current scope and known limitation + +The current model pack recognizes English text. Additional language packs, password entry, and a dedicated OCR download UI are separate product work. + +PDFOxide 0.3.74 cannot render some uncompressed, one-bit FlateDecode, and inline PDF images ([upstream issue #860](https://github.com/yfedoseev/pdf_oxide/issues/860)). OCR sees a blank render for those pages even when Poppler displays the scan. A genuine ten-page NASA archival scan passes Maple's full-page OCR path, while a genuine ten-page NARA one-bit Flate scan reproduces #860. The loader-policy fork intentionally does not patch this parser/renderer issue in the initial viability change. + +The MVP also caps each source image at 24 million pixels before PDFOxide decodes it. A typical 300-DPI archival scan fits; some 600-DPI scans do not, even though the final OCR render would be reduced to four million pixels. Maple reports the first affected page instead of attaching partial text. Raising the budget or adding decode-time downsampling requires separate desktop/mobile memory measurements. + +## Local model-backed test + +Provision the desktop runtime first, then run the ignored release-profile test with a real scanned PDF and the verified model pack: + +```sh +cd frontend/src-tauri +./scripts/provide-macos-onnxruntime.sh # use the platform-equivalent provider + +ORT_DYLIB_PATH=/absolute/path/from/the/provider \ +MAPLE_OCR_TEST_PDF=/absolute/path/to/scanned.pdf \ +MAPLE_OCR_MODEL_DIR=/absolute/path/to/models \ +nix develop -c cargo test --release --locked --lib \ + extracts_scanned_pdf_with_real_ocr_models -- --ignored --nocapture +``` + +Use the release profile for model-backed timing. Debug inference is not representative of the packaged application. diff --git a/frontend/src-tauri/.gitignore b/frontend/src-tauri/.gitignore index 0446195a..512c5713 100644 --- a/frontend/src-tauri/.gitignore +++ b/frontend/src-tauri/.gitignore @@ -10,5 +10,11 @@ # ONNX Runtime Linux (downloaded for bundling) /onnxruntime-linux/ +# ONNX Runtime macOS (downloaded for bundling) +/onnxruntime-macos/ + +# ONNX Runtime Android (verified AAR cache; JNI libraries are staged under gen/android) +/onnxruntime-android/ + # Generated cargo config for iOS builds /.cargo/ diff --git a/frontend/src-tauri/Cargo.lock b/frontend/src-tauri/Cargo.lock index 8422d4a4..05370ab8 100644 --- a/frontend/src-tauri/Cargo.lock +++ b/frontend/src-tauri/Cargo.lock @@ -3,19 +3,26 @@ version = 4 [[package]] -name = "adler2" -version = "2.0.1" +name = "ab_glyph" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" +dependencies = [ + "ab_glyph_rasterizer", + "owned_ttf_parser", +] [[package]] -name = "adobe-cmap-parser" -version = "0.4.1" +name = "ab_glyph_rasterizer" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8abfa9a4688de8fc9f42b3f013b6fffec18ed8a554f5f113577e0b9b3212a3" -dependencies = [ - "pom", -] +checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aead" @@ -34,10 +41,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", - "cipher", + "cipher 0.4.4", "cpufeatures 0.2.17", ] +[[package]] +name = "aes" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138" +dependencies = [ + "cipher 0.5.2", + "cpubits", + "cpufeatures 0.3.0", +] + [[package]] name = "aes-gcm" version = "0.10.3" @@ -45,8 +63,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" dependencies = [ "aead", - "aes", - "cipher", + "aes 0.8.4", + "cipher 0.4.4", "ctr", "ghash", "subtle", @@ -151,6 +169,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 = "alloc-no-stdlib" version = "2.0.4" @@ -185,7 +221,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbb4e440d04be07da1f1bf44fb4495ebd58669372fe0cffa6e48595ac5bd88a3" dependencies = [ "android_log-sys", - "env_filter", + "env_filter 0.1.4", "log", ] @@ -254,6 +290,15 @@ version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" +[[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" @@ -280,6 +325,17 @@ dependencies = [ "x11rb", ] +[[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.108", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -292,6 +348,15 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[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 = "asn1-rs" version = "0.6.2" @@ -301,7 +366,7 @@ dependencies = [ "asn1-rs-derive", "asn1-rs-impl", "displaydoc", - "nom", + "nom 7.1.3", "num-traits", "rusticata-macros", "thiserror 1.0.69", @@ -528,6 +593,16 @@ dependencies = [ "num-traits", ] +[[package]] +name = "atoi_simd" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cdb3708a128e559a30fb830e8a77a5022ee6902806925c216658652b452a44" +dependencies = [ + "debug_unsafe", + "rustversion", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -540,6 +615,49 @@ 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 2.0.18", + "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 = "aws-lc-rs" version = "1.16.3" @@ -668,6 +786,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + [[package]] name = "bitflags" version = "1.3.2" @@ -683,6 +807,15 @@ dependencies = [ "serde_core", ] +[[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 = "bitvec" version = "1.0.1" @@ -727,6 +860,15 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "block-padding" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710f1dd022ef4e93f8a438b4ba958de7f64308434fa6a87104481645cc30068b" +dependencies = [ + "hybrid-array", +] + [[package]] name = "block2" version = "0.5.1" @@ -828,6 +970,12 @@ dependencies = [ "serde_core", ] +[[package]] +name = "built" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c0e531d93d39c34eef561e929e8a7f86d77a5af08aac4f6d6e39976c51858e9" + [[package]] name = "bumpalo" version = "3.19.0" @@ -878,6 +1026,20 @@ name = "bytemuck" version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f65693059b6b9c588b9f62fed1cedbf0a8b805631457ea162d68f0de186f3de5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] [[package]] name = "byteorder" @@ -885,6 +1047,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[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.11.1" @@ -971,6 +1139,15 @@ dependencies = [ "toml 0.9.8", ] +[[package]] +name = "cbc" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2dc9ee5f88d11e0beb842c88b33c8a5cf0d1329c4b19494af42b07dbfe8896" +dependencies = [ + "cipher 0.5.2", +] + [[package]] name = "cc" version = "1.2.61" @@ -1006,7 +1183,7 @@ version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" dependencies = [ - "smallvec 1.15.1", + "smallvec", "target-lexicon", ] @@ -1029,7 +1206,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", - "cipher", + "cipher 0.4.4", "cpufeatures 0.2.17", ] @@ -1052,7 +1229,7 @@ checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ "aead", "chacha20 0.9.1", - "cipher", + "cipher 0.4.4", "poly1305", "zeroize", ] @@ -1115,10 +1292,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common 0.1.6", - "inout", + "inout 0.1.4", "zeroize", ] +[[package]] +name = "cipher" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" +dependencies = [ + "crypto-common 0.2.2", + "inout 0.2.2", +] + [[package]] name = "clap" version = "4.5.51" @@ -1234,6 +1421,12 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "const-random" version = "0.1.18" @@ -1370,6 +1563,12 @@ dependencies = [ "libm", ] +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -1514,7 +1713,7 @@ dependencies = [ "dtoa-short", "itoa", "phf 0.13.1", - "smallvec 1.15.1", + "smallvec", ] [[package]] @@ -1549,7 +1748,7 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ - "cipher", + "cipher 0.4.4", ] [[package]] @@ -1678,13 +1877,50 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "debug_unsafe" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eed2c4702fa172d1ce21078faa7c5203e69f5394d48cc436d25928394a867a2" + +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.18", +] + [[package]] name = "der" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "const-oid", + "const-oid 0.9.6", "pem-rfc7468", "zeroize", ] @@ -1697,7 +1933,7 @@ checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" dependencies = [ "asn1-rs", "displaydoc", - "nom", + "nom 7.1.3", "num-bigint", "num-traits", "rusticata-macros", @@ -1785,7 +2021,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", - "const-oid", + "const-oid 0.9.6", "crypto-common 0.1.6", "subtle", ] @@ -1797,6 +2033,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer 0.12.1", + "const-oid 0.10.2", "crypto-common 0.2.2", ] @@ -2076,6 +2313,49 @@ dependencies = [ "regex", ] +[[package]] +name = "env_filter" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d" +dependencies = [ + "anstream", + "anstyle", + "env_filter 1.0.0", + "jiff", + "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.108", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -2132,9 +2412,9 @@ dependencies = [ [[package]] name = "euclid" -version = "0.20.14" +version = "0.22.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bb7ef65b3777a325d1eeefefab5b6d4959da54747e33bd6258e789640f307ad" +checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" dependencies = [ "num-traits", ] @@ -2167,10 +2447,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab" dependencies = [ "futures-core", - "nom", + "nom 7.1.3", "pin-project-lite", ] +[[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 = "fancy-regex" version = "0.17.0" @@ -2182,12 +2479,36 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "fast-float2" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8eb564c5c7423d25c886fb561d1e4ee69f72354d16918afa32c08811f6b6a55" + +[[package]] +name = "fast_image_resize" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12dd43e5011e8d8411a3215a0d57a2ec5c68282fb90eb5d7221fab0113442174" +dependencies = [ + "cfg-if", + "document-features", + "num-traits", + "thiserror 2.0.18", +] + [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[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" @@ -2263,6 +2584,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", + "libz-rs-sys", "miniz_oxide", ] @@ -2307,31 +2629,63 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] -name = "foreign-types" -version = "0.3.2" +name = "font-types" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +checksum = "5b38ad915f6dadd993ced50848a8291a543bd41ca62bc10740d5e64e2ab4cfd7" dependencies = [ - "foreign-types-shared 0.1.1", + "bytemuck", ] [[package]] -name = "foreign-types" -version = "0.5.0" +name = "fontconfig-parser" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" dependencies = [ - "foreign-types-macros", - "foreign-types-shared 0.3.1", + "roxmltree", ] [[package]] -name = "foreign-types-macros" -version = "0.2.3" +name = "fontdb" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +checksum = "457e789b3d1202543297a350643cf459f836cade38934e7a4cf6a39e7cde2905" dependencies = [ - "proc-macro2", + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser", +] + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", "quote", "syn 2.0.108", ] @@ -2433,7 +2787,7 @@ dependencies = [ "futures-core", "futures-lite", "pin-project", - "smallvec 1.15.1", + "smallvec", ] [[package]] @@ -2677,9 +3031,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 6.0.0", "rand_core 0.10.1", + "wasm-bindgen", ] [[package]] @@ -2699,7 +3055,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" dependencies = [ "color_quant", - "weezl", + "weezl 0.1.10", +] + +[[package]] +name = "gif" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" +dependencies = [ + "color_quant", + "weezl 0.1.10", ] [[package]] @@ -2717,7 +3083,7 @@ dependencies = [ "libc", "once_cell", "pin-project-lite", - "smallvec 1.15.1", + "smallvec", "thiserror 1.0.69", ] @@ -2734,6 +3100,30 @@ dependencies = [ "winapi", ] +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f22fb22f065b308be0d8724e3706c7fa3fc2a6c7d6899df4cad7860e7a75436" + [[package]] name = "glib" version = "0.18.5" @@ -2753,7 +3143,7 @@ dependencies = [ "libc", "memchr", "once_cell", - "smallvec 1.15.1", + "smallvec", "thiserror 1.0.69", ] @@ -2841,7 +3231,7 @@ dependencies = [ "icu_calendar", "icu_locale", "ignore", - "image", + "image 0.24.9", "include_dir", "indexmap 2.12.0", "indoc", @@ -2853,7 +3243,7 @@ dependencies = [ "nanoid", "oauth2", "once_cell", - "pastey", + "pastey 0.2.3", "process-wrap", "pulldown-cmark", "rand 0.10.2", @@ -2994,6 +3384,12 @@ dependencies = [ "serde_json", ] +[[package]] +name = "grid" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b40ca9252762c466af32d0b1002e91e4e1bc5398f77455e55474deb466355ff5" + [[package]] name = "group" version = "0.13.0" @@ -3133,6 +3529,27 @@ dependencies = [ "hashbrown 0.15.5", ] +[[package]] +name = "hayro-ccitt" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f4d0e94ddd48749f06bbe4e5389fb9799a0c45bcaf00495042076ef05e3241a" + +[[package]] +name = "hayro-jbig2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69374b3668dd45aeb3d3145cda68f2c7b4f223aaa2511e67d076f1c7d741388d" +dependencies = [ + "hayro-ccitt", +] + +[[package]] +name = "hayro-jpeg2000" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab0c77d09c0d9b144d429d0bf0fcd4c63c01d5f6c33f9b8ed501283e0f1ef76" + [[package]] name = "heck" version = "0.4.1" @@ -3273,7 +3690,7 @@ dependencies = [ "itoa", "pin-project-lite", "pin-utils", - "smallvec 1.15.1", + "smallvec", "tokio", "want", ] @@ -3420,7 +3837,7 @@ dependencies = [ "icu_normalizer_data", "icu_properties", "icu_provider", - "smallvec 1.15.1", + "smallvec", "zerovec", ] @@ -3480,7 +3897,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", - "smallvec 1.15.1", + "smallvec", "utf8_iter", ] @@ -3519,12 +3936,71 @@ dependencies = [ "bytemuck", "byteorder", "color_quant", - "gif", + "gif 0.13.3", "jpeg-decoder", "num-traits", "png 0.17.16", ] +[[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 0.14.2", + "image-webp", + "moxcms", + "num-traits", + "png 0.18.1", + "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.3", + "image 0.25.10", + "itertools", + "nalgebra", + "num", + "rand 0.10.2", + "rand_distr 0.6.0", + "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" @@ -3594,6 +4070,27 @@ dependencies = [ "generic-array", ] +[[package]] +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "block-padding", + "hybrid-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.108", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -3635,6 +4132,15 @@ 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 = "itoa" version = "1.0.15" @@ -3664,6 +4170,42 @@ dependencies = [ "system-deps", ] +[[package]] +name = "jiff" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e184d09547b80eb7e20d141ba2fb1fbac843ca53f4cf1b31210adc4c1adc6e16" +dependencies = [ + "defmt", + "jiff-core", + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" +dependencies = [ + "defmt", +] + +[[package]] +name = "jiff-static" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "323da076b7a6faf914dc677cb05a4b907742ff7375c8322c9e7f5061e5e0e9de" +dependencies = [ + "jiff-core", + "proc-macro2", + "quote", + "syn 2.0.108", +] + [[package]] name = "jni" version = "0.21.1" @@ -3750,6 +4292,9 @@ name = "jpeg-decoder" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" +dependencies = [ + "rayon", +] [[package]] name = "js-sys" @@ -3852,6 +4397,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "kurbo" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b60dfc32f652b926df6192e55525b16d186c69d47876c3ead4da5cc9f8450e2" +dependencies = [ + "arrayvec", + "euclid", + "polycool", + "smallvec", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -3861,6 +4418,12 @@ dependencies = [ "spin", ] +[[package]] +name = "lebe" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" + [[package]] name = "libappindicator" version = "0.9.0" @@ -3900,6 +4463,16 @@ dependencies = [ "pkg-config", ] +[[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.7.4" @@ -3912,9 +4485,9 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.9" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" dependencies = [ "cfg-if", "windows-link 0.2.1", @@ -3948,6 +4521,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "libz-rs-sys" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c10501e7805cee23da17c7790e59df2870c0d4043ec6d03f67d31e2b53e77415" +dependencies = [ + "zlib-rs", +] + [[package]] name = "linux-raw-sys" version = "0.11.0" @@ -3985,21 +4567,12 @@ dependencies = [ ] [[package]] -name = "lopdf" -version = "0.34.0" +name = "loop9" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5c8ecfc6c72051981c0459f75ccc585e7ff67c70829560cda8e647882a9abff" +checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062" dependencies = [ - "encoding_rs", - "flate2", - "indexmap 2.12.0", - "itoa", - "log", - "md-5", - "nom", - "rangemap", - "time", - "weezl", + "imgref", ] [[package]] @@ -4026,6 +4599,7 @@ dependencies = [ "futures-util", "goose", "hound", + "image 0.25.10", "keyring", "libc", "log", @@ -4034,14 +4608,15 @@ dependencies = [ "once_cell", "openssl", "ort", - "pdf-extract", + "pdf_oxide", "plist", "process-wrap", "rand 0.8.6", - "rand_distr", + "rand_distr 0.4.3", "regex", "reqwest 0.13.2", "rmcp", + "rustls", "serde", "serde_json", "sha2 0.10.9", @@ -4057,9 +4632,11 @@ dependencies = [ "tauri-plugin-sign-in-with-apple", "tauri-plugin-single-instance", "tauri-plugin-updater", + "tempfile", "tokio", "tokio-util", "unicode-normalization", + "webpki-roots", "windows 0.62.2", ] @@ -4123,6 +4700,16 @@ dependencies = [ "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" @@ -4133,12 +4720,31 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "md-5" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" +dependencies = [ + "cfg-if", + "digest 0.11.3", +] + [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "memmap2" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" +dependencies = [ + "libc", +] + [[package]] name = "memo-map" version = "0.3.3" @@ -4219,6 +4825,16 @@ dependencies = [ "windows-sys 0.61.2", ] +[[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 = "muda" version = "0.19.2" @@ -4241,36 +4857,38 @@ dependencies = [ ] [[package]] -name = "nanoid" -version = "0.5.0" +name = "nalgebra" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8628de41fe064cc3f0cf07f3d299ee3e73521adaff72278731d5c8cae3797873" +checksum = "adc43a60c217b0c6ff46e47f26911015ad8d2e5a8be1af668c67e370d99a4346" dependencies = [ - "rand 0.9.4", + "approx", + "glam 0.30.10", + "glam 0.31.1", + "glam 0.32.1", + "glam 0.33.2", + "matrixmultiply", + "num-complex", + "num-rational", + "num-traits", + "simba", + "typenum", ] [[package]] -name = "native-tls" -version = "0.2.14" +name = "nanoid" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +checksum = "8628de41fe064cc3f0cf07f3d299ee3e73521adaff72278731d5c8cae3797873" dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe 0.1.6", - "openssl-sys", - "schannel", - "security-framework 2.11.1", - "security-framework-sys", - "tempfile", + "rand 0.9.4", ] [[package]] name = "ndarray" -version = "0.16.1" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841" +checksum = "520080814a7a6b4a6e9070823bb24b4531daac8c4627e08ba5de8c5ef2f2752d" dependencies = [ "matrixmultiply", "num-complex", @@ -4343,6 +4961,15 @@ dependencies = [ "libc", ] +[[package]] +name = "no_std_io2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418abd1b6d34fbf6cae440dc874771b0525a604428704c76e48b29a5e67b8003" +dependencies = [ + "memchr", +] + [[package]] name = "nom" version = "7.1.3" @@ -4353,6 +4980,21 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +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" @@ -4398,7 +5040,7 @@ dependencies = [ "num-iter", "num-traits", "rand 0.8.6", - "smallvec 1.15.1", + "smallvec", "zeroize", ] @@ -4414,6 +5056,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ + "bytemuck", "num-traits", ] @@ -4786,6 +5429,24 @@ dependencies = [ "objc2-foundation 0.3.2", ] +[[package]] +name = "office_oxide" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7150eeae2b40a6926717058d0575ee2f48a36806725376a2ff6b2e69b61c88e9" +dependencies = [ + "atoi_simd", + "encoding_rs", + "fast-float2", + "libc", + "log", + "quick-xml 0.41.0", + "serde", + "serde_json", + "thiserror 2.0.18", + "zip 8.6.0", +] + [[package]] name = "oid-registry" version = "0.7.1" @@ -4887,12 +5548,6 @@ dependencies = [ "syn 2.0.108", ] -[[package]] -name = "openssl-probe" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - [[package]] name = "openssl-probe" version = "0.2.1" @@ -4949,29 +5604,22 @@ dependencies = [ [[package]] name = "ort" -version = "2.0.0-rc.10" +version = "2.0.0-rc.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa7e49bd669d32d7bc2a15ec540a527e7764aec722a45467814005725bcd721" +checksum = "4a5df903c0d2c07b56950f1058104ab0c8557159f2741782223704de9be73c3c" dependencies = [ - "libloading 0.8.9", + "libloading 0.9.0", "ndarray", "ort-sys", - "smallvec 2.0.0-alpha.10", + "smallvec", "tracing", ] [[package]] name = "ort-sys" -version = "2.0.0-rc.10" +version = "2.0.0-rc.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2aba9f5c7c479925205799216e7e5d07cc1d4fa76ea8058c60a9a30f6a4e890" -dependencies = [ - "flate2", - "pkg-config", - "sha2 0.10.9", - "tar", - "ureq", -] +checksum = "06503bb33f294c5f1ba484011e053bfa6ae227074bdb841e9863492dc5960d4b" [[package]] name = "os_info" @@ -5005,6 +5653,15 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" +[[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 = "p256" version = "0.13.2" @@ -5067,10 +5724,22 @@ dependencies = [ "cfg-if", "libc", "redox_syscall", - "smallvec 1.15.1", + "smallvec", "windows-link 0.2.1", ] +[[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" @@ -5084,18 +5753,59 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] -name = "pdf-extract" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbb3a5387b94b9053c1e69d8abfd4dd6dae7afda65a5c5279bc1f42ab39df575" +name = "pdf_oxide" +version = "0.3.74" +source = "git+https://github.com/OpenSecretCloud/pdf_oxide.git?rev=f24b43ba997dd91ce60839640a8ce3ac92a87a5d#f24b43ba997dd91ce60839640a8ce3ac92a87a5d" dependencies = [ - "adobe-cmap-parser", + "aes 0.9.1", + "base64 0.22.1", + "bitflags 2.10.0", + "brotli", + "byteorder", + "bytes", + "cbc", + "chrono", "encoding_rs", - "euclid", - "lopdf", - "postscript", - "type1-encoding-parser", + "env_logger", + "fast_image_resize", + "fax", + "flate2", + "fontdb", + "getrandom 0.4.3", + "hayro-jbig2", + "hayro-jpeg2000", + "image 0.25.10", + "imageproc", + "jpeg-decoder", + "libc", + "log", + "md-5 0.11.0", + "memchr", + "ndarray", + "nom 8.0.0", + "office_oxide", + "ort", + "phf 0.14.0", + "qcms", + "quick-xml 0.41.0", + "regex", + "rustybuzz", + "serde", + "serde_json", + "sha2 0.11.0", + "smallvec", + "stringprep", + "subsetter", + "taffy", + "thiserror 2.0.18", + "tiny-skia", + "ttf-parser", + "unicode-bidi", + "unicode-linebreak", "unicode-normalization", + "ureq", + "uuid", + "weezl 0.2.1", ] [[package]] @@ -5138,18 +5848,29 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" dependencies = [ - "phf_macros", + "phf_macros 0.13.1", "phf_shared 0.13.1", "serde", ] +[[package]] +name = "phf" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "010378780309880b08997fae13be7834dba947d36393bd372f2b1556deb2a2f6" +dependencies = [ + "phf_macros 0.14.0", + "phf_shared 0.14.0", + "serde", +] + [[package]] name = "phf_codegen" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" dependencies = [ - "phf_generator", + "phf_generator 0.13.1", "phf_shared 0.13.1", ] @@ -5163,19 +5884,42 @@ dependencies = [ "phf_shared 0.13.1", ] +[[package]] +name = "phf_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeb62e0959d5a1bebc965f4d15d9e2b7cea002b6b0f5ba8cde6cc26738467100" +dependencies = [ + "fastrand", + "phf_shared 0.14.0", +] + [[package]] name = "phf_macros" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" dependencies = [ - "phf_generator", + "phf_generator 0.13.1", "phf_shared 0.13.1", "proc-macro2", "quote", "syn 2.0.108", ] +[[package]] +name = "phf_macros" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fa8d0ca26d424d27630da600c6624696e7dec8bf7b3b492b383c5dc49e5e085" +dependencies = [ + "phf_generator 0.14.0", + "phf_shared 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.108", +] + [[package]] name = "phf_shared" version = "0.12.1" @@ -5194,6 +5938,15 @@ dependencies = [ "siphasher", ] +[[package]] +name = "phf_shared" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6fd9027e2d9319be6349febd1db4e8d02aa544921200c9b777720ac34a3aa89" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.10" @@ -5272,7 +6025,7 @@ checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" dependencies = [ "base64 0.22.1", "indexmap 2.12.0", - "quick-xml", + "quick-xml 0.38.3", "serde", "time", ] @@ -5328,6 +6081,15 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "polycool" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50596ddc09eb5ad5f75cacd40209568e66df71baf86e1499a0e99c4cff12a5a6" +dependencies = [ + "arrayvec", +] + [[package]] name = "polyval" version = "0.6.2" @@ -5340,12 +6102,6 @@ dependencies = [ "universal-hash", ] -[[package]] -name = "pom" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60f6ce597ecdcc9a098e7fddacb1065093a3d66446fa16c675e7e71d1b5c28e6" - [[package]] name = "portable-atomic" version = "1.11.1" @@ -5361,12 +6117,6 @@ dependencies = [ "portable-atomic", ] -[[package]] -name = "postscript" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78451badbdaebaf17f053fd9152b3ffb33b516104eacb45e7864aaa9c712f306" - [[package]] name = "potential_utf" version = "0.1.4" @@ -5399,6 +6149,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "primal-check" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc0d895b311e3af9902528fbb8f928688abbd95872819320517cc24ca6b2bd08" +dependencies = [ + "num-integer", +] + [[package]] name = "primeorder" version = "0.13.6" @@ -5484,6 +6243,25 @@ dependencies = [ "windows 0.62.2", ] +[[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.108", +] + [[package]] name = "psl-types" version = "2.0.11" @@ -5531,6 +6309,56 @@ dependencies = [ "unicase", ] +[[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 = "qcms" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edecfcd5d755a5e5d98e24cf43113e7cdaec5a070edd0f6b250c03a573da30fa" + +[[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 = "quick-xml" version = "0.38.3" @@ -5540,6 +6368,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "quick-xml" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "quinn" version = "0.11.9" @@ -5710,10 +6548,73 @@ dependencies = [ ] [[package]] -name = "rangemap" -version = "1.6.0" +name = "rand_distr" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93e7e49bb0bf967717f7bd674458b3d6b0c5f48ec7e3038166026a69fc22223" +checksum = "4d431c2703ccf129de4d45253c03f49ebb22b97d6ad79ee3ecfc7e3f4862c1d8" +dependencies = [ + "num-traits", + "rand 0.10.2", +] + +[[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", + "libc", + "libfuzzer-sys", + "log", + "maybe-rayon", + "new_debug_unreachable", + "noop_proc_macro", + "num-derive", + "num-traits", + "paste", + "profiling", + "rand 0.9.4", + "rand_chacha 0.9.0", + "simd_helpers", + "thiserror 2.0.18", + "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 2.10.0", +] [[package]] name = "raw-window-handle" @@ -5738,14 +6639,30 @@ dependencies = [ ] [[package]] -name = "rayon-core" -version = "1.13.0" +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "read-fonts" +version = "0.39.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4ed38b89c2c77ff968c524145ad65fb010f38af5c7a224b53b81d47ac2daa81" +dependencies = [ + "bytemuck", + "font-types", +] + +[[package]] +name = "reborrow" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] +checksum = "03251193000f4bd3b042892be858ee50e8b3719f2b08e5833ac4353724632430" [[package]] name = "redox_syscall" @@ -5968,6 +6885,12 @@ dependencies = [ "windows-sys 0.60.2", ] +[[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" @@ -6026,7 +6949,7 @@ dependencies = [ "hyper", "hyper-util", "oauth2", - "pastey", + "pastey 0.2.3", "pin-project-lite", "process-wrap", "reqwest 0.13.2", @@ -6056,13 +6979,19 @@ dependencies = [ "syn 2.0.108", ] +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + [[package]] name = "rsa" version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ - "const-oid", + "const-oid 0.9.6", "digest 0.10.7", "num-bigint-dig", "num-integer", @@ -6117,13 +7046,36 @@ 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 = "rusticata-macros" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" dependencies = [ - "nom", + "nom 7.1.3", ] [[package]] @@ -6146,6 +7098,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7" dependencies = [ "aws-lc-rs", + "log", "once_cell", "ring", "rustls-pki-types", @@ -6160,10 +7113,10 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ - "openssl-probe 0.2.1", + "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.5.1", + "security-framework", ] [[package]] @@ -6191,7 +7144,7 @@ dependencies = [ "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki", - "security-framework 3.5.1", + "security-framework", "security-framework-sys", "webpki-root-certs", "windows-sys 0.61.2", @@ -6221,12 +7174,39 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "rustybuzz" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702" +dependencies = [ + "bitflags 2.10.0", + "bytemuck", + "core_maths", + "log", + "smallvec", + "ttf-parser", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "safe_arch" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a52ec151f024d703f9fd65abb7cbe81e7cdb39f18917a3a37e3014470dc7c59" +dependencies = [ + "bytemuck", +] + [[package]] name = "same-file" version = "1.0.6" @@ -6336,19 +7316,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "security-framework" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags 2.10.0", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - [[package]] name = "security-framework" version = "3.5.1" @@ -6388,7 +7355,7 @@ dependencies = [ "precomputed-hash", "rustc-hash", "servo_arc", - "smallvec 1.15.1", + "smallvec", ] [[package]] @@ -6678,6 +7645,18 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simba" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f45c644a9f3a386f9288625d9f0c1e999e1acf07a37df35d0516c7f199d9cb2" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "wide", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -6694,6 +7673,15 @@ dependencies = [ "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" @@ -6718,6 +7706,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +[[package]] +name = "skrifa" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c34617370ae968efb7161bb2beb517d9084659aae19e24b89e3db25b46e4564" +dependencies = [ + "bytemuck", + "read-fonts", +] + [[package]] name = "slab" version = "0.4.11" @@ -6725,19 +7723,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] -name = "smallvec" -version = "1.15.1" +name = "slotmap" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" dependencies = [ - "serde", + "version_check", ] [[package]] name = "smallvec" -version = "2.0.0-alpha.10" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d44cfb396c3caf6fbfd0ab422af02631b69ddd96d2eff0b0f0724f9024051b" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] [[package]] name = "socket2" @@ -6749,17 +7750,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "socks" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b" -dependencies = [ - "byteorder", - "libc", - "winapi", -] - [[package]] name = "softbuffer" version = "0.4.6" @@ -6867,7 +7857,7 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.9", - "smallvec 1.15.1", + "smallvec", "thiserror 2.0.18", "tokio", "tokio-stream", @@ -6939,7 +7929,7 @@ dependencies = [ "hmac", "itoa", "log", - "md-5", + "md-5 0.10.6", "memchr", "once_cell", "percent-encoding", @@ -6948,7 +7938,7 @@ dependencies = [ "serde", "sha1", "sha2 0.10.9", - "smallvec 1.15.1", + "smallvec", "sqlx-core", "stringprep", "thiserror 2.0.18", @@ -6979,14 +7969,14 @@ dependencies = [ "home", "itoa", "log", - "md-5", + "md-5 0.10.6", "memchr", "once_cell", "rand 0.8.6", "serde", "serde_json", "sha2 0.10.9", - "smallvec 1.15.1", + "smallvec", "sqlx-core", "stringprep", "thiserror 2.0.18", @@ -7050,6 +8040,18 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" +[[package]] +name = "strength_reduce" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" + +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" + [[package]] name = "string_cache" version = "0.9.0" @@ -7068,7 +8070,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" dependencies = [ - "phf_generator", + "phf_generator 0.13.1", "phf_shared 0.13.1", "proc-macro2", "quote", @@ -7133,6 +8135,18 @@ dependencies = [ "syn 2.0.108", ] +[[package]] +name = "subsetter" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38803281d1c23166c5ebcb455439a5d2afe711cc909cf88af72448c297756ad6" +dependencies = [ + "kurbo", + "rustc-hash", + "skrifa", + "write-fonts", +] + [[package]] name = "subtle" version = "2.6.1" @@ -7251,6 +8265,18 @@ dependencies = [ "version-compare", ] +[[package]] +name = "taffy" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "340a09581f29809fc0df82a3955501dc7f2a21f887e5d1c13dbe288fe1c0bef4" +dependencies = [ + "arrayvec", + "grid", + "serde", + "slotmap", +] + [[package]] name = "tao" version = "0.35.2" @@ -7635,7 +8661,7 @@ dependencies = [ "tokio", "url", "windows-sys 0.60.2", - "zip", + "zip 4.6.1", ] [[package]] @@ -7809,6 +8835,20 @@ 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 0.1.10", + "zune-jpeg", +] + [[package]] name = "tiktoken-rs" version = "0.12.0" @@ -7866,6 +8906,32 @@ dependencies = [ "crunchy", ] +[[package]] +name = "tiny-skia" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47ffee5eaaf5527f630fb0e356b90ebdec84d5d18d937c5e440350f88c5a91ea" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "log", + "png 0.18.1", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca365c3faccca67d06593c5980fa6c57687de727a03131735bb85f01fdeeb9" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + [[package]] name = "tinystr" version = "0.8.2" @@ -8262,7 +9328,7 @@ dependencies = [ "serde", "serde_json", "sharded-slab", - "smallvec 1.15.1", + "smallvec", "thread_local", "time", "tracing", @@ -8271,6 +9337,16 @@ dependencies = [ "tracing-serde", ] +[[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 = "tray-icon" version = "0.23.1" @@ -8409,6 +9485,15 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" +dependencies = [ + "core_maths", +] + [[package]] name = "tungstenite" version = "0.28.0" @@ -8427,13 +9512,10 @@ dependencies = [ ] [[package]] -name = "type1-encoding-parser" -version = "0.1.0" +name = "typed-path" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3d6cc09e1a99c7e01f2afe4953789311a1c50baebbdac5b477ecf78e2e92a5b" -dependencies = [ - "pom", -] +checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" [[package]] name = "typeid" @@ -8511,6 +9593,18 @@ version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" +[[package]] +name = "unicode-bidi-mirroring" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe" + +[[package]] +name = "unicode-ccc" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e" + [[package]] name = "unicode-general-category" version = "1.1.0" @@ -8523,6 +9617,12 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + [[package]] name = "unicode-normalization" version = "0.1.25" @@ -8538,6 +9638,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" +[[package]] +name = "unicode-script" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" + [[package]] name = "unicode-segmentation" version = "1.12.0" @@ -8579,15 +9685,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d39cb1dbab692d82a977c0392ffac19e188bd9186a9f32806f0aaa859d75585a" dependencies = [ "base64 0.22.1", - "der", "log", - "native-tls", "percent-encoding", + "rustls", "rustls-pki-types", - "socks", "ureq-proto", "utf-8", - "webpki-root-certs", + "webpki-roots", ] [[package]] @@ -8708,6 +9812,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1212fce830b75af194b578e55b3db9049f2c8c45f58d397fb25602fdb50fb3d" +[[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 = "v_htmlescape" version = "0.17.0" @@ -9046,6 +10161,12 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" +[[package]] +name = "weezl" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ca08e5ef825b65b056d9efbd95c8750683f0a6d0466d02e96dc2e4e360f3d2" + [[package]] name = "which" version = "8.0.4" @@ -9065,6 +10186,16 @@ dependencies = [ "wasite", ] +[[package]] +name = "wide" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfdfe6a32973f2d1b268b8895845a8a96cac2f0191e72c27cc929036060dbf89" +dependencies = [ + "bytemuck", + "safe_arch", +] + [[package]] name = "winapi" version = "0.3.9" @@ -9667,6 +10798,19 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +[[package]] +name = "write-fonts" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb731d4c4d93eacc69a1ad2f270f905788a98e4a3438267bcafbe08d3431c8d8" +dependencies = [ + "font-types", + "indexmap 2.12.0", + "kurbo", + "log", + "read-fonts", +] + [[package]] name = "writeable" version = "0.6.2" @@ -9786,7 +10930,7 @@ dependencies = [ "data-encoding", "der-parser", "lazy_static", - "nom", + "nom 7.1.3", "oid-registry", "rusticata-macros", "thiserror 1.0.69", @@ -9803,6 +10947,12 @@ dependencies = [ "rustix", ] +[[package]] +name = "y4m" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" + [[package]] name = "yasna" version = "0.5.2" @@ -10000,6 +11150,38 @@ dependencies = [ "memchr", ] +[[package]] +name = "zip" +version = "8.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b" +dependencies = [ + "crc32fast", + "flate2", + "indexmap 2.12.0", + "memchr", + "typed-path", + "zopfli", +] + +[[package]] +name = "zlib-rs" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + [[package]] name = "zstd" version = "0.13.3" @@ -10028,6 +11210,30 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "zune-core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9" + +[[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", +] + [[package]] name = "zvariant" version = "5.8.0" diff --git a/frontend/src-tauri/Cargo.toml b/frontend/src-tauri/Cargo.toml index 0a79151f..a37def0a 100644 --- a/frontend/src-tauri/Cargo.toml +++ b/frontend/src-tauri/Cargo.toml @@ -40,35 +40,36 @@ maple-proxy = "0.2.0" tauri-plugin-fs = "2.5.1" anyhow = "1.0" axum = "0.8" -pdf-extract = "0.7" +pdf_oxide = { version = "=0.3.74", git = "https://github.com/OpenSecretCloud/pdf_oxide.git", rev = "f24b43ba997dd91ce60839640a8ce3ac92a87a5d", features = ["ocr-ort", "rendering"] } +image = { version = "0.25", default-features = false, features = ["jpeg", "png"] } base64 = "0.22" +reqwest = { version = "0.13", features = ["stream"] } +futures-util = "0.3" +sha2 = "0.10" + +[target.'cfg(any(target_os = "macos", target_os = "windows", target_os = "linux", target_os = "android"))'.dependencies] +# PDF OCR uses the same explicitly packaged ONNX Runtime as desktop TTS. The +# loader policy lives in Maple so iOS can retain static linkage below. +ort = { version = "=2.0.0-rc.11", default-features = false, features = ["std", "ndarray", "load-dynamic"] } [target.'cfg(any(target_os = "macos", target_os = "windows"))'.dependencies] # TTS dependencies (Supertonic) - desktop only -ort = "2.0.0-rc.10" -ndarray = { version = "0.16", features = ["rayon"] } +ndarray = { version = "0.17", features = ["rayon"] } rand = "0.8.6" rand_distr = "0.4" hound = "3.5" unicode-normalization = "0.1" regex = "1.10" -reqwest = { version = "0.13", features = ["stream"] } -futures-util = "0.3" dirs = "6.0" -sha2 = "0.10" [target.'cfg(target_os = "linux")'.dependencies] -ort = { version = "2.0.0-rc.10", features = ["load-dynamic"] } -ndarray = { version = "0.16", features = ["rayon"] } +ndarray = { version = "0.17", features = ["rayon"] } rand = "0.8.6" rand_distr = "0.4" hound = "3.5" unicode-normalization = "0.1" regex = "1.10" -reqwest = { version = "0.13", features = ["stream"] } -futures-util = "0.3" dirs = "6.0" -sha2 = "0.10" [target.'cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))'.dependencies] # Pin Goose to an exact official upstream commit. Keep this as a git dependency @@ -86,23 +87,22 @@ libc = "0.2" [target.'cfg(target_os = "ios")'.dependencies] # TTS dependencies (Supertonic) - iOS -# We build ONNX Runtime 1.22.2 from source for iOS (see scripts/build-ios-onnxruntime.sh) +# We build ONNX Runtime 1.23.2 from source for iOS (see scripts/build-ios-onnxruntime.sh) # We disable download-binaries and copy-dylibs since we link our own xcframework # Need "std" for Error trait impl and file operations, "ndarray" for tensor creation -ort = { version = "2.0.0-rc.10", default-features = false, features = ["std", "ndarray"] } -ndarray = { version = "0.16" } +ort = { version = "=2.0.0-rc.11", default-features = false, features = ["std", "ndarray"] } +ndarray = { version = "0.17" } rand = "0.8.6" rand_distr = "0.4" hound = "3.5" unicode-normalization = "0.1" regex = "1.10" -reqwest = { version = "0.13", features = ["stream"] } -futures-util = "0.3" dirs = "6.0" -sha2 = "0.10" [target.'cfg(target_os = "android")'.dependencies] openssl = { version = "0.10.80", default-features = false, features = ["vendored"] } +rustls = { version = "0.23", default-features = false, features = ["aws_lc_rs", "std", "tls12"] } +webpki-roots = "1" [target.'cfg(target_os = "windows")'.dependencies] # Store the proxy API key in Windows Credential Manager rather than as @@ -110,6 +110,9 @@ openssl = { version = "0.10.80", default-features = false, features = ["vendored keyring = { version = "3", features = ["windows-native"] } windows = { version = "0.62.2", features = ["Win32_System_Threading"] } +[dev-dependencies] +tempfile = "3" + [patch.crates-io] # Local patch for tao 0.35.2 Android intent crashes: # https://github.com/tauri-apps/tao/issues/1217 diff --git a/frontend/src-tauri/resources/windows/README.md b/frontend/src-tauri/resources/windows/README.md index 8ffaa106..8365fd26 100644 --- a/frontend/src-tauri/resources/windows/README.md +++ b/frontend/src-tauri/resources/windows/README.md @@ -1,17 +1,15 @@ # Windows bundled runtime DLLs -`maple.exe` links **ONNX Runtime 1.22.0** with a load-time (compile-time -dynamic) import of `onnxruntime.dll`. On a clean Windows install, the Windows -loader would otherwise bind that import to the OS-shipped Windows-ML -`C:\Windows\System32\onnxruntime.dll` (a forwarder to `onnxruntime_x64.dll`, -currently **v1.17.x**). The version mismatch lets the app launch but **hangs** -the first time TTS calls `Session::builder()` (see `src/tts.rs`). - -To force the correct runtime, we ship these DLLs **next to `maple.exe`**. The -executable's own directory is searched **before** `System32` in the Windows DLL -search order, so our 1.22.0 `onnxruntime.dll` wins over the OS one. - -`onnxruntime.dll` 1.22.0 in turn depends on the MSVC C++ runtime +Maple loads **ONNX Runtime 1.23.2** by explicit path before PDF OCR or TTS +creates a session. Windows also ships a Windows-ML +`C:\Windows\System32\onnxruntime.dll` forwarder that may be an incompatible +version, so Maple must not rely on the ambient DLL search path. + +We ship the runtime **next to `maple.exe`**, and `src/onnxruntime.rs` passes +that exact path to the Rust `ort` loader. This prevents the System32 copy from +being selected accidentally. + +`onnxruntime.dll` 1.23.2 in turn depends on the MSVC C++ runtime (`VCRUNTIME140.dll`, `VCRUNTIME140_1.dll`, `MSVCP140.dll`, `MSVCP140_1.dll`), which is **not** present on a fresh Windows install. Rather than running `vc_redist.x64.exe` (which needs admin/UAC and clashes with our per-user @@ -34,7 +32,7 @@ land in the install dir (verified empirically — only `maple.exe` + | File | Source | |-----------------------|-----------------------------------------------------| -| `onnxruntime.dll` | Microsoft ONNX Runtime 1.22.0 win-x64 release | +| `onnxruntime.dll` | Microsoft ONNX Runtime 1.23.2 win-x64 release | | `VCRUNTIME140.dll` | MSVC 2015–2022 x64 redistributable | | `VCRUNTIME140_1.dll` | MSVC 2015–2022 x64 redistributable | | `MSVCP140.dll` | MSVC 2015–2022 x64 redistributable | @@ -84,7 +82,7 @@ file fails the build loudly rather than shipping a broken installer. ## Verify after a CI build Confirm the DLLs land **next to `maple.exe`** in the installed app -(`%LOCALAPPDATA%\Maple\`), not in a `resources\` subfolder — only then does the -search-order override work. The running process should load -`...\AppData\Local\Maple\onnxruntime.dll ver 1.22.0` (check via +(`%LOCALAPPDATA%\Maple\`), not in a `resources\` subfolder. The running process +should load +`...\AppData\Local\Maple\onnxruntime.dll ver 1.23.2` (check via `(Get-Process maple).Modules`), not the System32 copy. diff --git a/frontend/src-tauri/scripts/build-ios-onnxruntime-all.sh b/frontend/src-tauri/scripts/build-ios-onnxruntime-all.sh index b39c99f5..21b6e43c 100755 --- a/frontend/src-tauri/scripts/build-ios-onnxruntime-all.sh +++ b/frontend/src-tauri/scripts/build-ios-onnxruntime-all.sh @@ -9,7 +9,7 @@ # - Git # # Usage: ./build-ios-onnxruntime-all.sh [version] -# Example: ./build-ios-onnxruntime-all.sh 1.22.2 +# Example: ./build-ios-onnxruntime-all.sh 1.23.2 set -e @@ -17,7 +17,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "${SCRIPT_DIR}/onnxruntime-pins.sh" TAURI_DIR="$(dirname "$SCRIPT_DIR")" -ORT_VERSION="${1:-1.22.2}" +ORT_VERSION="${1:-1.23.2}" ORT_COMMIT="${ORT_COMMIT:-$(onnxruntime_ios_commit_for_version "${ORT_VERSION}")}" BUILD_DIR="${TAURI_DIR}/onnxruntime-build" OUTPUT_DIR="${TAURI_DIR}/onnxruntime-ios" @@ -94,8 +94,9 @@ clone_with_retry cd onnxruntime -# ONNX Runtime embeds CMAKE_CXX_FLAGS in its public build-info string. Keep the -# real compiler prefix maps, but canonicalize that generated string first. +# ONNX Runtime embeds the source revision in its public build-info string. Use +# the full commit so the provenance is unambiguous. Older versions also embed +# CMAKE_CXX_FLAGS; canonicalize that optional field when it is present. patch_reproducible_build_info() { python3 - "$PWD/cmake/CMakeLists.txt" <<'PY' from pathlib import Path @@ -114,13 +115,13 @@ old_commit = 'execute_process(COMMAND ${GIT_EXECUTABLE} log -1 --format=%h' new_commit = 'execute_process(COMMAND ${GIT_EXECUTABLE} log -1 --format=%H' changed = False -if old_commit in text: +if new_commit not in text: + if old_commit not in text: + sys.exit("Could not normalize the ONNX Runtime build-info commit hash.") text = text.replace(old_commit, new_commit, 1) changed = True -if new not in text: - if old not in text: - sys.exit("Could not patch ONNX Runtime build info generation.") +if old in text and new not in text: text = text.replace(old, new, 1) changed = True diff --git a/frontend/src-tauri/scripts/build-ios-onnxruntime.sh b/frontend/src-tauri/scripts/build-ios-onnxruntime.sh index 1fb463fa..de3e38d0 100755 --- a/frontend/src-tauri/scripts/build-ios-onnxruntime.sh +++ b/frontend/src-tauri/scripts/build-ios-onnxruntime.sh @@ -9,7 +9,7 @@ # - Git # # Usage: ./build-ios-onnxruntime.sh [version] -# Example: ./build-ios-onnxruntime.sh 1.20.1 +# Example: ./build-ios-onnxruntime.sh 1.23.2 set -e @@ -17,8 +17,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "${SCRIPT_DIR}/onnxruntime-pins.sh" TAURI_DIR="$(dirname "$SCRIPT_DIR")" -# Use latest 1.22.2 - older versions have Eigen hash mismatch issues with GitLab -ORT_VERSION="${1:-1.22.2}" +ORT_VERSION="${1:-1.23.2}" ORT_COMMIT="${ORT_COMMIT:-$(onnxruntime_ios_commit_for_version "${ORT_VERSION}")}" BUILD_DIR="${TAURI_DIR}/onnxruntime-build" OUTPUT_DIR="${TAURI_DIR}/onnxruntime-ios" diff --git a/frontend/src-tauri/scripts/onnxruntime-android-pins.sh b/frontend/src-tauri/scripts/onnxruntime-android-pins.sh new file mode 100644 index 00000000..0b28386c --- /dev/null +++ b/frontend/src-tauri/scripts/onnxruntime-android-pins.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +onnxruntime_android_version() { + printf '%s\n' "1.23.2" +} + +onnxruntime_android_aar_url_for_version() { + case "$1" in + 1.23.2) + printf '%s\n' "https://repo.maven.apache.org/maven2/com/microsoft/onnxruntime/onnxruntime-android/1.23.2/onnxruntime-android-1.23.2.aar" + ;; + *) + echo "No pinned Android ONNX Runtime AAR URL for version '$1'." >&2 + return 1 + ;; + esac +} + +onnxruntime_android_aar_sha256_for_version() { + case "$1" in + 1.23.2) + printf '%s\n' "82048d1f462218adae4ba76477089ab0ba76093d84f733540066db1a8ba6b827" + ;; + *) + echo "No pinned Android ONNX Runtime AAR SHA-256 for version '$1'." >&2 + return 1 + ;; + esac +} + +onnxruntime_android_lib_sha256_for_version() { + case "$1:$2" in + 1.23.2:arm64-v8a) + printf '%s\n' "e40f09d07dc53726b8bfbf48a7907673b8f86718a057655a62790a39874a7302" + ;; + 1.23.2:armeabi-v7a) + printf '%s\n' "57048b8d54896d16355ee367bfc129c5925468ae503b681b8d0cd49ceefa468e" + ;; + 1.23.2:x86) + printf '%s\n' "213d91ebb0cfd511c18c0057c69145de0abc6bdc9c63429bf04dcdeaf3fd861a" + ;; + 1.23.2:x86_64) + printf '%s\n' "972c17c056eaae946a415d9efdd8018b729639974df075e495f0092441478fb7" + ;; + *) + echo "No pinned Android ONNX Runtime library SHA-256 for version '$1' and ABI '$2'." >&2 + return 1 + ;; + esac +} + +onnxruntime_android_abis() { + printf '%s\n' \ + "arm64-v8a" \ + "armeabi-v7a" \ + "x86" \ + "x86_64" +} diff --git a/frontend/src-tauri/scripts/onnxruntime-pins.sh b/frontend/src-tauri/scripts/onnxruntime-pins.sh index abbf6fb3..51cb644d 100644 --- a/frontend/src-tauri/scripts/onnxruntime-pins.sh +++ b/frontend/src-tauri/scripts/onnxruntime-pins.sh @@ -2,8 +2,8 @@ onnxruntime_linux_x64_archive_sha256_for_version() { case "$1" in - 1.22.0) - printf '%s\n' "8344d55f93d5bc5021ce342db50f62079daf39aaafb5d311a451846228be49b3" + 1.23.2) + printf '%s\n' "1fa4dcaef22f6f7d5cd81b28c2800414350c10116f5fdd46a2160082551c5f9b" ;; *) echo "No pinned Linux x64 ONNX Runtime archive SHA-256 for version '$1'." >&2 @@ -14,8 +14,8 @@ onnxruntime_linux_x64_archive_sha256_for_version() { onnxruntime_linux_aarch64_archive_sha256_for_version() { case "$1" in - 1.22.0) - printf '%s\n' "bb76395092d150b52c7092dc6b8f2fe4d80f0f3bf0416d2f269193e347e24702" + 1.23.2) + printf '%s\n' "7c63c73560ed76b1fac6cff8204ffe34fe180e70d6582b5332ec094810241e5c" ;; *) echo "No pinned Linux aarch64 ONNX Runtime archive SHA-256 for version '$1'." >&2 @@ -26,8 +26,8 @@ onnxruntime_linux_aarch64_archive_sha256_for_version() { onnxruntime_linux_x64_dylib_sha256_for_version() { case "$1" in - 1.22.0) - printf '%s\n' "3da6146e14e7b8aaec625dde11d6114c7457c87a5f93d744897da8781e35c673" + 1.23.2) + printf '%s\n' "13ab8084954fa4a47c777880180b90810d6020f021441395712b48a75b74c68b" ;; *) echo "No pinned Linux x64 ONNX Runtime shared-library SHA-256 for version '$1'." >&2 @@ -38,8 +38,8 @@ onnxruntime_linux_x64_dylib_sha256_for_version() { onnxruntime_linux_aarch64_dylib_sha256_for_version() { case "$1" in - 1.22.0) - printf '%s\n' "0afd69a0ae38c5099fd0e8604dda398ac43dee67cd9c6394b5142b19e82528de" + 1.23.2) + printf '%s\n' "648ffa64fbe027ae27139109410900cf776a030dec2dbbac51053318cc44c286" ;; *) echo "No pinned Linux aarch64 ONNX Runtime shared-library SHA-256 for version '$1'." >&2 @@ -48,10 +48,34 @@ onnxruntime_linux_aarch64_dylib_sha256_for_version() { esac } +onnxruntime_macos_universal2_archive_sha256_for_version() { + case "$1" in + 1.23.2) + printf '%s\n' "49ae8e3a66ccb18d98ad3fe7f5906b6d7887df8a5edd40f49eb2b14e20885809" + ;; + *) + echo "No pinned macOS universal2 ONNX Runtime archive SHA-256 for version '$1'." >&2 + return 1 + ;; + esac +} + +onnxruntime_macos_universal2_dylib_sha256_for_version() { + case "$1" in + 1.23.2) + printf '%s\n' "6fee21a0dbcaa98fe082cb4f7ed07ec5def439df36198f47b61dc205e7d2a1fa" + ;; + *) + echo "No pinned macOS universal2 ONNX Runtime dylib SHA-256 for version '$1'." >&2 + return 1 + ;; + esac +} + onnxruntime_windows_x64_archive_sha256_for_version() { case "$1" in - 1.22.0) - printf '%s\n' "174c616efc0271194488642a72f1a514e01487da4dfe84c49296d66e40ebe0da" + 1.23.2) + printf '%s\n' "0b38df9af21834e41e73d602d90db5cb06dbd1ca618948b8f1d66d607ac9f3cd" ;; *) echo "No pinned Windows x64 ONNX Runtime archive SHA-256 for version '$1'." >&2 @@ -62,8 +86,8 @@ onnxruntime_windows_x64_archive_sha256_for_version() { onnxruntime_windows_x64_dll_sha256_for_version() { case "$1" in - 1.22.0) - printf '%s\n' "579b636403983254346a5c1d80bd28f1519cd1e284cd204f8d4ff41f8d711559" + 1.23.2) + printf '%s\n' "dec964ab1ee36cc9b0ae247d13b376627992fc57dec0454354017ab8fd84f1ea" ;; *) echo "No pinned Windows x64 ONNX Runtime DLL SHA-256 for version '$1'." >&2 @@ -130,8 +154,8 @@ windows_wix_cli_archive_sha256_for_version() { onnxruntime_ios_commit_for_version() { case "$1" in - 1.22.2) - printf '%s\n' "5630b081cd25e4eccc7516a652ff956e51676794" + 1.23.2) + printf '%s\n' "a83fc4d58cb48eb68890dd689f94f28288cf2278" ;; *) echo "No pinned ONNX Runtime iOS source commit for version '$1'." >&2 @@ -144,11 +168,8 @@ onnxruntime_ios_device_lib_sha256_for_version() { local xcode_build="${2:-${MAPLE_XCODE_BUILD_VERSION:-17F42}}" case "$1:${xcode_build}" in - 1.22.2:17F42) - printf '%s\n' "d202f35d0567b0f8d5cf14192ab6034dd17be481af58153c5eedbefcb9084fc7" - ;; - 1.22.2:17F5022i) - printf '%s\n' "d202f35d0567b0f8d5cf14192ab6034dd17be481af58153c5eedbefcb9084fc7" + 1.23.2:17F42) + printf '%s\n' "917cf5f30aa65f435371e323e347e094c99804fd6a268d3ae946cd8e5e58945f" ;; *) echo "No pinned ONNX Runtime iOS device library SHA-256 for version '$1' and Xcode build '${xcode_build}'." >&2 @@ -161,11 +182,8 @@ onnxruntime_ios_simulator_lib_sha256_for_version() { local xcode_build="${2:-${MAPLE_XCODE_BUILD_VERSION:-17F42}}" case "$1:${xcode_build}" in - 1.22.2:17F42) - printf '%s\n' "a24992c2e26049eb8b1aaf90e7dbf03fe24ed140e7b365b5c2880e3f0953baa9" - ;; - 1.22.2:17F5022i) - printf '%s\n' "a24992c2e26049eb8b1aaf90e7dbf03fe24ed140e7b365b5c2880e3f0953baa9" + 1.23.2:17F42) + printf '%s\n' "87c87225026db5ee2b5a1293468b9cc3410ad22eac182899056a05f2f2862071" ;; *) echo "No pinned ONNX Runtime iOS simulator library SHA-256 for version '$1' and Xcode build '${xcode_build}'." >&2 @@ -178,11 +196,8 @@ onnxruntime_ios_xcframework_sha256_for_version() { local xcode_build="${2:-${MAPLE_XCODE_BUILD_VERSION:-17F42}}" case "$1:${xcode_build}" in - 1.22.2:17F42) - printf '%s\n' "718e3c6e70702b82e87bc3ea1b035b00ac8451130f5922d682eaf7885c648c12" - ;; - 1.22.2:17F5022i) - printf '%s\n' "718e3c6e70702b82e87bc3ea1b035b00ac8451130f5922d682eaf7885c648c12" + 1.23.2:17F42) + printf '%s\n' "86871fe82fb5b043540d5bbc67de5ba73f2a52105ec05c66e835b7add8972af5" ;; *) echo "No pinned ONNX Runtime iOS xcframework SHA-256 for version '$1' and Xcode build '${xcode_build}'." >&2 diff --git a/frontend/src-tauri/scripts/provide-android-onnxruntime.sh b/frontend/src-tauri/scripts/provide-android-onnxruntime.sh new file mode 100755 index 00000000..04ffa8d6 --- /dev/null +++ b/frontend/src-tauri/scripts/provide-android-onnxruntime.sh @@ -0,0 +1,108 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TAURI_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" + +source "${SCRIPT_DIR}/onnxruntime-android-pins.sh" + +ORT_ANDROID_VERSION="${ORT_ANDROID_VERSION:-$(onnxruntime_android_version)}" +ORT_ANDROID_ROOT="${TAURI_DIR}/onnxruntime-android" +ORT_ANDROID_AAR="${ORT_ANDROID_ROOT}/onnxruntime-android-${ORT_ANDROID_VERSION}.aar" +ORT_ANDROID_AAR_URL="$(onnxruntime_android_aar_url_for_version "${ORT_ANDROID_VERSION}")" +ORT_ANDROID_AAR_SHA256="$(onnxruntime_android_aar_sha256_for_version "${ORT_ANDROID_VERSION}")" +ORT_ANDROID_JNI_LIBS_DIR="${TAURI_DIR}/gen/android/app/src/main/jniLibs" +download_tmp="" +stage_tmp="" + +cleanup() { + if [ -n "${download_tmp}" ]; then + rm -f "${download_tmp}" + fi + if [ -n "${stage_tmp}" ]; then + rm -f "${stage_tmp}" + fi +} +trap cleanup EXIT + +sha256_digest() { + local path="$1" + + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "${path}" | awk '{ print $1 }' + elif command -v shasum >/dev/null 2>&1; then + shasum -a 256 "${path}" | awk '{ print $1 }' + elif command -v openssl >/dev/null 2>&1; then + openssl dgst -sha256 -r "${path}" | awk '{ print $1 }' + else + echo "No SHA-256 tool found. Install sha256sum, shasum, or openssl." >&2 + return 1 + fi +} + +verify_sha256() { + local label="$1" + local path="$2" + local expected="$3" + local actual + + actual="$(sha256_digest "${path}")" + if [ "${actual}" != "${expected}" ]; then + echo "${label} SHA-256 mismatch for ${path}" >&2 + echo "expected: ${expected}" >&2 + echo "actual: ${actual}" >&2 + return 1 + fi +} + +mkdir -p "${ORT_ANDROID_ROOT}" + +if [ ! -f "${ORT_ANDROID_AAR}" ]; then + download_tmp="$(mktemp "${ORT_ANDROID_ROOT}/.onnxruntime-android-${ORT_ANDROID_VERSION}.aar.XXXXXX")" + curl -fL --retry 5 --retry-delay 2 --retry-all-errors \ + "${ORT_ANDROID_AAR_URL}" \ + --output "${download_tmp}" + verify_sha256 "ONNX Runtime Android AAR" "${download_tmp}" "${ORT_ANDROID_AAR_SHA256}" + mv "${download_tmp}" "${ORT_ANDROID_AAR}" + download_tmp="" +fi + +verify_sha256 "ONNX Runtime Android AAR" "${ORT_ANDROID_AAR}" "${ORT_ANDROID_AAR_SHA256}" + +while IFS= read -r abi; do + source_entry="jni/${abi}/libonnxruntime.so" + destination_dir="${ORT_ANDROID_JNI_LIBS_DIR}/${abi}" + destination="${destination_dir}/libonnxruntime.so" + expected="$(onnxruntime_android_lib_sha256_for_version "${ORT_ANDROID_VERSION}" "${abi}")" + entry_count="$(unzip -Z1 "${ORT_ANDROID_AAR}" | grep -Fxc -- "${source_entry}" || true)" + + if [ "${entry_count}" != "1" ]; then + echo "Expected exactly one ${source_entry} in ${ORT_ANDROID_AAR}; found ${entry_count}." >&2 + exit 1 + fi + + mkdir -p "${destination_dir}" + if [ -f "${destination}" ] && [ "$(sha256_digest "${destination}")" = "${expected}" ]; then + continue + fi + + stage_tmp="$(mktemp "${destination}.XXXXXX")" + if ! unzip -p "${ORT_ANDROID_AAR}" "${source_entry}" > "${stage_tmp}"; then + rm -f "${stage_tmp}" + exit 1 + fi + verify_sha256 "ONNX Runtime Android ${abi} library" "${stage_tmp}" "${expected}" + chmod 0755 "${stage_tmp}" + mv "${stage_tmp}" "${destination}" + stage_tmp="" +done < <(onnxruntime_android_abis) + +while IFS= read -r abi; do + library="${ORT_ANDROID_JNI_LIBS_DIR}/${abi}/libonnxruntime.so" + expected="$(onnxruntime_android_lib_sha256_for_version "${ORT_ANDROID_VERSION}" "${abi}")" + verify_sha256 "ONNX Runtime Android ${abi} library" "${library}" "${expected}" +done < <(onnxruntime_android_abis) + +printf 'ORT_ANDROID_VERSION=%s\n' "${ORT_ANDROID_VERSION}" +printf 'ORT_ANDROID_AAR_PATH=%s\n' "${ORT_ANDROID_AAR}" +printf 'ORT_ANDROID_JNI_LIBS_DIR=%s\n' "${ORT_ANDROID_JNI_LIBS_DIR}" diff --git a/frontend/src-tauri/scripts/provide-linux-onnxruntime.sh b/frontend/src-tauri/scripts/provide-linux-onnxruntime.sh index ab3bc0a2..8cc90324 100755 --- a/frontend/src-tauri/scripts/provide-linux-onnxruntime.sh +++ b/frontend/src-tauri/scripts/provide-linux-onnxruntime.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -ORT_VERSION="${ORT_VERSION:-1.22.0}" +ORT_VERSION="${ORT_VERSION:-1.23.2}" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "${SCRIPT_DIR}/onnxruntime-pins.sh" diff --git a/frontend/src-tauri/scripts/provide-macos-onnxruntime.sh b/frontend/src-tauri/scripts/provide-macos-onnxruntime.sh new file mode 100755 index 00000000..698c60f2 --- /dev/null +++ b/frontend/src-tauri/scripts/provide-macos-onnxruntime.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +set -euo pipefail + +ORT_VERSION="${ORT_VERSION:-1.23.2}" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/onnxruntime-pins.sh" + +if [ "$(uname -s)" != "Darwin" ]; then + echo "macOS ONNX Runtime provisioning must run on macOS." >&2 + exit 1 +fi + +TAURI_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +ORT_ROOT="${TAURI_DIR}/onnxruntime-macos" +ORT_DIR="${ORT_ROOT}/onnxruntime-osx-universal2-${ORT_VERSION}" +ORT_ARCHIVE="onnxruntime-osx-universal2-${ORT_VERSION}.tgz" +ORT_URL="https://github.com/microsoft/onnxruntime/releases/download/v${ORT_VERSION}/${ORT_ARCHIVE}" +ORT_DYLIB="${ORT_DIR}/lib/libonnxruntime.${ORT_VERSION}.dylib" +ORT_ARCHIVE_SHA256="$(onnxruntime_macos_universal2_archive_sha256_for_version "${ORT_VERSION}")" +ORT_DYLIB_SHA256="$(onnxruntime_macos_universal2_dylib_sha256_for_version "${ORT_VERSION}")" + +sha256_file() { + shasum -a 256 "$1" | awk '{print $1}' +} + +verify_sha256() { + local label="$1" + local path="$2" + local expected="$3" + local actual + + actual="$(sha256_file "${path}")" + if [ "${actual}" != "${expected}" ]; then + echo "${label} SHA-256 mismatch for ${path}" >&2 + echo "expected: ${expected}" >&2 + echo "actual: ${actual}" >&2 + return 1 + fi +} + +if [ ! -f "${ORT_DYLIB}" ]; then + rm -rf "${ORT_ROOT}" + mkdir -p "${ORT_ROOT}" + archive_path="${ORT_ROOT}/${ORT_ARCHIVE}" + + curl -fL --retry 5 --retry-delay 2 --retry-all-errors \ + "${ORT_URL}" \ + --output "${archive_path}" + + verify_sha256 "ONNX Runtime archive" "${archive_path}" "${ORT_ARCHIVE_SHA256}" + tar -xzf "${archive_path}" -C "${ORT_ROOT}" + rm -f "${archive_path}" +fi + +verify_sha256 "ONNX Runtime shared library" "${ORT_DYLIB}" "${ORT_DYLIB_SHA256}" + +architectures="$(lipo -archs "${ORT_DYLIB}")" +for required_architecture in arm64 x86_64; do + case " ${architectures} " in + *" ${required_architecture} "*) ;; + *) + echo "Expected a universal arm64+x86_64 ONNX Runtime dylib, got: ${architectures}" >&2 + exit 1 + ;; + esac +done + +echo "ORT_LIB_LOCATION=${ORT_DIR}" +echo "ORT_SKIP_DOWNLOAD=true" +echo "ORT_DYLIB_PATH=${ORT_DYLIB}" diff --git a/frontend/src-tauri/scripts/provide-windows-onnxruntime.sh b/frontend/src-tauri/scripts/provide-windows-onnxruntime.sh index 4fbf3142..713b5487 100755 --- a/frontend/src-tauri/scripts/provide-windows-onnxruntime.sh +++ b/frontend/src-tauri/scripts/provide-windows-onnxruntime.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -ORT_VERSION="${ORT_VERSION:-1.22.0}" +ORT_VERSION="${ORT_VERSION:-1.23.2}" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "${SCRIPT_DIR}/onnxruntime-pins.sh" diff --git a/frontend/src-tauri/scripts/run-with-desktop-onnxruntime.sh b/frontend/src-tauri/scripts/run-with-desktop-onnxruntime.sh new file mode 100755 index 00000000..b27710f9 --- /dev/null +++ b/frontend/src-tauri/scripts/run-with-desktop-onnxruntime.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [ "$#" -eq 0 ]; then + echo "Usage: $0 [args...]" >&2 + exit 2 +fi + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +case "$(uname -s)" in + Darwin) + provider="${SCRIPT_DIR}/provide-macos-onnxruntime.sh" + ;; + Linux) + provider="${SCRIPT_DIR}/provide-linux-onnxruntime.sh" + ;; + MINGW* | MSYS* | CYGWIN*) + provider="${SCRIPT_DIR}/provide-windows-onnxruntime.sh" + ;; + *) + echo "Unsupported desktop platform for ONNX Runtime provisioning: $(uname -s)" >&2 + exit 1 + ;; +esac + +ort_env="$("${provider}")" +ort_dylib_path="$(printf '%s\n' "${ort_env}" | sed -n 's/^ORT_DYLIB_PATH=//p')" +if [ -z "${ort_dylib_path}" ]; then + echo "The ONNX Runtime provider did not return ORT_DYLIB_PATH." >&2 + exit 1 +fi + +export ORT_DYLIB_PATH="${ort_dylib_path}" +exec "$@" diff --git a/frontend/src-tauri/scripts/setup-ios-cargo-config.sh b/frontend/src-tauri/scripts/setup-ios-cargo-config.sh index 4955b787..cf0bb780 100755 --- a/frontend/src-tauri/scripts/setup-ios-cargo-config.sh +++ b/frontend/src-tauri/scripts/setup-ios-cargo-config.sh @@ -59,11 +59,11 @@ CXXFLAGS_aarch64_apple_ios_sim = { value = "${PATH_PREFIX_MAP_FLAGS}", force = t [target.aarch64-apple-ios.onnxruntime] rustc-link-search = ["${XCFRAMEWORK_DIR}/ios-arm64"] -rustc-link-lib = ["static=onnxruntime", "c++"] +rustc-link-lib = ["static=onnxruntime", "c++", "framework=Foundation"] [target.aarch64-apple-ios-sim.onnxruntime] rustc-link-search = ["${XCFRAMEWORK_DIR}/ios-arm64-simulator"] -rustc-link-lib = ["static=onnxruntime", "c++"] +rustc-link-lib = ["static=onnxruntime", "c++", "framework=Foundation"] EOF echo "Created: $CONFIG_FILE" diff --git a/frontend/src-tauri/src/lib.rs b/frontend/src-tauri/src/lib.rs index e0e886d6..fda7b8fd 100644 --- a/frontend/src-tauri/src/lib.rs +++ b/frontend/src-tauri/src/lib.rs @@ -3,7 +3,9 @@ use tauri_plugin_deep_link::DeepLinkExt; #[cfg(desktop)] mod agent; +mod onnxruntime; mod pdf_extractor; +mod pdf_ocr; mod proxy; // TTS is available on desktop and iOS (not Android) #[cfg(any(desktop, target_os = "ios"))] diff --git a/frontend/src-tauri/src/onnxruntime.rs b/frontend/src-tauri/src/onnxruntime.rs new file mode 100644 index 00000000..e7788a9c --- /dev/null +++ b/frontend/src-tauri/src/onnxruntime.rs @@ -0,0 +1,80 @@ +use once_cell::sync::Lazy; +#[cfg(not(target_os = "ios"))] +use std::path::PathBuf; + +static ORT_ENVIRONMENT: Lazy> = Lazy::new(initialize); + +/// Initializes Maple's single ONNX Runtime environment before either OCR or +/// TTS creates a session. Dynamic targets share the same explicitly packaged +/// runtime; iOS keeps its existing static-library linkage. +pub fn ensure_initialized() -> Result<(), String> { + ORT_ENVIRONMENT + .as_ref() + .map(|_| ()) + .map_err(std::clone::Clone::clone) +} + +fn initialize() -> Result<(), String> { + #[cfg(not(target_os = "ios"))] + { + let path = runtime_library_path(); + let builder = ort::init_from(&path).map_err(|error| { + format!( + "Failed to load ONNX Runtime from {}: {error}", + path.display() + ) + })?; + let _ = builder.commit(); + } + + #[cfg(target_os = "ios")] + { + let _ = ort::init().commit(); + } + + Ok(()) +} + +#[cfg(not(target_os = "ios"))] +fn runtime_library_path() -> PathBuf { + if let Some(path) = std::env::var_os("ORT_DYLIB_PATH").filter(|path| !path.is_empty()) { + return path.into(); + } + + if let Ok(executable) = std::env::current_exe() { + if let Some(directory) = executable.parent() { + #[cfg(target_os = "linux")] + { + let bundled = directory.join("../lib/maple/libonnxruntime.so"); + if bundled.exists() { + return bundled; + } + } + + #[cfg(target_os = "macos")] + { + let bundled = directory.join("../Frameworks/libonnxruntime.1.23.2.dylib"); + if bundled.exists() { + return bundled; + } + } + + #[cfg(target_os = "windows")] + { + let bundled = directory.join("onnxruntime.dll"); + if bundled.exists() { + return bundled; + } + } + } + } + + #[cfg(any(target_os = "linux", target_os = "android"))] + return PathBuf::from("libonnxruntime.so"); + + #[cfg(target_os = "macos")] + return PathBuf::from("libonnxruntime.1.23.2.dylib"); + + #[cfg(target_os = "windows")] + return PathBuf::from("onnxruntime.dll"); +} diff --git a/frontend/src-tauri/src/pdf_extractor.rs b/frontend/src-tauri/src/pdf_extractor.rs index 2b652a24..4c0e2768 100644 --- a/frontend/src-tauri/src/pdf_extractor.rs +++ b/frontend/src-tauri/src/pdf_extractor.rs @@ -1,6 +1,23 @@ +use crate::pdf_ocr; use base64::{engine::general_purpose::STANDARD as BASE64, Engine}; -use pdf_extract::extract_text_from_mem; +use once_cell::sync::Lazy; +use pdf_oxide::extractors::auto::PageKind; +use pdf_oxide::ocr::OcrEngine; +use pdf_oxide::rendering::{self, RenderOptions}; +use pdf_oxide::PdfDocument; use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use tauri::AppHandle; +use tokio::sync::Semaphore; + +const MAX_DOCUMENT_BYTES: usize = 10 * 1024 * 1024; +const MAX_EXTRACTED_TEXT_BYTES: usize = MAX_DOCUMENT_BYTES; +const OCR_RENDER_MAX_DIMENSION: u32 = 2_000; +const MAX_OCR_SOURCE_IMAGE_PIXELS: u64 = 24_000_000; +const MAX_OCR_PAGE_SOURCE_PIXELS: u64 = 64_000_000; +const MAX_OCR_PAGE_IMAGES: usize = 256; +const PDF_PANIC_MESSAGE: &str = "Maple couldn't process this PDF because its parser stopped unexpectedly. The app is still running; try a different PDF."; +static PDF_JOB_SEMAPHORE: Lazy = Lazy::new(|| Semaphore::new(1)); #[derive(Debug, Serialize, Deserialize)] pub struct DocumentData { @@ -14,30 +31,49 @@ pub struct DocumentResponse { pub status: String, } +enum PdfPreflight { + Complete(String), + NeedsOcr { + native_fallback: String, + has_scanned_pages: bool, + }, +} + #[tauri::command] pub async fn extract_document_content( + app: AppHandle, + file_base64: String, + filename: String, + file_type: String, +) -> Result { + extract_document_content_impl(Some(&app), file_base64, filename, file_type).await +} + +async fn extract_document_content_impl( + app: Option<&AppHandle>, file_base64: String, filename: String, file_type: String, ) -> Result { - // Decode base64 file data + // Reject obviously oversized base64 before allocating the decoded buffer. + let max_encoded_size = MAX_DOCUMENT_BYTES.div_ceil(3) * 4 + 4; + if file_base64.len() > max_encoded_size { + return Err("Document too large (max 10MB)".to_string()); + } + let file_bytes = BASE64 .decode(&file_base64) .map_err(|e| format!("Failed to decode base64 file: {e}"))?; + if file_bytes.len() > MAX_DOCUMENT_BYTES { + return Err("Document too large (max 10MB)".to_string()); + } - let text_content = match file_type.as_str() { - "pdf" | "application/pdf" => { - // Extract text from PDF - extract_text_from_mem(&file_bytes) - .map_err(|e| format!("Failed to extract text from PDF: {e}"))? - } + let text_content = match file_type.to_ascii_lowercase().as_str() { + "pdf" | "application/pdf" => extract_pdf(app, file_bytes).await?, "txt" | "text/plain" | "md" | "text/markdown" => { - // For text files, just convert bytes to string String::from_utf8(file_bytes).map_err(|e| format!("Failed to decode text file: {e}"))? } - _ => { - return Err(format!("Unsupported file type: {file_type}")); - } + _ => return Err(format!("Unsupported file type: {file_type}")), }; Ok(DocumentResponse { @@ -49,16 +85,390 @@ pub async fn extract_document_content( }) } +async fn extract_pdf(app: Option<&AppHandle>, file_bytes: Vec) -> Result { + // PDF rendering and OCR can each use substantial memory. Serializing these + // user-initiated jobs keeps concurrent invokes from multiplying that peak, + // particularly on iOS and Android. + let _job_permit = PDF_JOB_SEMAPHORE + .acquire() + .await + .map_err(|_| "Maple's PDF processor is unavailable. Please try again.".to_string())?; + + let preflight_bytes = file_bytes.clone(); + match run_pdf_job(move || extract_native_or_request_ocr(preflight_bytes)).await? { + PdfPreflight::Complete(text) => ensure_document_has_text(text), + PdfPreflight::NeedsOcr { + native_fallback, + has_scanned_pages, + } => { + let Some(app) = app else { + if has_scanned_pages { + return Err("This scanned PDF needs Maple's on-device OCR models.".to_string()); + } + return ensure_document_has_text(native_fallback); + }; + let engine = match pdf_ocr::get_or_prepare_engine(app).await { + Ok(engine) => engine, + Err(error) if !has_scanned_pages => { + log::warn!( + "Optional PDF OCR enrichment is unavailable; using native text: {error}" + ); + return ensure_document_has_text(native_fallback); + } + Err(error) => return Err(error), + }; + match run_pdf_job(move || extract_pdf_with_ocr(file_bytes, engine)).await { + Ok(text) => ensure_document_has_text(text), + Err(error) if !has_scanned_pages => { + log::warn!("Optional PDF OCR enrichment failed; using native text: {error}"); + ensure_document_has_text(native_fallback) + } + Err(error) => Err(error), + } + } + } +} + +async fn run_pdf_job(operation: F) -> Result +where + T: Send + 'static, + F: FnOnce() -> Result + Send + 'static, +{ + match tokio::task::spawn_blocking(operation).await { + Ok(result) => result, + Err(error) if error.is_panic() => { + log::error!("PDF processing panicked inside its isolated worker: {error}"); + Err(PDF_PANIC_MESSAGE.to_string()) + } + Err(error) => { + log::error!("PDF processing worker could not complete: {error}"); + Err("Maple couldn't finish processing this PDF. Please try again.".to_string()) + } + } +} + +fn open_pdf(file_bytes: Vec) -> Result { + let document = PdfDocument::from_bytes(file_bytes) + .map_err(|e| format!("Maple couldn't read this PDF: {e}"))?; + if document.is_encrypted() && !document.is_authenticated() { + return Err( + "This PDF is password-protected. Maple cannot read protected PDFs yet.".to_string(), + ); + } + Ok(document) +} + +fn extract_native_or_request_ocr(file_bytes: Vec) -> Result { + let document = open_pdf(file_bytes)?; + let page_count = document + .page_count() + .map_err(|e| format!("Maple couldn't read the PDF's page list: {e}"))?; + + let mut pages = Vec::with_capacity(page_count); + let mut extracted_bytes = 0; + let mut needs_ocr = false; + let mut has_scanned_pages = false; + for page in 0..page_count { + let classification = document + .classify_page(page) + .map_err(|e| format!("Maple couldn't inspect PDF page {}: {e}", page + 1))?; + let text = match classification.kind { + PageKind::TextLayer => document + .extract_text(page) + .map_err(|e| format!("Maple couldn't extract PDF page {}: {e}", page + 1))?, + PageKind::Empty => String::new(), + PageKind::Scanned => { + needs_ocr = true; + has_scanned_pages = true; + extract_native_best_effort(&document, page) + } + PageKind::ImageText | PageKind::Mixed => { + needs_ocr = true; + extract_native_best_effort(&document, page) + } + _ => { + return Err(format!( + "PDF page {} uses a page type this Maple version does not support.", + page + 1 + )); + } + }; + push_page_with_budget(&mut pages, &mut extracted_bytes, text)?; + } + + let native_text = join_pages(pages); + if needs_ocr { + Ok(PdfPreflight::NeedsOcr { + native_fallback: native_text, + has_scanned_pages, + }) + } else { + Ok(PdfPreflight::Complete(native_text)) + } +} + +fn extract_pdf_with_ocr(file_bytes: Vec, engine: Arc) -> Result { + let document = open_pdf(file_bytes)?; + let page_count = document + .page_count() + .map_err(|e| format!("Maple couldn't read the PDF's page list: {e}"))?; + let mut pages = Vec::with_capacity(page_count); + let mut extracted_bytes = 0; + + for page in 0..page_count { + let classification = document + .classify_page(page) + .map_err(|e| format!("Maple couldn't inspect PDF page {}: {e}", page + 1))?; + let text = match classification.kind { + PageKind::TextLayer => document + .extract_text(page) + .map_err(|e| format!("Maple couldn't extract PDF page {}: {e}", page + 1))?, + PageKind::Empty => String::new(), + PageKind::Scanned => { + let native = extract_native_best_effort(&document, page); + match ocr_page(&document, page, &engine) { + Ok(fragments) => merge_native_and_ocr(&native, &fragments), + Err(error) => { + log::warn!("Required OCR failed on PDF page {}: {error}", page + 1); + return Err(error); + } + } + } + PageKind::ImageText | PageKind::Mixed => { + let native_result = document + .extract_text(page) + .map_err(|e| format!("Maple couldn't extract PDF page {}: {e}", page + 1)); + let ocr_result = ocr_page(&document, page, &engine); + match (native_result, ocr_result) { + (Ok(native), Ok(fragments)) => merge_native_and_ocr(&native, &fragments), + (Ok(native), Err(error)) => { + log::warn!( + "Optional OCR enrichment failed on PDF page {}: {error}", + page + 1 + ); + native + } + (Err(native_error), Ok(fragments)) if !fragments.is_empty() => { + log::warn!( + "Native extraction failed on PDF page {}; using OCR: {native_error}", + page + 1 + ); + merge_native_and_ocr("", &fragments) + } + (Err(native_error), Ok(_)) => return Err(native_error), + (Err(native_error), Err(ocr_error)) => { + return Err(format!( + "{native_error}; on-device OCR also failed: {ocr_error}" + )); + } + } + } + _ => { + return Err(format!( + "PDF page {} uses a page type this Maple version does not support.", + page + 1 + )); + } + }; + push_page_with_budget(&mut pages, &mut extracted_bytes, text)?; + } + + Ok(join_pages(pages)) +} + +fn ocr_page( + document: &PdfDocument, + page: usize, + engine: &OcrEngine, +) -> Result, String> { + validate_ocr_page_resources(document, page)?; + + // Always OCR one bounded rendering of the complete page. This captures + // tiled scans, inline images, vector text, and multiple image regions + // without PDFOxide's eager all-image decode or largest-image truncation. + let rendered = rendering::render_page_fit( + document, + page, + OCR_RENDER_MAX_DIMENSION, + OCR_RENDER_MAX_DIMENSION, + &RenderOptions::default().as_raw(), + ) + .map_err(|e| format!("Maple couldn't render PDF page {} for OCR: {e}", page + 1))?; + let rgba = image::RgbaImage::from_raw(rendered.width, rendered.height, rendered.data) + .ok_or_else(|| format!("Maple couldn't decode PDF page {} for OCR.", page + 1))?; + let image = image::DynamicImage::ImageRgba8(rgba); + let output = engine + .ocr_image(&image) + .map_err(|e| format!("On-device OCR failed on PDF page {}: {e}", page + 1))?; + + let mut spans = output.spans; + spans.sort_by(|left, right| { + const Y_BAND: f32 = 10.0; + let left_band = (left.polygon[0][1] / Y_BAND).round() as i64; + let right_band = (right.polygon[0][1] / Y_BAND).round() as i64; + left_band + .cmp(&right_band) + .then_with(|| left.polygon[0][0].total_cmp(&right.polygon[0][0])) + .then_with(|| left.polygon[0][1].total_cmp(&right.polygon[0][1])) + }); + Ok(spans + .into_iter() + .map(|span| span.text.trim().to_string()) + .filter(|text| !text.is_empty()) + .collect()) +} + +fn validate_ocr_page_resources(document: &PdfDocument, page: usize) -> Result<(), String> { + let handles = document.page_image_handles(page).map_err(|e| { + format!( + "Maple couldn't inspect images on PDF page {}: {e}", + page + 1 + ) + })?; + validate_ocr_source_budget( + page, + handles + .iter() + .map(|image| (image.width, image.height, image.byte_size_compressed)), + ) +} + +fn validate_ocr_source_budget( + page: usize, + images: impl IntoIterator, +) -> Result<(), String> { + let mut count = 0_usize; + let mut total_pixels = 0_u64; + for (width, height, compressed_bytes) in images { + count += 1; + let pixels = u64::from(width) + .checked_mul(u64::from(height)) + .ok_or_else(|| safe_ocr_complexity_error(page))?; + total_pixels = total_pixels + .checked_add(pixels) + .ok_or_else(|| safe_ocr_complexity_error(page))?; + if count > MAX_OCR_PAGE_IMAGES + || pixels > MAX_OCR_SOURCE_IMAGE_PIXELS + || total_pixels > MAX_OCR_PAGE_SOURCE_PIXELS + || compressed_bytes > MAX_DOCUMENT_BYTES as u64 + { + return Err(safe_ocr_complexity_error(page)); + } + } + Ok(()) +} + +fn safe_ocr_complexity_error(page: usize) -> String { + format!( + "PDF page {} is too complex for Maple to OCR safely. Try a lower-resolution PDF.", + page + 1 + ) +} + +fn extract_native_best_effort(document: &PdfDocument, page: usize) -> String { + document.extract_text(page).unwrap_or_else(|error| { + log::warn!( + "Native text extraction failed on OCR-routed PDF page {}: {error}", + page + 1 + ); + String::new() + }) +} + +// Compare individual OCR detection spans, rather than the engine's single +// space-joined page string, so one novel image caption does not duplicate an +// otherwise native-readable page. +fn merge_native_and_ocr(native: &str, ocr_fragments: &[String]) -> String { + let native_trimmed = native.trim_end(); + if ocr_fragments.is_empty() { + return native.to_string(); + } + if native_trimmed.trim().is_empty() { + return ocr_fragments.join("\n"); + } + + let normalize = |value: &str| { + value + .split_whitespace() + .collect::>() + .join(" ") + .to_lowercase() + }; + let native_normalized = normalize(native_trimmed); + let mut extras: Vec<&str> = Vec::new(); + for fragment in ocr_fragments { + let fragment = fragment.trim(); + let normalized = normalize(fragment); + if normalized.is_empty() + || native_normalized.contains(&normalized) + || extras.iter().any(|extra| normalize(extra) == normalized) + { + continue; + } + extras.push(fragment); + } + + if extras.is_empty() { + native.to_string() + } else { + format!("{native_trimmed}\n{}", extras.join("\n")) + } +} + +fn push_page_with_budget( + pages: &mut Vec, + extracted_bytes: &mut usize, + text: String, +) -> Result<(), String> { + *extracted_bytes = extracted_bytes + .checked_add(text.len().saturating_add(2)) + .ok_or_else(extracted_text_too_large)?; + if *extracted_bytes > MAX_EXTRACTED_TEXT_BYTES { + return Err(extracted_text_too_large()); + } + pages.push(text); + Ok(()) +} + +fn extracted_text_too_large() -> String { + "The extracted text from this PDF is too large for Maple (max 10MB).".to_string() +} + +fn join_pages(pages: Vec) -> String { + pages + .into_iter() + .map(|page| page.trim().to_string()) + .collect::>() + .join("\n\n") + .trim() + .to_string() +} + +fn ensure_document_has_text(text: String) -> Result { + if text.trim().is_empty() { + Err("This PDF does not contain text Maple can read.".to_string()) + } else { + Ok(text) + } +} + #[cfg(test)] mod tests { - use super::extract_document_content; + use super::{ + extract_document_content_impl, extract_pdf_with_ocr, merge_native_and_ocr, run_pdf_job, + validate_ocr_source_budget, PDF_PANIC_MESSAGE, + }; use base64::{engine::general_purpose::STANDARD as BASE64, Engine}; + use pdf_oxide::ocr::{OcrConfig, OcrEngine}; + use std::path::PathBuf; + use std::sync::Arc; #[tokio::test] async fn extract_document_content_text_plain_success() { let file_base64 = BASE64.encode(b"Hello, Maple!"); - let resp = extract_document_content( + let resp = extract_document_content_impl( + None, file_base64, "hello.txt".to_string(), "text/plain".to_string(), @@ -72,11 +482,95 @@ mod tests { } #[tokio::test] - async fn extract_document_content_rejects_unsupported_file_type() { - let file_base64 = BASE64.encode(b"whatever"); + async fn extracts_native_pdf_without_ocr_models() { + let file_base64 = BASE64.encode(native_text_pdf("MAPLE NATIVE PDF")); - let err = extract_document_content( + let resp = extract_document_content_impl( + None, file_base64, + "native.pdf".to_string(), + "application/pdf".to_string(), + ) + .await + .expect("native PDF should not require OCR models"); + + assert!(resp.document.text_content.contains("MAPLE NATIVE PDF")); + } + + #[tokio::test] + async fn native_readable_hybrid_pdf_does_not_require_ocr_models() { + let pdf = native_text_and_image_pdf("MAPLE HYBRID PDF KEEPS ITS NATIVE TEXT OFFLINE"); + let document = pdf_oxide::PdfDocument::from_bytes(pdf.clone()).expect("open hybrid PDF"); + let classification = document.classify_page(0).expect("classify hybrid PDF"); + assert!(matches!( + classification.kind, + pdf_oxide::extractors::auto::PageKind::ImageText + | pdf_oxide::extractors::auto::PageKind::Mixed + )); + + let response = extract_document_content_impl( + None, + BASE64.encode(pdf), + "hybrid.pdf".to_string(), + "application/pdf".to_string(), + ) + .await + .expect("native-readable hybrid PDF should work without OCR models"); + + assert!(response.document.text_content.contains("MAPLE HYBRID PDF")); + } + + #[tokio::test] + async fn invalid_pdf_returns_a_normal_error() { + let error = extract_document_content_impl( + None, + BASE64.encode(b"not a PDF"), + "invalid.pdf".to_string(), + "pdf".to_string(), + ) + .await + .expect_err("invalid PDF should fail"); + + assert!(error.contains("couldn't read this PDF"), "{error}"); + } + + #[tokio::test] + async fn type4_tint_transform_reproducer_returns_without_a_parser_panic() { + // Minimal public reproducer for the pdf-extract FunctionType 4 crash + // that prompted this migration. It contains no text, so a normal + // empty-document error is the expected result. + const TYPE4_PDF: &str = "JVBERi0xLjcKMSAwIG9iago8PC9UeXBlL1BhZ2VzL0tpZHNbNCAwIFJdL0NvdW50IDE+PgplbmRvYmoKMiAwIG9iago8PC9GdW5jdGlvblR5cGUgNC9Eb21haW5bMCAxXS9SYW5nZVswIDEgMCAxIDAgMV0vTGVuZ3RoIDExPj5zdHJlYW0KeyBkdXAgZHVwIH0KZW5kc3RyZWFtIAplbmRvYmoKMyAwIG9iago8PC9MZW5ndGggOD4+c3RyZWFtCi9DUzEgY3MKCmVuZHN0cmVhbSAKZW5kb2JqCjQgMCBvYmoKPDwvVHlwZS9QYWdlL1BhcmVudCAxIDAgUi9NZWRpYUJveFswIDAgMjAwIDIwMF0vUmVzb3VyY2VzPDwvQ29sb3JTcGFjZTw8L0NTMVsvU2VwYXJhdGlvbi9TcG90L0RldmljZVJHQiAyIDAgUl0+Pj4+L0NvbnRlbnRzIDMgMCBSPj4KZW5kb2JqCjUgMCBvYmoKPDwvVHlwZS9DYXRhbG9nL1BhZ2VzIDEgMCBSPj4KZW5kb2JqCjYgMCBvYmoKPDwvUm9vdCA1IDAgUi9UeXBlL1hSZWYvU2l6ZSA3L1dbMSA0IDJdL0luZGV4WzEgNl0vTGVuZ3RoIDQyPj5zdHJlYW0KAQAAAAkAAAEAAAA8AAABAAAApQAAAQAAANwAAAEAAAFvAAABAAABnAAACmVuZHN0cmVhbSAKZW5kb2JqCgpzdGFydHhyZWYKNDEyCiUlRU9G"; + + let error = extract_document_content_impl( + None, + TYPE4_PDF.to_string(), + "type4.pdf".to_string(), + "application/pdf".to_string(), + ) + .await + .expect_err("text-free reproducer should return a normal error"); + + assert_ne!(error, PDF_PANIC_MESSAGE); + } + + #[tokio::test] + async fn parser_panic_is_contained_and_a_later_job_still_runs() { + let panic_error = run_pdf_job::<(), _>(|| panic!("synthetic parser panic")) + .await + .expect_err("panic should become an ordinary error"); + assert_eq!(panic_error, PDF_PANIC_MESSAGE); + + let result = run_pdf_job(|| Ok::<_, String>("worker recovered".to_string())) + .await + .expect("a later worker should still run"); + assert_eq!(result, "worker recovered"); + } + + #[tokio::test] + async fn extract_document_content_rejects_unsupported_file_type() { + let err = extract_document_content_impl( + None, + BASE64.encode(b"whatever"), "file.bin".to_string(), "application/octet-stream".to_string(), ) @@ -91,7 +585,8 @@ mod tests { #[tokio::test] async fn extract_document_content_rejects_invalid_base64() { - let err = extract_document_content( + let err = extract_document_content_impl( + None, "not base64".to_string(), "file.txt".to_string(), "txt".to_string(), @@ -107,15 +602,127 @@ mod tests { #[tokio::test] async fn extract_document_content_rejects_invalid_utf8_for_text_files() { - let file_base64 = BASE64.encode([0xff, 0xfe, 0xfd]); - - let err = extract_document_content(file_base64, "bad.txt".to_string(), "txt".to_string()) - .await - .expect_err("expected invalid utf-8 to error"); + let err = extract_document_content_impl( + None, + BASE64.encode([0xff, 0xfe, 0xfd]), + "bad.txt".to_string(), + "txt".to_string(), + ) + .await + .expect_err("expected invalid utf-8 to error"); assert!( err.contains("Failed to decode text file"), "unexpected error: {err}" ); } + + #[test] + fn hybrid_merge_deduplicates_native_lines_and_keeps_image_text() { + assert_eq!( + merge_native_and_ocr( + "CONFIDENTIAL QUARTERLY MEMO 2026\nNative line", + &[ + "confidential quarterly memo 2026".to_string(), + "IMAGE CAPTION".to_string(), + ] + ), + "CONFIDENTIAL QUARTERLY MEMO 2026\nNative line\nIMAGE CAPTION" + ); + } + + #[test] + fn ocr_source_budget_rejects_unsafe_page_allocations() { + assert!(validate_ocr_source_budget(0, [(4_000, 6_000, 1_000)]).is_ok()); + assert!(validate_ocr_source_budget(0, [(6_000, 6_000, 1_000)]).is_err()); + assert!(validate_ocr_source_budget( + 0, + [ + (4_000, 4_000, 1_000), + (4_000, 4_000, 1_000), + (4_000, 4_000, 1_000), + (4_000, 4_000, 1_000), + (1, 1, 1_000) + ] + ) + .is_err()); + } + + #[tokio::test] + #[ignore = "set MAPLE_OCR_TEST_PDF and MAPLE_OCR_MODEL_DIR to run model-backed OCR"] + async fn extracts_scanned_pdf_with_real_ocr_models() { + let pdf_path = PathBuf::from( + std::env::var("MAPLE_OCR_TEST_PDF").expect("MAPLE_OCR_TEST_PDF is required"), + ); + let model_dir = PathBuf::from( + std::env::var("MAPLE_OCR_MODEL_DIR").expect("MAPLE_OCR_MODEL_DIR is required"), + ); + let pdf = std::fs::read(pdf_path).expect("read OCR PDF fixture"); + crate::onnxruntime::ensure_initialized().expect("initialize Maple's ONNX Runtime"); + let engine = OcrEngine::new( + model_dir.join("det.onnx"), + model_dir.join("rec.onnx"), + model_dir.join("en_dict.txt"), + OcrConfig::default(), + ) + .map(Arc::new) + .expect("load OCR models"); + + let text = run_pdf_job(move || extract_pdf_with_ocr(pdf, engine)) + .await + .expect("OCR extraction should succeed"); + let uppercase = text.to_uppercase(); + assert!(!text.trim().is_empty()); + assert!( + uppercase.contains("OCR") || uppercase.contains("TEST") || uppercase.contains("HELLO"), + "unexpected OCR output: {text:?}" + ); + } + + fn native_text_pdf(text: &str) -> Vec { + let content = format!("BT /F1 18 Tf 72 720 Td ({text}) Tj ET"); + build_pdf(&[ + "<< /Type /Catalog /Pages 2 0 R >>".to_string(), + "<< /Type /Pages /Kids [3 0 R] /Count 1 >>".to_string(), + "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R >>".to_string(), + "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>".to_string(), + format!("<< /Length {} >>\nstream\n{content}\nendstream", content.len()), + ]) + } + + fn native_text_and_image_pdf(text: &str) -> Vec { + let content = + format!("q 200 0 0 200 72 400 cm /Im1 Do Q BT /F1 18 Tf 72 720 Td ({text}) Tj ET"); + build_pdf(&[ + "<< /Type /Catalog /Pages 2 0 R >>".to_string(), + "<< /Type /Pages /Kids [3 0 R] /Count 1 >>".to_string(), + "<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Resources << /Font << /F1 4 0 R >> /XObject << /Im1 6 0 R >> >> /Contents 5 0 R >>".to_string(), + "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>".to_string(), + format!("<< /Length {} >>\nstream\n{content}\nendstream", content.len()), + "<< /Type /XObject /Subtype /Image /Width 300 /Height 300 /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /ASCIIHexDecode /Length 7 >>\nstream\nFF0000>\nendstream".to_string(), + ]) + } + + fn build_pdf(objects: &[String]) -> Vec { + let mut pdf = b"%PDF-1.4\n".to_vec(); + let mut offsets = Vec::new(); + for (index, object) in objects.iter().enumerate() { + offsets.push(pdf.len()); + pdf.extend_from_slice(format!("{} 0 obj\n{object}\nendobj\n", index + 1).as_bytes()); + } + let xref_offset = pdf.len(); + pdf.extend_from_slice(format!("xref\n0 {}\n", objects.len() + 1).as_bytes()); + pdf.extend_from_slice(b"0000000000 65535 f \n"); + for offset in offsets { + pdf.extend_from_slice(format!("{offset:010} 00000 n \n").as_bytes()); + } + pdf.extend_from_slice( + format!( + "trailer\n<< /Size {} /Root 1 0 R >>\nstartxref\n{xref_offset}\n%%EOF\n", + objects.len() + 1 + ) + .as_bytes(), + ); + pdf + } } diff --git a/frontend/src-tauri/src/pdf_ocr.rs b/frontend/src-tauri/src/pdf_ocr.rs new file mode 100644 index 00000000..53a59e2f --- /dev/null +++ b/frontend/src-tauri/src/pdf_ocr.rs @@ -0,0 +1,325 @@ +use futures_util::StreamExt; +use once_cell::sync::{Lazy, OnceCell}; +use pdf_oxide::ocr::{OcrConfig, OcrEngine}; +use serde::Serialize; +use sha2::{Digest, Sha256}; +use std::fs::{self, File}; +use std::io::{Read, Write}; +use std::path::Path; +use std::sync::Arc; +use std::time::Duration; +use tauri::{AppHandle, Emitter, Manager}; + +const OCR_MODEL_PACK_VERSION: &str = "paddleocr-en-v1"; +const TOTAL_MODEL_SIZE: u64 = 12_577_821; + +struct ModelFile { + name: &'static str, + url: &'static str, + size: u64, + sha256: &'static str, +} + +// These immutable revisions are intentionally independent from PDFOxide's +// mutable `resolve/main` model downloader. See docs/pdf-ocr.md. +const MODEL_FILES: &[ModelFile] = &[ + ModelFile { + name: "det.onnx", + url: "https://huggingface.co/SWHL/RapidOCR/resolve/1cfba2e90fc938db55889873735088de210cc173/PP-OCRv4/ch_PP-OCRv4_det_infer.onnx", + size: 4_745_517, + sha256: "d2a7720d45a54257208b1e13e36a8479894cb74155a5efe29462512d42f49da9", + }, + ModelFile { + name: "rec.onnx", + url: "https://huggingface.co/monkt/paddleocr-onnx/resolve/7b02d0a30a07ba2b92ad1ff5a8941ae2c633de65/languages/english/rec.onnx", + size: 7_830_888, + sha256: "4e16deb22c4da6468bdca539b2cd3c8687825538b67109177c47d359ab994cd7", + }, + ModelFile { + name: "en_dict.txt", + url: "https://huggingface.co/monkt/paddleocr-onnx/resolve/7b02d0a30a07ba2b92ad1ff5a8941ae2c633de65/languages/english/dict.txt", + size: 1_416, + sha256: "e025a66d31f327ba0c232e03f407ae8d105e1e709e7ccb3f408aa778c24e70d6", + }, +]; + +static OCR_ENGINE: OnceCell> = OnceCell::new(); +static OCR_SETUP_LOCK: Lazy> = Lazy::new(|| tokio::sync::Mutex::new(())); + +#[derive(Clone, Serialize)] +struct OcrDownloadProgress { + downloaded: u64, + total: u64, + file_name: String, + percent: f64, +} + +pub async fn get_or_prepare_engine(app: &AppHandle) -> Result, String> { + if let Some(engine) = OCR_ENGINE.get() { + return Ok(engine.clone()); + } + + let _setup_guard = OCR_SETUP_LOCK.lock().await; + if let Some(engine) = OCR_ENGINE.get() { + return Ok(engine.clone()); + } + + let models_dir = app + .path() + .app_cache_dir() + .map_err(|e| format!("Failed to locate Maple's OCR cache: {e}"))? + .join("ocr") + .join("models") + .join(OCR_MODEL_PACK_VERSION); + + ensure_models(app, &models_dir).await.map_err(|e| { + log::error!("OCR model setup failed: {e}"); + format!( + "Maple couldn't download its on-device OCR models. Check your connection and try the PDF again. ({e})" + ) + })?; + + crate::onnxruntime::ensure_initialized().map_err(|error| { + log::error!("ONNX Runtime setup failed before OCR initialization: {error}"); + format!("Maple couldn't start its on-device OCR engine. Try the PDF again. ({error})") + })?; + + let det_path = models_dir.join("det.onnx"); + let rec_path = models_dir.join("rec.onnx"); + let dict_path = models_dir.join("en_dict.txt"); + let engine = tokio::task::spawn_blocking(move || { + OcrEngine::new(det_path, rec_path, dict_path, OcrConfig::default()) + .map(Arc::new) + .map_err(|e| format!("Failed to initialize the on-device OCR engine: {e}")) + }) + .await + .map_err(|e| format!("The on-device OCR engine stopped unexpectedly: {e}"))??; + + // Another task cannot race this initialization because OCR_SETUP_LOCK is held. + let _ = OCR_ENGINE.set(engine.clone()); + Ok(engine) +} + +async fn ensure_models(app: &AppHandle, models_dir: &Path) -> Result<(), String> { + fs::create_dir_all(models_dir) + .map_err(|e| format!("Failed to create the OCR model cache: {e}"))?; + + let client = configure_download_tls(reqwest::Client::builder())? + .connect_timeout(Duration::from_secs(30)) + .timeout(Duration::from_secs(300)) + .build() + .map_err(|e| format!("Failed to create the OCR download client: {e}"))?; + + let mut completed = 0; + for model in MODEL_FILES { + let path = models_dir.join(model.name); + if verify_model_file(&path, model)? { + completed += model.size; + emit_progress(app, model.name, completed); + continue; + } + + if path.exists() { + fs::remove_file(&path) + .map_err(|e| format!("Failed to replace invalid {}: {e}", model.name))?; + } + + download_model(app, &client, models_dir, model, completed).await?; + completed += model.size; + } + + Ok(()) +} + +#[cfg(target_os = "android")] +fn configure_download_tls( + builder: reqwest::ClientBuilder, +) -> Result { + // Reqwest's platform verifier needs separate Kotlin/JNI initialization on + // Android. This download-only client instead uses the standard Mozilla root + // set already supported by rustls, keeping the mobile integration in Rust. + let mut roots = rustls::RootCertStore::empty(); + roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); + let provider = Arc::new(rustls::crypto::aws_lc_rs::default_provider()); + let tls = rustls::ClientConfig::builder_with_provider(provider) + .with_safe_default_protocol_versions() + .map_err(|e| format!("Failed to configure Android OCR download TLS: {e}"))? + .with_root_certificates(roots) + .with_no_client_auth(); + Ok(builder.tls_backend_preconfigured(tls)) +} + +#[cfg(not(target_os = "android"))] +fn configure_download_tls( + builder: reqwest::ClientBuilder, +) -> Result { + Ok(builder) +} + +async fn download_model( + app: &AppHandle, + client: &reqwest::Client, + models_dir: &Path, + model: &ModelFile, + already_downloaded: u64, +) -> Result<(), String> { + let final_path = models_dir.join(model.name); + let temp_path = models_dir.join(format!("{}.part", model.name)); + let _ = fs::remove_file(&temp_path); + + log::info!("Downloading pinned OCR model {}", model.name); + let result = async { + let response = client + .get(model.url) + .send() + .await + .map_err(|e| format!("Failed to download {}: {e}", model.name))?; + if !response.status().is_success() { + return Err(format!( + "Failed to download {}: HTTP {}", + model.name, + response.status() + )); + } + if let Some(length) = response.content_length() { + if length != model.size { + return Err(format!( + "Unexpected Content-Length for {}: expected {}, got {length}", + model.name, model.size + )); + } + } + + let mut file = File::create(&temp_path) + .map_err(|e| format!("Failed to create {}: {e}", model.name))?; + let mut stream = response.bytes_stream(); + let mut downloaded = 0_u64; + let mut hasher = Sha256::new(); + + while let Some(chunk) = stream.next().await { + let chunk = chunk.map_err(|e| format!("Download failed for {}: {e}", model.name))?; + downloaded = downloaded + .checked_add(chunk.len() as u64) + .ok_or_else(|| format!("Download size overflow for {}", model.name))?; + if downloaded > model.size { + return Err(format!( + "Download for {} exceeded its pinned size", + model.name + )); + } + file.write_all(&chunk) + .map_err(|e| format!("Failed to write {}: {e}", model.name))?; + hasher.update(&chunk); + emit_progress(app, model.name, already_downloaded + downloaded); + } + + if downloaded != model.size { + return Err(format!( + "Incomplete download for {}: expected {}, got {downloaded}", + model.name, model.size + )); + } + let actual_hash = format!("{:x}", hasher.finalize()); + if actual_hash != model.sha256 { + return Err(format!("Checksum verification failed for {}", model.name)); + } + + file.flush() + .map_err(|e| format!("Failed to flush {}: {e}", model.name))?; + file.sync_all() + .map_err(|e| format!("Failed to sync {}: {e}", model.name))?; + drop(file); + fs::rename(&temp_path, &final_path) + .map_err(|e| format!("Failed to finalize {}: {e}", model.name))?; + Ok(()) + } + .await; + + if result.is_err() { + let _ = fs::remove_file(&temp_path); + } + result +} + +fn verify_model_file(path: &Path, model: &ModelFile) -> Result { + let metadata = match fs::metadata(path) { + Ok(metadata) => metadata, + Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(false), + Err(error) => { + return Err(format!( + "Failed to inspect cached model {}: {error}", + model.name + )); + } + }; + if !metadata.is_file() || metadata.len() != model.size { + return Ok(false); + } + + let actual_hash = sha256_file(path) + .map_err(|e| format!("Failed to verify cached model {}: {e}", model.name))?; + Ok(actual_hash == model.sha256) +} + +fn sha256_file(path: &Path) -> Result { + let mut file = File::open(path)?; + let mut hasher = Sha256::new(); + let mut buffer = [0_u8; 64 * 1024]; + loop { + let read = file.read(&mut buffer)?; + if read == 0 { + break; + } + hasher.update(&buffer[..read]); + } + Ok(format!("{:x}", hasher.finalize())) +} + +fn emit_progress(app: &AppHandle, file_name: &str, downloaded: u64) { + let downloaded = downloaded.min(TOTAL_MODEL_SIZE); + let _ = app.emit( + "ocr-download-progress", + OcrDownloadProgress { + downloaded, + total: TOTAL_MODEL_SIZE, + file_name: file_name.to_string(), + percent: downloaded as f64 / TOTAL_MODEL_SIZE as f64 * 100.0, + }, + ); +} + +#[cfg(test)] +mod tests { + use super::{sha256_file, verify_model_file, ModelFile}; + use std::fs; + + #[test] + fn model_verification_checks_size_and_sha256() { + let dir = tempfile::tempdir().expect("temporary directory"); + let path = dir.path().join("tiny-model"); + fs::write(&path, b"maple").expect("write fixture"); + let model = ModelFile { + name: "tiny-model", + url: "https://example.invalid/tiny-model", + size: 5, + sha256: "49ead2b1066bda1e127f6ae0bd163778d08587e3e97d9ed58e8cc99972460a1c", + }; + + assert!(verify_model_file(&path, &model).expect("verify model")); + + fs::write(&path, b"Maple").expect("replace fixture"); + assert!(!verify_model_file(&path, &model).expect("reject bad hash")); + } + + #[test] + fn sha256_file_uses_lowercase_hex() { + let dir = tempfile::tempdir().expect("temporary directory"); + let path = dir.path().join("hash-input"); + fs::write(&path, b"maple").expect("write fixture"); + + assert_eq!( + sha256_file(&path).expect("hash fixture"), + "49ead2b1066bda1e127f6ae0bd163778d08587e3e97d9ed58e8cc99972460a1c" + ); + } +} diff --git a/frontend/src-tauri/src/tts.rs b/frontend/src-tauri/src/tts.rs index 2fb9d2d2..5a96ab49 100644 --- a/frontend/src-tauri/src/tts.rs +++ b/frontend/src-tauri/src/tts.rs @@ -17,33 +17,6 @@ use std::sync::Mutex; use tauri::{AppHandle, Emitter}; use unicode_normalization::UnicodeNormalization; -#[cfg(target_os = "linux")] -static ORT_ENV_INITIALIZED: Lazy> = Lazy::new(|| { - ort::init_from(onnxruntime_dylib_path()) - .commit() - .map_err(|e| e.to_string()) -}); - -#[cfg(target_os = "linux")] -fn onnxruntime_dylib_path() -> String { - if let Ok(path) = std::env::var("ORT_DYLIB_PATH") { - if !path.is_empty() { - return path; - } - } - - if let Ok(exe_path) = std::env::current_exe() { - if let Some(exe_dir) = exe_path.parent() { - let bundled_path = exe_dir.join("../lib/maple/libonnxruntime.so"); - if bundled_path.exists() { - return bundled_path.to_string_lossy().into_owned(); - } - } - } - - "libonnxruntime.so".to_string() -} - // Pre-compiled regexes for text preprocessing (compiled once, reused) static RE_BOLD: Lazy = Lazy::new(|| Regex::new(r"\*\*([^*]+)\*\*").unwrap()); static RE_BOLD2: Lazy = Lazy::new(|| Regex::new(r"__([^_]+)__").unwrap()); @@ -698,10 +671,7 @@ fn load_voice_style(models_dir: &Path) -> Result