From 31d1087b8614ddd423a6c522d91a1d93a37cd92e Mon Sep 17 00:00:00 2001 From: Joe Wilm Date: Sun, 8 Oct 2017 09:54:00 -0700 Subject: [PATCH] FreeType rasterizer respects some fontconfig The FreeType rasterizer now reads settings like antialias, rgba, lcdfilter, and hintstyle and chooses FreeType settings accordingly. The result is that Alacritty fonts should look similar to the rest of the system. --- font/src/ft/fc/mod.rs | 2 + font/src/ft/fc/pattern.rs | 13 ++- font/src/ft/mod.rs | 169 ++++++++++++++++++++++++++++++-------- 3 files changed, 149 insertions(+), 35 deletions(-) diff --git a/font/src/ft/fc/mod.rs b/font/src/ft/fc/mod.rs index c4cb40eb..f6d7d64a 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 5b5a80e7..55fbaefe 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 6c160042..1905d99d 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);