1use std::cell::Cell;
7use std::fmt::Write;
8use std::ops::{BitOr, BitXor};
9use std::ptr;
10use std::slice::ChunksExact;
11
12use crate::arena::{Arena, ArenaString};
13use crate::helpers::{CoordType, Point, Rect, Size};
14use crate::oklab::{oklab_blend, srgb_to_oklab};
15use crate::simd::{MemsetSafe, memset};
16use crate::unicode::MeasurementConfig;
17
18#[cfg(target_pointer_width = "32")]
20const HASH_MULTIPLIER: usize = 747796405; #[cfg(target_pointer_width = "64")]
22const HASH_MULTIPLIER: usize = 6364136223846793005; const CACHE_TABLE_LOG2_SIZE: usize = 8;
26const CACHE_TABLE_SIZE: usize = 1 << CACHE_TABLE_LOG2_SIZE;
27const CACHE_TABLE_SHIFT: usize = usize::BITS as usize - CACHE_TABLE_LOG2_SIZE;
31
32#[derive(Clone, Copy)]
34pub enum IndexedColor {
35 Black,
36 Red,
37 Green,
38 Yellow,
39 Blue,
40 Magenta,
41 Cyan,
42 White,
43 BrightBlack,
44 BrightRed,
45 BrightGreen,
46 BrightYellow,
47 BrightBlue,
48 BrightMagenta,
49 BrightCyan,
50 BrightWhite,
51
52 Background,
53 Foreground,
54}
55
56pub const INDEXED_COLORS_COUNT: usize = 18;
58
59pub const DEFAULT_THEME: [u32; INDEXED_COLORS_COUNT] = [
61 0xff000000, 0xff212cbe, 0xff3aae3f, 0xff4a9abe, 0xffbe4d20, 0xffbe54bb, 0xffb2a700, 0xffbebebe, 0xff808080, 0xff303eff, 0xff51ea58, 0xff44c9ff, 0xffff6a2f, 0xffff74fc, 0xfff0e100, 0xffffffff, 0xff000000, 0xffbebebe, ];
81
82pub struct Framebuffer {
93 indexed_colors: [u32; INDEXED_COLORS_COUNT],
95 buffers: [Buffer; 2],
97 frame_counter: usize,
99 auto_colors: [u32; 2],
103 contrast_colors: [Cell<(u32, u32)>; CACHE_TABLE_SIZE],
106 background_fill: u32,
107 foreground_fill: u32,
108}
109
110impl Framebuffer {
111 pub fn new() -> Self {
113 Self {
114 indexed_colors: DEFAULT_THEME,
115 buffers: Default::default(),
116 frame_counter: 0,
117 auto_colors: [
118 DEFAULT_THEME[IndexedColor::Black as usize],
119 DEFAULT_THEME[IndexedColor::BrightWhite as usize],
120 ],
121 contrast_colors: [const { Cell::new((0, 0)) }; CACHE_TABLE_SIZE],
122 background_fill: DEFAULT_THEME[IndexedColor::Background as usize],
123 foreground_fill: DEFAULT_THEME[IndexedColor::Foreground as usize],
124 }
125 }
126
127 pub fn set_indexed_colors(&mut self, colors: [u32; INDEXED_COLORS_COUNT]) {
132 self.indexed_colors = colors;
133 self.background_fill = 0;
134 self.foreground_fill = 0;
135
136 self.auto_colors = [
137 self.indexed_colors[IndexedColor::Black as usize],
138 self.indexed_colors[IndexedColor::BrightWhite as usize],
139 ];
140 if !Self::is_dark(self.auto_colors[0]) {
141 self.auto_colors.swap(0, 1);
142 }
143 }
144
145 pub fn flip(&mut self, size: Size) {
147 if size != self.buffers[0].bg_bitmap.size {
148 for buffer in &mut self.buffers {
149 buffer.text = LineBuffer::new(size);
150 buffer.bg_bitmap = Bitmap::new(size);
151 buffer.fg_bitmap = Bitmap::new(size);
152 buffer.attributes = AttributeBuffer::new(size);
153 }
154
155 let front = &mut self.buffers[self.frame_counter & 1];
156 front.fg_bitmap.fill(1);
158 front.cursor = Cursor::new_invalid();
160 }
161
162 self.frame_counter = self.frame_counter.wrapping_add(1);
163
164 let back = &mut self.buffers[self.frame_counter & 1];
165
166 back.text.fill_whitespace();
167 back.bg_bitmap.fill(self.background_fill);
168 back.fg_bitmap.fill(self.foreground_fill);
169 back.attributes.reset();
170 back.cursor = Cursor::new_disabled();
171 }
172
173 pub fn replace_text(
177 &mut self,
178 y: CoordType,
179 origin_x: CoordType,
180 clip_right: CoordType,
181 text: &str,
182 ) {
183 let back = &mut self.buffers[self.frame_counter & 1];
184 back.text.replace_text(y, origin_x, clip_right, text)
185 }
186
187 pub fn draw_scrollbar(
200 &mut self,
201 clip_rect: Rect,
202 track: Rect,
203 content_offset: CoordType,
204 content_height: CoordType,
205 ) -> CoordType {
206 let track_clipped = track.intersect(clip_rect);
207 if track_clipped.is_empty() {
208 return 0;
209 }
210
211 let viewport_height = track.height();
212 let content_height = content_height.max(viewport_height);
214
215 let content_offset_max = content_height - viewport_height;
217 if content_offset_max == 0 {
218 return 0;
219 }
220
221 let content_offset = content_offset.clamp(0, content_offset_max);
224
225 let viewport_height = viewport_height as i64 * 8;
229 let content_offset_max = content_offset_max as i64 * 8;
230 let content_offset = content_offset as i64 * 8;
231 let content_height = content_height as i64 * 8;
232
233 let numerator = viewport_height * viewport_height + content_height / 2;
241 let thumb_height = numerator / content_height;
242 let thumb_height = thumb_height.max(8);
244
245 let numerator = (viewport_height - thumb_height) * content_offset + content_offset_max / 2;
254 let thumb_top = numerator / content_offset_max;
255 let thumb_bottom = thumb_top + thumb_height;
257
258 let thumb_top = thumb_top + track.top as i64 * 8;
260 let thumb_bottom = thumb_bottom + track.top as i64 * 8;
261
262 let thumb_top = thumb_top.max(track_clipped.top as i64 * 8);
264 let thumb_bottom = thumb_bottom.min(track_clipped.bottom as i64 * 8);
265
266 let top_fract = (thumb_top % 8) as CoordType;
268 let bottom_fract = (thumb_bottom % 8) as CoordType;
269
270 let thumb_top = ((thumb_top + 7) / 8) as CoordType;
272 let thumb_bottom = (thumb_bottom / 8) as CoordType;
273
274 self.blend_bg(track_clipped, self.indexed(IndexedColor::BrightBlack));
275 self.blend_fg(track_clipped, self.indexed(IndexedColor::BrightWhite));
276
277 for y in thumb_top..thumb_bottom {
279 self.replace_text(y, track_clipped.left, track_clipped.right, "█");
280 }
281
282 let mut fract_buf = [0xE2, 0x96, 0x88];
286 if top_fract != 0 {
287 fract_buf[2] = (0x88 - top_fract) as u8;
288 self.replace_text(thumb_top - 1, track_clipped.left, track_clipped.right, unsafe {
289 std::str::from_utf8_unchecked(&fract_buf)
290 });
291 }
292 if bottom_fract != 0 {
293 fract_buf[2] = (0x88 - bottom_fract) as u8;
294 self.replace_text(thumb_bottom, track_clipped.left, track_clipped.right, unsafe {
295 std::str::from_utf8_unchecked(&fract_buf)
296 });
297 let rect = Rect {
298 left: track_clipped.left,
299 top: thumb_bottom,
300 right: track_clipped.right,
301 bottom: thumb_bottom + 1,
302 };
303 self.blend_bg(rect, self.indexed(IndexedColor::BrightWhite));
304 self.blend_fg(rect, self.indexed(IndexedColor::BrightBlack));
305 }
306
307 ((thumb_height + 4) / 8) as CoordType
308 }
309
310 #[inline]
311 pub fn indexed(&self, index: IndexedColor) -> u32 {
312 self.indexed_colors[index as usize]
313 }
314
315 #[inline]
320 pub fn indexed_alpha(&self, index: IndexedColor, numerator: u32, denominator: u32) -> u32 {
321 let c = self.indexed_colors[index as usize];
322 let a = 255 * numerator / denominator;
323 let r = (((c >> 16) & 0xFF) * numerator) / denominator;
324 let g = (((c >> 8) & 0xFF) * numerator) / denominator;
325 let b = ((c & 0xFF) * numerator) / denominator;
326 a << 24 | r << 16 | g << 8 | b
327 }
328
329 pub fn contrasted(&self, color: u32) -> u32 {
331 let idx = (color as usize).wrapping_mul(HASH_MULTIPLIER) >> CACHE_TABLE_SHIFT;
332 let slot = self.contrast_colors[idx].get();
333 if slot.0 == color { slot.1 } else { self.contrasted_slow(color) }
334 }
335
336 #[cold]
337 fn contrasted_slow(&self, color: u32) -> u32 {
338 let idx = (color as usize).wrapping_mul(HASH_MULTIPLIER) >> CACHE_TABLE_SHIFT;
339 let contrast = self.auto_colors[Self::is_dark(color) as usize];
340 self.contrast_colors[idx].set((color, contrast));
341 contrast
342 }
343
344 fn is_dark(color: u32) -> bool {
345 srgb_to_oklab(color).l < 0.5
346 }
347
348 pub fn blend_bg(&mut self, target: Rect, bg: u32) {
353 let back = &mut self.buffers[self.frame_counter & 1];
354 back.bg_bitmap.blend(target, bg);
355 }
356
357 pub fn blend_fg(&mut self, target: Rect, fg: u32) {
362 let back = &mut self.buffers[self.frame_counter & 1];
363 back.fg_bitmap.blend(target, fg);
364 }
365
366 pub fn reverse(&mut self, target: Rect) {
368 let back = &mut self.buffers[self.frame_counter & 1];
369
370 let target = target.intersect(back.bg_bitmap.size.as_rect());
371 if target.is_empty() {
372 return;
373 }
374
375 let top = target.top as usize;
376 let bottom = target.bottom as usize;
377 let left = target.left as usize;
378 let right = target.right as usize;
379 let stride = back.bg_bitmap.size.width as usize;
380
381 for y in top..bottom {
382 let beg = y * stride + left;
383 let end = y * stride + right;
384 let bg = &mut back.bg_bitmap.data[beg..end];
385 let fg = &mut back.fg_bitmap.data[beg..end];
386 bg.swap_with_slice(fg);
387 }
388 }
389
390 pub fn replace_attr(&mut self, target: Rect, mask: Attributes, attr: Attributes) {
392 let back = &mut self.buffers[self.frame_counter & 1];
393 back.attributes.replace(target, mask, attr);
394 }
395
396 pub fn set_cursor(&mut self, pos: Point, overtype: bool) {
400 let back = &mut self.buffers[self.frame_counter & 1];
401 back.cursor.pos = pos;
402 back.cursor.overtype = overtype;
403 }
404
405 pub fn render<'a>(&mut self, arena: &'a Arena) -> ArenaString<'a> {
408 let idx = self.frame_counter & 1;
409 let (back, front) = unsafe {
412 let ptr = self.buffers.as_mut_ptr();
413 let back = &mut *ptr.add(idx);
414 let front = &*ptr.add(1 - idx);
415 (back, front)
416 };
417
418 let mut front_lines = front.text.lines.iter(); let mut front_bgs = front.bg_bitmap.iter();
420 let mut front_fgs = front.fg_bitmap.iter();
421 let mut front_attrs = front.attributes.iter();
422
423 let mut back_lines = back.text.lines.iter();
424 let mut back_bgs = back.bg_bitmap.iter();
425 let mut back_fgs = back.fg_bitmap.iter();
426 let mut back_attrs = back.attributes.iter();
427
428 let mut result = ArenaString::new_in(arena);
429 let mut last_bg = u64::MAX;
430 let mut last_fg = u64::MAX;
431 let mut last_attr = Attributes::None;
432
433 for y in 0..front.text.size.height {
434 let front_line = unsafe { front_lines.next().unwrap_unchecked() };
437 let front_bg = unsafe { front_bgs.next().unwrap_unchecked() };
438 let front_fg = unsafe { front_fgs.next().unwrap_unchecked() };
439 let front_attr = unsafe { front_attrs.next().unwrap_unchecked() };
440
441 let back_line = unsafe { back_lines.next().unwrap_unchecked() };
442 let back_bg = unsafe { back_bgs.next().unwrap_unchecked() };
443 let back_fg = unsafe { back_fgs.next().unwrap_unchecked() };
444 let back_attr = unsafe { back_attrs.next().unwrap_unchecked() };
445
446 if front_line == back_line
449 && front_bg == back_bg
450 && front_fg == back_fg
451 && front_attr == back_attr
452 {
453 continue;
454 }
455
456 let line_bytes = back_line.as_bytes();
457 let mut cfg = MeasurementConfig::new(&line_bytes);
458 let mut chunk_end = 0;
459
460 if result.is_empty() {
461 result.push_str("\x1b[m");
462 }
463 _ = write!(result, "\x1b[{};1H", y + 1);
464
465 while {
466 let bg = back_bg[chunk_end];
467 let fg = back_fg[chunk_end];
468 let attr = back_attr[chunk_end];
469
470 while {
472 chunk_end += 1;
473 chunk_end < back_bg.len()
474 && back_bg[chunk_end] == bg
475 && back_fg[chunk_end] == fg
476 && back_attr[chunk_end] == attr
477 } {}
478
479 if last_bg != bg as u64 {
480 last_bg = bg as u64;
481 self.format_color(&mut result, false, bg);
482 }
483
484 if last_fg != fg as u64 {
485 last_fg = fg as u64;
486 self.format_color(&mut result, true, fg);
487 }
488
489 if last_attr != attr {
490 let diff = last_attr ^ attr;
491 if diff.is(Attributes::Italic) {
492 if attr.is(Attributes::Italic) {
493 result.push_str("\x1b[3m");
494 } else {
495 result.push_str("\x1b[23m");
496 }
497 }
498 if diff.is(Attributes::Underlined) {
499 if attr.is(Attributes::Underlined) {
500 result.push_str("\x1b[4m");
501 } else {
502 result.push_str("\x1b[24m");
503 }
504 }
505 last_attr = attr;
506 }
507
508 let beg = cfg.cursor().offset;
509 let end = cfg.goto_visual(Point { x: chunk_end as CoordType, y: 0 }).offset;
510 result.push_str(&back_line[beg..end]);
511
512 chunk_end < back_bg.len()
513 } {}
514 }
515
516 if !result.is_empty() || back.cursor != front.cursor {
520 if back.cursor.pos.x >= 0 && back.cursor.pos.y >= 0 {
521 _ = write!(
525 result,
526 "\x1b[{};{}H\x1b[{} q\x1b[?25h",
527 back.cursor.pos.y + 1,
528 back.cursor.pos.x + 1,
529 if back.cursor.overtype { 1 } else { 5 }
530 );
531 } else {
532 result.push_str("\x1b[?25l");
534 }
535 }
536
537 result
538 }
539
540 fn format_color(&self, dst: &mut ArenaString, fg: bool, mut color: u32) {
541 let typ = if fg { '3' } else { '4' };
542
543 if color == 0 {
557 _ = write!(dst, "\x1b[{typ}9m");
558 return;
559 }
560
561 if (color & 0xff000000) != 0xff000000 {
562 let idx = if fg { IndexedColor::Foreground } else { IndexedColor::Background };
563 let dst = self.indexed(idx);
564 color = oklab_blend(dst, color);
565 }
566
567 let r = color & 0xff;
568 let g = (color >> 8) & 0xff;
569 let b = (color >> 16) & 0xff;
570 _ = write!(dst, "\x1b[{typ}8;2;{r};{g};{b}m");
571 }
572}
573
574#[derive(Default)]
575struct Buffer {
576 text: LineBuffer,
577 bg_bitmap: Bitmap,
578 fg_bitmap: Bitmap,
579 attributes: AttributeBuffer,
580 cursor: Cursor,
581}
582
583#[derive(Default)]
585struct LineBuffer {
586 lines: Vec<String>,
587 size: Size,
588}
589
590impl LineBuffer {
591 fn new(size: Size) -> Self {
592 Self { lines: vec![String::new(); size.height as usize], size }
593 }
594
595 fn fill_whitespace(&mut self) {
596 let width = self.size.width as usize;
597 for l in &mut self.lines {
598 l.clear();
599 l.reserve(width + width / 2);
600
601 let buf = unsafe { l.as_mut_vec() };
602 buf.extend(std::iter::repeat_n(b' ', width));
604 }
605 }
606
607 fn replace_text(
611 &mut self,
612 y: CoordType,
613 origin_x: CoordType,
614 clip_right: CoordType,
615 text: &str,
616 ) {
617 let Some(line) = self.lines.get_mut(y as usize) else {
618 return;
619 };
620
621 let bytes = text.as_bytes();
622 let clip_right = clip_right.clamp(0, self.size.width);
623 let layout_width = clip_right - origin_x;
624
625 if layout_width <= 0 || bytes.is_empty() {
627 return;
628 }
629
630 let mut cfg = MeasurementConfig::new(&bytes);
631
632 let mut left = origin_x;
635 if left < 0 {
636 let mut cursor = cfg.goto_visual(Point { x: -left, y: 0 });
637
638 if left + cursor.visual_pos.x < 0 && cursor.offset < text.len() {
639 cursor = cfg.goto_logical(Point { x: cursor.logical_pos.x + 1, y: 0 });
642 }
643
644 left += cursor.visual_pos.x;
645 }
646
647 if left < 0 || left >= clip_right {
650 return;
651 }
652
653 let beg_off = cfg.cursor().offset;
655 let end = cfg.goto_visual(Point { x: layout_width, y: 0 });
656
657 let right = left + end.visual_pos.x;
659 let line_bytes = line.as_bytes();
660 let mut cfg_old = MeasurementConfig::new(&line_bytes);
661 let res_old_beg = cfg_old.goto_visual(Point { x: left, y: 0 });
662 let mut res_old_end = cfg_old.goto_visual(Point { x: right, y: 0 });
663
664 if res_old_end.visual_pos.x < right {
667 res_old_end = cfg_old.goto_logical(Point { x: res_old_end.logical_pos.x + 1, y: 0 });
668 }
669
670 let src = &text[beg_off..end.offset];
672 let overlap_beg = (left - res_old_beg.visual_pos.x).max(0) as usize;
673 let overlap_end = (res_old_end.visual_pos.x - right).max(0) as usize;
674 let total_add = src.len() + overlap_beg + overlap_end;
675 let total_del = res_old_end.offset - res_old_beg.offset;
676
677 unsafe {
681 let dst = line.as_mut_vec();
685
686 let dst_len = dst.len();
687 let src_len = src.len();
688
689 dst.reserve(total_add.saturating_sub(total_del));
693
694 let mut ptr = dst.as_mut_ptr().add(res_old_beg.offset);
696
697 if total_add != total_del {
700 ptr::copy(
702 ptr.add(total_del),
703 ptr.add(total_add),
704 dst_len - total_del - res_old_beg.offset,
705 );
706 }
707
708 for _ in 0..overlap_beg {
710 ptr.write(b' ');
711 ptr = ptr.add(1);
712 }
713
714 ptr::copy_nonoverlapping(src.as_ptr(), ptr, src_len);
716 ptr = ptr.add(src_len);
717
718 for _ in 0..overlap_end {
720 ptr.write(b' ');
721 ptr = ptr.add(1);
722 }
723
724 dst.set_len(dst_len - total_del + total_add);
726 }
727 }
728}
729
730#[derive(Default)]
732struct Bitmap {
733 data: Vec<u32>,
734 size: Size,
735}
736
737impl Bitmap {
738 fn new(size: Size) -> Self {
739 Self { data: vec![0; (size.width * size.height) as usize], size }
740 }
741
742 fn fill(&mut self, color: u32) {
743 memset(&mut self.data, color);
744 }
745
746 fn blend(&mut self, target: Rect, color: u32) {
751 if (color & 0xff000000) == 0x00000000 {
752 return;
753 }
754
755 let target = target.intersect(self.size.as_rect());
756 if target.is_empty() {
757 return;
758 }
759
760 let top = target.top as usize;
761 let bottom = target.bottom as usize;
762 let left = target.left as usize;
763 let right = target.right as usize;
764 let stride = self.size.width as usize;
765
766 for y in top..bottom {
767 let beg = y * stride + left;
768 let end = y * stride + right;
769 let data = &mut self.data[beg..end];
770
771 if (color & 0xff000000) == 0xff000000 {
772 memset(data, color);
773 } else {
774 let end = data.len();
775 let mut off = 0;
776
777 while {
778 let c = data[off];
779
780 let chunk_beg = off;
782 while {
783 off += 1;
784 off < end && data[off] == c
785 } {}
786 let chunk_end = off;
787
788 let c = oklab_blend(c, color);
789 memset(&mut data[chunk_beg..chunk_end], c);
790
791 off < end
792 } {}
793 }
794 }
795 }
796
797 fn iter(&self) -> ChunksExact<'_, u32> {
799 self.data.chunks_exact(self.size.width as usize)
800 }
801}
802
803#[repr(transparent)]
807#[derive(Default, Clone, Copy, PartialEq, Eq)]
808pub struct Attributes(u8);
809
810#[allow(non_upper_case_globals)]
811impl Attributes {
812 pub const None: Self = Self(0);
813 pub const Italic: Self = Self(0b1);
814 pub const Underlined: Self = Self(0b10);
815 pub const All: Self = Self(0b11);
816
817 pub const fn is(self, attr: Self) -> bool {
818 (self.0 & attr.0) == attr.0
819 }
820}
821
822unsafe impl MemsetSafe for Attributes {}
823
824impl BitOr for Attributes {
825 type Output = Self;
826
827 fn bitor(self, rhs: Self) -> Self::Output {
828 Self(self.0 | rhs.0)
829 }
830}
831
832impl BitXor for Attributes {
833 type Output = Self;
834
835 fn bitxor(self, rhs: Self) -> Self::Output {
836 Self(self.0 ^ rhs.0)
837 }
838}
839
840#[derive(Default)]
842struct AttributeBuffer {
843 data: Vec<Attributes>,
844 size: Size,
845}
846
847impl AttributeBuffer {
848 fn new(size: Size) -> Self {
849 Self { data: vec![Default::default(); (size.width * size.height) as usize], size }
850 }
851
852 fn reset(&mut self) {
853 memset(&mut self.data, Default::default());
854 }
855
856 fn replace(&mut self, target: Rect, mask: Attributes, attr: Attributes) {
857 let target = target.intersect(self.size.as_rect());
858 if target.is_empty() {
859 return;
860 }
861
862 let top = target.top as usize;
863 let bottom = target.bottom as usize;
864 let left = target.left as usize;
865 let right = target.right as usize;
866 let stride = self.size.width as usize;
867
868 for y in top..bottom {
869 let beg = y * stride + left;
870 let end = y * stride + right;
871 let dst = &mut self.data[beg..end];
872
873 if mask == Attributes::All {
874 memset(dst, attr);
875 } else {
876 for a in dst {
877 *a = Attributes(a.0 & !mask.0 | attr.0);
878 }
879 }
880 }
881 }
882
883 fn iter(&self) -> ChunksExact<'_, Attributes> {
885 self.data.chunks_exact(self.size.width as usize)
886 }
887}
888
889#[derive(Default, PartialEq, Eq)]
891struct Cursor {
892 pos: Point,
893 overtype: bool,
894}
895
896impl Cursor {
897 const fn new_invalid() -> Self {
898 Self { pos: Point::MIN, overtype: false }
899 }
900
901 const fn new_disabled() -> Self {
902 Self { pos: Point { x: -1, y: -1 }, overtype: false }
903 }
904}