[workspace]
members = [".", "pdf_oxide_mcp", "pdf_oxide_cli"]
exclude = ["js"]

# cargo-shear exemptions: these optional deps are referenced from `[features]`
# via `dep:X` and only link when their feature is on. cargo-shear reports them
# as "unused" because the default feature set doesn't activate them.
[workspace.metadata.cargo-shear]
ignored = [
    # Digital signatures stack (gated on `signatures` feature)
    "cms",
    "der",
    "pkcs1",
    "pkcs8",
    "rsa",
    "signature",
    "spki",
    "x509-parser",
    # ML / table detection / OCR (gated on `ml`, `table-ml`, `ocr` features)
    "imageproc",
    "linfa",
    "linfa-clustering",
    "pdfium-render",
    "tokenizers",
    "tract-onnx",
    # WASM-only runtime deps (gated on `wasm` feature)
    "getrandom_02",
    "web-sys",
]
ignored-paths = [
    # Runnable examples — not declared as [[example]] targets because they use
    # `cargo run --manifest-path examples/rust/.../Cargo.toml` pattern.
    "examples/rust/*/main.rs",
    # Placeholder modules kept disabled for future work (see src/pipeline/mod.rs)
    "src/pipeline/input_parsers/mod.rs",
    "src/structure/builder.rs",
]

# Clippy lint configuration shared by every workspace member and every target
# (lib, bin, test, example, bench). Keeps the allow-list in a single place so
# new bin targets don't need their own #![allow] attributes. Mirrors the stance
# previously declared only in src/lib.rs.
[workspace.lints.clippy]
type_complexity = "allow"
too_many_arguments = "allow"
needless_range_loop = "allow"
enum_variant_names = "allow"
wrong_self_convention = "allow"
explicit_counter_loop = "allow"
doc_overindented_list_items = "allow"
should_implement_trait = "allow"
redundant_guards = "allow"
regex_creation_in_loops = "allow"
manual_find = "allow"
match_like_matches_macro = "allow"
collapsible_match = "allow"
manual_checked_ops = "allow"

[package]
name = "pdf_oxide"
version = "0.3.50"
# MSRV — driven up from 1.82 for v0.3.38. Transitive deps pulled in
# this release push the floor to 1.88:
#   - hybrid-array 0.4.10 (via RustCrypto) → edition 2024 → 1.85
#   - image 0.25.10 / imageproc 0.26.1 → 1.87 / 1.88
#   - nalgebra 0.34.2 → 1.87
#   - libloading 0.9.0, ort 2.0.0-rc.12, ort-sys 2.0.0-rc.12 → 1.88
# 1.88 was released 2025-06-26, so this is still a reasonable floor.
rust-version = "1.88"
# Anchor patterns with a leading slash — Cargo uses gitignore-style globs, so
# a bare "README.md" matches every README.md recursively (was pulling in 20+
# subdir READMEs plus js/node_modules/*/README.md into the sdist). Leading /
# anchors to the package root.
include = [
    "/src/**/*",
    "/benches/**/*",
    "/Cargo.toml",
    "/LICENSE-MIT",
    "/LICENSE-APACHE",
    "/README.md",
    "/include/**/*",
]
edition = "2021"
authors = ["Yury Fedoseev <yfedoseev@gmail.com>"]
license = "MIT OR Apache-2.0"
description = "The fastest Rust PDF library with text extraction: 0.8ms mean, 100% pass rate on 3,830 PDFs. 5× faster than pdf_extract, 17× faster than oxidize_pdf. Extract, create, and edit PDFs."
repository = "https://github.com/yfedoseev/pdf_oxide"
homepage = "https://oxide.fyi"
documentation = "https://pdf.oxide.fyi/docs/getting-started/rust"
readme = "README.md"
keywords = ["pdf", "text-extraction", "pdf-parser", "pdf-to-markdown", "document-parser"]
categories = ["parsing", "text-processing"]

[lib]
name = "pdf_oxide"
# cdylib: Python (maturin), WASM, C# (P/Invoke), Node.js (N-API dynamic link)
# rlib: native Rust consumers
# staticlib: Go CGo (#334) and Node.js (#335) — produces libpdf_oxide.a for
# self-contained binaries with no runtime LD_LIBRARY_PATH/PATH lookups
crate-type = ["cdylib", "rlib", "staticlib"]

[lints]
workspace = true

