diff --git a/font/src/darwin/mod.rs b/font/src/darwin/mod.rs index c445996..99f080a 100644 --- a/font/src/darwin/mod.rs +++ b/font/src/darwin/mod.rs @@ -54,8 +54,6 @@ use self::byte_order::extract_rgb; use super::Size; -static FONT_LOAD_ERROR: &'static str = "font specified by FontKey has been loaded"; - /// Font descriptor /// /// The descriptor provides data about a font and supports creating a font. @@ -79,71 +77,107 @@ pub struct Rasterizer { device_pixel_ratio: f32, } -impl Rasterizer { - pub fn new(_dpi_x: f32, _dpi_y: f32, device_pixel_ratio: f32) -> Rasterizer { +/// Errors occurring when using the core text 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 { println!("device_pixel_ratio: {}", device_pixel_ratio); - Rasterizer { + Ok(Rasterizer { fonts: HashMap::new(), keys: HashMap::new(), device_pixel_ratio: device_pixel_ratio, - } + }) } /// Get metrics for font specified by FontKey - /// - /// # Panics - /// - /// If FontKey was not generated by `load_font`, this method will panic. - pub fn metrics(&self, key: FontKey, _size: Size) -> Metrics { + fn metrics(&self, key: FontKey, _size: Size) -> Result { // NOTE size is not needed here since the font loaded already contains // it. It's part of the API due to platform differences. - let font = self.fonts.get(&key).expect(FONT_LOAD_ERROR); - font.metrics() + let font = self.fonts + .get(&key) + .ok_or(Error::FontNotLoaded)?; + + Ok(font.metrics()) } - pub fn load_font(&mut self, desc: &FontDesc, size: Size) -> Option { + fn load_font(&mut self, desc: &FontDesc, size: Size) -> Result { self.keys .get(&(desc.to_owned(), size)) - .map(|k| *k) - .or_else(|| { - self.get_font(desc, size) - .map(|font| { - let key = FontKey::next(); + .map(|k| Ok(*k)) + .unwrap_or_else(|| { + let font = self.get_font(desc, size)?; + let key = FontKey::next(); - self.fonts.insert(key, font); - self.keys.insert((desc.clone(), size), key); + self.fonts.insert(key, font); + self.keys.insert((desc.clone(), size), key); - key - }) + Ok(key) }) } - fn get_font(&mut self, desc: &FontDesc, size: Size) -> Option { + /// Get rasterized glyph for given glyph key + fn get_glyph(&mut self, glyph: &GlyphKey) -> Result { + 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 { let descriptors = descriptors_for_family(&desc.name[..]); for descriptor in descriptors { if descriptor.style_name == desc.style { // Found the font we want let scaled_size = size.as_f32_pts() as f64 * self.device_pixel_ratio as f64; let font = descriptor.to_font(scaled_size); - return Some(font); + return Ok(font); } } - None - } - - /// 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 _) + Err(Error::MissingFont(desc.to_owned())) } } @@ -269,21 +303,9 @@ impl Font { ) } - pub fn get_glyph(&self, character: char, _size: f64) -> RasterizedGlyph { - let glyph_index = match self.glyph_index(character) { - Some(i) => i, - None => { - // TODO refactor this - return RasterizedGlyph { - c: ' ', - width: 0, - height: 0, - top: 0, - left: 0, - buf: Vec::new() - }; - } - }; + pub fn get_glyph(&self, character: char, _size: f64) -> Result { + let glyph_index = self.glyph_index(character) + .ok_or(Error::MissingGlyph(character))?; 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; if rasterized_width == 0 || rasterized_height == 0 { - return RasterizedGlyph { + return Ok(RasterizedGlyph { c: ' ', width: 0, height: 0, top: 0, left: 0, buf: Vec::new() - }; + }); } let mut cg_context = CGContext::create_bitmap_context( @@ -354,14 +376,14 @@ impl Font { let buf = extract_rgb(rasterized_pixels); - RasterizedGlyph { + Ok(RasterizedGlyph { c: character, left: rasterized_left, top: (bounds.size.height + bounds.origin.y).ceil() as i32, width: rasterized_width as i32, height: rasterized_height as i32, buf: buf, - } + }) } fn glyph_index(&self, character: char) -> Option { diff --git a/font/src/ft/mod.rs b/font/src/ft/mod.rs index c9abd69..8e65381 100644 --- a/font/src/ft/mod.rs +++ b/font/src/ft/mod.rs @@ -15,9 +15,7 @@ //! Rasterization powered by FreeType and FontConfig use std::collections::HashMap; -use freetype::Library; -use freetype::Face; -use freetype; +use freetype::{self, Library, Face}; mod list_fonts; @@ -25,7 +23,7 @@ use self::list_fonts::{Family, get_font_families}; use super::{FontDesc, RasterizedGlyph, Metrics, Size, FontKey, GlyphKey}; /// Rasterizes glyphs for a single font face. -pub struct Rasterizer { +pub struct FreeTypeRasterizer { faces: HashMap>, library: Library, system_fonts: HashMap, @@ -40,16 +38,13 @@ fn to_freetype_26_6(f: f32) -> isize { ((1i32 << 6) as f32 * f) as isize } -// #[inline] -// fn freetype_26_6_to_float(val: i64) -> f64 { -// val as f64 / (1i64 << 6) as f64 -// } +impl ::Rasterize for FreeTypeRasterizer { + type Err = Error; -impl Rasterizer { - pub fn new(dpi_x: f32, dpi_y: f32, device_pixel_ratio: f32) -> Rasterizer { - let library = Library::init().unwrap(); + fn new(dpi_x: f32, dpi_y: f32, device_pixel_ratio: f32) -> Result { + let library = Library::init()?; - Rasterizer { + Ok(FreeTypeRasterizer { system_fonts: get_font_families(), faces: HashMap::new(), keys: HashMap::new(), @@ -57,11 +52,13 @@ impl Rasterizer { dpi_x: dpi_x as u32, dpi_y: dpi_y as u32, dpr: device_pixel_ratio, - } + }) } - pub fn metrics(&self, key: FontKey, size: Size) -> Metrics { - let face = self.faces.get(&key).unwrap(); + fn metrics(&self, key: FontKey, size: Size) -> Result { + let face = self.faces + .get(&key) + .ok_or(Error::FontNotLoaded)?; 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 h_scale = h * scale_size / em_size; - Metrics { + Ok(Metrics { average_advance: w_scale, line_height: h_scale, - } + }) } - pub fn load_font(&mut self, desc: &FontDesc, _size: Size) -> Option { + fn load_font(&mut self, desc: &FontDesc, _size: Size) -> Result { self.keys .get(&desc.to_owned()) - .map(|k| *k) - .or_else(|| { - self.get_face(desc) - .map(|face| { - let key = FontKey::next(); - self.faces.insert(key, face); - key - }) + .map(|k| Ok(*k)) + .unwrap_or_else(|| { + let face = self.get_face(desc)?; + let key = FontKey::next(); + self.faces.insert(key, face); + Ok(key) }) } - fn get_face(&mut self, desc: &FontDesc) -> Option> { - 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 { + fn get_glyph(&mut self, glyph_key: &GlyphKey) -> Result { let face = self.faces .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 c = glyph_key.c; - face.set_char_size(to_freetype_26_6(size), 0, self.dpi_x, self.dpi_y).unwrap(); - face.load_char(c as usize, freetype::face::TARGET_LIGHT).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)?; let glyph = face.glyph(); - glyph.render_glyph(freetype::render_mode::RenderMode::Lcd).unwrap(); + glyph.render_glyph(freetype::render_mode::RenderMode::Lcd)?; unsafe { let ft_lib = self.library.raw(); @@ -134,18 +119,80 @@ impl Rasterizer { packed.extend_from_slice(&buf[start..stop]); } - RasterizedGlyph { + Ok(RasterizedGlyph { c: c, top: glyph.bitmap_top(), left: glyph.bitmap_left(), width: glyph.bitmap().width() / 3, height: glyph.bitmap().rows(), buf: packed, + }) + } +} + +impl FreeTypeRasterizer { + fn get_face(&mut self, desc: &FontDesc) -> Result, 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 for Error { + fn from(val: freetype::Error) -> Error { + Error::FreeType(val) + } +} + +unsafe impl Send for FreeTypeRasterizer {} #[cfg(test)] mod tests { diff --git a/font/src/lib.rs b/font/src/lib.rs index 7ec5984..afe2b94 100644 --- a/font/src/lib.rs +++ b/font/src/lib.rs @@ -46,7 +46,7 @@ use std::sync::atomic::{AtomicU32, ATOMIC_U32_INIT, Ordering}; #[cfg(not(target_os = "macos"))] mod ft; #[cfg(not(target_os = "macos"))] -pub use ft::*; +pub use ft::{FreeTypeRasterizer as Rasterizer, Error}; // If target is macos, reexport everything from darwin #[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 #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pub struct FontKey { @@ -156,3 +162,21 @@ pub struct Metrics { pub average_advance: 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 + where Self: Sized; + + /// Get `Metrics` for the given `FontKey` and `Size` + fn metrics(&self, FontKey, Size) -> Result; + + /// Load the font described by `FontDesc` and `Size` + fn load_font(&mut self, &FontDesc, Size) -> Result; + + /// Rasterize the glyph described by `GlyphKey`. + fn get_glyph(&mut self, &GlyphKey) -> Result; +} diff --git a/src/display.rs b/src/display.rs index 8cf19f4..8e2134a 100644 --- a/src/display.rs +++ b/src/display.rs @@ -22,7 +22,7 @@ use Rgb; use ansi::Color; use cli; use config::Config; -use font; +use font::{self, Rasterize}; use meter::Meter; use renderer::{GlyphCache, QuadRenderer}; use selection::Selection; @@ -92,7 +92,8 @@ impl Display { 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 let mut renderer = QuadRenderer::new(config, size); diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 54db485..838555b 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -20,7 +20,7 @@ use std::ptr; use std::sync::mpsc; use cgmath; -use font::{self, Rasterizer, RasterizedGlyph, FontDesc, GlyphKey, FontKey}; +use font::{self, Rasterizer, Rasterize, RasterizedGlyph, FontDesc, GlyphKey, FontKey}; use gl::types::*; use gl; use notify::{Watcher as WatcherApi, RecommendedWatcher as Watcher, op}; @@ -138,7 +138,7 @@ impl GlyphCache { let bold = if bold_desc == regular_desc { regular } else { - rasterizer.load_font(&bold_desc, size).unwrap_or_else(|| regular) + rasterizer.load_font(&bold_desc, size).unwrap_or_else(|_| regular) }; // Load italic font @@ -149,7 +149,7 @@ impl GlyphCache { regular } else { rasterizer.load_font(&italic_desc, size) - .unwrap_or_else(|| regular) + .unwrap_or_else(|_| regular) }; let mut cache = GlyphCache { @@ -181,13 +181,15 @@ impl GlyphCache { } 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(&mut self, glyph_key: GlyphKey, loader: &mut L) 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); self.cache.insert(glyph_key, glyph); }