orbtk_tinyskia/tinyskia/
font.rs

1use rusttype::OutlineBuilder;
2use tiny_skia::{FillRule, Paint, PathBuilder, Pixmap, Transform};
3
4#[derive(Debug)]
5struct GlyphTracer {
6    path_builder: PathBuilder,
7    position: rusttype::Point<f32>,
8}
9
10impl GlyphTracer {
11    #[inline(always)]
12    fn map_point(&self, x: f32, y: f32) -> (f32, f32) {
13        (self.position.x + x, self.position.y + y)
14    }
15}
16
17impl OutlineBuilder for GlyphTracer {
18    fn move_to(&mut self, x: f32, y: f32) {
19        let (x, y) = self.map_point(x, y);
20        self.path_builder.move_to(x, y);
21    }
22
23    fn line_to(&mut self, x: f32, y: f32) {
24        let (x, y) = self.map_point(x, y);
25        self.path_builder.line_to(x, y);
26    }
27
28    fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
29        let (x, y) = self.map_point(x, y);
30        let (x1, y1) = self.map_point(x1, y1);
31        self.path_builder.quad_to(x1, y1, x, y);
32    }
33
34    fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
35        let (x, y) = self.map_point(x, y);
36        let (x1, y1) = self.map_point(x1, y1);
37        let (x2, y2) = self.map_point(x2, y2);
38        self.path_builder.cubic_to(x1, y1, x2, y2, x, y);
39    }
40
41    fn close(&mut self) {
42        self.path_builder.close();
43    }
44}
45
46/// Structure used to hold font objects.
47#[derive(Debug, Clone)]
48pub struct Font {
49    inner: rusttype::Font<'static>,
50}
51
52impl Font {
53    /// Read the font from byte stream.
54    pub fn from_bytes(bytes: &'static [u8]) -> Result<Self, &'static str> {
55        rusttype::Font::try_from_bytes(bytes)
56            .map(|font| Font { inner: font })
57            .ok_or("Could not load font from bytes")
58    }
59
60    /// Measures the text width and height (given in pixels).
61    pub fn measure_text(&self, text: &str, size: f64) -> (f64, f64) {
62        let scale = rusttype::Scale::uniform(size as f32);
63        let v_metrics = self.inner.v_metrics(scale);
64        let offset = rusttype::point(0.0, v_metrics.ascent);
65
66        let pixel_height = size.ceil();
67
68        // Glyphs to draw for "RustType". Feel free to try other strings.
69        let glyphs: Vec<rusttype::PositionedGlyph> =
70            self.inner.layout(text, scale, offset).collect();
71
72        let width = glyphs
73            .iter()
74            .rev()
75            .map(|g| g.position().x as f32 + g.unpositioned().h_metrics().advance_width)
76            .next()
77            .unwrap_or(0.0)
78            .ceil() as f64;
79
80        (width, pixel_height)
81    }
82
83    /// Renders the given text object.
84    pub fn render_text(
85        &self,
86        font_size: f64,
87        paint: &Paint,
88        pixmap: &mut Pixmap,
89        position: (f64, f64),
90        text: &str,
91    ) {
92        let scale = rusttype::Scale::uniform(font_size as f32);
93
94        // The origin of a line of text is at the baseline (roughly where non-descending letters sit).
95        // We don't want to clip the text, so we shift it down with an offset when laying it out.
96        // v_metrics.ascent is the distance between the baseline and the highest edge of any glyph in
97        // the font. That's enough to guarantee that there's no clipping.
98        let v_metrics = self.inner.v_metrics(scale);
99        let offset = rusttype::point(0.0, v_metrics.ascent);
100
101        let glyphs: Vec<rusttype::PositionedGlyph> =
102            self.inner.layout(text, scale, offset).collect();
103
104        let mut glyph_tracer = GlyphTracer {
105            path_builder: PathBuilder::new(),
106            position: rusttype::point(0.0, 0.0),
107        };
108        for g in glyphs.iter() {
109            let mut gpos = match g.pixel_bounding_box() {
110                Some(bbox) => rusttype::point(bbox.min.x as f32, bbox.min.y as f32),
111                None => {
112                    continue;
113                }
114            };
115            gpos.x += position.0 as f32;
116            gpos.y += position.1 as f32;
117            glyph_tracer.position = gpos;
118            g.build_outline(&mut glyph_tracer);
119        }
120        if let Some(path) = glyph_tracer.path_builder.finish() {
121            pixmap.fill_path(&path, paint, FillRule::Winding, Transform::identity(), None);
122        }
123    }
124}