cosmic_text/
render.rs

1//! Helpers for rendering buffers and editors
2
3#[cfg(not(feature = "std"))]
4use core_maths::CoreFloat;
5
6use crate::{Color, DecorationSpan, LayoutRun, PhysicalGlyph, UnderlineStyle};
7#[cfg(feature = "swash")]
8use crate::{FontSystem, SwashCache};
9
10/// Custom renderer for buffers and editors
11pub trait Renderer {
12    /// Render a rectangle at x, y with size w, h and the provided [`Color`].
13    fn rectangle(&mut self, x: i32, y: i32, w: u32, h: u32, color: Color);
14
15    /// Render a [`PhysicalGlyph`] with the provided [`Color`].
16    /// For performance, consider using [`SwashCache`].
17    fn glyph(&mut self, physical_glyph: PhysicalGlyph, color: Color);
18}
19
20/// Draw text decoration lines (underline, strikethrough, overline) for a layout run.
21pub fn render_decoration<R: Renderer>(renderer: &mut R, run: &LayoutRun, default_color: Color) {
22    for span in run.decorations {
23        draw_decoration_span(renderer, run, span, default_color);
24    }
25}
26
27fn draw_decoration_span<R: Renderer>(
28    renderer: &mut R,
29    run: &LayoutRun,
30    span: &DecorationSpan,
31    default_color: Color,
32) {
33    let glyphs = &run.glyphs[span.glyph_range.clone()];
34    if glyphs.is_empty() {
35        return;
36    }
37
38    let deco = &span.data;
39    let td = &deco.text_decoration;
40    let font_size = span.font_size;
41
42    // Compute x extent as min/max over all glyphs, not first/last,
43    // because RTL paragraphs store glyphs in right-to-left order.
44    let mut x_min = f32::INFINITY;
45    let mut x_max = f32::NEG_INFINITY;
46    for g in glyphs {
47        x_min = x_min.min(g.x);
48        x_max = x_max.max(g.x + g.w);
49    }
50    let width = x_max - x_min;
51    if width <= 0.0 {
52        return;
53    }
54    let w = width as u32;
55    if w == 0 {
56        return;
57    }
58    let x_start = x_min;
59
60    // Underline
61    match td.underline {
62        UnderlineStyle::None => {}
63        UnderlineStyle::Single => {
64            let color = td
65                .underline_color_opt
66                .or(span.color_opt)
67                .unwrap_or(default_color);
68            let thickness = (deco.underline_metrics.thickness * font_size)
69                .max(1.0)
70                .ceil();
71            let y = run.line_y - deco.underline_metrics.offset * font_size;
72            renderer.rectangle(x_start as i32, y as i32, w, thickness as u32, color);
73        }
74        UnderlineStyle::Double => {
75            let color = td
76                .underline_color_opt
77                .or(span.color_opt)
78                .unwrap_or(default_color);
79            let thickness = (deco.underline_metrics.thickness * font_size)
80                .max(1.0)
81                .ceil();
82            let gap = thickness;
83            let y = run.line_y - deco.underline_metrics.offset * font_size;
84            renderer.rectangle(x_start as i32, y as i32, w, thickness as u32, color);
85            renderer.rectangle(
86                x_start as i32,
87                (y + thickness + gap) as i32,
88                w,
89                thickness as u32,
90                color,
91            );
92        }
93    }
94
95    // Strikethrough
96    if td.strikethrough {
97        let color = td
98            .strikethrough_color_opt
99            .or(span.color_opt)
100            .unwrap_or(default_color);
101        let thickness = (deco.strikethrough_metrics.thickness * font_size)
102            .max(1.0)
103            .ceil();
104        let y = run.line_y - deco.strikethrough_metrics.offset * font_size;
105        renderer.rectangle(x_start as i32, y as i32, w, thickness as u32, color);
106    }
107
108    // Overline
109    if td.overline {
110        let color = td
111            .overline_color_opt
112            .or(span.color_opt)
113            .unwrap_or(default_color);
114        // Reuse underline thickness for overline
115        let thickness = (deco.underline_metrics.thickness * font_size)
116            .max(1.0)
117            .ceil();
118        // clamped so it doesn't go above the line top.
119        let y = (run.line_y - deco.ascent * font_size).max(run.line_top);
120        renderer.rectangle(x_start as i32, y as i32, w, thickness as u32, color);
121    }
122}
123
124/// Helper to migrate from old renderer
125//TODO: remove in future version
126#[cfg(feature = "swash")]
127#[derive(Debug)]
128pub struct LegacyRenderer<'a, F: FnMut(i32, i32, u32, u32, Color)> {
129    pub font_system: &'a mut FontSystem,
130    pub cache: &'a mut SwashCache,
131    pub callback: F,
132}
133
134#[cfg(feature = "swash")]
135impl<'a, F: FnMut(i32, i32, u32, u32, Color)> Renderer for LegacyRenderer<'a, F> {
136    fn rectangle(&mut self, x: i32, y: i32, w: u32, h: u32, color: Color) {
137        (self.callback)(x, y, w, h, color);
138    }
139
140    fn glyph(&mut self, physical_glyph: PhysicalGlyph, color: Color) {
141        self.cache.with_pixels(
142            self.font_system,
143            physical_glyph.cache_key,
144            color,
145            |x, y, pixel_color| {
146                (self.callback)(
147                    physical_glyph.x + x,
148                    physical_glyph.y + y,
149                    1,
150                    1,
151                    pixel_color,
152                );
153            },
154        );
155    }
156}