use unicode_script::Script;
use crate::types::Lang;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct SegmentContext {
has_kana: bool,
has_hangul: bool,
}
impl SegmentContext {
#[must_use]
pub const fn new(has_kana: bool, has_hangul: bool) -> Self {
Self {
has_kana,
has_hangul,
}
}
#[must_use]
pub const fn has_kana(&self) -> bool {
self.has_kana
}
#[must_use]
pub const fn has_hangul(&self) -> bool {
self.has_hangul
}
#[must_use]
pub fn from_text(text: &str) -> Self {
let mut ctx = Self::default();
for ch in text.chars() {
match Script::from(ch) {
Script::Hiragana | Script::Katakana => ctx.has_kana = true,
Script::Hangul => ctx.has_hangul = true,
_ => {}
}
if ctx.has_kana && ctx.has_hangul {
break;
}
}
ctx
}
}
#[must_use]
pub const fn is_no_space_cjk_lang(lang: &Lang) -> bool {
matches!(lang, Lang::Ja | Lang::Zh | Lang::Yue | Lang::Ko)
}
#[must_use]
pub const fn is_latin_script_lang(lang: &Lang) -> bool {
matches!(
lang,
Lang::En
| Lang::Es
| Lang::Fr
| Lang::De
| Lang::It
| Lang::Pt
| Lang::Nl
| Lang::Sv
| Lang::No
| Lang::Da
| Lang::Fi
| Lang::Pl
| Lang::Ro
| Lang::Cs
| Lang::Hu
| Lang::Tr
| Lang::Vi
| Lang::Ca
| Lang::Sk
| Lang::Sl
| Lang::Hr
| Lang::Lt
| Lang::Lv
| Lang::Et
| Lang::Id
| Lang::Ms
| Lang::Sw
| Lang::Af
| Lang::Eu
| Lang::Gl
| Lang::Cy
| Lang::Is
| Lang::Mt
| Lang::Sq
| Lang::Tl
| Lang::Haw
| Lang::Ln
| Lang::Ha
| Lang::Yo
| Lang::So
| Lang::Oc
| Lang::Br
| Lang::Lb
| Lang::Nn
| Lang::Fo
| Lang::Ht
| Lang::Tk
| Lang::Jw
| Lang::Su
| Lang::Mg
| Lang::Mi
| Lang::Sn
| Lang::La
)
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum CharClass {
Lang(Lang),
Carry,
}
#[must_use]
pub fn script_to_lang(ch: char, ctx: SegmentContext, state_lang: Option<&Lang>) -> CharClass {
match Script::from(ch) {
Script::Hiragana | Script::Katakana => CharClass::Lang(Lang::Ja),
Script::Hangul => CharClass::Lang(Lang::Ko),
Script::Han => {
if ctx.has_kana {
CharClass::Lang(Lang::Ja)
} else if ctx.has_hangul {
CharClass::Lang(Lang::Ko)
} else {
CharClass::Lang(Lang::Zh)
}
}
Script::Latin => match state_lang {
Some(l) if is_latin_script_lang(l) => CharClass::Lang(l.clone()),
_ => CharClass::Lang(Lang::En),
},
Script::Common | Script::Inherited | Script::Unknown => CharClass::Carry,
_ => match state_lang {
Some(l) => CharClass::Lang(l.clone()),
None => CharClass::Carry,
},
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn segment_context_pure_english() {
let ctx = SegmentContext::from_text("hello world");
assert!(!ctx.has_kana);
assert!(!ctx.has_hangul);
}
#[test]
fn segment_context_pure_chinese() {
let ctx = SegmentContext::from_text("你好世界");
assert!(!ctx.has_kana);
assert!(!ctx.has_hangul);
}
#[test]
fn segment_context_jp_with_kana() {
let ctx = SegmentContext::from_text("これは日本語です");
assert!(ctx.has_kana);
assert!(!ctx.has_hangul);
}
#[test]
fn segment_context_ko_with_hangul() {
let ctx = SegmentContext::from_text("안녕하세요");
assert!(!ctx.has_kana);
assert!(ctx.has_hangul);
}
#[test]
fn hiragana_maps_to_ja() {
let ctx = SegmentContext::default();
assert_eq!(script_to_lang('あ', ctx, None), CharClass::Lang(Lang::Ja),);
}
#[test]
fn katakana_maps_to_ja() {
let ctx = SegmentContext::default();
assert_eq!(script_to_lang('カ', ctx, None), CharClass::Lang(Lang::Ja),);
}
#[test]
fn hangul_maps_to_ko() {
let ctx = SegmentContext::default();
assert_eq!(script_to_lang('한', ctx, None), CharClass::Lang(Lang::Ko),);
}
#[test]
fn han_default_is_zh() {
let ctx = SegmentContext::default();
assert_eq!(script_to_lang('語', ctx, None), CharClass::Lang(Lang::Zh),);
}
#[test]
fn han_with_kana_context_is_ja() {
let ctx = SegmentContext {
has_kana: true,
has_hangul: false,
};
assert_eq!(script_to_lang('語', ctx, None), CharClass::Lang(Lang::Ja),);
}
#[test]
fn han_with_hangul_context_is_ko() {
let ctx = SegmentContext {
has_kana: false,
has_hangul: true,
};
assert_eq!(script_to_lang('語', ctx, None), CharClass::Lang(Lang::Ko),);
}
#[test]
fn han_kana_beats_hangul_when_both_present() {
let ctx = SegmentContext {
has_kana: true,
has_hangul: true,
};
assert_eq!(script_to_lang('語', ctx, None), CharClass::Lang(Lang::Ja),);
}
#[test]
fn latin_no_hint_defaults_to_en() {
let ctx = SegmentContext::default();
assert_eq!(script_to_lang('h', ctx, None), CharClass::Lang(Lang::En),);
}
#[test]
fn latin_with_es_hint_uses_es() {
let ctx = SegmentContext::default();
assert_eq!(
script_to_lang('h', ctx, Some(&Lang::Es)),
CharClass::Lang(Lang::Es),
);
}
#[test]
fn latin_with_zh_hint_routes_to_en() {
let ctx = SegmentContext::default();
assert_eq!(
script_to_lang('h', ctx, Some(&Lang::Zh)),
CharClass::Lang(Lang::En),
);
}
#[test]
fn latin_with_ja_hint_routes_to_en() {
let ctx = SegmentContext::default();
assert_eq!(
script_to_lang('U', ctx, Some(&Lang::Ja)),
CharClass::Lang(Lang::En),
);
}
#[test]
fn latin_with_ko_hint_routes_to_en() {
let ctx = SegmentContext::default();
assert_eq!(
script_to_lang('K', ctx, Some(&Lang::Ko)),
CharClass::Lang(Lang::En),
);
}
#[test]
fn latin_with_no_state_lang_falls_back_to_en() {
let ctx = SegmentContext::default();
assert_eq!(script_to_lang('h', ctx, None), CharClass::Lang(Lang::En),);
}
#[test]
fn punctuation_carries() {
let ctx = SegmentContext::default();
assert_eq!(script_to_lang(',', ctx, None), CharClass::Carry);
assert_eq!(script_to_lang('.', ctx, None), CharClass::Carry);
assert_eq!(script_to_lang('?', ctx, None), CharClass::Carry);
}
#[test]
fn whitespace_carries() {
let ctx = SegmentContext::default();
assert_eq!(script_to_lang(' ', ctx, None), CharClass::Carry);
assert_eq!(script_to_lang('\t', ctx, None), CharClass::Carry);
}
#[test]
fn digits_carry() {
let ctx = SegmentContext::default();
assert_eq!(script_to_lang('0', ctx, None), CharClass::Carry);
assert_eq!(script_to_lang('9', ctx, None), CharClass::Carry);
}
#[test]
fn cyrillic_with_ru_hint_uses_ru() {
let ctx = SegmentContext::default();
assert_eq!(
script_to_lang('п', ctx, Some(&Lang::Ru)),
CharClass::Lang(Lang::Ru),
);
}
#[test]
fn cyrillic_without_hint_carries() {
let ctx = SegmentContext::default();
assert_eq!(script_to_lang('п', ctx, None), CharClass::Carry);
}
#[test]
fn is_latin_script_lang_known_set() {
assert!(is_latin_script_lang(&Lang::En));
assert!(is_latin_script_lang(&Lang::Es));
assert!(is_latin_script_lang(&Lang::Vi));
assert!(is_latin_script_lang(&Lang::Tr));
assert!(!is_latin_script_lang(&Lang::Zh));
assert!(!is_latin_script_lang(&Lang::Ja));
assert!(!is_latin_script_lang(&Lang::Ko));
assert!(!is_latin_script_lang(&Lang::Ar));
assert!(!is_latin_script_lang(&Lang::Ru));
assert!(!is_latin_script_lang(&Lang::He));
}
}