Propagate font rasterizer errors

This allows consumers of the font crate to handle errors instead of the
library panicking.
This commit is contained in:
Joe Wilm 2016-12-31 11:17:02 -08:00
parent a00970c9a8
commit 2738969f29
5 changed files with 206 additions and 110 deletions

View File

@ -54,8 +54,6 @@ use self::byte_order::extract_rgb;
use super::Size; use super::Size;
static FONT_LOAD_ERROR: &'static str = "font specified by FontKey has been loaded";
/// Font descriptor /// Font descriptor
/// ///
/// The descriptor provides data about a font and supports creating a font. /// The descriptor provides data about a font and supports creating a font.
@ -79,71 +77,107 @@ pub struct Rasterizer {
device_pixel_ratio: f32, device_pixel_ratio: f32,
} }
impl Rasterizer { /// Errors occurring when using the core text rasterizer
pub fn new(_dpi_x: f32, _dpi_y: f32, device_pixel_ratio: f32) -> Rasterizer { #[derive(Debug)]
pub enum Error {
/// Tried to rasterize a glyph but it was not available
MissingGlyph(char),
/// Couldn't find font matching description
MissingFont(FontDesc),
/// Requested an operation with a FontKey that isn't known to the rasterizer
FontNotLoaded,
}
impl ::std::error::Error for Error {
fn description(&self) -> &str {
match *self {
Error::MissingGlyph(ref _c) => "couldn't find the requested glyph",
Error::MissingFont(ref _desc) => "couldn't find the requested font",
Error::FontNotLoaded => "tried to operate on font that hasn't been loaded",
}
}
}
impl ::std::fmt::Display for Error {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
match *self {
Error::MissingGlyph(ref c) => {
write!(f, "Glyph not found for char {:?}", c)
},
Error::MissingFont(ref desc) => {
write!(f, "Couldn't find a font with {}", desc)
},
Error::FontNotLoaded => {
f.write_str("Tried to use a font that hasn't been loaded")
}
}
}
}
impl ::Rasterize for Rasterizer {
type Err = Error;
fn new(_dpi_x: f32, _dpi_y: f32, device_pixel_ratio: f32) -> Result<Rasterizer, Error> {
println!("device_pixel_ratio: {}", device_pixel_ratio); println!("device_pixel_ratio: {}", device_pixel_ratio);
Rasterizer { Ok(Rasterizer {
fonts: HashMap::new(), fonts: HashMap::new(),
keys: HashMap::new(), keys: HashMap::new(),
device_pixel_ratio: device_pixel_ratio, device_pixel_ratio: device_pixel_ratio,
} })
} }
/// Get metrics for font specified by FontKey /// Get metrics for font specified by FontKey
/// fn metrics(&self, key: FontKey, _size: Size) -> Result<Metrics, Error> {
/// # Panics
///
/// If FontKey was not generated by `load_font`, this method will panic.
pub fn metrics(&self, key: FontKey, _size: Size) -> Metrics {
// NOTE size is not needed here since the font loaded already contains // NOTE size is not needed here since the font loaded already contains
// it. It's part of the API due to platform differences. // it. It's part of the API due to platform differences.
let font = self.fonts.get(&key).expect(FONT_LOAD_ERROR); let font = self.fonts
font.metrics() .get(&key)
.ok_or(Error::FontNotLoaded)?;
Ok(font.metrics())
} }
pub fn load_font(&mut self, desc: &FontDesc, size: Size) -> Option<FontKey> { fn load_font(&mut self, desc: &FontDesc, size: Size) -> Result<FontKey, Error> {
self.keys self.keys
.get(&(desc.to_owned(), size)) .get(&(desc.to_owned(), size))
.map(|k| *k) .map(|k| Ok(*k))
.or_else(|| { .unwrap_or_else(|| {
self.get_font(desc, size) let font = self.get_font(desc, size)?;
.map(|font| { let key = FontKey::next();
let key = FontKey::next();
self.fonts.insert(key, font); self.fonts.insert(key, font);
self.keys.insert((desc.clone(), size), key); self.keys.insert((desc.clone(), size), key);
key Ok(key)
})
}) })
} }
fn get_font(&mut self, desc: &FontDesc, size: Size) -> Option<Font> { /// Get rasterized glyph for given glyph key
fn get_glyph(&mut self, glyph: &GlyphKey) -> Result<RasterizedGlyph, Error> {
let scaled_size = self.device_pixel_ratio * glyph.size.as_f32_pts();
self.fonts
.get(&glyph.font_key)
.ok_or(Error::FontNotLoaded)?
.get_glyph(glyph.c, scaled_size as _)
}
}
impl Rasterizer {
fn get_font(&mut self, desc: &FontDesc, size: Size) -> Result<Font, Error> {
let descriptors = descriptors_for_family(&desc.name[..]); let descriptors = descriptors_for_family(&desc.name[..]);
for descriptor in descriptors { for descriptor in descriptors {
if descriptor.style_name == desc.style { if descriptor.style_name == desc.style {
// Found the font we want // Found the font we want
let scaled_size = size.as_f32_pts() as f64 * self.device_pixel_ratio as f64; let scaled_size = size.as_f32_pts() as f64 * self.device_pixel_ratio as f64;
let font = descriptor.to_font(scaled_size); let font = descriptor.to_font(scaled_size);
return Some(font); return Ok(font);
} }
} }
None Err(Error::MissingFont(desc.to_owned()))
}
/// Get rasterized glyph for given glyph key
///
/// # Panics
///
/// Panics if the FontKey specified in GlyphKey was not generated from `load_font`
pub fn get_glyph(&mut self, glyph: &GlyphKey) -> RasterizedGlyph {
let scaled_size = self.device_pixel_ratio * glyph.size.as_f32_pts();
self.fonts
.get(&glyph.font_key)
.expect(FONT_LOAD_ERROR)
.get_glyph(glyph.c, scaled_size as _)
} }
} }
@ -269,21 +303,9 @@ impl Font {
) )
} }
pub fn get_glyph(&self, character: char, _size: f64) -> RasterizedGlyph { pub fn get_glyph(&self, character: char, _size: f64) -> Result<RasterizedGlyph, Error> {
let glyph_index = match self.glyph_index(character) { let glyph_index = self.glyph_index(character)
Some(i) => i, .ok_or(Error::MissingGlyph(character))?;
None => {
// TODO refactor this
return RasterizedGlyph {
c: ' ',
width: 0,
height: 0,
top: 0,
left: 0,
buf: Vec::new()
};
}
};
let bounds = self.bounding_rect_for_glyph(Default::default(), glyph_index); let bounds = self.bounding_rect_for_glyph(Default::default(), glyph_index);
@ -295,14 +317,14 @@ impl Font {
let rasterized_height = (rasterized_descent + rasterized_ascent) as u32; let rasterized_height = (rasterized_descent + rasterized_ascent) as u32;
if rasterized_width == 0 || rasterized_height == 0 { if rasterized_width == 0 || rasterized_height == 0 {
return RasterizedGlyph { return Ok(RasterizedGlyph {
c: ' ', c: ' ',
width: 0, width: 0,
height: 0, height: 0,
top: 0, top: 0,
left: 0, left: 0,
buf: Vec::new() buf: Vec::new()
}; });
} }
let mut cg_context = CGContext::create_bitmap_context( let mut cg_context = CGContext::create_bitmap_context(
@ -354,14 +376,14 @@ impl Font {
let buf = extract_rgb(rasterized_pixels); let buf = extract_rgb(rasterized_pixels);
RasterizedGlyph { Ok(RasterizedGlyph {
c: character, c: character,
left: rasterized_left, left: rasterized_left,
top: (bounds.size.height + bounds.origin.y).ceil() as i32, top: (bounds.size.height + bounds.origin.y).ceil() as i32,
width: rasterized_width as i32, width: rasterized_width as i32,
height: rasterized_height as i32, height: rasterized_height as i32,
buf: buf, buf: buf,
} })
} }
fn glyph_index(&self, character: char) -> Option<u32> { fn glyph_index(&self, character: char) -> Option<u32> {

View File

@ -15,9 +15,7 @@
//! Rasterization powered by FreeType and FontConfig //! Rasterization powered by FreeType and FontConfig
use std::collections::HashMap; use std::collections::HashMap;
use freetype::Library; use freetype::{self, Library, Face};
use freetype::Face;
use freetype;
mod list_fonts; mod list_fonts;
@ -25,7 +23,7 @@ use self::list_fonts::{Family, get_font_families};
use super::{FontDesc, RasterizedGlyph, Metrics, Size, FontKey, GlyphKey}; use super::{FontDesc, RasterizedGlyph, Metrics, Size, FontKey, GlyphKey};
/// Rasterizes glyphs for a single font face. /// Rasterizes glyphs for a single font face.
pub struct Rasterizer { pub struct FreeTypeRasterizer {
faces: HashMap<FontKey, Face<'static>>, faces: HashMap<FontKey, Face<'static>>,
library: Library, library: Library,
system_fonts: HashMap<String, Family>, system_fonts: HashMap<String, Family>,
@ -40,16 +38,13 @@ fn to_freetype_26_6(f: f32) -> isize {
((1i32 << 6) as f32 * f) as isize ((1i32 << 6) as f32 * f) as isize
} }
// #[inline] impl ::Rasterize for FreeTypeRasterizer {
// fn freetype_26_6_to_float(val: i64) -> f64 { type Err = Error;
// val as f64 / (1i64 << 6) as f64
// }
impl Rasterizer { fn new(dpi_x: f32, dpi_y: f32, device_pixel_ratio: f32) -> Result<FreeTypeRasterizer, Error> {
pub fn new(dpi_x: f32, dpi_y: f32, device_pixel_ratio: f32) -> Rasterizer { let library = Library::init()?;
let library = Library::init().unwrap();
Rasterizer { Ok(FreeTypeRasterizer {
system_fonts: get_font_families(), system_fonts: get_font_families(),
faces: HashMap::new(), faces: HashMap::new(),
keys: HashMap::new(), keys: HashMap::new(),
@ -57,11 +52,13 @@ impl Rasterizer {
dpi_x: dpi_x as u32, dpi_x: dpi_x as u32,
dpi_y: dpi_y as u32, dpi_y: dpi_y as u32,
dpr: device_pixel_ratio, dpr: device_pixel_ratio,
} })
} }
pub fn metrics(&self, key: FontKey, size: Size) -> Metrics { fn metrics(&self, key: FontKey, size: Size) -> Result<Metrics, Error> {
let face = self.faces.get(&key).unwrap(); let face = self.faces
.get(&key)
.ok_or(Error::FontNotLoaded)?;
let scale_size = self.dpr as f64 * size.as_f32_pts() as f64; let scale_size = self.dpr as f64 * size.as_f32_pts() as f64;
@ -72,48 +69,36 @@ impl Rasterizer {
let w_scale = w * scale_size / em_size; let w_scale = w * scale_size / em_size;
let h_scale = h * scale_size / em_size; let h_scale = h * scale_size / em_size;
Metrics { Ok(Metrics {
average_advance: w_scale, average_advance: w_scale,
line_height: h_scale, line_height: h_scale,
} })
} }
pub fn load_font(&mut self, desc: &FontDesc, _size: Size) -> Option<FontKey> { fn load_font(&mut self, desc: &FontDesc, _size: Size) -> Result<FontKey, Error> {
self.keys self.keys
.get(&desc.to_owned()) .get(&desc.to_owned())
.map(|k| *k) .map(|k| Ok(*k))
.or_else(|| { .unwrap_or_else(|| {
self.get_face(desc) let face = self.get_face(desc)?;
.map(|face| { let key = FontKey::next();
let key = FontKey::next(); self.faces.insert(key, face);
self.faces.insert(key, face); Ok(key)
key
})
}) })
} }
fn get_face(&mut self, desc: &FontDesc) -> Option<Face<'static>> { fn get_glyph(&mut self, glyph_key: &GlyphKey) -> Result<RasterizedGlyph, Error> {
self.system_fonts
.get(&desc.name[..])
.and_then(|font| font.variants().get(&desc.style[..]))
.map(|variant| {
self.library.new_face(variant.path(), variant.index())
.expect("TODO handle new_face error")
})
}
pub fn get_glyph(&mut self, glyph_key: &GlyphKey) -> RasterizedGlyph {
let face = self.faces let face = self.faces
.get(&glyph_key.font_key) .get(&glyph_key.font_key)
.expect("TODO handle get_face error"); .ok_or(Error::FontNotLoaded)?;
let size = glyph_key.size.as_f32_pts() * self.dpr; let size = glyph_key.size.as_f32_pts() * self.dpr;
let c = glyph_key.c; let c = glyph_key.c;
face.set_char_size(to_freetype_26_6(size), 0, self.dpi_x, self.dpi_y).unwrap(); face.set_char_size(to_freetype_26_6(size), 0, self.dpi_x, self.dpi_y)?;
face.load_char(c as usize, freetype::face::TARGET_LIGHT).unwrap(); face.load_char(c as usize, freetype::face::TARGET_LIGHT)?;
let glyph = face.glyph(); let glyph = face.glyph();
glyph.render_glyph(freetype::render_mode::RenderMode::Lcd).unwrap(); glyph.render_glyph(freetype::render_mode::RenderMode::Lcd)?;
unsafe { unsafe {
let ft_lib = self.library.raw(); let ft_lib = self.library.raw();
@ -134,18 +119,80 @@ impl Rasterizer {
packed.extend_from_slice(&buf[start..stop]); packed.extend_from_slice(&buf[start..stop]);
} }
RasterizedGlyph { Ok(RasterizedGlyph {
c: c, c: c,
top: glyph.bitmap_top(), top: glyph.bitmap_top(),
left: glyph.bitmap_left(), left: glyph.bitmap_left(),
width: glyph.bitmap().width() / 3, width: glyph.bitmap().width() / 3,
height: glyph.bitmap().rows(), height: glyph.bitmap().rows(),
buf: packed, buf: packed,
})
}
}
impl FreeTypeRasterizer {
fn get_face(&mut self, desc: &FontDesc) -> Result<Face<'static>, Error> {
self.system_fonts
.get(&desc.name[..])
.and_then(|font| font.variants().get(&desc.style[..]))
.ok_or_else(|| Error::MissingFont(desc.to_owned()))
.and_then(|variant| Ok(self.library.new_face(variant.path(), variant.index())?))
}
}
/// Errors occurring when using the freetype rasterizer
#[derive(Debug)]
pub enum Error {
/// Error occurred within the FreeType library
FreeType(freetype::Error),
/// Couldn't find font matching description
MissingFont(FontDesc),
/// Requested an operation with a FontKey that isn't known to the rasterizer
FontNotLoaded,
}
impl ::std::error::Error for Error {
fn cause(&self) -> Option<&::std::error::Error> {
match *self {
Error::FreeType(ref err) => Some(err),
_ => None,
}
}
fn description(&self) -> &str {
match *self {
Error::FreeType(ref err) => err.description(),
Error::MissingFont(ref _desc) => "couldn't find the requested font",
Error::FontNotLoaded => "tried to operate on font that hasn't been loaded",
} }
} }
} }
unsafe impl Send for Rasterizer {} impl ::std::fmt::Display for Error {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
match *self {
Error::FreeType(ref err) => {
err.fmt(f)
},
Error::MissingFont(ref desc) => {
write!(f, "Couldn't find a font with {}", desc)
},
Error::FontNotLoaded => {
f.write_str("Tried to use a font that hasn't been loaded")
}
}
}
}
impl From<freetype::Error> for Error {
fn from(val: freetype::Error) -> Error {
Error::FreeType(val)
}
}
unsafe impl Send for FreeTypeRasterizer {}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {

View File

@ -46,7 +46,7 @@ use std::sync::atomic::{AtomicU32, ATOMIC_U32_INIT, Ordering};
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
mod ft; mod ft;
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
pub use ft::*; pub use ft::{FreeTypeRasterizer as Rasterizer, Error};
// If target is macos, reexport everything from darwin // If target is macos, reexport everything from darwin
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
@ -71,6 +71,12 @@ impl FontDesc {
} }
} }
impl fmt::Display for FontDesc {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "name '{}' and style '{}'", self.name, self.style)
}
}
/// Identifier for a Font for use in maps/etc /// Identifier for a Font for use in maps/etc
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct FontKey { pub struct FontKey {
@ -156,3 +162,21 @@ pub struct Metrics {
pub average_advance: f64, pub average_advance: f64,
pub line_height: f64, pub line_height: f64,
} }
pub trait Rasterize {
/// Errors occurring in Rasterize methods
type Err: ::std::error::Error + Send + Sync + 'static;
/// Create a new Rasterize
fn new(dpi_x: f32, dpi_y: f32, device_pixel_ratio: f32) -> Result<Self, Self::Err>
where Self: Sized;
/// Get `Metrics` for the given `FontKey` and `Size`
fn metrics(&self, FontKey, Size) -> Result<Metrics, Self::Err>;
/// Load the font described by `FontDesc` and `Size`
fn load_font(&mut self, &FontDesc, Size) -> Result<FontKey, Self::Err>;
/// Rasterize the glyph described by `GlyphKey`.
fn get_glyph(&mut self, &GlyphKey) -> Result<RasterizedGlyph, Self::Err>;
}

View File

@ -22,7 +22,7 @@ use Rgb;
use ansi::Color; use ansi::Color;
use cli; use cli;
use config::Config; use config::Config;
use font; use font::{self, Rasterize};
use meter::Meter; use meter::Meter;
use renderer::{GlyphCache, QuadRenderer}; use renderer::{GlyphCache, QuadRenderer};
use selection::Selection; use selection::Selection;
@ -92,7 +92,8 @@ impl Display {
println!("device_pixel_ratio: {}", dpr); println!("device_pixel_ratio: {}", dpr);
let rasterizer = font::Rasterizer::new(dpi.x(), dpi.y(), dpr); // TODO ERROR HANDLING
let rasterizer = font::Rasterizer::new(dpi.x(), dpi.y(), dpr).unwrap();
// Create renderer // Create renderer
let mut renderer = QuadRenderer::new(config, size); let mut renderer = QuadRenderer::new(config, size);

View File

@ -20,7 +20,7 @@ use std::ptr;
use std::sync::mpsc; use std::sync::mpsc;
use cgmath; use cgmath;
use font::{self, Rasterizer, RasterizedGlyph, FontDesc, GlyphKey, FontKey}; use font::{self, Rasterizer, Rasterize, RasterizedGlyph, FontDesc, GlyphKey, FontKey};
use gl::types::*; use gl::types::*;
use gl; use gl;
use notify::{Watcher as WatcherApi, RecommendedWatcher as Watcher, op}; use notify::{Watcher as WatcherApi, RecommendedWatcher as Watcher, op};
@ -138,7 +138,7 @@ impl GlyphCache {
let bold = if bold_desc == regular_desc { let bold = if bold_desc == regular_desc {
regular regular
} else { } else {
rasterizer.load_font(&bold_desc, size).unwrap_or_else(|| regular) rasterizer.load_font(&bold_desc, size).unwrap_or_else(|_| regular)
}; };
// Load italic font // Load italic font
@ -149,7 +149,7 @@ impl GlyphCache {
regular regular
} else { } else {
rasterizer.load_font(&italic_desc, size) rasterizer.load_font(&italic_desc, size)
.unwrap_or_else(|| regular) .unwrap_or_else(|_| regular)
}; };
let mut cache = GlyphCache { let mut cache = GlyphCache {
@ -181,13 +181,15 @@ impl GlyphCache {
} }
pub fn font_metrics(&self) -> font::Metrics { pub fn font_metrics(&self) -> font::Metrics {
self.rasterizer.metrics(self.font_key, self.font_size) // TODO ERROR HANDLING
self.rasterizer.metrics(self.font_key, self.font_size).unwrap()
} }
fn load_and_cache_glyph<L>(&mut self, glyph_key: GlyphKey, loader: &mut L) fn load_and_cache_glyph<L>(&mut self, glyph_key: GlyphKey, loader: &mut L)
where L: LoadGlyph where L: LoadGlyph
{ {
let rasterized = self.rasterizer.get_glyph(&glyph_key); // TODO ERROR HANDLING
let rasterized = self.rasterizer.get_glyph(&glyph_key).unwrap();
let glyph = loader.load_glyph(&rasterized); let glyph = loader.load_glyph(&rasterized);
self.cache.insert(glyph_key, glyph); self.cache.insert(glyph_key, glyph);
} }