use core::marker::PhantomData;
use crate::{
encoding::Encoding,
props::{
CB_CONTROL, CB_CR, CB_EXTEND, CB_EXTEND_INCB_LINKER, CB_L, CB_LF, CB_LV, CB_LVT, CB_MASK,
CB_OTHER_INCB_CONSONANT, CB_PREPEND, CB_RI, CB_SPACING_MARK, CB_T, CB_V, CB_ZWJ, EPIC_BIT,
INCB_EXTEND_BIT, WIDTH_EMOJI_TEXT, WIDTH_SHIFT, is_emoji_modifier_base, props,
},
unit::Unit,
utf8::Utf8,
};
pub struct Grapheme<'a, E: Encoding> {
pub units: &'a [E::Unit],
pub width: usize,
}
impl<E: Encoding> Clone for Grapheme<'_, E> {
#[inline(always)]
fn clone(&self) -> Self {
*self
}
}
impl<E: Encoding> Copy for Grapheme<'_, E> {}
pub struct Graphemes<'a, E: Encoding> {
rest: &'a [E::Unit],
_encoding: PhantomData<E>,
}
impl<E: Encoding> Clone for Graphemes<'_, E> {
#[inline(always)]
fn clone(&self) -> Self {
Self { rest: self.rest, _encoding: PhantomData }
}
}
impl<'a, E: Encoding> Iterator for Graphemes<'a, E> {
type Item = Grapheme<'a, E>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.rest.is_empty() {
return None;
}
let scan = next_cluster::<E>(self.rest);
let (units, rest) = self.rest.split_at(scan.units);
self.rest = rest;
Some(Grapheme { units, width: scan.width })
}
#[inline(always)]
fn size_hint(&self) -> (usize, Option<usize>) {
(usize::from(!self.rest.is_empty()), Some(self.rest.len()))
}
}
#[inline(always)]
pub const fn graphemes<E: Encoding>(input: &[E::Unit]) -> Graphemes<'_, E> {
Graphemes { rest: input, _encoding: PhantomData }
}
#[inline]
pub fn graphemes_str(input: &str) -> impl Iterator<Item = &str> + Clone {
graphemes::<Utf8>(input.as_bytes()).map(|g| {
unsafe { core::str::from_utf8_unchecked(g.units) }
})
}
pub struct ClusterScan {
pub units: usize,
pub width: usize,
}
#[inline(always)]
pub const fn width_value(p: u8) -> usize {
let width = (p >> WIDTH_SHIFT) & 3;
if width == WIDTH_EMOJI_TEXT {
1
} else {
width as usize
}
}
pub struct ClusterState {
width: usize,
prev: u8,
prev_cp: u32,
epic: u8,
incb: u8,
ri_odd: bool,
after_zwj: bool,
promotable: bool,
promote: bool,
}
impl ClusterState {
#[inline(always)]
pub fn start(cp0: u32, p0: u8) -> Self {
let c0 = p0 & CB_MASK;
Self {
width: width_value(p0),
prev: c0,
prev_cp: cp0,
epic: u8::from(p0 & EPIC_BIT != 0),
incb: u8::from(c0 == CB_OTHER_INCB_CONSONANT),
ri_odd: c0 == CB_RI,
after_zwj: false,
promotable: (p0 >> WIDTH_SHIFT) & 3 == WIDTH_EMOJI_TEXT,
promote: false,
}
}
#[inline(always)]
pub const fn joins_plain(&self) -> bool {
self.prev == CB_PREPEND
}
#[inline(always)]
pub fn try_join(&mut self, cp: u32, p: u8) -> bool {
let c = p & CB_MASK;
if matches!(self.prev, CB_CR | CB_LF | CB_CONTROL) {
if self.prev != CB_CR || c != CB_LF {
return false;
}
} else {
let join = match c {
CB_CR | CB_LF | CB_CONTROL => false,
CB_EXTEND | CB_EXTEND_INCB_LINKER | CB_ZWJ => true,
CB_SPACING_MARK => true,
_ if self.prev == CB_PREPEND => true,
CB_L => self.prev == CB_L,
CB_V => matches!(self.prev, CB_L | CB_LV | CB_V),
CB_T => matches!(self.prev, CB_LV | CB_V | CB_LVT | CB_T),
CB_LV | CB_LVT => self.prev == CB_L,
CB_RI => self.prev == CB_RI && self.ri_odd,
CB_OTHER_INCB_CONSONANT => self.incb == 2,
_ => self.prev == CB_ZWJ && self.epic == 2 && p & EPIC_BIT != 0,
};
if !join {
return false;
}
}
if c == CB_EXTEND || c == CB_EXTEND_INCB_LINKER {
if self.epic != 1 {
self.epic = 0;
}
} else if c == CB_ZWJ {
self.epic = if self.epic == 1 { 2 } else { 0 };
} else if p & EPIC_BIT != 0 {
self.epic = 1;
} else {
self.epic = 0;
}
if c == CB_OTHER_INCB_CONSONANT {
self.incb = 1;
} else if c == CB_EXTEND_INCB_LINKER {
self.incb = if self.incb != 0 { 2 } else { 0 };
} else if p & INCB_EXTEND_BIT == 0 {
self.incb = 0;
}
self.ri_odd = c == CB_RI && !self.ri_odd;
if cp == 0xfe0f || cp == 0x20e3 {
self.promote = true;
}
if c == CB_ZWJ {
self.after_zwj = true;
} else if !self.after_zwj {
let modifier = (c == CB_EXTEND || c == CB_EXTEND_INCB_LINKER)
&& width_value(p) == 2
&& is_emoji_modifier_base(self.prev_cp);
if !modifier {
self.width += width_value(p);
}
}
self.prev_cp = cp;
self.prev = c;
true
}
#[inline(always)]
pub fn finish(&self) -> usize {
if self.promote && self.promotable {
self.width.max(2)
} else {
self.width
}
}
}
#[inline]
pub fn next_cluster<E: Encoding>(input: &[E::Unit]) -> ClusterScan {
if !E::FOREIGN {
let u0 = input[0].to_u32();
if u0 < 0x80 {
if u0 == 0x0d {
if input.len() > 1 && input[1].to_u32() == 0x0a {
return ClusterScan { units: 2, width: 0 };
}
return ClusterScan { units: 1, width: 0 };
}
if input.len() == 1 || input[1].to_u32() < 0x80 {
let width = usize::from((0x20..=0x7e).contains(&u0));
return ClusterScan { units: 1, width };
}
}
}
let mut rest = input;
let cp0 = E::decode(&mut rest);
let mut state = ClusterState::start(cp0, props(cp0));
while !rest.is_empty() {
let mut peek = rest;
let cp = E::decode(&mut peek);
if !state.try_join(cp, props(cp)) {
break;
}
rest = peek;
}
ClusterScan { units: input.len() - rest.len(), width: state.finish() }
}