use image::imageops::{rotate180, rotate270, rotate90};
use image::RgbImage;
use crate::layout::Region;
use crate::ocr::OcrModel;
use crate::ocr_prep::{prep_region_lines, PrepLine};
use docling_core::debug_log;
pub(crate) fn enabled() -> bool {
let raw = docling_core::env::nonempty("DOCLING_RS_OCR_ORIENTATION").unwrap_or_default();
let v = raw.trim().to_ascii_lowercase();
match v.as_str() {
"" | "auto" | "on" | "1" | "true" => true,
"off" | "0" | "false" | "none" => false,
_ => {
eprintln!(
"docling-pdf: DOCLING_RS_OCR_ORIENTATION={raw:?} is not auto|off; using auto"
);
true
}
}
}
const PROBES: usize = 6;
const UPRIGHT_CONF: f32 = 0.90;
const UPRIGHT_CHARS: usize = 20;
const OVERTURN: f32 = 1.2;
const MIN_CHARS: usize = 8;
const MIN_CONF: f32 = 0.55;
struct Score {
weighted: f32,
chars: usize,
}
impl Score {
fn mean_conf(&self) -> f32 {
if self.chars == 0 {
0.0
} else {
self.weighted / self.chars as f32
}
}
}
fn probe(img: &RgbImage, ocr: &mut OcrModel) -> Result<Score, String> {
let page = Region {
label: "text",
score: 1.0,
l: 0.0,
t: 0.0,
r: img.width() as f32,
b: img.height() as f32,
};
let (_, mut lines) = prep_region_lines(img, std::slice::from_ref(&page), 1.0);
let mut order: Vec<usize> = (0..lines.len()).collect();
order.sort_by(|&a, &b| lines[b].w.cmp(&lines[a].w).then(a.cmp(&b)));
order.truncate(PROBES);
order.sort_unstable();
let probes: Vec<PrepLine> = order.iter().rev().map(|&i| lines.swap_remove(i)).collect();
let (weighted, chars) = ocr.score_lines(&probes)?;
Ok(Score { weighted, chars })
}
pub(crate) fn detect(img: &RgbImage, ocr: &mut OcrModel) -> u16 {
let fail = |e: String| {
debug_log!("docling-pdf: orientation probe failed ({e}); assuming upright");
0
};
let s0 = match probe(img, ocr) {
Ok(s) => s,
Err(e) => return fail(e),
};
if s0.chars >= UPRIGHT_CHARS && s0.mean_conf() >= UPRIGHT_CONF {
debug_log!(
"docling-pdf: orientation 0° reads {} chars at {:.2} — upright, no probes",
s0.chars,
s0.mean_conf()
);
return 0;
}
let hypotheses: [(u16, RgbImage); 3] = [
(90, rotate270(img)),
(180, rotate180(img)),
(270, rotate90(img)),
];
debug_log!(
"docling-pdf: orientation 0°: {} chars at {:.2} (weighted {:.1})",
s0.chars,
s0.mean_conf(),
s0.weighted
);
let (mut best_deg, mut best) = (
0u16,
Score {
weighted: 0.0,
chars: 0,
},
);
for (deg, rotated) in &hypotheses {
let s = match probe(rotated, ocr) {
Ok(s) => s,
Err(e) => return fail(e),
};
debug_log!(
"docling-pdf: orientation {deg}°: {} chars at {:.2} (weighted {:.1})",
s.chars,
s.mean_conf(),
s.weighted
);
if s.weighted > best.weighted {
(best_deg, best) = (*deg, s);
}
}
if best_deg != 0
&& best.chars >= MIN_CHARS
&& best.mean_conf() >= MIN_CONF
&& best.weighted > OVERTURN * s0.weighted
{
return best_deg;
}
0
}