use super::matcher::longest_word;
use super::{Dictionaries, Dictionary, DictionaryScript, next_word};
use crate::{GraphemeProps, GraphemeState, ScriptId, core_props};
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub(crate) enum RunScript {
Thai,
Lao,
Khmer,
Myanmar,
ChineseJapanese,
Other,
}
impl RunScript {
#[inline]
pub(crate) fn from_sa_char(ch: char) -> Self {
match core_props(ch).script() {
ScriptId::THAI => Self::Thai,
ScriptId::LAO => Self::Lao,
ScriptId::KHMER => Self::Khmer,
ScriptId::MYANMAR => Self::Myanmar,
_ => Self::Other,
}
}
pub(crate) fn dictionary(self) -> Option<DictionaryScript> {
Some(match self {
Self::Thai => DictionaryScript::Thai,
Self::Lao => DictionaryScript::Lao,
Self::Khmer => DictionaryScript::Khmer,
Self::Myanmar => DictionaryScript::Myanmar,
Self::ChineseJapanese => DictionaryScript::ChineseJapanese,
Self::Other => return None,
})
}
#[inline]
pub(crate) fn find(self, dictionaries: Option<&Dictionaries>) -> Option<&Dictionary> {
dictionaries?.get(self.dictionary()?)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
pub(crate) struct Run {
script: Option<RunScript>,
next_word: Option<usize>,
}
impl Run {
#[inline]
pub(crate) fn script(&self) -> Option<RunScript> {
self.script
}
#[inline]
pub(crate) fn end(&mut self) {
self.script = None;
}
#[inline]
pub(crate) fn step(
&mut self,
offset: usize,
script: RunScript,
mark: bool,
dictionaries: Option<&Dictionaries>,
find: impl FnOnce(&Dictionary) -> Option<usize>,
) -> Option<bool> {
let dictionary = script.find(dictionaries);
let previous = self.script.replace(script);
if previous == Some(script) {
if self.next_word != Some(offset) {
return Some(false);
}
self.next_word = dictionary.and_then(find);
return Some(true);
}
self.next_word = dictionary.and_then(find);
let previous = previous?;
(dictionary.is_some() || previous.find(dictionaries).is_some()).then_some(!mark)
}
}
const KATAKANA_WORD: usize = 8;
#[inline]
pub(crate) fn is_katakana(ch: char) -> bool {
matches!(u32::from(ch), 0x30A1..=0x30FA | 0x30FC..=0x30FE | 0xFF66..=0xFF9F)
}
#[expect(clippy::too_many_arguments, reason = "find_next_word's, and one")]
pub(crate) fn find_next_cj_word<I: Iterator<Item = (usize, char)> + Clone>(
dictionary: &Dictionary,
offset: usize,
ch: char,
rest: I,
continues: impl Fn(char) -> bool + Copy,
props: impl Fn(char) -> GraphemeProps + Copy,
graphemes: GraphemeState,
katakana_start: bool,
) -> Option<usize> {
if katakana_start && is_katakana(ch) {
let mut end = None;
let mut length = 1;
let mut least = offset.saturating_add(1);
for (at, next) in rest.clone() {
if at < least || !continues(next) || !is_katakana(next) {
end = (at >= least).then_some(at);
break;
}
least = at.saturating_add(1);
length += 1;
if length > KATAKANA_WORD {
break;
}
}
if length <= KATAKANA_WORD {
let first = find_cj_words(
dictionary, offset, ch, rest, continues, props, graphemes, end,
);
if let Some(first) = first {
return first;
}
return end;
}
}
find_next_word(dictionary, offset, ch, rest, continues, props, graphemes)
}
#[expect(clippy::too_many_arguments, reason = "find_next_word's, and one")]
fn find_cj_words<I: Iterator<Item = (usize, char)> + Clone>(
dictionary: &Dictionary,
offset: usize,
ch: char,
rest: I,
continues: impl Fn(char) -> bool + Copy,
props: impl Fn(char) -> GraphemeProps + Copy,
graphemes: GraphemeState,
end: Option<usize>,
) -> Option<Option<usize>> {
let inside = |at: usize| end.is_none_or(|end| at < end);
let mut first = None;
let (mut at, mut start) = (offset, ch);
loop {
let mut least = at.saturating_add(1);
let chars = core::iter::once((at, start)).chain(
rest.clone()
.skip_while(move |&(next, _)| next <= at)
.take_while(move |&(next, ch)| {
let inside = next >= least && continues(ch);
least = next.saturating_add(1);
inside
}),
);
let (next, found) = longest_word(dictionary, chars, props, graphemes);
if !found {
return None;
}
let first = *first.get_or_insert(next);
match next {
Some(next) if inside(next) => {
let Some((_, ch)) = rest.clone().find(|&(found, _)| found == next) else {
return Some(first);
};
(at, start) = (next, ch);
}
_ => return Some(first),
}
}
}
pub(crate) fn find_next_word(
dictionary: &Dictionary,
offset: usize,
ch: char,
rest: impl Iterator<Item = (usize, char)>,
continues: impl Fn(char) -> bool,
props: impl Fn(char) -> GraphemeProps,
graphemes: GraphemeState,
) -> Option<usize> {
let mut least = offset.saturating_add(1);
let rest = rest.take_while(move |&(at, next)| {
let inside = at >= least && continues(next);
least = at.saturating_add(1);
inside
});
next_word(
dictionary,
core::iter::once((offset, ch)).chain(rest),
props,
graphemes,
)
}