use std::fmt::Write as _;
use ratatui::{buffer::CellDiffOption, layout::Rect, style::Color, widgets::Widget};
use unicode_width::UnicodeWidthChar as _;
fn unicode_chunks(chars: &[char], max_width: u8) -> impl Iterator<Item = (&[char], u8)> {
let mut start = 0;
std::iter::from_fn(move || {
if start >= chars.len() {
return None;
}
let mut end = start;
let mut width = 0;
while end < chars.len() {
let char_width = chars[end].width().unwrap_or(1) as u8;
if char_width > 1 {
if width > 0 {
break; }
width = char_width;
end += 1;
break;
}
if width + char_width > max_width {
break;
}
width += char_width;
end += 1;
}
let chunk = &chars[start..end];
start = end;
Some((chunk, width))
})
}
pub struct BigText<'a> {
text: &'a str,
tier: u8,
color: Option<Color>,
}
impl<'a> BigText<'a> {
pub fn new(text: &'a str, tier: u8, color: Option<Color>) -> Self {
BigText { text, tier, color }
}
pub fn size_ratio(tier: u8) -> (u8, u8) {
match tier {
1 => (7, 7),
2 => (5, 6),
3 => (3, 4),
4 => (2, 3),
5 => (3, 5),
_ => (1, 3),
}
}
fn text_sizing_sequence(&self, area_width: u16) -> String {
let (n, d) = BigText::size_ratio(self.tier);
let mut symbol = String::new();
write!(symbol, "\x1b[{}X\x1B[?7l", area_width).expect("write to string");
write!(symbol, "\x1b[1B").expect("write to string");
write!(symbol, "\x1b[{}X\x1B[?7l", area_width).expect("write to string");
write!(symbol, "\x1b[1A").expect("write to string");
if let Some(color) = self.color {
match color {
Color::Indexed(ansi_index) => {
write!(symbol, "\x1b[{ansi_index}m").expect("write to string");
}
Color::Rgb(r, g, b) => {
write!(symbol, "\x1b[38;2;{r};{g};{b}m").expect("write to string");
}
_ => {}
}
}
let chars: Vec<char> = self.text.chars().collect();
for (chunk, chunk_width) in unicode_chunks(&chars, d) {
let w = if chunk_width == d {
n
} else {
(chunk_width * n).div_ceil(d)
};
write!(symbol, "\x1b]66;s=2:n={n}:d={d}:w={w};").expect("write to string");
symbol.extend(chunk);
write!(symbol, "\x1b\\").expect("write to string");
}
if self.color.is_some() {
write!(symbol, "\x1b[0m").expect("write to string");
}
symbol
}
}
impl Widget for BigText<'_> {
fn render(self, area: Rect, buf: &mut ratatui::buffer::Buffer) {
if area.width == 0 || area.height == 0 {
return;
}
let symbol = self.text_sizing_sequence(area.width);
let mut skip_first = false;
for y in area.top()..area.bottom() {
for x in area.left()..area.right() {
if skip_first {
buf.cell_mut((x, y))
.map(|cell| cell.set_diff_option(CellDiffOption::Skip));
} else {
skip_first = true;
buf.cell_mut((x, y)).map(|cell| cell.set_symbol(&symbol));
}
}
}
}
}