use crate::{
_impl_init, CharIter, Slice, TextCohesion, TextCursor, TextFit, TextIndex, TextLayoutSpan,
TextLayoutStep, TextSymbol, TextUnit, is, unwrap,
};
#[doc = crate::_tags!(text layout namespace)]
#[doc = crate::_doc_location!("text/layout")]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub struct TextLayout;
_impl_init![ConstInit: Self => TextLayout];
impl TextLayout {
pub const fn step(
&self,
symbols: &[TextSymbol],
cursor: TextCursor,
extent: Option<TextUnit>, out_spans: &mut [TextLayoutSpan],
) -> TextLayoutStep {
let mut index = cursor.index.as_usize();
let mut remaining = unwrap![some_or extent, TextUnit::MAX];
let mut consumed: TextUnit = 0;
let mut span_start: Option<usize> = None;
let mut span_units: TextUnit = 0;
let mut partial_break: bool = false;
let mut partial_index: usize = 0;
macro_rules! consume_whole {
($sym:ident) => {{
is![span_start.is_none(), span_start = Some(index)];
span_units += $sym.units;
consumed += $sym.units;
remaining -= $sym.units;
index += 1;
}};
}
while index < symbols.len() && remaining > 0 {
let sym = symbols[index];
match sym.cohesion {
TextCohesion::Elidable => index += 1,
TextCohesion::Atomic => is![sym.units <= remaining, consume_whole!(sym), break],
TextCohesion::Breakable => {
if sym.units <= remaining {
consume_whole!(sym);
} else {
is![span_start.is_none(), span_start = Some(index)];
span_units += remaining;
consumed += remaining;
partial_break = true;
partial_index = index;
break;
}
}
}
}
let mut span_count = 0;
if let Some(start) = span_start {
let end = is![partial_break, (partial_index + 1) as u32, index as u32];
out_spans[0] = TextLayoutSpan::from_prim(start as u32, end, span_units);
span_count = 1;
}
let fit = if consumed == 0 {
TextFit::None
} else if index >= symbols.len() {
TextFit::Full
} else {
TextFit::Partial
};
let carry = if partial_break {
Some(TextCursor { index: TextIndex(partial_index as u32) })
} else if index < symbols.len() {
Some(TextCursor { index: TextIndex(index as u32) })
} else {
None
};
TextLayoutStep { span_count, consumed, carry, fit }
}
pub const fn prepare_symbols_from_chars<'a>(
&self,
text: &'a str,
buf: &'a mut [TextSymbol],
) -> &'a [TextSymbol] {
let len = CharIter::<&str>::new(text).fill_text_symbols(buf);
Slice::range_to(buf, len)
}
pub fn fill_symbols_from_chars_with<F>(text: &str, out: &mut [TextSymbol], mut f: F) -> usize
where
F: FnMut(char) -> TextSymbol,
{
let mut it = CharIter::<&str>::new(text);
let mut len = 0;
while let Some(ch) = it.next_char() {
is![len == out.len(), break];
out[len] = f(ch);
len += 1;
}
len
}
pub fn fill_symbols_from_chars_with_index<F>(
text: &str,
out: &mut [TextSymbol],
mut f: F,
) -> usize
where
F: FnMut(usize, char) -> TextSymbol,
{
let mut it = CharIter::<&str>::new(text);
let mut i = 0;
let mut len = 0;
while let Some(ch) = it.next_char() {
is![len == out.len(), break];
out[len] = f(i, ch);
i += 1;
len += 1;
}
len
}
}