use core::marker::PhantomData;
use crate::{encoding::Encoding, grapheme::next_cluster, unit::Unit, utf8::Utf8};
pub struct Wrapped<'a, E: Encoding> {
rest: &'a [E::Unit],
max_width: usize,
done: bool,
marker: PhantomData<E>,
}
impl<E: Encoding> Clone for Wrapped<'_, E> {
#[inline]
fn clone(&self) -> Self {
Self {
rest: self.rest,
max_width: self.max_width,
done: self.done,
marker: PhantomData,
}
}
}
#[inline(always)]
fn unit_of<E: Encoding>(ascii: u8) -> E::Unit {
let unit = E::Unit::from_u32(ascii as u32);
if E::FOREIGN { unit.swap_bytes() } else { unit }
}
impl<'a, E: Encoding> Iterator for Wrapped<'a, E> {
type Item = &'a [E::Unit];
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.done {
return None;
}
let s = self.rest;
let space = unit_of::<E>(b' ');
let (lf, cr) = (unit_of::<E>(b'\n'), unit_of::<E>(b'\r'));
let mut pos = 0;
let mut width = 0usize;
let mut candidate = None;
let mut trail = None;
loop {
if pos == s.len() {
self.done = true;
return Some(&s[..trail.unwrap_or(pos)]);
}
let scan = next_cluster::<E>(&s[pos..]);
let first = s[pos];
if first == lf || first == cr {
self.rest = &s[pos + scan.units..];
return Some(&s[..trail.unwrap_or(pos)]);
}
if scan.units == 1 && first == space {
if trail.is_none() {
trail = Some(pos);
candidate = Some(pos);
}
} else {
trail = None;
}
if width.saturating_add(scan.width) > self.max_width && pos > 0 {
if let Some(cut) = candidate {
let mut resume = cut;
while resume < s.len() {
let next = next_cluster::<E>(&s[resume..]);
if next.units == 1 && s[resume] == space {
resume += 1;
} else {
break;
}
}
self.rest = &s[resume..];
return Some(&s[..cut]);
}
self.rest = &s[pos..];
return Some(&s[..pos]);
}
pos += scan.units;
width = width.saturating_add(scan.width);
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
(usize::from(!self.done), Some(self.rest.len() + 1))
}
}
#[inline]
pub const fn wrap<E: Encoding>(input: &[E::Unit], max_width: usize) -> Wrapped<'_, E> {
Wrapped { rest: input, max_width, done: false, marker: PhantomData }
}
#[inline]
pub fn wrap_str(input: &str, max_width: usize) -> impl Iterator<Item = &str> + Clone {
wrap::<Utf8>(input.as_bytes(), max_width).map(|line| {
unsafe { core::str::from_utf8_unchecked(line) }
})
}