diff --git a/font/src/ft/fc/mod.rs b/font/src/ft/fc/mod.rs index c4cb40e..f6d7d64 100644 --- a/font/src/ft/fc/mod.rs +++ b/font/src/ft/fc/mod.rs @@ -44,6 +44,8 @@ pub mod pattern; pub use self::pattern::{Pattern, PatternRef}; /// Find the font closest matching the provided pattern. +/// +/// The returned pattern is the result of Pattern::render_prepare. pub fn font_match( config: &ConfigRef, pattern: &mut PatternRef, diff --git a/font/src/ft/fc/pattern.rs b/font/src/ft/fc/pattern.rs index 5b5a80e..55fbaef 100644 --- a/font/src/ft/fc/pattern.rs +++ b/font/src/ft/fc/pattern.rs @@ -24,7 +24,7 @@ use foreign_types::{ForeignType, ForeignTypeRef}; use super::ffi::FcResultMatch; use super::ffi::{FcPatternDestroy, FcPatternAddCharSet}; use super::ffi::{FcPatternGetString, FcPatternCreate, FcPatternAddString}; -use super::ffi::{FcPatternGetInteger, FcPatternAddInteger}; +use super::ffi::{FcPatternGetInteger, FcPatternAddInteger, FcPatternPrint}; use super::ffi::{FcChar8, FcPattern, FcDefaultSubstitute, FcConfigSubstitute}; use super::ffi::{FcFontRenderPrepare, FcPatternGetBool, FcBool}; @@ -373,6 +373,17 @@ macro_rules! boolean_getter { } impl PatternRef { + // Prints the pattern to stdout + // + // FontConfig doesn't expose a way to iterate over all members of a pattern; + // instead, we just defer to FcPatternPrint. Otherwise, this could have been + // a `fmt::Debug` impl. + pub fn print(&self) { + unsafe { + FcPatternPrint(self.as_ptr()) + } + } + /// Add a string value to the pattern /// /// If the returned value is `true`, the value is added at the end of diff --git a/font/src/ft/mod.rs b/font/src/ft/mod.rs index 6c16004..1905d99 100644 --- a/font/src/ft/mod.rs +++ b/font/src/ft/mod.rs @@ -16,8 +16,10 @@ use std::collections::HashMap; use std::cmp::min; use std::path::PathBuf; +use std::fmt; use freetype::{self, Library}; +use libc::c_uint; pub mod fc; @@ -27,6 +29,28 @@ use super::{FontDesc, RasterizedGlyph, Metrics, Size, FontKey, GlyphKey, Weight, struct Face { ft_face: freetype::Face<'static>, key: FontKey, + load_flags: freetype::face::LoadFlag, + render_mode: freetype::RenderMode, + lcd_filter: c_uint, +} + +impl fmt::Debug for Face { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Face") + .field("ft_face", &self.ft_face) + .field("key", &self.key) + .field("load_flags", &self.load_flags) + .field("render_mode", &match self.render_mode { + freetype::RenderMode::Normal => "Normal", + freetype::RenderMode::Light => "Light", + freetype::RenderMode::Mono => "Mono", + freetype::RenderMode::Lcd => "Lcd", + freetype::RenderMode::LcdV => "LcdV", + freetype::RenderMode::Max => "Max", + }) + .field("lcd_filter", &self.lcd_filter) + .finish() + } } /// Rasterizes glyphs for a single font face. @@ -80,10 +104,7 @@ impl ::Rasterize for FreeTypeRasterizer { } fn load_font(&mut self, desc: &FontDesc, _size: Size) -> Result { - let face = Face { - ft_face: self.get_face(desc)?, - key: FontKey::next(), - }; + let face = self.get_face(desc)?; let key = face.key; self.faces.insert(key, face); Ok(key) @@ -124,7 +145,7 @@ impl IntoFontconfigType for Weight { impl FreeTypeRasterizer { /// Load a font face accoring to `FontDesc` - fn get_face(&mut self, desc: &FontDesc) -> Result, Error> { + fn get_face(&mut self, desc: &FontDesc) -> Result { match desc.style { Style::Description { slant, weight } => { // Match nearest font @@ -142,7 +163,7 @@ impl FreeTypeRasterizer { desc: &FontDesc, slant: Slant, weight: Weight - ) -> Result, Error> { + ) -> Result { let mut pattern = fc::Pattern::new(); pattern.add_family(&desc.name); pattern.set_weight(weight.into_fontconfig_type()); @@ -151,30 +172,50 @@ impl FreeTypeRasterizer { let font = fc::font_match(fc::Config::get_current(), &mut pattern) .ok_or_else(|| Error::MissingFont(desc.to_owned()))?; - if let (Some(path), Some(index)) = (font.file(0), font.index().nth(0)) { - return Ok(self.library.new_face(path, index)?); - } - - Err(Error::MissingFont(desc.to_owned())) + self.face_from_pattern(&font) + .and_then(|pattern| { + pattern + .map(Ok) + .unwrap_or_else(|| Err(Error::MissingFont(desc.to_owned()))) + }) } fn get_specific_face( &mut self, desc: &FontDesc, style: &str - ) -> Result, Error> { + ) -> Result { let mut pattern = fc::Pattern::new(); pattern.add_family(&desc.name); pattern.add_style(style); let font = fc::font_match(fc::Config::get_current(), &mut pattern) .ok_or_else(|| Error::MissingFont(desc.to_owned()))?; - if let (Some(path), Some(index)) = (font.file(0), font.index().nth(0)) { - println!("got font path={:?}", path); - Ok(self.library.new_face(path, index)?) - } - else { - Err(Error::MissingFont(desc.to_owned())) + self.face_from_pattern(&font) + .and_then(|pattern| { + pattern + .map(Ok) + .unwrap_or_else(|| Err(Error::MissingFont(desc.to_owned()))) + }) + } + + fn face_from_pattern(&self, pattern: &fc::Pattern) -> Result, Error> { + if let (Some(path), Some(index)) = (pattern.file(0), pattern.index().nth(0)) { + trace!("got font path={:?}", path); + let ft_face = self.library.new_face(path, index)?; + let face = Face { + ft_face: ft_face, + key: FontKey::next(), + load_flags: Self::ft_load_flags(pattern), + render_mode: Self::ft_render_mode(pattern), + lcd_filter: Self::ft_lcd_filter(pattern), + }; + + debug!("Loaded Face {:?}", face); + + Ok(Some(face)) + } else { + Ok(None) } } @@ -212,18 +253,15 @@ impl FreeTypeRasterizer { let face = self.faces.get(&font_key).unwrap(); let index = face.ft_face.get_char_index(glyph_key.c as usize); - face.ft_face.load_glyph(index as u32, freetype::face::TARGET_LIGHT)?; - let glyph = face.ft_face.glyph(); - glyph.render_glyph(freetype::render_mode::RenderMode::Lcd)?; - unsafe { let ft_lib = self.library.raw(); - freetype::ffi::FT_Library_SetLcdFilter( - ft_lib, - freetype::ffi::FT_LCD_FILTER_DEFAULT - ); + freetype::ffi::FT_Library_SetLcdFilter(ft_lib, face.lcd_filter); } + face.ft_face.load_glyph(index as u32, face.load_flags)?; + let glyph = face.ft_face.glyph(); + glyph.render_glyph(face.render_mode)?; + let (pixel_width, buf) = Self::normalize_buffer(&glyph.bitmap())?; Ok(RasterizedGlyph { @@ -236,6 +274,70 @@ impl FreeTypeRasterizer { }) } + fn ft_load_flags(pat: &fc::Pattern) -> freetype::face::LoadFlag { + let antialias = pat.antialias().next().unwrap_or(true); + let hinting = pat.hintstyle().next().unwrap_or(fc::HintStyle::Slight); + let rgba = pat.rgba().next().unwrap_or(fc::Rgba::Unknown); + + use freetype::face::*; + + match (antialias, hinting, rgba) { + (false, fc::HintStyle::None, _) => NO_HINTING | MONOCHROME, + (false, _, _) => TARGET_MONO | MONOCHROME, + (true, fc::HintStyle::None, _) => NO_HINTING | TARGET_NORMAL, + // hintslight does *not* use LCD hinting even when a subpixel mode + // is selected. + // + // According to the FreeType docs, + // + // > You can use a hinting algorithm that doesn't correspond to the + // > same rendering mode. As an example, it is possible to use the + // > ‘light’ hinting algorithm and have the results rendered in + // > horizontal LCD pixel mode. + // + // In practice, this means we can have `FT_LOAD_TARGET_LIGHT` with + // subpixel render modes like `FT_RENDER_MODE_LCD`. Libraries like + // cairo take the same approach and consider `hintslight` to always + // prefer `FT_LOAD_TARGET_LIGHT` + (true, fc::HintStyle::Slight, _) => TARGET_LIGHT, + // If LCD hinting is to be used, must select hintmedium or hintfull, + // have AA enabled, and select a subpixel mode. + (true, _, fc::Rgba::Rgb) | + (true, _, fc::Rgba::Bgr) => TARGET_LCD, + (true, _, fc::Rgba::Vrgb) | + (true, _, fc::Rgba::Vbgr) => TARGET_LCD_V, + // For non-rgba modes with either Medium or Full hinting, just use + // the default hinting algorithm. + // + // TODO should Medium/Full control whether to use the auto hinter? + (true, _, fc::Rgba::Unknown) => TARGET_NORMAL, + (true, _, fc::Rgba::None) => TARGET_NORMAL, + } + } + + fn ft_render_mode(pat: &fc::Pattern) -> freetype::RenderMode { + let antialias = pat.antialias().next().unwrap_or(true); + let rgba = pat.rgba().next().unwrap_or(fc::Rgba::Unknown); + + match (antialias, rgba) { + (false, _) => freetype::RenderMode::Mono, + (_, fc::Rgba::Rgb) | + (_, fc::Rgba::Bgr) => freetype::RenderMode::Lcd, + (_, fc::Rgba::Vrgb) | + (_, fc::Rgba::Vbgr) => freetype::RenderMode::LcdV, + (true, _) => freetype::RenderMode::Normal, + } + } + + fn ft_lcd_filter(pat: &fc::Pattern) -> c_uint { + match pat.lcdfilter().next().unwrap_or(fc::LcdFilter::Default) { + fc::LcdFilter::None => freetype::ffi::FT_LCD_FILTER_NONE, + fc::LcdFilter::Default => freetype::ffi::FT_LCD_FILTER_DEFAULT, + fc::LcdFilter::Light => freetype::ffi::FT_LCD_FILTER_LIGHT, + fc::LcdFilter::Legacy => freetype::ffi::FT_LCD_FILTER_LEGACY, + } + } + /// Given a FreeType `Bitmap`, returns packed buffer with 1 byte per LCD channel. /// /// The i32 value in the return type is the number of pixels per row. @@ -313,22 +415,21 @@ impl FreeTypeRasterizer { let config = fc::Config::get_current(); match fc::font_match(config, &mut pattern) { - Some(font) => { - if let (Some(path), Some(index)) = (font.file(0), font.index().nth(0)) { + Some(pattern) => { + if let (Some(path), Some(_)) = (pattern.file(0), pattern.index().nth(0)) { match self.keys.get(&path) { // We've previously loaded this font, so don't // load it again. Some(&key) => { - debug!("Hit for font {:?}", path); + debug!("Hit for font {:?}; no need to load.", path); Ok(key) }, None => { - debug!("Miss for font {:?}", path); - let face = Face { - ft_face: self.library.new_face(&path, index)?, - key: FontKey::next(), - }; + debug!("Miss for font {:?}; loading now.", path); + // Safe to unwrap the option since we've already checked for the path + // and index above. + let face = self.face_from_pattern(&pattern)?.unwrap(); let key = face.key; self.faces.insert(key, face); self.keys.insert(path, key);