Skip to main content

terminal/
renderer.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::paths::{
6    Line, maybe_path_for_char, maybe_path_for_cursor_style, path_for_strikeout, path_for_underline,
7};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub struct Rgb {
11    pub r: u8,
12    pub g: u8,
13    pub b: u8,
14}
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub enum CursorShape {
18    Block,
19    Underline,
20    Beam,
21    HollowBlock,
22    Hidden,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26pub struct CursorStyle {
27    pub shape: CursorShape,
28    pub blinking: bool,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
32pub struct Flags(pub u8);
33
34impl Flags {
35    pub const BOLD: Self = Self(1 << 0);
36    pub const ITALIC: Self = Self(1 << 1);
37    pub const UNDERLINE: Self = Self(1 << 2);
38    pub const STRIKEOUT: Self = Self(1 << 3);
39    pub const BOLD_ITALIC: Self = Self(1 << 0 | 1 << 1);
40
41    pub fn empty() -> Self {
42        Self(0)
43    }
44
45    pub fn intersects(&self, other: Self) -> bool {
46        self.0 & other.0 != 0
47    }
48
49    pub fn contains(&self, other: Self) -> bool {
50        self.0 & other.0 == other.0
51    }
52}
53
54impl std::ops::BitOr for Flags {
55    type Output = Self;
56    fn bitor(self, rhs: Self) -> Self::Output {
57        Self(self.0 | rhs.0)
58    }
59}
60
61impl std::ops::BitAnd for Flags {
62    type Output = Self;
63    fn bitand(self, rhs: Self) -> Self::Output {
64        Self(self.0 & rhs.0)
65    }
66}
67use carnelian::Size;
68use carnelian::color::Color;
69use carnelian::drawing::{FontFace, Glyph, TextGrid};
70use carnelian::render::{
71    BlendMode, Context as RenderContext, Fill, FillRule, Layer, Raster, Style,
72};
73use carnelian::scene::{LayerGroup, SceneOrder};
74use euclid::{Rect, point2};
75use rustc_hash::{FxHashMap, FxHashSet};
76use std::collections::BTreeSet;
77use std::collections::hash_map::Entry;
78use std::mem;
79
80// Supported scale factors.
81//
82// These values are hard-coded in order to ensure that we use a grid size
83// that is efficient and aligns with physical pixels.
84const SCALE_FACTORS: &[f32] = &[1.0, 1.25, 2.0, 3.0, 4.0];
85
86/// Returns a scale factor given a set of DPI buckets and an actual DPI value.
87pub fn get_scale_factor(dpi: &BTreeSet<u32>, actual_dpi: f32) -> f32 {
88    let mut scale_factor = 0;
89    for value in dpi.iter() {
90        if *value as f32 > actual_dpi {
91            break;
92        }
93        scale_factor += 1;
94    }
95    *SCALE_FACTORS.get(scale_factor).unwrap_or(SCALE_FACTORS.last().unwrap())
96}
97
98/// Returns the cell size given a cell height.
99pub fn cell_size_from_cell_height(font_set: &FontSet, height: f32) -> Size {
100    let rounded_height = height.round();
101
102    // Use a cell width that matches the horizontal advance of character
103    // '0' as closely as possible. This minimizes the amount of horizontal
104    // stretching used for glyph outlines. Fallback to half of cell height
105    // if glyph '0' is missing.
106    let face = &font_set.font.face;
107    let width = face.glyph_index('0').map_or(height / 2.0, |glyph_index| {
108        let ascent = face.ascender() as f32;
109        let descent = face.descender() as f32;
110        let horizontal_advance =
111            face.glyph_hor_advance(glyph_index).expect("glyph_hor_advance") as f32;
112        rounded_height * horizontal_advance / (ascent - descent)
113    });
114
115    Size::new(width.round(), rounded_height)
116}
117
118#[derive(Clone)]
119pub struct FontSet {
120    font: FontFace,
121    bold_font: Option<FontFace>,
122    italic_font: Option<FontFace>,
123    bold_italic_font: Option<FontFace>,
124    fallback_fonts: Vec<FontFace>,
125}
126
127impl FontSet {
128    pub fn new(
129        font: FontFace,
130        bold_font: Option<FontFace>,
131        italic_font: Option<FontFace>,
132        bold_italic_font: Option<FontFace>,
133        fallback_fonts: Vec<FontFace>,
134    ) -> Self {
135        Self { font, bold_font, italic_font, bold_italic_font, fallback_fonts }
136    }
137}
138
139#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
140pub enum LayerContent {
141    Cursor(CursorStyle),
142    Char((char, Flags)),
143}
144
145#[derive(PartialEq)]
146struct LayerId {
147    content: LayerContent,
148    rgb: Rgb,
149}
150
151fn maybe_raster_for_cursor_style(
152    render_context: &mut RenderContext,
153    cursor_style: CursorStyle,
154    cell_size: &Size,
155) -> Option<Raster> {
156    maybe_path_for_cursor_style(render_context, cursor_style, cell_size).as_ref().map(|p| {
157        let mut raster_builder = render_context.raster_builder().expect("raster_builder");
158        raster_builder.add(p, None);
159        raster_builder.build()
160    })
161}
162
163fn maybe_fallback_glyph_for_char(
164    render_context: &mut RenderContext,
165    c: char,
166    cell_size: &Size,
167) -> Option<Glyph> {
168    maybe_path_for_char(render_context, c, cell_size).as_ref().map(|p| {
169        let mut raster_builder = render_context.raster_builder().expect("raster_builder");
170        raster_builder.add(p, None);
171        let raster = raster_builder.build();
172        let bounding_box = Rect::from_size(*cell_size);
173        Glyph { raster, bounding_box }
174    })
175}
176
177fn maybe_glyph_for_char(
178    context: &mut RenderContext,
179    c: char,
180    flags: Flags,
181    textgrid: &TextGrid,
182    font_set: &FontSet,
183) -> Option<Glyph> {
184    let maybe_bold_italic_font = match flags & Flags::BOLD_ITALIC {
185        Flags::BOLD => font_set.bold_font.as_ref(),
186        Flags::ITALIC => font_set.italic_font.as_ref(),
187        Flags::BOLD_ITALIC => font_set.bold_italic_font.as_ref(),
188        _ => None,
189    };
190    let scale = textgrid.scale;
191    let offset = textgrid.offset;
192
193    // Glyph search order:
194    //
195    // 1. Bold/italic font first if appropriate.
196    // 2. Regular font.
197    // 3. Fallback fonts.
198    //
199    // The fallback font can be used to provide icons/emojis
200    // that are not expected to be part of the regular font.
201    for font in maybe_bold_italic_font
202        .iter()
203        .map(|font| *font)
204        .chain(std::iter::once(&font_set.font))
205        .chain(font_set.fallback_fonts.iter())
206    {
207        if let Some(glyph_index) = font.face.glyph_index(c) {
208            let glyph = Glyph::with_scale_and_offset(context, font, scale, offset, glyph_index);
209            return Some(glyph);
210        }
211    }
212
213    // Try fallback glyph if we failed to locate glyph in fonts.
214    maybe_fallback_glyph_for_char(context, c, &textgrid.cell_size)
215}
216
217fn maybe_raster_for_char(
218    context: &mut RenderContext,
219    c: char,
220    flags: Flags,
221    textgrid: &TextGrid,
222    font_set: &FontSet,
223) -> Option<Raster> {
224    // Get a potential glyph for this character.
225    let maybe_glyph = maybe_glyph_for_char(context, c, flags, textgrid, font_set);
226
227    // Create an extra raster if underline or strikeout flag is set.
228    let maybe_extra_raster = if flags.intersects(Flags::UNDERLINE | Flags::STRIKEOUT) {
229        let mut raster_builder = context.raster_builder().expect("raster_builder");
230        if flags.contains(Flags::UNDERLINE) {
231            // TODO(https://fxbug.dev/42172477): Avoid glyph overlap.
232            let line_metrics = font_set.font.face.underline_metrics();
233            raster_builder.add(
234                &path_for_underline(
235                    &textgrid.cell_size,
236                    context,
237                    line_metrics.map(|line_metrics| Line::new(line_metrics, textgrid)),
238                ),
239                None,
240            );
241        }
242        if flags.contains(Flags::STRIKEOUT) {
243            let line_metrics = font_set.font.face.strikeout_metrics();
244            raster_builder.add(
245                &path_for_strikeout(
246                    &textgrid.cell_size,
247                    context,
248                    line_metrics.map(|line_metrics| Line::new(line_metrics, textgrid)),
249                ),
250                None,
251            );
252        }
253        Some(raster_builder.build())
254    } else {
255        None
256    };
257
258    // Return a union of glyph raster and extra raster.
259    match (maybe_glyph, maybe_extra_raster) {
260        (Some(glyph), Some(extra_raster)) => Some(glyph.raster + extra_raster),
261        (Some(glyph), None) => Some(glyph.raster),
262        (None, Some(extra_raster)) => Some(extra_raster),
263        _ => None,
264    }
265}
266
267fn maybe_raster_for_layer_content(
268    render_context: &mut RenderContext,
269    content: &LayerContent,
270    column: usize,
271    row: usize,
272    textgrid: &TextGrid,
273    font_set: &FontSet,
274    raster_cache: &mut FxHashMap<LayerContent, Option<Raster>>,
275) -> Option<Raster> {
276    raster_cache
277        .entry(*content)
278        .or_insert_with(|| match content {
279            LayerContent::Cursor(cursor_style) => {
280                maybe_raster_for_cursor_style(render_context, *cursor_style, &textgrid.cell_size)
281            }
282            LayerContent::Char((c, flags)) => {
283                maybe_raster_for_char(render_context, *c, *flags, textgrid, font_set)
284            }
285        })
286        .as_ref()
287        .map(|r| {
288            let cell_size = &textgrid.cell_size;
289            let cell_position =
290                point2(cell_size.width * column as f32, cell_size.height * row as f32);
291            let raster = r.clone().translate(cell_position.to_vector().to_i32());
292            // Add empty raster to enable caching of the translated cursor.
293            // TODO: add more appropriate API for this.
294            let empty_raster = {
295                let raster_builder = render_context.raster_builder().unwrap();
296                raster_builder.build()
297            };
298            raster + empty_raster
299        })
300}
301
302fn make_color(term_color: &Rgb) -> Color {
303    Color { r: term_color.r, g: term_color.g, b: term_color.b, a: 0xff }
304}
305
306#[derive(PartialEq, Debug)]
307pub struct RenderableLayer {
308    pub order: usize,
309    pub column: usize,
310    pub row: usize,
311    pub content: LayerContent,
312    pub rgb: Rgb,
313}
314
315pub struct Offset {
316    pub column: usize,
317    pub row: usize,
318}
319
320pub fn renderable_layers<'b>(
321    screen: &'b vt100::Screen,
322    default_bg: Rgb,
323    default_fg: Rgb,
324    offset: &'b Offset,
325) -> impl Iterator<Item = RenderableLayer> + 'b {
326    let columns = screen.size().1 as usize;
327    let rows = screen.size().0 as usize;
328    let stride = columns * 4;
329
330    let resolve_color = move |vt100_color: vt100::Color, is_fg: bool| -> Rgb {
331        match vt100_color {
332            vt100::Color::Default => {
333                if is_fg {
334                    default_fg
335                } else {
336                    default_bg
337                }
338            }
339            vt100::Color::Idx(idx) => {
340                // simple fallback
341                let ansi_colors = [
342                    Rgb { r: 0, g: 0, b: 0 },
343                    Rgb { r: 170, g: 0, b: 0 },
344                    Rgb { r: 0, g: 170, b: 0 },
345                    Rgb { r: 170, g: 85, b: 0 },
346                    Rgb { r: 0, g: 0, b: 170 },
347                    Rgb { r: 170, g: 0, b: 170 },
348                    Rgb { r: 0, g: 170, b: 170 },
349                    Rgb { r: 170, g: 170, b: 170 },
350                    Rgb { r: 85, g: 85, b: 85 },
351                    Rgb { r: 255, g: 85, b: 85 },
352                    Rgb { r: 85, g: 255, b: 85 },
353                    Rgb { r: 255, g: 255, b: 85 },
354                    Rgb { r: 85, g: 85, b: 255 },
355                    Rgb { r: 255, g: 85, b: 255 },
356                    Rgb { r: 85, g: 255, b: 255 },
357                    Rgb { r: 255, g: 255, b: 255 },
358                ];
359                if (idx as usize) < ansi_colors.len() {
360                    ansi_colors[idx as usize]
361                } else {
362                    default_fg
363                }
364            }
365            vt100::Color::Rgb(r, g, b) => Rgb { r, g, b },
366        }
367    };
368
369    let cursor_pos = screen.cursor_position();
370    let hide_cursor = screen.hide_cursor();
371
372    (0..rows).flat_map(move |r| {
373        (0..columns).flat_map(move |col| {
374            let cell = screen.cell(r as u16, col as u16);
375            let row_idx = r + offset.row;
376            let cell_order = row_idx * stride + (col + offset.column);
377            let is_cursor = !hide_cursor && cursor_pos.0 == r as u16 && cursor_pos.1 == col as u16;
378
379            let (mut fg, mut bg, inverse, bold, italic, underline, contents, has_contents) =
380                if let Some(cell) = cell {
381                    (
382                        cell.fgcolor(),
383                        cell.bgcolor(),
384                        cell.inverse(),
385                        cell.bold(),
386                        cell.italic(),
387                        cell.underline(),
388                        cell.contents().to_string(),
389                        cell.has_contents(),
390                    )
391                } else {
392                    (
393                        vt100::Color::Default,
394                        vt100::Color::Default,
395                        false,
396                        false,
397                        false,
398                        false,
399                        String::new(),
400                        false,
401                    )
402                };
403
404            if inverse {
405                std::mem::swap(&mut fg, &mut bg);
406            }
407
408            if is_cursor {
409                std::mem::swap(&mut fg, &mut bg);
410            }
411
412            let fg_rgb = resolve_color(fg, true);
413            let bg_rgb = resolve_color(bg, false);
414
415            let is_default_bg = matches!(bg, vt100::Color::Default) && !is_cursor && !inverse;
416
417            let mut layers = Vec::with_capacity(3);
418
419            if !is_default_bg || is_cursor {
420                layers.push(RenderableLayer {
421                    order: cell_order,
422                    column: col,
423                    row: row_idx,
424                    content: LayerContent::Cursor(CursorStyle {
425                        shape: CursorShape::Block,
426                        blinking: false,
427                    }),
428                    rgb: bg_rgb,
429                });
430            }
431
432            let mut flags = Flags::empty();
433            if bold {
434                flags = Flags(flags.0 | Flags::BOLD.0);
435            }
436            if italic {
437                flags = Flags(flags.0 | Flags::ITALIC.0);
438            }
439            if underline {
440                flags = Flags(flags.0 | Flags::UNDERLINE.0);
441            }
442
443            let c = if has_contents { contents.chars().next().unwrap_or(' ') } else { ' ' };
444            let layer_content = LayerContent::Char((if c == '\t' { ' ' } else { c }, flags));
445
446            layers.push(RenderableLayer {
447                order: cell_order + columns * 3,
448                column: col,
449                row: row_idx,
450                content: layer_content,
451                rgb: fg_rgb,
452            });
453
454            layers.into_iter()
455        })
456    })
457}
458pub struct Renderer {
459    textgrid: TextGrid,
460    raster_cache: FxHashMap<LayerContent, Option<Raster>>,
461    layers: FxHashMap<SceneOrder, LayerId>,
462    old_layers: FxHashSet<SceneOrder>,
463    new_layers: FxHashSet<SceneOrder>,
464}
465
466impl Renderer {
467    pub fn new(font_set: &FontSet, cell_size: &Size) -> Self {
468        let textgrid = TextGrid::new(&font_set.font, cell_size);
469        let raster_cache = FxHashMap::default();
470        let layers = FxHashMap::default();
471        let old_layers = FxHashSet::default();
472        let new_layers = FxHashSet::default();
473
474        Self { textgrid, raster_cache, layers, old_layers, new_layers }
475    }
476
477    pub fn render<I>(
478        &mut self,
479        layer_group: &mut dyn LayerGroup,
480        render_context: &mut RenderContext,
481        font_set: &FontSet,
482        layers: I,
483    ) where
484        I: IntoIterator<Item = RenderableLayer>,
485    {
486        let raster_cache = &mut self.raster_cache;
487        let textgrid = &self.textgrid;
488
489        // Process all layers and update the layer group as needed.
490        for RenderableLayer { order, column, row, content, rgb } in layers.into_iter() {
491            let id = LayerId { content, rgb };
492            let order = SceneOrder::try_from(order).unwrap_or_else(|e| panic!("{}", e));
493
494            // Remove from old layers.
495            self.old_layers.remove(&order);
496
497            match self.layers.entry(order) {
498                Entry::Occupied(entry) => {
499                    if *entry.get() != id {
500                        let raster = maybe_raster_for_layer_content(
501                            render_context,
502                            &id.content,
503                            column,
504                            row,
505                            textgrid,
506                            font_set,
507                            raster_cache,
508                        );
509                        if let Some(raster) = raster {
510                            let value = entry.into_mut();
511                            *value = id;
512
513                            let did_not_exist = self.new_layers.insert(order);
514                            assert!(
515                                did_not_exist,
516                                "multiple layers with order: {}",
517                                order.as_u32()
518                            );
519                            layer_group.insert(
520                                order,
521                                Layer {
522                                    raster,
523                                    clip: None,
524                                    style: Style {
525                                        fill_rule: FillRule::NonZero,
526                                        fill: Fill::Solid(make_color(&rgb)),
527                                        blend_mode: BlendMode::Over,
528                                    },
529                                },
530                            );
531                        } else {
532                            entry.remove_entry();
533                            layer_group.remove(order);
534                        }
535                    } else {
536                        let did_not_exist = self.new_layers.insert(order);
537                        assert!(did_not_exist, "multiple layers with order: {}", order.as_u32());
538                    }
539                }
540                Entry::Vacant(entry) => {
541                    let raster = maybe_raster_for_layer_content(
542                        render_context,
543                        &id.content,
544                        column,
545                        row,
546                        textgrid,
547                        font_set,
548                        raster_cache,
549                    );
550                    if let Some(raster) = raster {
551                        entry.insert(id);
552                        let did_not_exist = self.new_layers.insert(order);
553                        assert!(did_not_exist, "multiple layers with order: {}", order.as_u32());
554                        layer_group.insert(
555                            order,
556                            Layer {
557                                raster,
558                                clip: None,
559                                style: Style {
560                                    fill_rule: FillRule::NonZero,
561                                    fill: Fill::Solid(make_color(&rgb)),
562                                    blend_mode: BlendMode::Over,
563                                },
564                            },
565                        );
566                    }
567                }
568            }
569        }
570
571        // Remove any remaining old layers.
572        for order in self.old_layers.drain() {
573            self.layers.remove(&order);
574            layer_group.remove(order);
575        }
576
577        // Swap old layers for new layers.
578        mem::swap(&mut self.old_layers, &mut self.new_layers);
579    }
580}