use crate::bytes::u32_at;
const HEADER: usize = 12;
const DECOMP_ENTRY: usize = 8;
const CCC_ENTRY: usize = 4;
const POOL_ENTRY: usize = 3;
pub(crate) const HANGUL_S_BASE: u32 = 0xAC00;
pub(crate) const HANGUL_S_COUNT: u32 = 19 * HANGUL_N_COUNT;
const HANGUL_L_BASE: u32 = 0x1100;
const HANGUL_V_BASE: u32 = 0x1161;
const HANGUL_T_BASE: u32 = 0x11A7;
const HANGUL_T_COUNT: u32 = 28;
const HANGUL_N_COUNT: u32 = 21 * HANGUL_T_COUNT;
#[derive(Clone, Copy, Debug)]
pub struct NfcMap<'a> {
decomp: &'a [u8],
ccc: &'a [u8],
pool: &'a [u8],
}
impl NfcMap<'_> {
pub const EMPTY: NfcMap<'static> = NfcMap {
decomp: &[],
ccc: &[],
pool: &[],
};
}
impl<'a> NfcMap<'a> {
pub fn from_bytes(b: &'a [u8]) -> Option<NfcMap<'a>> {
if b.is_empty() {
return Some(NfcMap::EMPTY);
}
let n_decomp = usize::try_from(u32_at(b, 0)?).ok()?;
let n_ccc = usize::try_from(u32_at(b, 4)?).ok()?;
let n_pool = usize::try_from(u32_at(b, 8)?).ok()?;
let decomp_len = n_decomp.checked_mul(DECOMP_ENTRY)?;
let ccc_len = n_ccc.checked_mul(CCC_ENTRY)?;
let pool_len = n_pool.checked_mul(POOL_ENTRY)?;
let at_ccc = HEADER.checked_add(decomp_len)?;
let at_pool = at_ccc.checked_add(ccc_len)?;
let end = at_pool.checked_add(pool_len)?;
if b.len() != end {
return None;
}
Some(NfcMap {
decomp: b.get(HEADER..at_ccc)?,
ccc: b.get(at_ccc..at_pool)?,
pool: b.get(at_pool..end)?,
})
}
pub fn is_empty(&self) -> bool {
self.decomp.is_empty()
}
pub fn code_points(&self) -> usize {
self.decomp.len() / DECOMP_ENTRY
}
pub fn len_bytes(&self) -> usize {
if self.is_empty() && self.ccc.is_empty() && self.pool.is_empty() {
0
} else {
HEADER + self.decomp.len() + self.ccc.len() + self.pool.len()
}
}
pub fn decomposition(&self, ch: char) -> Option<DecompChars<'a>> {
let cp = u32::from(ch);
if let Some(s) = cp
.checked_sub(HANGUL_S_BASE)
.filter(|s| *s < HANGUL_S_COUNT)
{
let l = char::from_u32(HANGUL_L_BASE + s / HANGUL_N_COUNT)?;
let v = char::from_u32(HANGUL_V_BASE + (s % HANGUL_N_COUNT) / HANGUL_T_COUNT)?;
let t = s % HANGUL_T_COUNT;
let (third, len) = if t == 0 {
(l, 2)
} else {
(char::from_u32(HANGUL_T_BASE + t)?, 3)
};
return Some(DecompChars {
inner: Inner::Inline {
chs: [l, v, third],
at: 0,
len,
},
});
}
let at = find(self.decomp, DECOMP_ENTRY, cp)?;
let len = *self.decomp.get(at.checked_add(3)?)?;
if len == 0 {
return Some(DecompChars {
inner: Inner::Inline {
chs: [ch, ch, ch],
at: 0,
len: 1,
},
});
}
let off = usize::try_from(u32_at(self.decomp, at.checked_add(4)?)?).ok()?;
let start = off.checked_mul(POOL_ENTRY)?;
Some(DecompChars {
inner: Inner::Pool {
pool: self.pool,
at: start,
left: len,
},
})
}
pub fn ccc(&self, ch: char) -> u8 {
let Some(at) = find(self.ccc, CCC_ENTRY, u32::from(ch)) else {
return 0;
};
at.checked_add(3)
.and_then(|i| self.ccc.get(i))
.copied()
.unwrap_or(0)
}
}
#[derive(Clone, Debug)]
pub struct DecompChars<'a> {
inner: Inner<'a>,
}
#[derive(Clone, Copy, Debug)]
enum Inner<'a> {
Pool { pool: &'a [u8], at: usize, left: u8 },
Inline { chs: [char; 3], at: u8, len: u8 },
}
impl Iterator for DecompChars<'_> {
type Item = char;
fn next(&mut self) -> Option<char> {
match &mut self.inner {
Inner::Pool { pool, at, left } => {
if *left == 0 {
return None;
}
let ch = code_point(pool, *at).and_then(char::from_u32)?;
*at = at.checked_add(POOL_ENTRY)?;
*left -= 1;
Some(ch)
}
Inner::Inline { chs, at, len } => {
if at >= len {
return None;
}
let ch = *chs.get(usize::from(*at))?;
*at += 1;
Some(ch)
}
}
}
}
fn code_point(b: &[u8], at: usize) -> Option<u32> {
match b.get(at..at.checked_add(POOL_ENTRY)?)? {
&[x0, x1, x2] => Some(u32::from_le_bytes([x0, x1, x2, 0])),
_ => None,
}
}
fn find(b: &[u8], stride: usize, cp: u32) -> Option<usize> {
let mut lo = 0usize;
let mut hi = b.len() / stride;
while lo < hi {
let mid = lo + (hi - lo) / 2;
let at = mid.checked_mul(stride)?;
let key = code_point(b, at)?;
match key.cmp(&cp) {
core::cmp::Ordering::Less => lo = mid.checked_add(1)?,
core::cmp::Ordering::Greater => hi = mid,
core::cmp::Ordering::Equal => return Some(at),
}
}
None
}