use crate::{
encoding::Encoding,
grapheme::{ClusterState, width_value},
props::{CB_LV, CB_LVT, CB_MASK, CB_OTHER, EPIC_BIT, WIDTH_EMOJI_TEXT, WIDTH_SHIFT, props},
simd::plain_prefix,
unit::Unit,
utf8::Utf8,
};
#[inline(always)]
const fn simple(p: u8) -> bool {
matches!(p & CB_MASK, CB_OTHER | CB_LV | CB_LVT)
&& p & EPIC_BIT == 0
&& (p >> WIDTH_SHIFT) & 3 != WIDTH_EMOJI_TEXT
}
#[inline(always)]
const fn promotable_ascii(cp: u32) -> bool {
matches!(cp, 0x23 | 0x2a | 0x30..=0x39)
}
pub fn width<E: Encoding>(input: &[E::Unit]) -> usize {
let mut w = 0usize;
let mut rest = input;
'bulk: loop {
if !E::FOREIGN {
let run = plain_prefix(rest);
if run > 0 {
let promote = promotable_ascii(rest[run - 1].to_u32());
let take = if run < rest.len() && promote {
run - 1
} else {
run
};
w += take;
rest = &rest[take..];
}
}
if rest.is_empty() {
return w;
}
let mut cp = E::decode(&mut rest);
let mut p = props(cp);
'cluster: loop {
let mut state = 'simple: {
while simple(p) {
if rest.is_empty() {
return w + width_value(p);
}
let mut peek = rest;
let next = E::decode(&mut peek);
if !E::FOREIGN && next.wrapping_sub(0x20) <= 0x5e {
w += width_value(p);
continue 'bulk;
}
let np = props(next);
if !simple(np) {
let mut state = ClusterState::start(cp, p);
rest = peek;
if state.try_join(next, np) {
break 'simple state;
}
w += state.finish();
break 'simple ClusterState::start(next, np);
}
w += width_value(p);
cp = next;
p = np;
rest = peek;
}
break 'simple ClusterState::start(cp, p);
};
loop {
if rest.is_empty() {
return w + state.finish();
}
let mut peek = rest;
let next = E::decode(&mut peek);
if !E::FOREIGN && next.wrapping_sub(0x20) <= 0x5e && !state.joins_plain() {
w += state.finish();
continue 'bulk;
}
let np = props(next);
if state.try_join(next, np) {
rest = peek;
continue;
}
w += state.finish();
rest = peek;
if simple(np) {
cp = next;
p = np;
continue 'cluster;
}
state = ClusterState::start(next, np);
}
}
}
}
#[inline]
pub fn width_str(input: &str) -> usize {
width::<Utf8>(input.as_bytes())
}