1use unicode_segmentation::UnicodeSegmentation as _;
13use unicode_width::UnicodeWidthChar as _;
14
15use super::{Cells, Inner, Surface};
16use crate::colour::{Attrs, Colour, Style};
17use crate::geometry::Rect;
18
19const CELL_WORDS: usize = 4;
20
21#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
23pub struct Cell {
24 pub grapheme: Option<char>,
27 pub style: Style,
28}
29
30fn is_forbidden(character: char) -> bool {
33 character.is_control()
34 || matches!(
35 character,
36 '\u{061c}' | '\u{200e}' | '\u{200f}' | '\u{202a}'..='\u{202e}' | '\u{2066}'..='\u{2069}'
37 )
38}
39
40pub(crate) fn char_width(character: char) -> u32 {
43 if is_forbidden(character) {
44 return 0;
45 }
46 match character.width() {
47 Some(1) => 1,
48 Some(2) => 2,
49 _ => 0,
50 }
51}
52
53fn word_width(word: u32) -> u32 {
54 if word == 0 || word >= 0x8000_0000 {
55 return 0;
56 }
57 char::from_u32(word).map_or(0, char_width)
58}
59
60struct Grid<'a> {
61 slot: &'a mut [u32],
62 cols: u32,
63 rows: u32,
64}
65
66impl Grid<'_> {
67 fn index(&self, x: u32, y: u32) -> usize {
68 (y as usize * self.cols as usize + x as usize) * CELL_WORDS
69 }
70
71 fn grapheme(&self, x: u32, y: u32) -> u32 {
72 self.slot[self.index(x, y)]
73 }
74
75 fn write(&mut self, x: u32, y: u32, grapheme: u32, style: Style) {
76 let index = self.index(x, y);
77 self.slot[index..index + CELL_WORDS].copy_from_slice(&[
78 grapheme,
79 style.fg.encode(),
80 style.bg.encode(),
81 u32::from(style.attrs.0),
82 ]);
83 }
84
85 fn put(&mut self, x: u32, y: u32, character: u32, width: u32, style: Style) -> Rect {
90 let mut left = x;
91 let mut right = x + width;
92 if x > 0 && word_width(self.grapheme(x - 1, y)) == 2 {
95 let index = self.index(x - 1, y);
96 self.slot[index] = 0;
97 left = x - 1;
98 }
99 let last = x + width - 1;
102 if word_width(self.grapheme(last, y)) == 2 && last + 1 < self.cols {
103 right = last + 2;
104 }
105 self.write(x, y, character, style);
106 if width == 2 {
107 self.write(x + 1, y, 0, style);
108 }
109 Rect::new(left, y, right - left, 1)
110 }
111}
112
113impl Inner {
114 fn grid(&mut self) -> Option<(Grid<'_>, &mut super::DirtyRects)> {
115 let (slot, layout, dirty) = self.back()?;
116 let g = layout.geometry;
117 Some((
118 Grid {
119 slot,
120 cols: g.cols,
121 rows: g.rows,
122 },
123 dirty,
124 ))
125 }
126}
127
128impl Surface<Cells> {
129 pub fn put(&mut self, x: u32, y: u32, character: char, style: Style) -> u32 {
135 let mut inner = self.inner.borrow_mut();
136 let Some((mut grid, dirty)) = inner.grid() else {
137 return 0;
138 };
139 put_char(&mut grid, dirty, x, y, character, style)
140 }
141
142 pub fn text(&mut self, x: u32, y: u32, text: &str, style: Style) -> u32 {
146 write_text_clipped(self, i64::from(x), i64::from(y), text, style)
147 }
148
149 pub fn fill(&mut self, rect: Rect, character: char, style: Style) {
151 let mut inner = self.inner.borrow_mut();
152 let Some((mut grid, dirty)) = inner.grid() else {
153 return;
154 };
155 let Some(rect) = rect.clamp(grid.cols, grid.rows) else {
156 return;
157 };
158 let step = char_width(character).max(1);
159 for y in rect.y..rect.bottom() {
160 let mut x = rect.x;
161 while x < rect.right() {
162 if x + step > rect.right() {
163 put_char(&mut grid, dirty, x, y, ' ', style);
164 break;
165 }
166 put_char(&mut grid, dirty, x, y, character, style);
167 x += step;
168 }
169 }
170 }
171
172 pub fn clear(&mut self) {
175 self.clear_with(Style::DEFAULT);
176 }
177
178 pub fn clear_with(&mut self, style: Style) {
180 let mut inner = self.inner.borrow_mut();
181 let Some((slot, _)) = inner.back_discard() else {
182 return;
183 };
184 let words = [
185 0,
186 style.fg.encode(),
187 style.bg.encode(),
188 u32::from(style.attrs.0),
189 ];
190 for cell in slot.chunks_exact_mut(CELL_WORDS) {
191 cell.copy_from_slice(&words);
192 }
193 }
194
195 pub fn cell(&self, x: u32, y: u32) -> Option<Cell> {
197 let mut inner = self.inner.borrow_mut();
198 let (grid, _) = inner.grid()?;
199 if x >= grid.cols || y >= grid.rows {
200 return None;
201 }
202 let index = grid.index(x, y);
203 let words = &grid.slot[index..index + CELL_WORDS];
204 Some(Cell {
205 grapheme: (word_width(words[0]) > 0)
206 .then(|| char::from_u32(words[0]))
207 .flatten(),
208 style: Style {
209 fg: Colour::decode(words[1]),
210 bg: Colour::decode(words[2]),
211 attrs: Attrs(words[3] as u16),
212 },
213 })
214 }
215
216 pub fn row_text(&self, y: u32) -> alloc::string::String {
219 let (cols, _) = self.size();
220 let mut text = alloc::string::String::new();
221 let mut x = 0;
222 while x < cols {
223 let Some(cell) = self.cell(x, y) else {
224 break;
225 };
226 match cell.grapheme {
227 Some(character) => {
228 text.push(character);
229 x += char_width(character);
230 }
231 None => {
232 text.push(' ');
233 x += 1;
234 }
235 }
236 }
237 text
238 }
239}
240
241fn put_char(
242 grid: &mut Grid<'_>,
243 dirty: &mut super::DirtyRects,
244 x: u32,
245 y: u32,
246 character: char,
247 style: Style,
248) -> u32 {
249 if x >= grid.cols || y >= grid.rows {
250 return 0;
251 }
252 let width = char_width(character);
253 if width == 0 {
254 return 0;
255 }
256 let (word, width) = if x + width > grid.cols {
257 (0, 1)
258 } else {
259 (character as u32, width)
260 };
261 let rect = grid.put(x, y, word, width, style);
262 dirty.add(rect, grid.cols, grid.rows);
263 width
264}
265
266pub(crate) fn write_text_clipped(
269 surface: &mut Surface<Cells>,
270 x: i64,
271 y: i64,
272 text: &str,
273 style: Style,
274) -> u32 {
275 let mut inner = surface.inner.borrow_mut();
276 let Some((mut grid, dirty)) = inner.grid() else {
277 return 0;
278 };
279 if y < 0 || y >= i64::from(grid.rows) {
280 return 0;
281 }
282 let mut column = x;
283 let mut written = 0;
284 for grapheme in text.graphemes(true) {
285 let Some(character) = grapheme.chars().next() else {
286 continue;
287 };
288 let width = char_width(character);
289 if width == 0 {
290 continue;
291 }
292 if column >= i64::from(grid.cols) {
293 break;
294 }
295 if column >= 0 {
296 written += put_char(&mut grid, dirty, column as u32, y as u32, character, style);
297 } else if column + i64::from(width) > 0 {
298 written += put_char(&mut grid, dirty, 0, y as u32, ' ', style);
301 }
302 column += i64::from(width);
303 }
304 written
305}