[dependencies]
# ── cargo-geiger audit (last run: 2026-04-26) ─────────────────────────────────
# pdf_oxide itself: 91.4% fn coverage, 96.3% expr coverage. Remaining unsafe is
# in ffi.rs (C ABI boundary), python.rs/wasm.rs (PyO3/wasm-bindgen internals),
# and font/image parsers (pointer arithmetic for binary format decoding).
#
# Dependencies marked ! (unsafe present) are accepted:
#   aes/sha2/md-5/cbc  — RustCrypto; SIMD intrinsics, formally audited
#   brotli             — C-derived decompressor; unavoidable unsafe
#   bytemuck           — safe-transmute library; unsafe is its raison d'être
#   bytes              — Buf/BufMut ring buffer; unsafe for performance
#   byteorder          — binary parsing; minimal, well-audited unsafe
#   chrono             — OS timezone lookup; syscall boundary
#   encoding_rs        — Mozilla SIMD text decode; audited
#   getrandom          — OS RNG; syscall boundary
#   hybrid-array/zerocopy — zero-copy type aliasing; unsafe by design
#   libc               — OS interface crate; entirely unsafe by design
#   smallvec           — stack alloc optimisation; well-audited
# ─────────────────────────────────────────────────────────────────────────────

# Core dependencies
thiserror = "2.0"
log = "0.4"
libc = "0.2"        # malloc/free for FFI byte buffers (free_bytes needs length-free dealloc)
env_logger = "0.11"
chrono = "0.4"      # Timestamp generation for benchmark results

# Parsing
nom = "8.0"
regex = "1.12"

# Data structures
bytes = "1.5"
memchr = "2"      # SIMD-accelerated byte/substring search for content stream scanning
byteorder = "1.5" # For binary parsing (TrueType cmap tables)
bitflags = "2"    # For form field flags
smallvec = "1.13" # Stack-allocated small vectors for content stream operands (#165)

# Compression/Decompression
flate2 = { version = "1.1", default-features = false, features = ["zlib-rs"] }
weezl = "0.1"                                                                  # LZW compression
brotli = "8"                                                                   # Brotli compression (PDF 2.0, ISO 32000-2:2020)

# Font parsing
ttf-parser = "0.25" # TrueType font parser for embedded font cmap tables
subsetter = "0.2"   # Binary TrueType/OpenType subsetting (Typst's; MIT/Apache). Used by writer-side
# EmbeddedFont to shrink embedded fonts to only the glyphs actually used.
taffy = { version = "0.10", default-features = false, features = [
    "std",
    "taffy_tree",
    "block_layout",
    "flexbox",
    "grid",
] }
unicode-linebreak = "0.1" # UAX #14 line-break opportunities for the inline formatter (LAYOUT-3)
# Block/flex/grid layout (Dioxus; MIT, no MPL). Used by Phase LAYOUT to position
# box tree nodes; inline formatting is ours (Phase LAYOUT inline_formatting module).
phf = { version = "0.13", features = ["macros"] } # Perfect hash maps for Adobe Glyph List
encoding_rs = "0.8"                               # Shift-JIS, EUC-JP, and other CJK encodings for predefined CMaps

# Unicode bidirectional algorithm (UAX #9) — used by both the HTML+CSS
# inline formatter (Phase LAYOUT-3) and the markdown converter's RTL
# emphasis cleanup (#377 D7).
unicode-bidi = { version = "0.3", default-features = false }

# Image processing
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "tiff"] }
jpeg-decoder = "0.3"                                                                       # Direct decoder access for CMYK JPEGs (Adobe inversion handling)
base64 = "0.22"                                                                            # For embedding images as data URIs
fax = "0.2"                                                                                # CCITT Group 3/4 decompression for scanned PDFs (more lenient with malformed EOFB)

# Encryption support
aes = "0.9"                                  # AES encryption for PDF 1.6+
cbc = "0.2"                                  # CBC mode for AES
md-5 = { version = "0.11", optional = true } # MD5 for legacy R≤4 key derivation
sha2 = "0.11"                                # SHA-256 for newer encryption
stringprep = "0.1"                           # SASLprep for Unicode PDF passwords (R>=5)

# FIPS-validated CryptoProvider backend (issue #236).
# `aws-lc-rs` is Amazon's BoringSSL fork; the `fips` feature gates it
# to NIST FIPS 140-3 algorithms only (validated 2024). Optional
# because compiling AWS-LC source adds ~5 minutes to a fresh build.
# Enable via `--features fips` (default: off, falls back to
# `RustCryptoProvider`).
aws-lc-rs = { version = "1", optional = true, features = ["fips"] }

