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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
use std::cell::RefCell;
use std::path::Path;
use std::rc::Rc;

use ab_glyph::{Font as AbFont, FontRef, FontVec, PxScale, ScaleFont};

use crate::error::{Result, TetraError};
use crate::fs;
use crate::graphics::text::cache::{FontCache, RasterizedGlyph, Rasterizer};
use crate::graphics::text::Font;
use crate::graphics::Rectangle;
use crate::math::Vec2;
use crate::Context;

pub(crate) struct VectorRasterizer<F> {
    font: Rc<F>,
    scale: PxScale,
}

impl<F> VectorRasterizer<F>
where
    F: AbFont,
{
    pub fn new(font: Rc<F>, size: f32) -> VectorRasterizer<F> {
        VectorRasterizer {
            font,
            scale: PxScale::from(size),
        }
    }
}

impl<F> Rasterizer for VectorRasterizer<F>
where
    F: AbFont,
{
    fn rasterize(&self, ch: char, position: Vec2<f32>) -> Option<RasterizedGlyph> {
        let font = self.font.as_scaled(self.scale);

        let mut glyph = font.scaled_glyph(ch);

        glyph.position = ab_glyph::point(position.x, position.y);

        if let Some(outline) = font.outline_glyph(glyph.clone()) {
            let mut data = Vec::new();

            outline.draw(|_, _, v| {
                data.extend_from_slice(&[255, 255, 255, (v * 255.0) as u8]);
            });

            let bounds = outline.px_bounds();

            Some(RasterizedGlyph {
                data,
                bounds: Rectangle::new(
                    bounds.min.x - glyph.position.x,
                    bounds.min.y - glyph.position.y,
                    bounds.width(),
                    bounds.height(),
                ),
            })
        } else {
            None
        }
    }

    fn h_advance(&self, glyph: char) -> f32 {
        let scaled_font = self.font.as_scaled(self.scale);

        scaled_font.h_advance(scaled_font.glyph_id(glyph))
    }

    fn height(&self) -> f32 {
        let scaled_font = self.font.as_scaled(self.scale);

        scaled_font.height()
    }

    fn line_gap(&self) -> f32 {
        let scaled_font = self.font.as_scaled(self.scale);

        scaled_font.line_gap()
    }

    fn ascent(&self) -> f32 {
        let scaled_font = self.font.as_scaled(self.scale);

        scaled_font.ascent()
    }

    fn kerning(&self, previous: char, current: char) -> f32 {
        let scaled_font = self.font.as_scaled(self.scale);

        scaled_font.kern(
            // TODO: This is slow in debug mode
            scaled_font.glyph_id(previous),
            scaled_font.glyph_id(current),
        )
    }
}

/// Abstracts over the two Font types provided by ab_glyph.
///
/// This is preferable to using FontArc because that would incur a double
/// indirection once we type erase the Rasterizer.
#[derive(Debug, Clone)]
enum VectorFontData {
    Owned(Rc<FontVec>),
    Slice(Rc<FontRef<'static>>),
}

/// A builder for vector-based fonts.
///
/// TrueType and OpenType fonts are supported. The font data will only be loaded
/// into memory once, and it will be shared between all [`Font`](struct.Font.html)s that
/// are subsequently created by the builder instance.
///
/// [`Font::vector`](struct.Font.html#method.vector) provides a simpler API for loading
/// vector fonts, if you don't need all of the functionality of this struct.
///
/// # Performance
///
/// Creating a `VectorFontBuilder` is a relatively expensive operation. If you need to create
/// extra sizes of the font later on, store the `VectorFontBuilder` rather than building a new one.
///
/// Cloning a `VectorFontBuilder` is a very cheap operation, as the underlying data is shared between the
/// original instance and the clone via [reference-counting](https://doc.rust-lang.org/std/rc/struct.Rc.html).
#[derive(Debug, Clone)]
pub struct VectorFontBuilder {
    data: VectorFontData,
}

impl VectorFontBuilder {
    /// Loads a vector font from the given file.
    ///
    /// # Errors
    ///
    /// * `TetraError::FailedToLoadAsset` will be returned if the file could not be loaded.
    /// * `TetraError::InvalidFont` will be returned if the font data was invalid.
    pub fn new<P>(path: P) -> Result<VectorFontBuilder>
    where
        P: AsRef<Path>,
    {
        let font_bytes = fs::read(path)?;
        let font = FontVec::try_from_vec(font_bytes).map_err(|_| TetraError::InvalidFont)?;

        Ok(VectorFontBuilder {
            data: VectorFontData::Owned(Rc::new(font)),
        })
    }

    /// Loads a vector font from a slice of binary data.
    ///
    /// # Errors
    ///
    /// * `TetraError::InvalidFont` will be returned if the font data was invalid.
    pub fn from_file_data(data: &'static [u8]) -> Result<VectorFontBuilder> {
        let font = FontRef::try_from_slice(data).map_err(|_| TetraError::InvalidFont)?;

        Ok(VectorFontBuilder {
            data: VectorFontData::Slice(Rc::new(font)),
        })
    }

    /// Creates a `Font` with the given size.
    ///
    /// # Errors
    ///
    /// * `TetraError::PlatformError` will be returned if the GPU cache for the font
    ///   could not be created.
    pub fn with_size(&self, ctx: &mut Context, size: f32) -> Result<Font> {
        let rasterizer: Box<dyn Rasterizer> = match &self.data {
            VectorFontData::Owned(f) => Box::new(VectorRasterizer::new(Rc::clone(f), size)),
            VectorFontData::Slice(f) => Box::new(VectorRasterizer::new(Rc::clone(f), size)),
        };

        let cache = FontCache::new(&mut ctx.device, rasterizer)?;

        Ok(Font {
            data: Rc::new(RefCell::new(cache)),
        })
    }
}