use unicode_segmentation::UnicodeSegmentation as _;
use unicode_width::UnicodeWidthChar as _;
use super::{Cells, Inner, Surface};
use crate::colour::{Attrs, Colour, Style};
use crate::geometry::Rect;
const CELL_WORDS: usize = 4;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Cell {
pub grapheme: Option<char>,
pub style: Style,
}
fn is_forbidden(character: char) -> bool {
character.is_control()
|| matches!(
character,
'\u{061c}' | '\u{200e}' | '\u{200f}' | '\u{202a}'..='\u{202e}' | '\u{2066}'..='\u{2069}'
)
}
pub(crate) fn char_width(character: char) -> u32 {
if is_forbidden(character) {
return 0;
}
match character.width() {
Some(1) => 1,
Some(2) => 2,
_ => 0,
}
}
fn word_width(word: u32) -> u32 {
if word == 0 || word >= 0x8000_0000 {
return 0;
}
char::from_u32(word).map_or(0, char_width)
}
struct Grid<'a> {
slot: &'a mut [u32],
cols: u32,
rows: u32,
}
impl Grid<'_> {
fn index(&self, x: u32, y: u32) -> usize {
(y as usize * self.cols as usize + x as usize) * CELL_WORDS
}
fn grapheme(&self, x: u32, y: u32) -> u32 {
self.slot[self.index(x, y)]
}
fn write(&mut self, x: u32, y: u32, grapheme: u32, style: Style) {
let index = self.index(x, y);
self.slot[index..index + CELL_WORDS].copy_from_slice(&[
grapheme,
style.fg.encode(),
style.bg.encode(),
u32::from(style.attrs.0),
]);
}
fn put(&mut self, x: u32, y: u32, character: u32, width: u32, style: Style) -> Rect {
let mut left = x;
let mut right = x + width;
if x > 0 && word_width(self.grapheme(x - 1, y)) == 2 {
let index = self.index(x - 1, y);
self.slot[index] = 0;
left = x - 1;
}
let last = x + width - 1;
if word_width(self.grapheme(last, y)) == 2 && last + 1 < self.cols {
right = last + 2;
}
self.write(x, y, character, style);
if width == 2 {
self.write(x + 1, y, 0, style);
}
Rect::new(left, y, right - left, 1)
}
}
impl Inner {
fn grid(&mut self) -> Option<(Grid<'_>, &mut super::DirtyRects)> {
let (slot, layout, dirty) = self.back()?;
let g = layout.geometry;
Some((
Grid {
slot,
cols: g.cols,
rows: g.rows,
},
dirty,
))
}
}
impl Surface<Cells> {
pub fn put(&mut self, x: u32, y: u32, character: char, style: Style) -> u32 {
let mut inner = self.inner.borrow_mut();
let Some((mut grid, dirty)) = inner.grid() else {
return 0;
};
put_char(&mut grid, dirty, x, y, character, style)
}
pub fn text(&mut self, x: u32, y: u32, text: &str, style: Style) -> u32 {
write_text_clipped(self, i64::from(x), i64::from(y), text, style)
}
pub fn fill(&mut self, rect: Rect, character: char, style: Style) {
let mut inner = self.inner.borrow_mut();
let Some((mut grid, dirty)) = inner.grid() else {
return;
};
let Some(rect) = rect.clamp(grid.cols, grid.rows) else {
return;
};
let step = char_width(character).max(1);
for y in rect.y..rect.bottom() {
let mut x = rect.x;
while x < rect.right() {
if x + step > rect.right() {
put_char(&mut grid, dirty, x, y, ' ', style);
break;
}
put_char(&mut grid, dirty, x, y, character, style);
x += step;
}
}
}
pub fn clear(&mut self) {
self.clear_with(Style::DEFAULT);
}
pub fn clear_with(&mut self, style: Style) {
let mut inner = self.inner.borrow_mut();
let Some((slot, _)) = inner.back_discard() else {
return;
};
let words = [
0,
style.fg.encode(),
style.bg.encode(),
u32::from(style.attrs.0),
];
for cell in slot.chunks_exact_mut(CELL_WORDS) {
cell.copy_from_slice(&words);
}
}
pub fn cell(&self, x: u32, y: u32) -> Option<Cell> {
let mut inner = self.inner.borrow_mut();
let (grid, _) = inner.grid()?;
if x >= grid.cols || y >= grid.rows {
return None;
}
let index = grid.index(x, y);
let words = &grid.slot[index..index + CELL_WORDS];
Some(Cell {
grapheme: (word_width(words[0]) > 0)
.then(|| char::from_u32(words[0]))
.flatten(),
style: Style {
fg: Colour::decode(words[1]),
bg: Colour::decode(words[2]),
attrs: Attrs(words[3] as u16),
},
})
}
pub fn row_text(&self, y: u32) -> alloc::string::String {
let (cols, _) = self.size();
let mut text = alloc::string::String::new();
let mut x = 0;
while x < cols {
let Some(cell) = self.cell(x, y) else {
break;
};
match cell.grapheme {
Some(character) => {
text.push(character);
x += char_width(character);
}
None => {
text.push(' ');
x += 1;
}
}
}
text
}
}
fn put_char(
grid: &mut Grid<'_>,
dirty: &mut super::DirtyRects,
x: u32,
y: u32,
character: char,
style: Style,
) -> u32 {
if x >= grid.cols || y >= grid.rows {
return 0;
}
let width = char_width(character);
if width == 0 {
return 0;
}
let (word, width) = if x + width > grid.cols {
(0, 1)
} else {
(character as u32, width)
};
let rect = grid.put(x, y, word, width, style);
dirty.add(rect, grid.cols, grid.rows);
width
}
pub(crate) fn write_text_clipped(
surface: &mut Surface<Cells>,
x: i64,
y: i64,
text: &str,
style: Style,
) -> u32 {
let mut inner = surface.inner.borrow_mut();
let Some((mut grid, dirty)) = inner.grid() else {
return 0;
};
if y < 0 || y >= i64::from(grid.rows) {
return 0;
}
let mut column = x;
let mut written = 0;
for grapheme in text.graphemes(true) {
let Some(character) = grapheme.chars().next() else {
continue;
};
let width = char_width(character);
if width == 0 {
continue;
}
if column >= i64::from(grid.cols) {
break;
}
if column >= 0 {
written += put_char(&mut grid, dirty, column as u32, y as u32, character, style);
} else if column + i64::from(width) > 0 {
written += put_char(&mut grid, dirty, 0, y as u32, ' ', style);
}
column += i64::from(width);
}
written
}