1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/// A module that contains functions and objects relating to fontconfig
pub mod fontconfig;

use rusttype::{point, Font, Scale, VMetrics};
use std::fs::File;
use std::io::Read;
use std::path::PathBuf;
use Canvas;
use Drawable;

/// A drawable object that represents text
pub struct Text<'a> {
    /// The position of the text on the canvas
    pub pos: (usize, usize),
    /// The color of the text
    pub color: [u8; 4],
    /// The text that is rendered to the canvas on draw
    pub text: String,
    /// The font used in rendering the text
    pub font: Font<'a>,
    /// The scale that is applied to the text
    pub scale: Scale,
    /// The vertical metrics of the text
    pub v_metrics: VMetrics,
}

/// Loads a font file into a `Vec<u8>`
pub fn load_font_file<P: Into<PathBuf>>(path: P) -> Vec<u8> {
    let mut data: Vec<u8> = Vec::new();
    let mut file = File::open(path.into()).expect("Could not open font file");
    file.read_to_end(&mut data)
        .expect("Could not read font file");
    data
}

impl<'a> Text<'a> {
    /// Creates a new Text object
    pub fn new<T: Into<String>>(
        pos: (usize, usize),
        color: [u8; 4],
        font_data: &'a [u8],
        height: f32,
        width_scale: f32,
        text: T,
    ) -> Text<'a> {
        let text = text.into();
        // Create font
        let font = Font::try_from_bytes(font_data).expect("Error constructing Font");
        // Create scale
        let scale = Scale {
            x: height * width_scale,
            y: height,
        };
        // Create needed metrics
        let v_metrics = font.v_metrics(scale);
        Text {
            pos,
            color,
            text: text.clone(),
            scale,
            v_metrics,
            font,
        }
    }

    fn draw_text(&self, canvas: &mut Canvas) {
        let glyphs: Vec<_> = self
            .font
            .layout(&self.text, self.scale, point(0.0, self.v_metrics.ascent))
            .collect();
        for glyph in glyphs {
            if let Some(bounding_box) = glyph.pixel_bounding_box() {
                glyph.draw(|x, y, v| {
                    let x = ((x as usize + self.pos.0) as i32 + bounding_box.min.x) as usize;
                    let y = ((y as usize + self.pos.1) as i32 + bounding_box.min.y) as usize;

                    if x < canvas.width && y < canvas.height {
                        let mut color = self.color;
                        color[0] = (f32::from(color[0]) * v) as u8;
                        canvas.draw_point(x, y, color);
                    }
                });
            }
        }
    }

    /// Calculates the width in pixels of the text
    pub fn get_width(&self) -> usize {
        let glyphs: Vec<_> = self
            .font
            .layout(&self.text, self.scale, point(0.0, self.v_metrics.ascent))
            .collect();
        let min_x = glyphs
            .first()
            .map(|g| {
                if let Some(bb) = g.pixel_bounding_box() {
                    bb.min.x
                } else {
                    g.position().x as i32
                }
            })
            .unwrap_or(0);
        let max_x = glyphs
            .last()
            .map(|g| {
                if let Some(bb) = g.pixel_bounding_box() {
                    bb.max.x
                } else {
                    (g.position().x + g.unpositioned().h_metrics().advance_width) as i32
                }
            })
            .unwrap_or(0);
        (max_x - min_x) as usize
    }
}

impl<'a> Drawable for Text<'a> {
    fn draw(&self, canvas: &mut Canvas) {
        self.draw_text(canvas);
    }
}