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 #[allow(unsafe_code)]
329 pub fn grapheme(&self) -> Option<&str> {
330 if self.flags.contains(CellFlags::OVERFLOW) {
331 return None;
332 }
333 Some(unsafe {
335 std::str::from_utf8_unchecked(&self.grapheme[..self.grapheme_len as usize])
336 })
337 }
338
339 #[inline]
341 pub const fn overflow_index(&self) -> Option<u32> {
342 if self.flags.contains(CellFlags::OVERFLOW) {
343 Some(u32::from_le_bytes(self.grapheme))
344 } else {
345 None
346 }
347 }
348
349 #[inline]
351 pub const fn is_overflow(&self) -> bool {
352 self.flags.contains(CellFlags::OVERFLOW)
353 }
354
355 #[inline]
357 pub const fn is_wide_continuation(&self) -> bool {
358 self.flags.contains(CellFlags::WIDE_CONTINUATION)
359 }
360
361 #[inline]
363 pub const fn display_width(&self) -> u8 {
364 self.display_width
365 }
366
367 #[inline]
369 pub const fn fg(&self) -> Rgb {
370 self.fg
371 }
372
373 #[inline]
375 pub const fn bg(&self) -> Rgb {
376 self.bg
377 }
378
379 #[inline]
381 pub const fn modifiers(&self) -> Modifiers {
382 self.modifiers
383 }
384
385 #[inline]
387 pub const fn flags(&self) -> CellFlags {
388 self.flags
389 }
390
391 #[inline]
393 pub const fn set_fg(&mut self, fg: Rgb) -> &mut Self {
394 self.fg = fg;
395 self
396 }
397
398 #[inline]
400 pub const fn set_bg(&mut self, bg: Rgb) -> &mut Self {
401 self.bg = bg;
402 self
403 }
404
405 #[inline]
407 pub const fn set_modifiers(&mut self, modifiers: Modifiers) -> &mut Self {
408 self.modifiers = modifiers;
409 self
410 }
411
412 #[inline]
414 #[must_use]
415 pub const fn with_fg(mut self, fg: Rgb) -> Self {
416 self.fg = fg;
417 self
418 }
419
420 #[inline]
422 #[must_use]
423 pub const fn with_bg(mut self, bg: Rgb) -> Self {
424 self.bg = bg;
425 self
426 }
427
428 #[inline]
430 #[must_use]
431 pub const fn with_modifiers(mut self, modifiers: Modifiers) -> Self {
432 self.modifiers = modifiers;
433 self
434 }
435
436 #[inline]
438 pub const fn reset(&mut self) {
439 *self = Self::EMPTY;
440 }
441}
442
443impl PartialEq for Cell {
444 #[inline]
451 fn eq(&self, other: &Self) -> bool {
452 self.grapheme == other.grapheme
454 && self.grapheme_len == other.grapheme_len
455 && self.fg == other.fg
456 && self.bg == other.bg
457 && self.modifiers == other.modifiers
458 && self.flags == other.flags
459 && self.display_width == other.display_width
460 }
461}
462
463impl Eq for Cell {}
464
465impl Hash for Cell {
466 fn hash<H: Hasher>(&self, state: &mut H) {
467 self.grapheme.hash(state);
468 self.grapheme_len.hash(state);
469 self.display_width.hash(state);
470 self.fg.hash(state);
471 self.bg.hash(state);
472 self.modifiers.hash(state);
473 self.flags.hash(state);
474 }
475}
476
477impl std::fmt::Debug for Cell {
478 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
479 let grapheme = self.grapheme().unwrap_or("<overflow>");
480 f.debug_struct("Cell")
481 .field("grapheme", &grapheme)
482 .field("width", &self.display_width)
483 .field("fg", &self.fg)
484 .field("bg", &self.bg)
485 .field("modifiers", &self.modifiers)
486 .field("flags", &self.flags)
487 .finish_non_exhaustive()
488 }
489}
490
491#[cfg(test)]
492mod tests {
493 use super::*;
494
495 #[test]
496 fn test_cell_size() {
497 assert_eq!(std::mem::size_of::<Cell>(), 16);
498 }
499
500 #[test]
501 fn test_rgb_from_tuple() {
502 let rgb: Rgb = (255, 128, 0).into();
503 assert_eq!(rgb.r, 255);
504 assert_eq!(rgb.g, 128);
505 assert_eq!(rgb.b, 0);
506 }
507
508 #[test]
509 fn test_rgb_from_hex() {
510 let rgb: Rgb = 0xFF8000.into();
511 assert_eq!(rgb.r, 255);
512 assert_eq!(rgb.g, 128);
513 assert_eq!(rgb.b, 0);
514 }
515
516 #[test]
517 fn test_cell_new_ascii() {
518 let cell = Cell::new('A');
519 assert_eq!(cell.grapheme(), Some("A"));
520 assert_eq!(cell.display_width(), 1);
521 }
522
523 #[test]
524 fn test_cell_from_char_unicode() {
525 let cell = Cell::from_char('日');
526 assert_eq!(cell.grapheme(), Some("日"));
527 assert_eq!(cell.display_width(), 2); }
529
530 #[test]
531 fn test_cell_from_grapheme_fits() {
532 let cell = Cell::from_grapheme("é").unwrap();
533 assert_eq!(cell.grapheme(), Some("é"));
534 assert_eq!(cell.display_width(), 1);
535 }
536
537 #[test]
538 fn test_cell_from_grapheme_overflow() {
539 let result = Cell::from_grapheme("👨👩👧");
541 assert!(result.is_none());
542 }
543
544 #[test]
545 fn test_cell_overflow() {
546 let cell = Cell::overflow(42, 2);
547 assert!(cell.is_overflow());
548 assert_eq!(cell.overflow_index(), Some(42));
549 assert_eq!(cell.grapheme(), None);
550 }
551
552 #[test]
553 fn test_cell_equality() {
554 let a = Cell::new('A').with_fg(Rgb::new(255, 0, 0));
555 let b = Cell::new('A').with_fg(Rgb::new(255, 0, 0));
556 let c = Cell::new('A').with_fg(Rgb::new(0, 255, 0));
557
558 assert_eq!(a, b);
559 assert_ne!(a, c);
560 }
561
562 #[test]
563 fn test_cell_builder_pattern() {
564 let cell = Cell::new('X')
565 .with_fg(Rgb::new(255, 0, 0))
566 .with_bg(Rgb::new(0, 0, 255))
567 .with_modifiers(Modifiers::BOLD | Modifiers::ITALIC);
568
569 assert_eq!(cell.fg(), Rgb::new(255, 0, 0));
570 assert_eq!(cell.bg(), Rgb::new(0, 0, 255));
571 assert!(cell.modifiers().contains(Modifiers::BOLD));
572 assert!(cell.modifiers().contains(Modifiers::ITALIC));
573 }
574
575 #[test]
576 fn test_modifiers_bitflags() {
577 let mods = Modifiers::BOLD | Modifiers::UNDERLINE;
578 assert!(mods.contains(Modifiers::BOLD));
579 assert!(mods.contains(Modifiers::UNDERLINE));
580 assert!(!mods.contains(Modifiers::ITALIC));
581 }
582
583 #[test]
584 fn test_cell_reset() {
585 let mut cell = Cell::new('X').with_fg(Rgb::new(255, 0, 0));
586 cell.reset();
587 assert_eq!(cell, Cell::EMPTY);
588 }
589
590 #[test]
591 fn test_wide_continuation() {
592 let cont = Cell::wide_continuation();
593 assert!(cont.is_wide_continuation());
594 assert_eq!(cont.display_width(), 0);
595 }
596}