1use cosmic_text::{Attrs, Buffer, FontSystem, Metrics, Shaping, Style as FontStyle};
50use rustc_hash::FxHashMap;
51
52use crate::atlas::GlyphAtlas;
53use crate::color::Color;
54use crate::display::{Clip, ClipId, Quad, QuadKind};
55use crate::geom::{Rect, Size, Vec2};
56use crate::key::Key;
57use crate::resources::Resources;
58use crate::spec::TextStyle;
59use crate::text::{Raster, glyph_kind, raster_glyph};
60
61mod boxdraw;
62
63pub mod flags {
65 pub const BOLD: u8 = 1;
66 pub const ITALIC: u8 = 2;
67 pub const UNDERLINE: u8 = 4;
68 pub const STRIKETHROUGH: u8 = 8;
69 pub const WIDE: u8 = 16;
71 pub const WAVY: u8 = 32;
74 pub const DOTTED: u8 = 64;
76 pub const LINES: u8 = UNDERLINE | STRIKETHROUGH | WAVY | DOTTED;
79}
80
81#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
85pub struct Cell {
86 pub ch: char,
87 pub fg: u32,
88 pub bg: u32,
89 pub flags: u8,
90 pub ul: u32,
92}
93
94impl Cell {
95 pub const fn new(ch: char, fg: u32, bg: u32) -> Self {
96 Self {
97 ch,
98 fg,
99 bg,
100 flags: 0,
101 ul: 0,
102 }
103 }
104
105 pub const fn underline_color(mut self, ul: u32) -> Self {
107 self.flags |= flags::UNDERLINE;
108 self.ul = ul;
109 self
110 }
111
112 pub const fn with(mut self, flags: u8) -> Self {
113 self.flags |= flags;
114 self
115 }
116}
117
118#[derive(Clone, Copy, Debug, PartialEq, Eq)]
120pub enum CursorShape {
121 Block,
123 Bar,
125 Underline,
127}
128
129impl CursorShape {
130 pub const NAMES: &[&str] = &["block", "bar", "underline"];
133
134 pub fn from_index(i: usize) -> Option<Self> {
135 match i {
136 0 => Some(Self::Block),
137 1 => Some(Self::Bar),
138 2 => Some(Self::Underline),
139 _ => None,
140 }
141 }
142
143 pub fn from_name(s: &str) -> Option<Self> {
144 Self::NAMES
145 .iter()
146 .position(|n| *n == s)
147 .and_then(Self::from_index)
148 }
149}
150
151#[derive(Clone, Copy, Debug)]
155pub struct CellGrid<'a> {
156 pub rows: usize,
157 pub cols: usize,
158 pub cells: &'a [Cell],
159 pub style: TextStyle,
160 pub cursor: Option<(usize, usize, CursorShape, Color)>,
162 pub origin_line: u64,
172}
173
174#[derive(Clone, Copy, Debug, PartialEq, Eq)]
176pub struct CellsId(pub u32);
177
178struct Entry {
179 key: Key,
183 rows: usize,
184 cols: usize,
185 cells: Vec<Cell>,
186 style: TextStyle,
187 cursor: Option<(usize, usize, CursorShape, Color)>,
188 origin_line: u64,
189}
190
191#[derive(Clone, Copy)]
194struct CellGlyph {
195 x: f32,
196 y: f32,
197 w: f32,
198 h: f32,
199 uv: [u32; 4],
200 kind: QuadKind,
201}
202
203struct StyleTable {
206 cell_w: f32,
207 cell_h: f32,
208 ascii: Vec<Option<Option<CellGlyph>>>,
209 other: FxHashMap<(char, u8), Option<CellGlyph>>,
210 epoch: u64,
212}
213
214const VARIANTS: usize = 4;
215
216fn variant(flags: u8) -> usize {
217 (flags & (flags::BOLD | flags::ITALIC)) as usize
218}
219
220pub struct CellStore {
225 frame: crate::retain::Kept<Entry>,
228 tables: FxHashMap<u64, StyleTable>,
229 scale: f32,
230}
231
232impl Default for CellStore {
233 fn default() -> Self {
234 Self::new()
235 }
236}
237
238impl CellStore {
239 pub fn new() -> Self {
240 Self {
241 frame: Default::default(),
242 tables: FxHashMap::default(),
243 scale: 1.0,
244 }
245 }
246
247 pub(crate) fn forget_shaped(&mut self) {
250 self.tables.clear();
251 }
252
253 pub(crate) fn begin_frame(&mut self, scale: f32) {
254 if (scale - self.scale).abs() > f32::EPSILON {
255 self.tables.clear();
256 }
257 self.scale = scale;
258 self.frame.begin(true);
259 }
260
261 pub(crate) fn find(&self, key: Key, prev: bool) -> Option<CellsId> {
263 self.list(prev)
264 .iter()
265 .position(|e| e.key == key)
266 .map(|i| CellsId(i as u32))
267 }
268
269 fn list(&self, prev: bool) -> &[Entry] {
270 if prev { self.frame.prev() } else { &self.frame }
271 }
272
273 fn entry(&self, id: CellsId, prev: bool) -> &Entry {
274 &self.list(prev)[id.0 as usize]
275 }
276
277 pub(crate) fn add(&mut self, key: Key, grid: &CellGrid<'_>) -> CellsId {
278 let n = grid.rows * grid.cols;
279 let mut cells = Vec::with_capacity(n);
280 cells.extend_from_slice(&grid.cells[..grid.cells.len().min(n)]);
281 cells.resize(n, Cell::default());
282 self.frame.push(Entry {
283 key,
284 rows: grid.rows,
285 cols: grid.cols,
286 cells,
287 style: grid.style,
288 cursor: grid.cursor,
289 origin_line: grid.origin_line,
290 });
291 CellsId((self.frame.len() - 1) as u32)
292 }
293
294 fn table_key(style: &TextStyle, scale: f32) -> u64 {
295 crate::text::TextSystem::style_key("", style, scale)
296 }
297
298 fn table(&mut self, style: &TextStyle, res: &Resources, fs: &mut FontSystem) -> u64 {
302 let key = Self::table_key(style, self.scale);
303 if !self.tables.contains_key(&key) {
304 let scale = self.scale;
305 let cell_w = shape_one(style, "M", 0, res, fs, scale)
306 .map_or(style.size * scale * 0.6, |(_, advance, _)| advance)
307 .round()
308 .max(1.0);
309 self.tables.insert(
310 key,
311 StyleTable {
312 cell_w,
313 cell_h: (style.line_height * scale).round().max(1.0),
314 ascii: vec![None; VARIANTS * 128],
315 other: FxHashMap::default(),
316 epoch: u64::MAX,
317 },
318 );
319 }
320 key
321 }
322
323 pub(crate) fn cell_size(
325 &mut self,
326 id: CellsId,
327 prev: bool,
328 res: &Resources,
329 fs: &mut FontSystem,
330 ) -> Size {
331 let style = self.entry(id, prev).style;
332 let key = self.table(&style, res, fs);
333 let t = &self.tables[&key];
334 Size::new(t.cell_w / self.scale, t.cell_h / self.scale)
335 }
336
337 pub(crate) fn origin_line(&self, id: CellsId, prev: bool) -> u64 {
339 self.entry(id, prev).origin_line
340 }
341
342 pub(crate) fn cell_char(
350 &self,
351 id: CellsId,
352 row: usize,
353 col: usize,
354 prev: bool,
355 ) -> Option<(char, bool)> {
356 let e = self.entry(id, prev);
357 if row >= e.rows || col >= e.cols {
358 return None;
359 }
360 let c = e.cells[row * e.cols + col];
361 Some((c.ch, self.is_spacer(e, row, col)))
362 }
363
364 fn is_spacer(&self, e: &Entry, row: usize, col: usize) -> bool {
366 col > 0 && e.cells[row * e.cols + col - 1].flags & flags::WIDE != 0
367 }
368
369 pub(crate) fn word_at(
375 &self,
376 id: CellsId,
377 row: usize,
378 col: usize,
379 prev: bool,
380 ) -> Option<(usize, usize)> {
381 let e = self.entry(id, prev);
382 if row >= e.rows || col >= e.cols {
383 return None;
384 }
385 let class = |c: usize| -> u8 {
386 let c = if self.is_spacer(e, row, c) { c - 1 } else { c };
389 let ch = e.cells[row * e.cols + c].ch;
390 if ch == '\0' || ch.is_whitespace() {
391 1
392 } else if ch.is_alphanumeric() || ch == '_' {
393 0
394 } else {
395 2
396 }
397 };
398 let here = class(col);
399 let mut from = col;
400 while from > 0 && class(from - 1) == here {
401 from -= 1;
402 }
403 let mut to = col + 1;
404 while to < e.cols && class(to) == here {
405 to += 1;
406 }
407 Some((from, to))
408 }
409
410 pub(crate) fn dims(&self, id: CellsId, prev: bool) -> (usize, usize) {
411 let e = self.entry(id, prev);
412 (e.rows, e.cols)
413 }
414
415 pub(crate) fn value(&self, id: CellsId) -> String {
418 let e = &self.frame[id.0 as usize];
419 let mut out = String::with_capacity(e.rows * (e.cols + 1));
420 for r in 0..e.rows {
421 let row = &e.cells[r * e.cols..(r + 1) * e.cols];
422 let end = row
423 .iter()
424 .rposition(|c| c.ch != ' ' && c.ch != '\0')
425 .map_or(0, |i| i + 1);
426 for c in &row[..end] {
427 out.push(if c.ch == '\0' { ' ' } else { c.ch });
428 }
429 if r + 1 < e.rows {
430 out.push('\n');
431 }
432 }
433 out
434 }
435
436 pub(crate) fn size(&mut self, id: CellsId, res: &Resources, fs: &mut FontSystem) -> Size {
438 let (rows, cols, style) = {
439 let e = &self.frame[id.0 as usize];
440 (e.rows, e.cols, e.style)
441 };
442 let key = self.table(&style, res, fs);
443 let t = &self.tables[&key];
444 Size::new(
445 cols as f32 * t.cell_w / self.scale,
446 rows as f32 * t.cell_h / self.scale,
447 )
448 }
449
450 #[allow(clippy::too_many_arguments, clippy::needless_range_loop)]
454 pub(crate) fn emit(
455 &mut self,
456 id: CellsId,
457 origin: Vec2,
458 clip: Clip,
459 clip_id: ClipId,
460 res: &Resources,
461 fs: &mut FontSystem,
462 raster: &mut Raster,
463 atlas: &mut GlyphAtlas,
464 out: &mut Vec<Quad>,
465 sel: Option<(&crate::select::CellSelection, Color)>,
470 ) {
471 let scale = self.scale;
472 let style = self.frame[id.0 as usize].style;
473 let key = self.table(&style, res, fs);
474 let ox = crate::geom::snap_px(origin.x * scale);
475 let oy = crate::geom::snap_px(origin.y * scale);
476 let entry = &self.frame[id.0 as usize];
477 let table = self.tables.get_mut(&key).expect("just built");
478 if table.epoch != atlas.stamp {
479 table.ascii.iter_mut().for_each(|g| *g = None);
483 table.other.clear();
484 table.epoch = atlas.stamp;
485 }
486 let (cw, ch) = (table.cell_w, table.cell_h);
487 let quad = |rect: Rect, color: Color, kind: QuadKind, uv: [u32; 4]| Quad {
488 rect,
489 color,
490 border_color: Color::TRANSPARENT,
491 radius: [0.0; 4],
492 border_w: 0.0,
493 blur: 0.0,
494 kind,
495 clip: clip_id,
496 uv,
497 };
498 let r0 = (((clip.rect.y - oy) / ch).floor().max(0.0)) as usize;
500 let r1 = (((clip.rect.y + clip.rect.h - oy) / ch).ceil().max(0.0) as usize).min(entry.rows);
501 let c0 = (((clip.rect.x - ox) / cw).floor().max(0.0)) as usize;
502 let c1 = (((clip.rect.x + clip.rect.w - ox) / cw).ceil().max(0.0) as usize).min(entry.cols);
503 if r0 >= r1 || c0 >= c1 {
504 return;
505 }
506 let stroke = (scale).round().max(1.0);
507 if let Some((sel, tint)) = sel {
512 for r in r0..r1 {
513 let line = entry.origin_line + r as u64;
514 let Some((from, to)) = sel.cols_on(line, entry.cols) else {
515 continue;
516 };
517 let (from, to) = (from.max(c0), to.min(c1));
518 if from >= to {
519 continue;
520 }
521 out.push(quad(
522 Rect::new(
523 ox + from as f32 * cw,
524 oy + r as f32 * ch,
525 (to - from) as f32 * cw,
526 ch,
527 ),
528 tint,
529 QuadKind::Solid,
530 [0; 4],
531 ));
532 }
533 }
534 for r in r0..r1 {
535 let row = &entry.cells[r * entry.cols..(r + 1) * entry.cols];
536 let cy = oy + r as f32 * ch;
537 let mut run_start = c0;
539 let mut run_bg = row[c0].bg;
540 for c in c0..=c1 {
541 let bg = if c < c1 { row[c].bg } else { !run_bg };
542 if bg != run_bg {
543 if run_bg & 0xff != 0 {
544 out.push(quad(
545 Rect::new(
546 ox + run_start as f32 * cw,
547 cy,
548 (c - run_start) as f32 * cw,
549 ch,
550 ),
551 Color::hex(run_bg),
552 QuadKind::Solid,
553 [0; 4],
554 ));
555 }
556 run_start = c;
557 run_bg = bg;
558 }
559 }
560 if let Some((cr, cc, shape, color)) = entry.cursor
562 && cr == r
563 && cc >= c0
564 && cc < c1
565 {
566 let cx = ox + cc as f32 * cw;
567 let rect = match shape {
568 CursorShape::Block => Rect::new(cx, cy, cw, ch),
569 CursorShape::Bar => Rect::new(cx, cy, 2.0 * stroke, ch),
570 CursorShape::Underline => {
571 Rect::new(cx, cy + ch - 2.0 * stroke, cw, 2.0 * stroke)
572 }
573 };
574 out.push(quad(rect, color, QuadKind::Solid, [0; 4]));
575 }
576 let mut line_run: Option<(usize, u8, u32, u32)> = None;
579 for c in c0..c1 {
580 let cell = &row[c];
581 let cx = ox + c as f32 * cw;
582 if cell.ch != ' ' && cell.ch != '\0' {
583 let g = lookup(
584 table, cell.ch, cell.flags, &style, res, fs, raster, atlas, scale,
585 );
586 if let Some(g) = g {
587 out.push(quad(
588 Rect::new(cx + g.x, cy + g.y, g.w, g.h),
589 Color::hex(cell.fg),
590 g.kind,
591 g.uv,
592 ));
593 }
594 }
595 let lines = cell.flags & flags::LINES;
596 let same = line_run
597 .is_some_and(|(_, f, fg, ul)| f == lines && fg == cell.fg && ul == cell.ul);
598 if !same {
599 if let Some((start, f, fg, ul)) = line_run.take()
600 && f != 0
601 {
602 push_lines(
603 out, &quad, clip_id, ox, cy, cw, ch, stroke, start, c, f, fg, ul,
604 );
605 }
606 line_run = Some((c, lines, cell.fg, cell.ul));
607 }
608 }
609 if let Some((start, f, fg, ul)) = line_run
610 && f != 0
611 {
612 push_lines(
613 out, &quad, clip_id, ox, cy, cw, ch, stroke, start, c1, f, fg, ul,
614 );
615 }
616 }
617 }
618}
619
620#[allow(clippy::too_many_arguments)]
621fn push_lines(
622 out: &mut Vec<Quad>,
623 quad: &dyn Fn(Rect, Color, QuadKind, [u32; 4]) -> Quad,
624 clip_id: ClipId,
625 ox: f32,
626 cy: f32,
627 cw: f32,
628 ch: f32,
629 stroke: f32,
630 start: usize,
631 end: usize,
632 f: u8,
633 fg: u32,
634 ul: u32,
635) {
636 let x = ox + start as f32 * cw;
637 let w = (end - start) as f32 * cw;
638 if f & (flags::UNDERLINE | flags::WAVY | flags::DOTTED) != 0 {
639 let style = if f & flags::WAVY != 0 {
642 crate::spec::UnderlineStyle::Wavy
643 } else if f & flags::DOTTED != 0 {
644 crate::spec::UnderlineStyle::Dotted
645 } else {
646 crate::spec::UnderlineStyle::Solid
647 };
648 let color = Color::hex(if ul != 0 { ul } else { fg });
649 crate::deco::push_line(
650 out,
651 style,
652 x,
653 cy + ch - 2.0 * stroke,
654 w,
655 stroke,
656 color,
657 clip_id,
658 );
659 }
660 if f & flags::STRIKETHROUGH != 0 {
661 out.push(quad(
662 Rect::new(x, (cy + ch * 0.55).round(), w, stroke),
663 Color::hex(fg),
664 QuadKind::Solid,
665 [0; 4],
666 ));
667 }
668}
669
670#[allow(clippy::too_many_arguments)]
674fn lookup(
675 table: &mut StyleTable,
676 ch: char,
677 flags: u8,
678 style: &TextStyle,
679 res: &Resources,
680 fs: &mut FontSystem,
681 raster: &mut Raster,
682 atlas: &mut GlyphAtlas,
683 scale: f32,
684) -> Option<CellGlyph> {
685 let v = variant(flags);
686 if (ch as u32) < 128 {
687 let i = v * 128 + ch as usize;
688 if let Some(g) = table.ascii[i] {
689 return g;
690 }
691 let g = shape_cell(
692 ch,
693 flags,
694 style,
695 res,
696 fs,
697 raster,
698 atlas,
699 scale,
700 (table.cell_w, table.cell_h),
701 );
702 table.ascii[i] = Some(g);
703 return g;
704 }
705 if let Some(g) = table.other.get(&(ch, v as u8)) {
706 return *g;
707 }
708 let g = shape_cell(
709 ch,
710 flags,
711 style,
712 res,
713 fs,
714 raster,
715 atlas,
716 scale,
717 (table.cell_w, table.cell_h),
718 );
719 table.other.insert((ch, v as u8), g);
720 g
721}
722
723#[allow(clippy::too_many_arguments)]
727fn shape_cell(
728 ch: char,
729 flags: u8,
730 style: &TextStyle,
731 res: &Resources,
732 fs: &mut FontSystem,
733 raster: &mut Raster,
734 atlas: &mut GlyphAtlas,
735 scale: f32,
736 cell: (f32, f32),
737) -> Option<CellGlyph> {
738 if boxdraw::draws(ch) {
739 let (w, h) = (cell.0 as u32, cell.1 as u32);
740 let slot = atlas.get_or_insert_synth(ch, w, h, || boxdraw::raster(ch, w, h))?;
741 return Some(CellGlyph {
742 x: 0.0,
743 y: 0.0,
744 w: w as f32,
745 h: h as f32,
746 uv: [slot.x, slot.y, slot.w, slot.h],
747 kind: QuadKind::GlyphMask,
748 });
749 }
750 let mut buf = [0u8; 4];
751 let (key, _, (px, py, line_y)) =
752 shape_one(style, ch.encode_utf8(&mut buf), flags, res, fs, scale)?;
753 let slot = raster_glyph(key, fs, raster, atlas)?;
754 Some(CellGlyph {
755 x: px as f32 + slot.left as f32,
756 y: line_y.round() + py as f32 - slot.top as f32,
757 w: slot.w as f32,
758 h: slot.h as f32,
759 uv: [slot.x, slot.y, slot.w, slot.h],
760 kind: glyph_kind(&slot),
761 })
762}
763
764fn shape_one(
767 style: &TextStyle,
768 text: &str,
769 flags: u8,
770 res: &Resources,
771 fs: &mut FontSystem,
772 scale: f32,
773) -> Option<(cosmic_text::CacheKey, f32, (i32, i32, f32))> {
774 let metrics = Metrics::new(style.size * scale, style.line_height * scale);
775 let mut buffer = Buffer::new(fs, metrics);
776 buffer.set_size(None, None);
777 let mut attrs = res.weights_of(style.family).apply(
779 Attrs::new().family(res.family_of(style.family)),
780 flags & flags::BOLD != 0,
781 );
782 if flags & flags::ITALIC != 0 {
783 attrs = attrs.style(FontStyle::Italic);
784 }
785 buffer.set_text(text, &attrs, Shaping::Advanced, None);
786 buffer.shape_until_scroll(fs, false);
787 let run = buffer.layout_runs().next()?;
788 let glyph = run.glyphs.first()?;
789 let physical = glyph.physical((0.0, 0.0), 1.0);
790 Some((
791 physical.cache_key,
792 glyph.w,
793 (physical.x, physical.y, run.line_y),
794 ))
795}