use insta::assert_snapshot;
use ocr::core::*;
use ocr::lang::{CJKLanguage, CJKProcessor as LangCJKProcessor};
use ocr::recognition::{ModelConfig, *};
use serde::{Deserialize, Serialize};
use serde_json;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum UnicodeCategory {
Latin,
Greek,
Cyrillic,
Arabic,
Hebrew,
CJKUnifiedIdeographs,
Hiragana,
Katakana,
HangulSyllables,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ScriptType {
Latin,
Chinese,
Japanese,
Korean,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ReadingOrder {
LeftToRight,
RightToLeft,
TopToBottom,
}
#[test]
fn test_cjk_character_detection_snapshot() {
let processor = LangCJKProcessor::new();
let test_cases = vec![
('中', "Chinese character"),
('ひ', "Japanese Hiragana"),
('カ', "Japanese Katakana"),
('한', "Korean Hangul"),
('A', "Latin character"),
('1', "Digit"),
('!', "Punctuation"),
];
let mut results = Vec::new();
for (char, description) in test_cases {
let is_cjk = LangCJKProcessor::is_cjk_character(char);
let is_chinese = LangCJKProcessor::is_cjk_character(char)
&& char as u32 >= 0x4E00
&& char as u32 <= 0x9FFF;
let is_japanese = LangCJKProcessor::is_cjk_character(char)
&& (char as u32 >= 0x3040 && char as u32 <= 0x309F
|| char as u32 >= 0x30A0 && char as u32 <= 0x30FF);
let is_korean = LangCJKProcessor::is_cjk_character(char)
&& char as u32 >= 0xAC00
&& char as u32 <= 0xD7AF;
let unicode_category = LangCJKProcessor::get_character_category(char);
let script_type = "CJK";
results.push(format!(
"Character: {} ({})\n CJK: {}\n Chinese: {}\n Japanese: {}\n Korean: {}\n Unicode Category: {:?}\n Script Type: {:?}\n",
char, description, is_cjk, is_chinese, is_japanese, is_korean, unicode_category, script_type
));
}
let output = results.join("\n");
assert_snapshot!(output);
}
#[test]
fn test_cjk_language_detection_snapshot() {
let processor = LangCJKProcessor::new();
let test_texts = vec![
"Hello World",
"中文测试",
"ひらがなカタカナ",
"한글테스트",
"Hello 世界",
"こんにちは World",
"안녕하세요 World",
"Mixed 中文 and English",
"日本語とEnglish混合",
"한국어와English混合",
];
let mut results = Vec::new();
for text in test_texts {
let scores = processor.detect_cjk_language(text);
let mut score_strings: Vec<String> = scores
.iter()
.map(|(lang, score)| format!("{:?}: {:.3}", lang, score))
.collect();
score_strings.sort();
results.push(format!(
"Text: {}\nLanguage scores: {}\n",
text,
score_strings.join(", ")
));
}
let output = results.join("\n");
assert_snapshot!(output);
}
#[test]
fn test_cjk_text_segmentation_snapshot() {
let processor = LangCJKProcessor::new();
let test_cases = vec![
("中文测试", CJKLanguage::ChineseSimplified),
("ひらがなカタカナ", CJKLanguage::Japanese),
("한글테스트", CJKLanguage::Korean),
("Hello World", CJKLanguage::ChineseSimplified), ];
let mut results = Vec::new();
for (text, language) in test_cases {
let result = processor.segment_text(text, language).unwrap();
let mut segment_strings: Vec<String> = result
.segments
.iter()
.map(|seg| {
format!(
"Text: '{}', Category: {:?}, Confidence: {:.3}",
seg.text, seg.category, seg.confidence
)
})
.collect();
results.push(format!(
"Input: '{}' (Language: {:?})\nSegments:\n{}\n",
text,
result.language,
segment_strings.join("\n")
));
}
let output = results.join("\n");
assert_snapshot!(output);
}
#[test]
fn test_geometry_types_snapshot() {
let mut results = Vec::new();
let coord1 = ICoord::new(10, 20);
let coord2 = ICoord::new(-5, 15);
let coord3 = coord1 + coord2;
let coord4 = coord1 - coord2;
results.push(format!(
"ICoord Tests:\n coord1: {:?}\n coord2: {:?}\n coord1 + coord2: {:?}\n coord1 - coord2: {:?}\n coord1 length: {:.3}\n",
coord1, coord2, coord3, coord4, coord1.length()
));
let fcoord1 = FCoord::new(10.5, 20.7);
let fcoord2 = FCoord::new(-5.2, 15.3);
let fcoord3 = fcoord1 + fcoord2;
let fcoord4 = fcoord1 - fcoord2;
results.push(format!(
"FCoord Tests:\n fcoord1: {:?}\n fcoord2: {:?}\n fcoord1 + fcoord2: {:?}\n fcoord1 - fcoord2: {:?}\n fcoord1 length: {:.3}\n",
fcoord1, fcoord2, fcoord3, fcoord4, fcoord1.length()
));
let bbox1 = TBox::new(0, 0, 100, 200);
let bbox2 = TBox::new(50, 50, 150, 250);
let bbox3 = bbox1.intersection(&bbox2);
let bbox4 = bbox1.union(&bbox2);
results.push(format!(
"TBox Tests:\n bbox1: {:?} (area: {})\n bbox2: {:?} (area: {})\n intersection: {:?}\n union: {:?}\n bbox1 contains (25, 25): {}\n bbox1 overlaps bbox2: {}\n",
bbox1, bbox1.area(),
bbox2, bbox2.area(),
bbox3,
bbox4,
bbox1.contains(ICoord::new(25, 25)),
bbox1.overlaps(&bbox2)
));
let output = results.join("\n");
assert_snapshot!(output);
}
#[test]
fn test_text_structures_snapshot() {
let mut results = Vec::new();
let blob_choice = BlobChoice::new(
65, 0.1,
0.9,
0, 10.0,
20.0,
0.0,
BlobChoiceClassifier::StaticClassifier,
);
results.push(format!(
"BlobChoice:\n Character: '{}' (ID: {})\n Rating: {:.3}\n Certainty: {:.3}\n Script: {}\n X-height: {:.1}-{:.1}\n Classifier: {:?}\n",
blob_choice.unichar_id as u8 as char,
blob_choice.unichar_id,
blob_choice.rating,
blob_choice.certainty,
blob_choice.script_id,
blob_choice.min_xheight,
blob_choice.max_xheight,
blob_choice.classifier
));
let mut word_choice = WordChoice::new();
word_choice.add_choice(vec![blob_choice.clone()]);
word_choice.rating = 0.8;
word_choice.certainty = 0.85;
word_choice.blanks = 1;
word_choice.script_id = 0;
results.push(format!(
"WordChoice:\n Text: '{}'\n Rating: {:.3}\n Certainty: {:.3}\n Blanks: {}\n Script: {}\n Choices: {}\n",
word_choice.text(),
word_choice.rating,
word_choice.certainty,
word_choice.blanks,
word_choice.script_id,
word_choice.choices.len()
));
let mut word = Word::new();
word.set_flag(WordFlag::Bold, true);
word.set_flag(WordFlag::Italic, false);
word.set_flag(WordFlag::StartOfLine, true);
word.correct_text = "Hello".to_string();
word.blanks = 2;
word.script_id = 0;
let mut sorted_flags: Vec<String> = word.flags.iter().map(|f| format!("{:?}", f)).collect();
sorted_flags.sort();
results.push(format!(
"Word:\n Text: '{}'\n Blanks: {}\n Script: {}\n Bold: {}\n Italic: {}\n Start of Line: {}\n Flags: {{{}}}\n",
word.correct_text,
word.blanks,
word.script_id,
word.has_flag(WordFlag::Bold),
word.has_flag(WordFlag::Italic),
word.has_flag(WordFlag::StartOfLine),
sorted_flags.join(", ")
));
let output = results.join("\n");
assert_snapshot!(output);
}
#[test]
fn test_model_configuration_snapshot() {
let configs = vec![
ModelConfig {
model_type: ModelType::LSTM,
model_path: "tesseract_lstm.lstm".to_string(),
supported_languages: vec![
LanguageVariant::English,
LanguageVariant::ChineseSimplified,
LanguageVariant::ChineseTraditional,
],
input_shape: (32, 128, 1),
max_text_length: Some(100),
confidence_threshold: 0.5,
device: DeviceType::CPU,
quantization: Some(QuantizationType::FP32),
},
ModelConfig {
model_type: ModelType::Transformer,
model_path: "trocr_model.onnx".to_string(),
supported_languages: vec![
LanguageVariant::English,
LanguageVariant::ChineseSimplified,
LanguageVariant::Japanese,
LanguageVariant::Korean,
],
input_shape: (224, 224, 3),
max_text_length: Some(200),
confidence_threshold: 0.8,
device: DeviceType::GPU,
quantization: Some(QuantizationType::FP16),
},
ModelConfig {
model_type: ModelType::VisionTransformer,
model_path: "vit_ocr_model.onnx".to_string(),
supported_languages: vec![LanguageVariant::English],
input_shape: (384, 384, 3),
max_text_length: Some(150),
confidence_threshold: 0.9,
device: DeviceType::NPU,
quantization: Some(QuantizationType::INT8),
},
];
let mut results = Vec::new();
for (i, config) in configs.iter().enumerate() {
results.push(format!(
"Model Config {}:\n Type: {:?}\n Path: {}\n Languages: {:?}\n Input Shape: {:?}\n Max Text Length: {:?}\n Confidence Threshold: {:.1}\n Device: {:?}\n Quantization: {:?}\n",
i + 1,
config.model_type,
config.model_path,
config.supported_languages,
config.input_shape,
config.max_text_length,
config.confidence_threshold,
config.device,
config.quantization
));
}
let output = results.join("\n");
assert_snapshot!(output);
}
#[test]
fn test_recognition_result_snapshot() {
let mut result = ocr::core::RecognitionResult::new("Hello World".to_string(), 0.95);
result.model_type = Some(ModelType::LSTM);
result.processing_time_ms = Some(150);
result.language = Some("en".to_string());
result.character_results = vec![
CharacterRecognition::new('H', 0.98),
CharacterRecognition::new('e', 0.96),
CharacterRecognition::new('l', 0.94),
CharacterRecognition::new('l', 0.92),
CharacterRecognition::new('o', 0.90),
];
result.word_results = vec![
WordRecognition::new("Hello".to_string(), 0.94),
WordRecognition::new("World".to_string(), 0.92),
];
result.line_results = vec![LineRecognition::new("Hello World".to_string(), 0.93)];
let json = serde_json::to_string_pretty(&result).unwrap();
assert_snapshot!(json);
}
#[test]
fn test_cjk_mixed_script_snapshot() {
let processor = LangCJKProcessor::new();
let mixed_texts = vec![
"Hello 世界",
"こんにちは World",
"안녕하세요 World",
"Mixed 中文 and English text",
"日本語とEnglish混合テキスト",
"한국어와English混合텍스트",
"Complex 中文日本語한국어English混合",
];
let mut results = Vec::new();
for text in mixed_texts {
let language_scores = processor.detect_cjk_language(text);
let mut score_strings: Vec<String> = language_scores
.iter()
.map(|(lang, score)| format!("{:?}: {:.3}", lang, score))
.collect();
score_strings.sort();
let mut char_analysis = Vec::new();
for ch in text.chars() {
let is_cjk = LangCJKProcessor::is_cjk_character(ch);
let category = LangCJKProcessor::get_character_category(ch);
let script = "CJK";
char_analysis.push(format!(
"'{}': CJK={}, Category={:?}, Script={:?}",
ch, is_cjk, category, script
));
}
results.push(format!(
"Text: '{}'\nLanguage scores: {}\nCharacter analysis:\n{}\n",
text,
score_strings.join(", "),
char_analysis.join("\n")
));
}
let output = results.join("\n");
assert_snapshot!(output);
}
#[test]
fn test_model_type_device_combinations_snapshot() {
let model_types = vec![
ModelType::LSTM,
ModelType::Transformer,
ModelType::VisionTransformer,
ModelType::CNN,
ModelType::Hybrid,
ModelType::EndToEnd,
ModelType::Custom("CustomModel".to_string()),
];
let device_types = vec![
DeviceType::CPU,
DeviceType::GPU,
DeviceType::NPU,
DeviceType::Auto,
];
let quantization_types = vec![
QuantizationType::FP32,
QuantizationType::FP16,
QuantizationType::INT8,
QuantizationType::Dynamic,
];
let mut results = Vec::new();
for model_type in &model_types {
for device_type in &device_types {
for quantization in &quantization_types {
let config = ModelConfig {
model_type: model_type.clone(),
model_path: format!(
"model_{:?}_{:?}_{:?}.onnx",
model_type, device_type, quantization
),
supported_languages: vec![LanguageVariant::English],
input_shape: (224, 224, 3),
max_text_length: Some(100),
confidence_threshold: 0.8,
device: *device_type,
quantization: Some(*quantization),
};
results.push(format!(
"Model: {:?}, Device: {:?}, Quantization: {:?}\n Path: {}\n Input Shape: {:?}\n Confidence: {:.1}\n",
model_type, device_type, quantization,
config.model_path,
config.input_shape,
config.confidence_threshold
));
}
}
}
let output = results.join("\n");
assert_snapshot!(output);
}