1use bitflags::bitflags;
21use std::hash::{Hash, Hasher};
22
23#[repr(C)]
28#[derive(Clone, Copy, PartialEq, Eq, Default, Hash)]
29pub struct Rgb {
30 pub r: u8,
32 pub g: u8,
34 pub b: u8,
36}
37
38impl Rgb {
39 #[inline]
41 pub const fn new(r: u8, g: u8, b: u8) -> Self {
42 Self { r, g, b }
43 }
44
45 pub const BLACK: Self = Self::new(0, 0, 0);
47 pub const WHITE: Self = Self::new(255, 255, 255);
49 pub const DEFAULT_FG: Self = Self::WHITE;
51 pub const DEFAULT_BG: Self = Self::BLACK;
53
54 #[inline]
56 pub const fn from_u32(hex: u32) -> Self {
57 Self::new(
58 ((hex >> 16) & 0xFF) as u8,
59 ((hex >> 8) & 0xFF) as u8,
60 (hex & 0xFF) as u8,
61 )
62 }
63}
64
65impl std::fmt::Debug for Rgb {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 write!(f, "#{:02x}{:02x}{:02x}", self.r, self.g, self.b)
68 }
69}
70
71impl From<(u8, u8, u8)> for Rgb {
72 #[inline]
73 fn from((r, g, b): (u8, u8, u8)) -> Self {
74 Self::new(r, g, b)
75 }
76}
77
78impl From<u32> for Rgb {
79 #[inline]
81 fn from(hex: u32) -> Self {
82 Self::new(
83 ((hex >> 16) & 0xFF) as u8,
84 ((hex >> 8) & 0xFF) as u8,
85 (hex & 0xFF) as u8,
86 )
87 }
88}
89
90bitflags! {
91 #[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
101 pub struct Modifiers: u8 {
102 const BOLD = 0b0000_0001;
104 const DIM = 0b0000_0010;
106 const ITALIC = 0b0000_0100;
108 const UNDERLINE = 0b0000_1000;
110 const BLINK = 0b0001_0000;
112 const REVERSED = 0b0010_0000;
114 const HIDDEN = 0b0100_0000;
116 const STRIKETHROUGH = 0b1000_0000;
118 }
119}
120
121impl std::fmt::Debug for Modifiers {
122 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123 bitflags::parser::to_writer(self, f)
124 }
125}
126
127bitflags! {
128 #[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
130 pub struct CellFlags: u8 {
131 const OVERFLOW = 0b0000_0001;
133 const DIRTY = 0b0000_0010;
135 const WIDE_CONTINUATION = 0b0000_0100;
137 }
138}
139
140impl std::fmt::Debug for CellFlags {
141 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142 bitflags::parser::to_writer(self, f)
143 }
144}
145
146#[repr(C)]
170#[derive(Clone, Copy)]
171pub struct Cell {
172 grapheme: [u8; 4],
175 grapheme_len: u8,
177 display_width: u8,
179 fg: Rgb,
181 bg: Rgb,
183 modifiers: Modifiers,
185 flags: CellFlags,
187 _padding: [u8; 2],
189}
190
191const _: () = assert!(
193 std::mem::size_of::<Cell>() == 16,
194 "Cell must be exactly 16 bytes for cache efficiency"
195);
196
197impl Default for Cell {
198 fn default() -> Self {
199 Self::EMPTY
200 }
201}
202
203impl Cell {
204 pub const EMPTY: Self = Self {
206 grapheme: [b' ', 0, 0, 0],
207 grapheme_len: 1,
208 display_width: 1,
209 fg: Rgb::DEFAULT_FG,
210 bg: Rgb::DEFAULT_BG,
211 modifiers: Modifiers::empty(),
212 flags: CellFlags::empty(),
213 _padding: [0, 0],
214 };
215
216 #[inline]
221 pub fn new(c: char) -> Self {
222 debug_assert!(c.is_ascii(), "Use Cell::from_char for non-ASCII");
223 Self {
224 grapheme: [c as u8, 0, 0, 0],
225 grapheme_len: 1,
226 display_width: 1,
227 fg: Rgb::DEFAULT_FG,
228 bg: Rgb::DEFAULT_BG,
229 modifiers: Modifiers::empty(),
230 flags: CellFlags::empty(),
231 _padding: [0, 0],
232 }
233 }
234
235 #[inline]
241 #[allow(clippy::missing_panics_doc)]
242 pub fn from_char(c: char) -> Self {
243 let mut grapheme = [0u8; 4];
244 let s = c.encode_utf8(&mut grapheme);
245 let len = u8::try_from(s.len()).unwrap();
246 let width = u8::try_from(unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)).unwrap();
247
248 Self {
249 grapheme,
250 grapheme_len: len,
251 display_width: width,
252 fg: Rgb::DEFAULT_FG,
253 bg: Rgb::DEFAULT_BG,
254 modifiers: Modifiers::empty(),
255 flags: CellFlags::empty(),
256 _padding: [0, 0],
257 }
258 }
259
260 #[inline]
265 #[allow(clippy::missing_panics_doc)]
266 pub fn from_grapheme(s: &str) -> Option<Self> {
267 let bytes = s.as_bytes();
268 if bytes.len() > 4 {
269 return None;
271 }
272
273 let mut grapheme = [0u8; 4];
274 grapheme[..bytes.len()].copy_from_slice(bytes);
275 let width = u8::try_from(unicode_width::UnicodeWidthStr::width(s)).unwrap_or(1);
276
277 Some(Self {
278 grapheme,
279 grapheme_len: u8::try_from(bytes.len()).unwrap(),
280 display_width: width,
281 fg: Rgb::DEFAULT_FG,
282 bg: Rgb::DEFAULT_BG,
283 modifiers: Modifiers::empty(),
284 flags: CellFlags::empty(),
285 _padding: [0, 0],
286 })
287 }
288
289 #[inline]
293 pub const fn overflow(index: u32, display_width: u8) -> Self {
294 Self {
295 grapheme: index.to_le_bytes(),
296 grapheme_len: 0, display_width,
298 fg: Rgb::DEFAULT_FG,
299 bg: Rgb::DEFAULT_BG,
300 modifiers: Modifiers::empty(),
301 flags: CellFlags::OVERFLOW,
302 _padding: [0, 0],
303 }
304 }
305
306 #[inline]
310 pub const fn wide_continuation() -> Self {
311 Self {
312 grapheme: [0, 0, 0, 0],
313 grapheme_len: 0,
314 display_width: 0,
315 fg: Rgb::DEFAULT_FG,
316 bg: Rgb::DEFAULT_BG,
317 modifiers: Modifiers::empty(),
318 flags: CellFlags::WIDE_CONTINUATION,
319 _padding: [0, 0],
320 }
321 }
322
323 #[inline]
328 pub fn grapheme(&self) -> Option<&str> {
329 if self.flags.contains(CellFlags::OVERFLOW) {
330 return None;
331 }
332 std::str::from_utf8(&self.grapheme[..self.grapheme_len as usize]).ok()
334 }
335
336 #[inline]
338 pub const fn overflow_index(&self) -> Option<u32> {
339 if self.flags.contains(CellFlags::OVERFLOW) {
340 Some(u32::from_le_bytes(self.grapheme))
341 } else {
342 None
343 }
344 }
345
346 #[inline]
348 pub const fn is_overflow(&self) -> bool {
349 self.flags.contains(CellFlags::OVERFLOW)
350 }
351
352 #[inline]
354 pub const fn is_wide_continuation(&self) -> bool {
355 self.flags.contains(CellFlags::WIDE_CONTINUATION)
356 }
357
358 #[inline]
360 pub const fn display_width(&self) -> u8 {
361 self.display_width
362 }
363
364 #[inline]
366 pub const fn fg(&self) -> Rgb {
367 self.fg
368 }
369
370 #[inline]
372 pub const fn bg(&self) -> Rgb {
373 self.bg
374 }
375
376 #[inline]
378 pub const fn modifiers(&self) -> Modifiers {
379 self.modifiers
380 }
381
382 #[inline]
384 pub const fn flags(&self) -> CellFlags {
385 self.flags
386 }
387
388 #[inline]
390 pub const fn set_fg(&mut self, fg: Rgb) -> &mut Self {
391 self.fg = fg;
392 self
393 }
394
395 #[inline]
397 pub const fn set_bg(&mut self, bg: Rgb) -> &mut Self {
398 self.bg = bg;
399 self
400 }
401
402 #[inline]
404 pub const fn set_modifiers(&mut self, modifiers: Modifiers) -> &mut Self {
405 self.modifiers = modifiers;
406 self
407 }
408
409 #[inline]
411 #[must_use]
412 pub const fn with_fg(mut self, fg: Rgb) -> Self {
413 self.fg = fg;
414 self
415 }
416
417 #[inline]
419 #[must_use]
420 pub const fn with_bg(mut self, bg: Rgb) -> Self {
421 self.bg = bg;
422 self
423 }
424
425 #[inline]
427 #[must_use]
428 pub const fn with_modifiers(mut self, modifiers: Modifiers) -> Self {
429 self.modifiers = modifiers;
430 self
431 }
432
433 #[inline]
435 pub const fn reset(&mut self) {
436 *self = Self::EMPTY;
437 }
438}
439
440impl PartialEq for Cell {
441 #[inline]
448 fn eq(&self, other: &Self) -> bool {
449 self.grapheme == other.grapheme
451 && self.grapheme_len == other.grapheme_len
452 && self.fg == other.fg
453 && self.bg == other.bg
454 && self.modifiers == other.modifiers
455 && self.flags == other.flags
456 && self.display_width == other.display_width
457 }
458}
459
460impl Eq for Cell {}
461
462impl Hash for Cell {
463 fn hash<H: Hasher>(&self, state: &mut H) {
464 self.grapheme.hash(state);
465 self.grapheme_len.hash(state);
466 self.display_width.hash(state);
467 self.fg.hash(state);
468 self.bg.hash(state);
469 self.modifiers.hash(state);
470 self.flags.hash(state);
471 }
472}
473
474impl std::fmt::Debug for Cell {
475 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
476 let grapheme = self.grapheme().unwrap_or("<overflow>");
477 f.debug_struct("Cell")
478 .field("grapheme", &grapheme)
479 .field("width", &self.display_width)
480 .field("fg", &self.fg)
481 .field("bg", &self.bg)
482 .field("modifiers", &self.modifiers)
483 .field("flags", &self.flags)
484 .finish_non_exhaustive()
485 }
486}
487
488#[cfg(test)]
489mod tests {
490 use super::*;
491
492 #[test]
493 fn test_cell_size() {
494 assert_eq!(std::mem::size_of::<Cell>(), 16);
495 }
496
497 #[test]
498 fn test_rgb_from_tuple() {
499 let rgb: Rgb = (255, 128, 0).into();
500 assert_eq!(rgb.r, 255);
501 assert_eq!(rgb.g, 128);
502 assert_eq!(rgb.b, 0);
503 }
504
505 #[test]
506 fn test_rgb_from_hex() {
507 let rgb: Rgb = 0xFF8000.into();
508 assert_eq!(rgb.r, 255);
509 assert_eq!(rgb.g, 128);
510 assert_eq!(rgb.b, 0);
511 }
512
513 #[test]
514 fn test_cell_new_ascii() {
515 let cell = Cell::new('A');
516 assert_eq!(cell.grapheme(), Some("A"));
517 assert_eq!(cell.display_width(), 1);
518 }
519
520 #[test]
521 fn test_cell_from_char_unicode() {
522 let cell = Cell::from_char('日');
523 assert_eq!(cell.grapheme(), Some("日"));
524 assert_eq!(cell.display_width(), 2); }
526
527 #[test]
528 fn test_cell_from_grapheme_fits() {
529 let cell = Cell::from_grapheme("é").unwrap();
530 assert_eq!(cell.grapheme(), Some("é"));
531 assert_eq!(cell.display_width(), 1);
532 }
533
534 #[test]
535 fn test_cell_from_grapheme_overflow() {
536 let result = Cell::from_grapheme("👨👩👧");
538 assert!(result.is_none());
539 }
540
541 #[test]
542 fn test_cell_overflow() {
543 let cell = Cell::overflow(42, 2);
544 assert!(cell.is_overflow());
545 assert_eq!(cell.overflow_index(), Some(42));
546 assert_eq!(cell.grapheme(), None);
547 }
548
549 #[test]
550 fn test_cell_equality() {
551 let a = Cell::new('A').with_fg(Rgb::new(255, 0, 0));
552 let b = Cell::new('A').with_fg(Rgb::new(255, 0, 0));
553 let c = Cell::new('A').with_fg(Rgb::new(0, 255, 0));
554
555 assert_eq!(a, b);
556 assert_ne!(a, c);
557 }
558
559 #[test]
560 fn test_cell_builder_pattern() {
561 let cell = Cell::new('X')
562 .with_fg(Rgb::new(255, 0, 0))
563 .with_bg(Rgb::new(0, 0, 255))
564 .with_modifiers(Modifiers::BOLD | Modifiers::ITALIC);
565
566 assert_eq!(cell.fg(), Rgb::new(255, 0, 0));
567 assert_eq!(cell.bg(), Rgb::new(0, 0, 255));
568 assert!(cell.modifiers().contains(Modifiers::BOLD));
569 assert!(cell.modifiers().contains(Modifiers::ITALIC));
570 }
571
572 #[test]
573 fn test_modifiers_bitflags() {
574 let mods = Modifiers::BOLD | Modifiers::UNDERLINE;
575 assert!(mods.contains(Modifiers::BOLD));
576 assert!(mods.contains(Modifiers::UNDERLINE));
577 assert!(!mods.contains(Modifiers::ITALIC));
578 }
579
580 #[test]
581 fn test_cell_reset() {
582 let mut cell = Cell::new('X').with_fg(Rgb::new(255, 0, 0));
583 cell.reset();
584 assert_eq!(cell, Cell::EMPTY);
585 }
586
587 #[test]
588 fn test_wide_continuation() {
589 let cont = Cell::wide_continuation();
590 assert!(cont.is_wide_continuation());
591 assert_eq!(cont.display_width(), 0);
592 }
593}