use alloc::vec::Vec;
const MAX_KATAKANA_LENGTH: usize = 8;
const MAX_KATAKANA_GROUP_LENGTH: usize = 20;
const MAX_SNLP: u32 = 255;
const MAX_WORD_SIZE: usize = 20;
#[inline]
fn katakana_cost(word_length: usize) -> u32 {
const COST: [u32; MAX_KATAKANA_LENGTH + 1] = [8192, 984, 408, 240, 204, 252, 300, 372, 480];
if word_length > MAX_KATAKANA_LENGTH {
8192
} else {
COST[word_length]
}
}
#[inline]
fn is_katakana(c: char) -> bool {
let v = c as u32;
(0x30A1..=0x30FE).contains(&v) && v != 0x30FB || (0xFF66..=0xFF9F).contains(&v)
}
#[inline]
pub(crate) fn is_cjk_dict_char(c: char) -> bool {
use super::script::Script;
matches!(
super::script::script(c),
Script::Han | Script::Hiragana | Script::Katakana
) || matches!(c as u32, 0x30FC | 0xFF70 | 0xFF9E | 0xFF9F)
}
const DICT: &[u8] = include_bytes!("segment_dict_cjk.bin");
#[inline]
fn rd_u32(o: usize) -> Option<u32> {
let b = DICT.get(o..o.checked_add(4)?)?;
Some(u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
}
struct Dict {
n: usize,
root: usize,
values_base: usize,
off_base: usize,
edges_base: usize,
}
impl Dict {
fn load() -> Option<Dict> {
let n = rd_u32(0)? as usize;
let e = rd_u32(4)? as usize;
let root = rd_u32(8)? as usize;
let values_base = 12usize;
let off_base = values_base.checked_add(n)?;
let edges_base = off_base.checked_add((n.checked_add(1)?).checked_mul(4)?)?;
if edges_base.checked_add(e.checked_mul(5)?)? > DICT.len() || root >= n {
return None;
}
Some(Dict {
n,
root,
values_base,
off_base,
edges_base,
})
}
#[inline]
fn cost(&self, node: usize) -> u8 {
if node >= self.n {
return 0;
}
DICT.get(self.values_base + node).copied().unwrap_or(0)
}
#[inline]
fn edge_range(&self, node: usize) -> (usize, usize) {
let a = rd_u32(self.off_base + node * 4).unwrap_or(0) as usize;
let b = rd_u32(self.off_base + (node + 1) * 4).unwrap_or(0) as usize;
(a, b)
}
#[inline]
fn child(&self, node: usize, sym: u16) -> Option<usize> {
let (mut lo, mut hi) = self.edge_range(node);
while lo < hi {
let mid = lo + (hi - lo) / 2;
let rec = self.edges_base + mid * 5;
let s = u16::from_le_bytes([*DICT.get(rec)?, *DICT.get(rec + 1)?]);
match s.cmp(&sym) {
core::cmp::Ordering::Less => lo = mid + 1,
core::cmp::Ordering::Greater => hi = mid,
core::cmp::Ordering::Equal => {
let t = [
*DICT.get(rec + 2)?,
*DICT.get(rec + 3)?,
*DICT.get(rec + 4)?,
0,
];
return Some(u32::from_le_bytes(t) as usize);
}
}
}
None
}
}
#[inline]
fn sym(c: char) -> Option<u16> {
u16::try_from(c as u32).ok()
}
#[inline]
fn nfkc_boundary_before(c: char) -> bool {
use super::normalize;
let lead = normalize::nfkd(core::iter::once(c)).next().unwrap_or(c);
normalize::canonical_combining_class(lead) == 0
}
fn build_normalized(run: &str, chars: &mut Vec<char>, byte_off: &mut Vec<usize>) {
use super::normalize::nfkc;
let orig: Vec<(usize, char)> = run.char_indices().collect();
let n = orig.len();
let mut i = 0;
while i < n {
let start_byte = orig[i].0;
i += 1;
while i < n && !nfkc_boundary_before(orig[i].1) {
i += 1;
}
let end_byte = if i < n { orig[i].0 } else { run.len() };
for c in nfkc(run[start_byte..end_byte].chars()) {
chars.push(c);
byte_off.push(start_byte);
}
}
byte_off.push(run.len());
}
pub(crate) fn segment(run: &str, out: &mut Vec<usize>) {
out.clear();
if run.is_empty() {
return;
}
let Some(dict) = Dict::load() else {
out.push(run.len());
return;
};
let mut chars: Vec<char> = Vec::new();
let mut byte_off: Vec<usize> = Vec::new();
if super::normalize::quick_check_nfkc(run.chars()) == super::normalize::IsNormalized::Yes {
for (i, c) in run.char_indices() {
chars.push(c);
byte_off.push(i);
}
byte_off.push(run.len());
} else {
build_normalized(run, &mut chars, &mut byte_off);
}
let ncp = chars.len();
if ncp == 0 {
out.push(run.len());
return;
}
let mut best = alloc::vec![u32::MAX; ncp + 1];
best[0] = 0;
let mut prev = alloc::vec![usize::MAX; ncp + 1];
let mut cand_len: Vec<usize> = Vec::with_capacity(MAX_WORD_SIZE);
let mut cand_cost: Vec<u32> = Vec::with_capacity(MAX_WORD_SIZE);
let mut is_prev_katakana = false;
for i in 0..ncp {
if best[i] == u32::MAX {
continue;
}
cand_len.clear();
cand_cost.clear();
let mut node = dict.root;
let mut k = i;
while k < ncp && (k - i) < MAX_WORD_SIZE {
let Some(s) = sym(chars[k]) else { break };
let Some(nx) = dict.child(node, s) else { break };
node = nx;
k += 1;
let cost = dict.cost(node);
if cost != 0 {
cand_len.push(k - i);
cand_cost.push(cost as u32);
}
}
if cand_len.first() != Some(&1) {
cand_len.push(1);
cand_cost.push(MAX_SNLP);
}
for (idx, &len) in cand_len.iter().enumerate() {
let ni = i + len;
let nsnlp = best[i].saturating_add(cand_cost[idx]);
if nsnlp < best[ni] {
best[ni] = nsnlp;
prev[ni] = i;
}
}
let is_kata = is_katakana(chars[i]);
if !is_prev_katakana && is_kata {
let mut run_len = 1usize;
let mut j = i + 1;
while j < ncp && run_len < MAX_KATAKANA_GROUP_LENGTH && is_katakana(chars[j]) {
j += 1;
run_len += 1;
}
if run_len < MAX_KATAKANA_GROUP_LENGTH {
let ni = i + run_len;
let nsnlp = best[i].saturating_add(katakana_cost(run_len));
if nsnlp < best[ni] {
best[ni] = nsnlp;
prev[ni] = i;
}
}
}
is_prev_katakana = is_kata;
}
if best[ncp] == u32::MAX {
out.push(run.len());
return;
}
let mut i = ncp;
while i > 0 {
let b = byte_off[i];
if out.last() != Some(&b) {
out.push(b);
}
let p = prev[i];
if p == usize::MAX || p >= i {
break; }
i = p;
}
out.reverse();
}