Skip to main content

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 deco = &span.data;
34    let td = &deco.text_decoration;
35    let font_size = span.font_size;
36
37    let x_range = span.x_range(run);
38    let w = (x_range.end - x_range.start) as u32;
39    if w == 0 {
40        return;
41    }
42    let x_start = x_range.start;
43
44    // Underline
45    match td.underline {
46        UnderlineStyle::None => {}
47        UnderlineStyle::Single => {
48            let color = td
49                .underline_color_opt
50                .or(span.color_opt)
51                .unwrap_or(default_color);
52            let thickness = (deco.underline_metrics.thickness * font_size)
53                .max(1.0)
54                .ceil();
55            let y = run.line_y - deco.underline_metrics.offset * font_size;
56            renderer.rectangle(x_start as i32, y as i32, w, thickness as u32, color);
57        }
58        UnderlineStyle::Double => {
59            let color = td
60                .underline_color_opt
61                .or(span.color_opt)
62                .unwrap_or(default_color);
63            let thickness = (deco.underline_metrics.thickness * font_size)
64                .max(1.0)
65                .ceil();
66            let gap = thickness;
67            let y = run.line_y - deco.underline_metrics.offset * font_size;
68            renderer.rectangle(x_start as i32, y as i32, w, thickness as u32, color);
69            renderer.rectangle(
70                x_start as i32,
71                (y + thickness + gap) as i32,
72                w,
73                thickness as u32,
74                color,
75            );
76        }
77    }
78
79    // Strikethrough
80    if td.strikethrough {
81        let color = td
82            .strikethrough_color_opt
83            .or(span.color_opt)
84            .unwrap_or(default_color);
85        let thickness = (deco.strikethrough_metrics.thickness * font_size)
86            .max(1.0)
87            .ceil();
88        let y = run.line_y - deco.strikethrough_metrics.offset * font_size;
89        renderer.rectangle(x_start as i32, y as i32, w, thickness as u32, color);
90    }
91
92    // Overline
93    if td.overline {
94        let color = td
95            .overline_color_opt
96            .or(span.color_opt)
97            .unwrap_or(default_color);
98        // Reuse underline thickness for overline
99        let thickness = (deco.underline_metrics.thickness * font_size)
100            .max(1.0)
101            .ceil();
102        // clamped so it doesn't go above the line top.
103        let y = (run.line_y - deco.ascent * font_size).max(run.line_top);
104        renderer.rectangle(x_start as i32, y as i32, w, thickness as u32, color);
105    }
106}
107
108/// Helper to migrate from old renderer
109//TODO: remove in future version
110#[cfg(feature = "swash")]
111#[derive(Debug)]
112pub struct LegacyRenderer<'a, F: FnMut(i32, i32, u32, u32, Color)> {
113    pub font_system: &'a mut FontSystem,
114    pub cache: &'a mut SwashCache,
115    pub callback: F,
116}
117
118#[cfg(feature = "swash")]
119impl<'a, F: FnMut(i32, i32, u32, u32, Color)> Renderer for LegacyRenderer<'a, F> {
120    fn rectangle(&mut self, x: i32, y: i32, w: u32, h: u32, color: Color) {
121        (self.callback)(x, y, w, h, color);
122    }
123
124    fn glyph(&mut self, physical_glyph: PhysicalGlyph, color: Color) {
125        self.cache.with_pixels(
126            self.font_system,
127            physical_glyph.cache_key,
128            color,
129            |x, y, pixel_color| {
130                (self.callback)(
131                    physical_glyph.x + x,
132                    physical_glyph.y + y,
133                    1,
134                    1,
135                    pixel_color,
136                );
137            },
138        );
139    }
140}