# Digital signatures (optional)
# NOTE: der/spki/pkcs1/pkcs8/rsa/signature/cms are pinned to 0.7/0.9/0.10/2.x track.
# Dependabot PRs #469/#467/#429/#389 track the 0.8 stack upgrades; merge once CI green.
# cms 0.3 is still pre-release; upgrade cms + rsa + pkcs1 together when stable.
x509-parser = { version = "0.18", optional = true }   # X.509 certificate parsing
cms = { version = "0.2", optional = true }            # CMS/PKCS#7 signatures
rsa = { version = "0.9", optional = true }            # RSA signing
der = { version = "0.7", optional = true }            # DER encoding
spki = { version = "0.7", optional = true }           # SubjectPublicKeyInfo — matches der 0.7
pkcs1 = { version = "0.7", optional = true }          # PKCS#1 RSA format
pkcs8 = { version = "0.10", optional = true }         # PKCS#8 private keys
p12-keystore = { version = "0.2.1", optional = true } # PKCS#12 (.p12/.pfx) parsing
signature = { version = "2.2", optional = true }      # Signature traits
sha1 = { version = "0.11", optional = true }          # SHA-1 for legacy signatures
# sha2 v0.10 alias: rsa 0.9 and p256/p384 0.13 require digest 0.10 trait bounds.
# Our main codebase uses sha2 0.11 (digest 0.11). The alias lets the signatures
# module use sha2 0.10 types specifically where rsa::pss / ecdsa crate generics
# require it, without changing the rest of the codebase. See issue #420.
sha2_v10 = { package = "sha2", version = "0.10", optional = true }
p256 = { version = "0.13", optional = true }                       # ECDSA P-256 signature verification
p384 = { version = "0.13", optional = true }                       # ECDSA P-384 signature verification

# Parallel extraction
rayon = { version = "1.12", optional = true }

# ICC colour management (Firefox's qcms, pure Rust). Default features
# bring in `cmyk` + `iccv4-enabled` which cover every colour space PDF
# /ICCBased streams actually use.
qcms = { version = "0.3", optional = true }

# Math and algorithms
ndarray = { version = "0.17", optional = true, features = ["std"] }
linfa = { version = "0.8", optional = true }
linfa-clustering = { version = "0.8", optional = true }

# ML (CPU-only)
tract-onnx = { version = "0.22", optional = true }
tokenizers = { version = "0.22", optional = true, default-features = false, features = ["onig"] }

# OCR - PaddleOCR via ONNX Runtime (optional)
ort = { version = "=2.0.0-rc.11", optional = true, default-features = false, features = [
    "ndarray",
    "load-dynamic",
] }
imageproc = { version = "0.26.2", optional = true }

# Serialization
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

# XML parsing (for XMP metadata)
quick-xml = "0.39"

# Python bindings (optional)
# Using latest version to support Python 3.14+
pyo3 = { version = "0.28", features = ["abi3-py38", "generate-import-lib"], optional = true }
# Bridges Rust `log` macros to Python `logging` module (issue #280)
pyo3-log = { version = "0.13", optional = true }

# WASM support (optional)
wasm-bindgen = { version = "0.2", optional = true }
web-sys = { version = "0.3", optional = true, features = ["console"] }
js-sys = { version = "0.3", optional = true }
console_error_panic_hook = { version = "0.1", optional = true }
# Forwards Rust `log` macros to the browser console (issue #280)
console_log = { version = "1", optional = true, features = ["color"] }
getrandom = "0.4"
getrandom_02 = { package = "getrandom", version = "0.2", optional = true, features = ["js"] }
serde-wasm-bindgen = { version = "0.6", optional = true }

# Table detection - page rendering (optional)
pdfium-render = { version = "0.9", optional = true }

# Rendering (optional - for page to image conversion)
tiny-skia = { version = "0.12", optional = true }
fast_image_resize = { version = "6", optional = true }                       # SIMD-accelerated image downscaling
hayro-jbig2 = { version = "0.3", optional = true, default-features = false } # Pure-Rust JBIG2 decoder (Apache-2.0 OR MIT)
fontdb = { version = "0.23", optional = true }
rustybuzz = { version = "0.20", optional = true }

uuid = { version = "1.23", features = ["v4", "js"] }

# Barcode/QR code generation (optional)
qrcode = { version = "0.14", optional = true }
barcoders = { version = "2.0", optional = true, features = ["image"] }

