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