use super::{Overflow, Text};
use crate::style::Span;
use unicode_width::UnicodeWidthStr;
pub(crate) fn split_spans_on_newlines(spans: &[Span]) -> Vec<Vec<Span>> {
let mut lines: Vec<Vec<Span>> = Vec::new();
let mut current_line: Vec<Span> = Vec::new();
for span in spans {
for (i, part) in span.content.split('\n').enumerate() {
if i > 0 {
lines.push(std::mem::take(&mut current_line));
}
if !part.is_empty() {
current_line.push(Span::new(part).style(span.style));
}
}
}
lines.push(current_line);
lines
}
pub fn measure_text_constrained(text: &Text, max_w: Option<u16>) -> (u16, u16) {
let wrap_width = if matches!(text.overflow, Overflow::Wrap | Overflow::Auto) {
max_w
} else {
None
};
let logical_lines = split_spans_on_newlines(&text.spans);
let mut max_line_w = 0usize;
let mut total_h = 0usize;
for logical_line in &logical_lines {
let logical_w: usize = logical_line
.iter()
.map(|s| UnicodeWidthStr::width(s.content.as_ref()))
.sum();
max_line_w = max_line_w.max(logical_w);
match wrap_width {
Some(ww) if ww > 0 => {
let wrapped = crate::utils::text::wrap_spans_for_budgets(logical_line, ww, ww);
total_h += wrapped.len();
}
Some(_) => {
}
None => {
total_h += 1;
}
}
}
let width = max_w
.map(|w| max_line_w.min(w as usize))
.unwrap_or(max_line_w);
let h = total_h.max(1).min(u16::MAX as usize) as u16;
let w = width.min(u16::MAX as usize) as u16;
(w, h)
}
#[cfg(test)]
mod tests {
use crate::style::Span;
use crate::widgets::{Overflow, Text};
use super::measure_text_constrained;
#[test]
fn measures_newlines_across_span_boundaries() {
let text = Text::from_spans([Span::from("ab"), Span::from("\ncd")]);
assert_eq!(measure_text_constrained(&text, None), (2, 2));
}
#[test]
fn wraps_across_span_boundaries_without_flattening() {
let text =
Text::from_spans([Span::from("hello "), Span::from("world")]).overflow(Overflow::Wrap);
assert_eq!(measure_text_constrained(&text, Some(8)), (8, 2));
}
#[test]
fn wraps_at_slash_break_char() {
let text = Text::new("foo/bar/baz").overflow(Overflow::Wrap);
assert_eq!(measure_text_constrained(&text, Some(5)), (5, 3));
}
#[test]
fn wraps_at_dot_break_char() {
let text = Text::new("src.widgets.text").overflow(Overflow::Wrap);
assert_eq!(measure_text_constrained(&text, Some(6)), (6, 3));
}
#[test]
fn long_word_force_breaks() {
let text = Text::new("abcdefghij").overflow(Overflow::Wrap);
assert_eq!(measure_text_constrained(&text, Some(4)), (4, 3));
}
#[test]
fn wraps_wide_characters_correctly() {
let text = Text::new("你好世界").overflow(Overflow::Wrap);
assert_eq!(measure_text_constrained(&text, Some(5)), (5, 2));
assert_eq!(measure_text_constrained(&text, Some(3)), (3, 4));
}
}