# Office document creation and export via office_oxide (always required)
office_oxide = { version = "0.1.2", default-features = false }
x509-tsp = { version = "0.1", optional = true }
ureq = { version = "3", default-features = false, features = ["rustls"], optional = true }
cmpv2 = { version = "0.2", optional = true }

# `getrandom`'s `wasm_js` backend is only meaningful when targeting
# `wasm32-unknown-unknown` (browser / WASI runtimes that route entropy
# via `crypto.getRandomValues`). Pinning it here avoids dragging
# wasm-specific cfg into native dependency graphs while still keeping
# the WASM build's RNG functional.
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
getrandom = { version = "0.4", features = ["wasm_js"] }

[dev-dependencies]
criterion = "0.8"
tempfile = "3.27"
crc32fast = "1.3"
wasm-bindgen-test = "0.3"
hex-literal = "1.1.0"

[features]
default = ["icc", "legacy-crypto"]

# Enable logging for the extraction pipeline
logging = []

# FIPS 140-3 validated cryptographic backend (issue #236).
# When enabled, opt into `AwsLcProvider` (Amazon's `aws-lc-rs` with
# the `fips` feature) as an alternative to the default
# `RustCryptoProvider`. Pulls in the AWS-LC C source — fresh builds
# add ~5 minutes. The provider must still be installed at runtime
# via `pdf_oxide::crypto::set_provider(...)` in your application's
# main(); compiling with this feature alone does not change the
# default behaviour.
#
# IMPORTANT: `fips` is mutually exclusive with `legacy-crypto` (which
# is on by default). Always build FIPS without default features:
#   cargo build --no-default-features --features fips,icc
# Combining `fips` with `legacy-crypto` is a compile-time error.
fips = ["dep:aws-lc-rs", "aws-lc-rs/fips"]

# Real ICC colour management via qcms (Firefox's colour engine).
# When enabled, ICCBased colour spaces and CMYK images are converted
# using the embedded profile instead of ISO 32000-1 §10.3.5's
# additive-clamp fallback. Pure Rust, so WASM and C# AOT targets keep
# working without a C toolchain dependency.
icc = ["dep:qcms"]

# Gate MD5 key-derivation (R≤4 PDF Standard Security) and RC4 cipher
# behind this feature. Default-on so existing users keep full R≤4 read/write
# support; opt out with `default-features = false` when building for
# environments that must not load legacy cryptography.
legacy-crypto = ["dep:md-5"]

# Python bindings (v0.1.0)
python = [
    "pyo3",
    "pyo3-log",
    "rendering",
    "parallel",
    "logging",
    "signatures",
    "tsa-client",
    "barcodes",
]

# The following features are planned for v1.0 and are not yet production-ready.
# We define them here to avoid check-cfg warnings, but their dependencies are commented out.
ml = ["dep:tract-onnx", "dep:ndarray", "dep:linfa", "dep:linfa-clustering", "dep:tokenizers"]
table-ml = ["ml", "dep:pdfium-render"]
ocr = ["dep:ort", "dep:imageproc", "dep:ndarray"]
gpu = ["dep:ort", "ml"]
wasm = [
    "dep:wasm-bindgen",
    "dep:web-sys",
    "dep:js-sys",
    "dep:console_error_panic_hook",
    "dep:console_log",
    "dep:getrandom_02",
    "dep:serde-wasm-bindgen",
    "logging",
    "signatures",
    "barcodes",
]
wasm-ml = ["wasm", "ml"]

# Debug feature for span merging analysis
debug-span-merging = []

# Page rendering to images (pure Rust via tiny-skia)
rendering = ["dep:tiny-skia", "dep:fast_image_resize", "system-fonts", "dep:hayro-jbig2"]

# System font discovery (fontdb) + shaping (rustybuzz). v0.3.35 splits
# this out of `rendering` so HTML+CSS→PDF can pick it up without pulling
# in tiny-skia. Default-on for native bindings; off for WASM (no
# filesystem) and off for the bare Rust crate so library embedders opt in.
system-fonts = ["dep:fontdb", "dep:rustybuzz"]

# Digital signatures (create and verify PDF signatures)
signatures = [
    "dep:x509-parser",
    "dep:cms",
    "dep:rsa",
    "dep:der",
    "dep:spki",
    "dep:pkcs1",
    "dep:pkcs8",
    "dep:p12-keystore",
    "dep:signature",
    "dep:sha1",
    "dep:x509-tsp",
    "dep:sha2_v10",
    "dep:p256",
    "dep:p384",
]

