const MIN_CHARS: usize = 40;
const MAX_WORD_LIKE_RATIO: f32 = 0.40;
const MAX_COHERENT_CHAR_RATIO: f32 = 0.55;
pub fn is_incoherent_text(text: &str) -> bool {
let m = match TextMetrics::of(text) {
Some(m) => m,
None => return false,
};
m.word_like_ratio < MAX_WORD_LIKE_RATIO && m.coherent_char_ratio < MAX_COHERENT_CHAR_RATIO
}
struct TextMetrics {
word_like_ratio: f32,
coherent_char_ratio: f32,
}
impl TextMetrics {
fn of(text: &str) -> Option<Self> {
let chars = text.chars().filter(|c| !c.is_whitespace()).count();
if chars < MIN_CHARS {
return None;
}
let coherent = text.chars().filter(|c| is_coherent(*c)).count();
let tokens = text.split_whitespace();
let (total, word_like) = tokens.fold((0usize, 0usize), |(total, word_like), token| {
let is_word = token.chars().count() >= 2 && token.chars().any(is_coherent);
(total + 1, word_like + usize::from(is_word))
});
if total == 0 {
return None;
}
Some(Self {
word_like_ratio: word_like as f32 / total as f32,
coherent_char_ratio: coherent as f32 / chars as f32,
})
}
}
fn is_coherent(c: char) -> bool {
if !c.is_alphabetic() && !c.is_numeric() {
return false;
}
let cp = c as u32;
!matches!(cp,
0x1100..=0x11FF | 0x3130..=0x318F | 0xFFA0..=0xFFDC | 0x2E80..=0x2EFF | 0x2F00..=0x2FDF )
}
#[cfg(test)]
mod tests {
use super::*;
const OCR_GARBAGE: &str = "검 ,φ 끄 Φ ¸ ㅓ Φ Φ ,φ ∽ ㄱ υ Φ σ Φ ' OΦ ⊃ O::ⅱ OΦ \
° – ° =→ ↔ :。 , ㅂ ¸ ¸ :'Φ φ 0、 0ㄲ – ∽ 0 '呂 ㅒ 句 ㄱ";
const KOREAN_PROSE: &str = "이 문서는 제품 설치와 운영 절차를 설명한다. 설치 전에 \
시스템 요구사항을 확인하고, 필요한 패키지를 미리 준비한다. \
각 단계는 순서대로 수행해야 한다.";
const ENGLISH_PROSE: &str = "The parser resolves each character code through the \
font's CMap before layout analysis runs. When no CMap \
is available the text is dropped rather than guessed.";
#[test]
fn flags_ocr_garbage() {
assert!(is_incoherent_text(OCR_GARBAGE));
}
#[test]
fn accepts_prose() {
assert!(!is_incoherent_text(KOREAN_PROSE));
assert!(!is_incoherent_text(ENGLISH_PROSE));
}
#[test]
fn accepts_dimension_labels() {
let labels = "700A 1K 32EA SS275 SOFF 1200 x 800 mm t=6 SCALE 1:50 REV B \
2024-08-12 DWG No. 1927";
assert!(!is_incoherent_text(labels));
}
#[test]
fn accepts_scripts_beyond_the_tuning_corpus() {
let hindi = "यह दस्तावेज़ स्थापना और संचालन प्रक्रिया का वर्णन करता है। \
स्थापना से पहले सिस्टम आवश्यकताओं की जाँच करें।";
let greek = "Το έγγραφο περιγράφει τη διαδικασία εγκατάστασης και λειτουργίας \
του προϊόντος. Ελέγξτε πρώτα τις απαιτήσεις του συστήματος.";
assert!(!is_incoherent_text(hindi));
assert!(!is_incoherent_text(greek));
}
#[test]
fn ignores_text_too_short_to_judge() {
assert!(!is_incoherent_text("φ ∽ ㄱ υ Φ"));
assert!(!is_incoherent_text(""));
}
}