1use crate::{GlyphRegistry, Style};
2use unicode_segmentation::UnicodeSegmentation;
3
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub enum Cell {
6 Empty,
7 Glyph {
8 grapheme: String,
9 style: Style,
10 },
11 Continuation,
13}
14
15impl Cell {
16 pub fn is_continuation(&self) -> bool {
17 matches!(self, Cell::Continuation)
18 }
19}
20
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct Grid {
23 pub width: u16,
24 pub height: u16,
25 cells: Vec<Cell>,
26}
27
28impl Grid {
29 pub fn new(width: u16, height: u16) -> Self {
30 let len = width as usize * height as usize;
31 Self {
32 width,
33 height,
34 cells: vec![Cell::Empty; len],
35 }
36 }
37
38 pub fn clear(&mut self) {
39 for c in &mut self.cells {
40 *c = Cell::Empty;
41 }
42 }
43
44 pub fn get(&self, x: u16, y: u16) -> Option<&Cell> {
45 let idx = self.idx(x, y)?;
46 self.cells.get(idx)
47 }
48
49 pub fn set(&mut self, x: u16, y: u16, cell: Cell) {
50 if let Some(idx) = self.idx(x, y) {
51 self.cells[idx] = cell;
52 }
53 }
54
55 pub fn idx(&self, x: u16, y: u16) -> Option<usize> {
56 if x >= self.width || y >= self.height {
57 return None;
58 }
59 Some(y as usize * self.width as usize + x as usize)
60 }
61
62 fn clear_overlaps_at(&mut self, x: u16, y: u16, reg: &GlyphRegistry) {
63 let here = self.get(x, y).cloned();
64
65 if matches!(here, Some(Cell::Continuation)) {
67 self.set(x, y, Cell::Empty);
68
69 if x > 0 {
70 if let Some(Cell::Glyph { grapheme, .. }) = self.get(x - 1, y).cloned() {
71 if reg.width(&grapheme) == 2 {
72 self.set(x - 1, y, Cell::Empty);
73 }
74 }
75 }
76 return;
77 }
78
79 if let Some(Cell::Glyph { grapheme, .. }) = here {
81 if reg.width(&grapheme) == 2
82 && x + 1 < self.width
83 && matches!(self.get(x + 1, y), Some(Cell::Continuation))
84 {
85 self.set(x + 1, y, Cell::Empty);
86 }
87 }
88
89 if x > 0 {
91 if let Some(Cell::Glyph { grapheme, .. }) = self.get(x - 1, y).cloned() {
92 if reg.width(&grapheme) == 2 && !matches!(self.get(x, y), Some(Cell::Continuation))
93 {
94 self.set(x - 1, y, Cell::Empty);
95 }
96 }
97 }
98 }
99
100 pub fn put_text(
105 &mut self,
106 mut x: u16,
107 y: u16,
108 text: &str,
109 style: Style,
110 reg: &GlyphRegistry,
111 ) -> u16 {
112 if y >= self.height {
113 return x;
114 }
115
116 for g in UnicodeSegmentation::graphemes(text, true) {
117 if g == "\n" || g == "\r" {
118 break;
119 }
120
121 let w = reg.width(g);
122 if x >= self.width {
123 break;
124 }
125 if w == 2 && x + 1 >= self.width {
127 break;
128 }
129
130 self.clear_overlaps_at(x, y, reg);
132 if w == 2 {
133 self.clear_overlaps_at(x + 1, y, reg);
134 }
135
136 self.set(
137 x,
138 y,
139 Cell::Glyph {
140 grapheme: g.to_string(),
141 style,
142 },
143 );
144
145 if w == 2 {
146 self.set(x + 1, y, Cell::Continuation);
147 x = x.saturating_add(2);
148 } else {
149 x = x.saturating_add(1);
150 }
151 }
152
153 x
154 }
155
156 pub fn rows(&self) -> impl Iterator<Item = &[Cell]> {
157 self.cells.chunks(self.width as usize)
158 }
159}
160
161#[derive(Debug, Clone, PartialEq, Eq)]
162pub enum InvariantError {
163 ContinuationAtColumn0 { y: u16 },
164 OrphanContinuation { x: u16, y: u16 },
165 MissingContinuationHalf { x: u16, y: u16 },
166}
167
168impl Grid {
169 pub fn validate_invariants(&self, reg: &GlyphRegistry) -> Result<(), InvariantError> {
174 for y in 0..self.height {
175 for x in 0..self.width {
176 let c = self.get(x, y).expect("in-bounds");
177 match c {
178 Cell::Continuation => {
179 if x == 0 {
180 return Err(InvariantError::ContinuationAtColumn0 { y });
181 }
182 let prev = self.get(x - 1, y).expect("in-bounds");
183 match prev {
184 Cell::Glyph { grapheme, .. } => {
185 if reg.width(grapheme) != 2 {
186 return Err(InvariantError::OrphanContinuation { x, y });
187 }
188 }
189 _ => return Err(InvariantError::OrphanContinuation { x, y }),
190 }
191 }
192 Cell::Glyph { grapheme, .. } => {
193 if reg.width(grapheme) == 2 {
194 if x + 1 >= self.width {
195 return Err(InvariantError::MissingContinuationHalf { x, y });
197 }
198 let next = self.get(x + 1, y).expect("in-bounds");
199 if !matches!(next, Cell::Continuation) {
200 return Err(InvariantError::MissingContinuationHalf { x, y });
201 }
202 }
203 }
204 Cell::Empty => {}
205 }
206 }
207 }
208 Ok(())
209 }
210}