# RFC 3161 TSA HTTP client (optional, on top of signatures). Adds ureq
# for POSTing the TimeStampReq to a Time Stamp Authority. Kept behind a
# dedicated feature so embedded / WASM / AOT C# builds don't pull in an
# HTTP stack unless the consumer actually needs it.
tsa-client = ["signatures", "dep:ureq", "dep:cmpv2"]

# Barcode/QR code generation
barcodes = ["dep:qrcode", "dep:barcoders"]

# Parallel page extraction via rayon
parallel = ["dep:rayon"]

[profile.release]
opt-level = 3
lto = true
codegen-units = 1
strip = true

# Size-optimized release (for WASM)
[profile.release-small]
inherits = "release"
opt-level = "z"
lto = true
codegen-units = 1
strip = true
panic = "abort"

[profile.bench]
opt-level = 3
lto = true

# Benchmarks
[[bench]]
name = "pdf_extraction_performance"
harness = false

[[bench]]
name = "word_boundary_benchmarks"
harness = false

[[bench]]
name = "script_detection_benchmarks"
harness = false

[[bench]]
name = "ligature_benchmarks"
harness = false

[[bench]]
name = "cjk_benchmarks"
harness = false

[[bench]]
name = "rtl_benchmarks"
harness = false

[[bench]]
name = "complex_script_benchmarks"
harness = false

[[bench]]
name = "full_pipeline_benchmarks"
harness = false

# #393 release gate: StreamingTable must stay linear at 30k rows.
# Runs 1k / 5k / 10k / 30k sizes via criterion; CI smoke job exercises
# only the 5k size (see tools/benchmark-harness for the dedicated CI
# invocation).
[[bench]]
name = "streaming_table_scaling"
harness = false

# Cross-language benchmark runner (Rust baseline for comparing against Go/C#/JS bindings)
[[bin]]
name = "rust_bench"
path = "bench/rust_bench.rs"

# ── v0.3.39 per-feature examples (examples/rust/09-new-features/) ─────────────
[[example]]
name = "showcase_barcode_svg"
path = "examples/rust/09-new-features/barcode_svg/main.rs"
required-features = ["barcodes"]

[[example]]
name = "showcase_streaming_table"
path = "examples/rust/09-new-features/streaming_table/main.rs"

[[example]]
name = "showcase_image_embedding"
path = "examples/rust/09-new-features/image_embedding/main.rs"

[[example]]
name = "showcase_pdf_ua_image"
path = "examples/rust/09-new-features/pdf_ua_image/main.rs"

[[example]]
name = "showcase_in_memory_roundtrip"
path = "examples/rust/09-new-features/in_memory_roundtrip/main.rs"

[[example]]
name = "showcase_pdfa_conversion"
path = "examples/rust/09-new-features/pdfa_conversion/main.rs"

[[example]]
name = "showcase_office_conversion"
path = "examples/rust/09-new-features/office_conversion/main.rs"

[[example]]
name = "showcase_pkcs12_signing"
path = "examples/rust/09-new-features/pkcs12_signing/main.rs"
required-features = ["signatures"]

[[example]]
name = "showcase_rfc3161_timestamp"
path = "examples/rust/09-new-features/rfc3161_timestamp/main.rs"
required-features = ["signatures"]

# ── Tutorial examples 01-08 (examples/rust/) ──────────────────────────────────
[[example]]
name = "tutorial_extract_text"
path = "examples/rust/01-extract-text/main.rs"

[[example]]
name = "tutorial_convert_formats"
path = "examples/rust/02-convert-formats/main.rs"

[[example]]
name = "tutorial_create_pdf"
path = "examples/rust/03-create-pdf/main.rs"

[[example]]
name = "tutorial_search_text"
path = "examples/rust/04-search-text/main.rs"

[[example]]
name = "tutorial_extract_structured"
path = "examples/rust/05-extract-structured/main.rs"

[[example]]
name = "tutorial_edit_document"
path = "examples/rust/06-edit-document/main.rs"

[[example]]
name = "tutorial_forms_annotations"
path = "examples/rust/07-forms-annotations/main.rs"

[[example]]
name = "tutorial_batch_processing"
path = "examples/rust/08-batch-processing/main.rs"

[package.metadata.docs.rs]
# Document with safe features only (tesseract-rs fails on docs.rs sandbox)
features = ["python", "logging", "debug-span-merging", "parallel"]
rustdoc-args = ["--cfg", "docsrs"]

# Python packaging (Maturin)
[package.metadata.maturin]
name = "pdf_oxide"
python-source = "python"
