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
//! Module for loading fonts used in image macros.

use std::error::Error;
use std::fmt;
use std::io;
use std::path::Path;

use rusttype::{self, FontCollection};

use super::Loader;
use super::filesystem::{BytesLoader, FileLoader};


/// File extension of font files.
pub const FILE_EXTENSION: &'static str = "ttf";


macro_attr! {
    /// Font that can be used to caption image macros.
    #[derive(NewtypeDeref!, NewtypeFrom!)]
    pub struct Font(rusttype::Font<'static>);
    // TODO: add font name for better Debug
}
impl fmt::Debug for Font {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        write!(fmt, "Font(...)")
    }
}


/// Error that may occur during font load.
#[derive(Debug)]
pub enum FontError {
    /// Error while loading the font file.
    File(io::Error),
    /// Error for when the font file contains no fonts.
    NoFonts,
    /// Error for when the font file contains too many fonts.
    TooManyFonts(usize),
}

impl Error for FontError {
    fn description(&self) -> &str { "font loading error" }
    fn cause(&self) -> Option<&Error> {
        match *self {
            FontError::File(ref e) => Some(e),
            _ => None,
        }
    }
}

impl fmt::Display for FontError {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            FontError::File(ref e) => write!(fmt, "I/O error while loading font: {}", e),
            FontError::NoFonts => write!(fmt, "no fonts found in the file"),
            FontError::TooManyFonts(c) =>
                write!(fmt, "expected a single font in the file, found {}", c),
        }
    }
}


/// Loader for fonts stored in a directory.
///
/// Font names are translated directly into file names, loaded, and cached.
#[derive(Debug)]
pub struct FontLoader {
    inner: BytesLoader<'static>,
}

impl FontLoader {
    /// Create a new font loader.
    #[inline]
    pub fn new<D: AsRef<Path>>(directory: D) -> Self {
        FontLoader{
            inner: BytesLoader::new(
                FileLoader::for_extension(directory, FILE_EXTENSION))
        }
    }
}

impl Loader for FontLoader {
    type Item = Font;
    type Err = FontError;

    /// Load a font with given name.
    fn load<'n>(&self, name: &'n str) -> Result<Font, Self::Err> {
        let bytes = self.inner.load(name).map_err(FontError::File)?;

        let fonts: Vec<_> = FontCollection::from_bytes(bytes).into_fonts().collect();
        match fonts.len() {
            0 => {
                error!("No fonts in a file for `{}` font resource", name);
                Err(FontError::NoFonts)
            }
            1 => {
                debug!("Font `{}` loaded successfully", name);
                Ok(fonts.into_iter().next().unwrap().into())
            }
            count => {
                error!("Font file for `{}` resource contains {} fonts, expected one",
                    name, count);
                Err(FontError::TooManyFonts(count))
            }
        }
    }
}