1
use cosmic_text::Attrs;
2
use cosmic_text::Buffer;
3
use cosmic_text::FontSystem;
4
use cosmic_text::Metrics;
5
use cosmic_text::Shaping;
6
use cosmic_text::SwashCache;
7
use tiny_skia::PathBuilder;
8
use tiny_skia::PixmapMut;
9
use tiny_skia::PixmapPaint;
10
use tiny_skia::Transform;
11

            
12
pub struct TextCache {
13
    /// A FontSystem provides access to detected system fonts, create one per application
14
    font_system: FontSystem,
15

            
16
    /// A SwashCache stores rasterized glyphs, create one per application
17
    swash_cache: SwashCache,
18
}
19

            
20
impl TextCache {
21
2
    pub fn new() -> TextCache {
22
2
        let font_system = FontSystem::new();
23
2
        let swash_cache = SwashCache::new();
24
2

            
25
2
        TextCache {
26
2
            font_system,
27
2
            swash_cache,
28
2
        }
29
2
    }
30

            
31
    /// Font metrics indicate the font size and line height of a buffer
32
21
    pub fn create_buffer(&mut self, text: &str, font_metrics: Metrics) -> Buffer {
33
21
        // A Buffer provides shaping and layout for a UTF-8 string, create one per text widget
34
21
        let mut buffer = Buffer::new(&mut self.font_system, font_metrics);
35
21

            
36
21
        // Set a size for the text buffer, in pixels
37
21
        buffer.set_size(&mut self.font_system, None, None);
38
21

            
39
21
        // Attributes indicate what font to choose.
40
21
        let attrs = Attrs::new();
41
21

            
42
21
        // Add some text!
43
21
        buffer.set_text(&mut self.font_system, text, attrs, Shaping::Advanced);
44
21

            
45
21
        // Perform shaping as desired
46
21
        buffer.shape_until_scroll(&mut self.font_system, true);
47
21
        buffer
48
21
    }
49

            
50
    /// Resizes the font metrics of the buffer.
51
21
    pub fn resize(&mut self, buffer: &mut Buffer, font_metrics: Metrics) {
52
21
        buffer.set_metrics(&mut self.font_system, font_metrics);
53
21
        buffer.shape_until_scroll(&mut self.font_system, true);
54
21
    }
55

            
56
    /// Draw the given cached text at the given location.
57
93
    pub fn draw(&mut self, buffer: &Buffer, pixmap: &mut PixmapMut, transform: Transform) {
58
93
        let paint = tiny_skia::Paint::default();
59

            
60
        // Draw the buffer
61
93
        for run in buffer.layout_runs() {
62
542
            for glyph in run.glyphs.iter() {
63
542
                let physical_glyph = glyph.physical((0., 0.), 1.0);
64

            
65
                // let glyph_color = match glyph.color_opt {
66
                //     Some(some) => some,
67
                //     None => Color,
68
                // };
69

            
70
                // Try to get the font outline, which we can draw directly with tiny-skia.
71
542
                if let Some(outline) = self
72
542
                    .swash_cache
73
542
                    .get_outline_commands(&mut self.font_system, physical_glyph.cache_key)
74
                {
75
542
                    let mut path_builder = PathBuilder::new();
76

            
77
10016
                    for command in outline {
78
9474
                        match *command {
79
693
                            cosmic_text::Command::MoveTo(p0) => {
80
693
                                path_builder.move_to(p0.x, p0.y);
81
693
                            }
82
2348
                            cosmic_text::Command::LineTo(p0) => {
83
2348
                                path_builder.line_to(p0.x, p0.y);
84
2348
                            }
85
                            cosmic_text::Command::CurveTo(p0, p1, p2) => {
86
                                path_builder.cubic_to(p0.x, p0.y, p1.x, p1.y, p2.x, p2.y);
87
                            }
88
693
                            cosmic_text::Command::Close => {
89
693
                                path_builder.close();
90
693
                            }
91
5740
                            cosmic_text::Command::QuadTo(p0, p1) => {
92
5740
                                path_builder.quad_to(p0.x, p0.y, p1.x, p1.y);
93
5740
                            }
94
                        }
95
                    }
96

            
97
542
                    if let Some(path) = path_builder.finish() {
98
524
                        pixmap.fill_path(
99
524
                            &path,
100
524
                            &paint,
101
524
                            tiny_skia::FillRule::Winding,
102
524
                            Transform::from_translate(physical_glyph.x as f32, physical_glyph.y as f32)
103
524
                                .pre_scale(1.0, -1.0)
104
524
                                .post_concat(transform),
105
524
                            None,
106
524
                        );
107
524
                    }
108
                } else {
109
                    // Otherwise render the image using skia.
110
                    if let Some(image) = self
111
                        .swash_cache
112
                        .get_image(&mut self.font_system, physical_glyph.cache_key)
113
                    {
114
                        let mut data = image.data.clone();
115
                        let pixmap_image =
116
                            PixmapMut::from_bytes(&mut data, image.placement.width, image.placement.height);
117

            
118
                        pixmap.draw_pixmap(
119
                            0,
120
                            0,
121
                            pixmap_image.unwrap().as_ref(),
122
                            &PixmapPaint::default(),
123
                            transform,
124
                            None,
125
                        );
126
                    };
127
                }
128
            }
129
        }
130
93
    }
131
}
132

            
133
#[cfg(test)]
134
mod tests {
135
    use tiny_skia::Pixmap;
136

            
137
    use super::*;
138

            
139
    #[test]
140
1
    fn test_textcache() {
141
1
        let mut cache = TextCache::new();
142
1

            
143
1
        // Create a simple text label and resize it.
144
1
        let mut buffer = cache.create_buffer("A test label", Metrics::new(14.0, 14.0));
145
1
        cache.resize(&mut buffer, Metrics::new(50.0, 50.0));
146
1

            
147
1
        let mut pixel_buffer = Pixmap::new(800, 600).unwrap();
148
1
        cache.draw(
149
1
            &buffer,
150
1
            &mut PixmapMut::from_bytes(pixel_buffer.data_mut(), 800, 600).unwrap(),
151
1
            Transform::default(),
152
1
        );
153
1
    }
154
}