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
use crate::*;

use riddle_common::Color;
use riddle_image::Image;

use futures::{AsyncRead, AsyncReadExt};
use std::io::Read;

/// Represents a parsed TTF file, and facilitates simple rendering
pub struct TTFont {
    font: rusttype::Font<'static>,
}

impl TTFont {
    /// Construct a new TTFont from a `Read` instance. The source will be read to
    /// the end, and the entire buffer parsed as a TTF font file.
    ///
    /// # Example
    ///
    /// ```
    /// # use riddle_font::*;
    /// # fn main() -> Result<(), FontError> {
    /// let ttf_bytes = include_bytes!("../../example_assets/Roboto-Regular.ttf");
    /// let font = TTFont::load(&ttf_bytes[..])?;
    /// # Ok (()) }
    /// ```
    pub fn load<R: Read>(mut r: R) -> Result<Self> {
        let mut data = vec![0u8; 0];
        r.read_to_end(&mut data)?;

        let font = rusttype::Font::try_from_vec(data).ok_or(FontError::FontParseFailed)?;
        Ok(TTFont { font })
    }

    /// Construct a new TTFont from an `AsyncRead` instance. The source will be read
    /// the end, and the entire buffer parsed as a TTF font file.
    ///
    /// # Example
    ///
    /// ```
    /// # use riddle_font::*; fn main() -> Result<(), FontError> { futures::executor::block_on(async_main()) }
    /// # async fn async_main() -> Result<(), FontError> {
    /// let ttf_bytes = include_bytes!("../../example_assets/Roboto-Regular.ttf");
    /// let font = TTFont::load_async(&ttf_bytes[..]).await?;
    /// # Ok(()) }
    /// ```
    pub async fn load_async<R: AsyncRead + Unpin>(mut r: R) -> Result<Self> {
        let mut data = vec![0u8; 0];
        r.read_to_end(&mut data).await?;

        let font = rusttype::Font::try_from_vec(data).ok_or(FontError::FontParseFailed)?;
        Ok(TTFont { font })
    }

    /// Render a string in this font to an image. It will only be a single line of text, even
    /// if newlines are present.
    ///
    /// # Arguments
    ///
    /// * `text` - The string to be rendered, as a single line of text.
    /// * `pixel_height` - The font size (in pixels) to render the font in. The output image will have
    ///                    the same height.
    ///
    /// # Example
    ///
    /// ```
    /// # use riddle_font::*;
    /// # fn main() -> Result<(), FontError> {
    /// # let ttf_bytes = include_bytes!("../../example_assets/Roboto-Regular.ttf");
    /// let font = TTFont::load(&ttf_bytes[..])?;
    /// let image = font.render_simple("A String", 24)?;
    /// # Ok (()) }
    /// ```
    pub fn render_simple(&self, text: &str, pixel_height: u32) -> Result<Image> {
        let scale = rusttype::Scale::uniform(pixel_height as f32);
        let layout: Vec<rusttype::PositionedGlyph> = self
            .font
            .layout(text, scale, rusttype::Point { x: 0.0, y: 0.0 })
            .collect();

        if layout.is_empty() {
            Ok(Image::new(0, 0))
        } else {
            let base_line = self.font.v_metrics(scale).ascent as i32;

            let max_x = layout
                .iter()
                .map(|glyph| glyph.pixel_bounding_box().unwrap_or_default().max.x)
                .max()
                .unwrap();

            let mut img = Image::new(max_x as u32, pixel_height);

            for glyph in layout {
                let bb = glyph.pixel_bounding_box().unwrap_or_default();
                glyph.draw(|x, y, v| {
                    let b = (255.0 * v) as u8;
                    img.set_pixel(
                        (bb.min.x + x as i32) as u32,
                        (base_line + bb.min.y + (y as i32)) as u32,
                        Color::rgba(255, 255, 255, b),
                    );
                })
            }

            Ok(img)
        }
    }
}

impl rusttype_ext::RustTypeTTFontExt for TTFont {
    fn rustype_font(&self) -> &rusttype::Font<'static> {
        &self.font
    }
}