use crate::GraphemeClusterBreak;
#[cfg(test)]
mod tests;
const OTHER: u8 = GraphemeClusterBreak::Other as u8;
const CONTROL: u8 = GraphemeClusterBreak::Control as u8;
const CR: u8 = GraphemeClusterBreak::Cr as u8;
const LF: u8 = GraphemeClusterBreak::Lf as u8;
const EXTEND: u8 = GraphemeClusterBreak::Extend as u8;
const ZWJ: u8 = GraphemeClusterBreak::Zwj as u8;
const L: u8 = GraphemeClusterBreak::L as u8;
const V: u8 = GraphemeClusterBreak::V as u8;
const T: u8 = GraphemeClusterBreak::T as u8;
const LV: u8 = GraphemeClusterBreak::Lv as u8;
const LVT: u8 = GraphemeClusterBreak::Lvt as u8;
const SPACING_MARK: u8 = GraphemeClusterBreak::SpacingMark as u8;
const PREPEND: u8 = GraphemeClusterBreak::Prepend as u8;
const REGIONAL_INDICATOR: u8 = GraphemeClusterBreak::RegionalIndicator as u8;
const CLASS_MASK: u8 = 0xF;
const CONJUNCT_MASK: u8 = 0x30;
const CONJUNCT_CONSONANT: u8 = 0x10;
const CONJUNCT_EXTEND: u8 = 0x20;
const CONJUNCT_LINKER: u8 = 0x30;
const PICTOGRAPHIC: u8 = 0x40;
#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
pub struct GraphemeProps(u8);
impl GraphemeProps {
#[inline]
pub(crate) const fn from_bits(bits: u8) -> Self {
Self(bits)
}
#[inline]
const fn class(self) -> u8 {
self.0 & CLASS_MASK
}
#[inline]
const fn joining_flags(self) -> u8 {
let mut flags = 0;
if self.0 & CONJUNCT_MASK == CONJUNCT_CONSONANT {
flags |= AFTER_LINKER;
}
if self.0 & PICTOGRAPHIC != 0 {
flags |= AFTER_PICTOGRAPHIC_ZWJ;
}
if self.class() == REGIONAL_INDICATOR {
flags |= ODD_REGIONAL_INDICATORS;
}
flags
}
}
#[inline]
pub fn grapheme_props(ch: char) -> GraphemeProps {
match LATIN_1.get(ch as usize) {
Some(&props) => GraphemeProps(props),
None => crate::rare_props(ch).grapheme_props(),
}
}
static LATIN_1: [u8; 256] = {
let mut table = [OTHER; 256];
let mut ch = 0;
while ch < 256 {
table[ch] = match ch {
0x0D => CR,
0x0A => LF,
0x00..=0x1F | 0x7F..=0x9F | 0xAD => CONTROL,
0xA9 | 0xAE => OTHER | PICTOGRAPHIC,
_ => OTHER,
};
ch += 1;
}
table
};
static JOINS: [u16; 16] = {
let mut table = [0; 16];
let mut left = 0;
while left < 16 {
let mut right = 0;
while right < 16 {
if joins(left as u8, right as u8) {
table[left] |= 1 << right;
}
right += 1;
}
left += 1;
}
table
};
const fn joins(left: u8, right: u8) -> bool {
match (left, right) {
(CR, LF) => true,
(CONTROL | CR | LF, _) | (_, CONTROL | CR | LF) => false,
(L, L | V | LV | LVT) | (LV | V, V | T) | (LVT | T, T) => true,
(_, EXTEND | ZWJ | SPACING_MARK) | (PREPEND, _) => true,
_ => false,
}
}
const ODD_REGIONAL_INDICATORS: u8 = 1;
const AFTER_PICTOGRAPHIC: u8 = 2;
const AFTER_PICTOGRAPHIC_ZWJ: u8 = 4;
const AFTER_LINKER: u8 = 8;
#[cfg(test)]
const AFTER_CONSONANT: u8 = 16;
#[cfg(test)]
const UNICODE_17_CONJUNCTS: u8 = 32;
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct GraphemeState {
left: u8,
flags: u8,
}
impl GraphemeState {
#[inline]
pub const fn new() -> Self {
Self {
left: CONTROL,
flags: 0,
}
}
#[cfg(test)]
pub(crate) const fn with_unicode_17_conjuncts() -> Self {
Self {
left: CONTROL,
flags: UNICODE_17_CONJUNCTS,
}
}
#[inline]
pub fn advance(&mut self, next: GraphemeProps) -> bool {
let class = next.class();
let joined = JOINS[usize::from(self.left & CLASS_MASK)] & (1u16 << class) != 0;
let boundary = !joined && (self.flags == 0 || self.flags & next.joining_flags() == 0);
self.flags = self.next_flags(next);
self.left = class;
boundary
}
#[inline]
const fn next_flags(&self, next: GraphemeProps) -> u8 {
let old = self.flags;
let class = next.class();
let mut flags = 0;
if next.0 & PICTOGRAPHIC != 0 {
flags |= AFTER_PICTOGRAPHIC;
}
if old & AFTER_PICTOGRAPHIC != 0 {
if class == EXTEND {
flags |= AFTER_PICTOGRAPHIC;
} else if class == ZWJ {
flags |= AFTER_PICTOGRAPHIC_ZWJ;
}
}
if class == REGIONAL_INDICATOR && old & ODD_REGIONAL_INDICATORS == 0 {
flags |= ODD_REGIONAL_INDICATORS;
}
match next.0 & CONJUNCT_MASK {
CONJUNCT_LINKER => flags |= AFTER_LINKER,
CONJUNCT_EXTEND => flags |= old & AFTER_LINKER,
_ => {}
}
#[cfg(test)]
if old & UNICODE_17_CONJUNCTS != 0 {
flags = (flags & !AFTER_LINKER) | unicode_17_conjuncts(old, next);
}
flags
}
}
impl Default for GraphemeState {
#[inline]
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
const fn unicode_17_conjuncts(old: u8, next: GraphemeProps) -> u8 {
let after_consonant = old & AFTER_CONSONANT != 0;
let mut flags = UNICODE_17_CONJUNCTS;
match next.0 & CONJUNCT_MASK {
CONJUNCT_CONSONANT => flags |= AFTER_CONSONANT,
CONJUNCT_LINKER if after_consonant => flags |= AFTER_CONSONANT | AFTER_LINKER,
CONJUNCT_EXTEND if after_consonant => flags |= AFTER_CONSONANT | (old & AFTER_LINKER),
_ => {}
}
flags
}
#[derive(Clone, Debug)]
pub struct Graphemes<I> {
scalars: I,
state: GraphemeState,
least: Option<usize>,
}
impl<I> Graphemes<I> {
#[inline]
pub const fn new(scalars: I) -> Self {
Self {
scalars,
state: GraphemeState::new(),
least: Some(0),
}
}
}
impl<I: Iterator<Item = (usize, char)>> Iterator for Graphemes<I> {
type Item = usize;
#[inline]
fn next(&mut self) -> Option<usize> {
let mut least = self.least?;
for (offset, ch) in self.scalars.by_ref() {
if offset < least {
break;
}
least = offset.saturating_add(1);
if self.state.advance(grapheme_props(ch)) {
self.least = Some(least);
return Some(offset);
}
}
self.least = None;
None
}
}
impl<I: Iterator<Item = (usize, char)>> core::iter::FusedIterator for Graphemes<I> {}