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.
This commit is contained in:
Joe Wilm 2017-10-08 09:54:00 -07:00 committed by Joe Wilm
parent 46d88ad244
commit 31d1087b86
3 changed files with 149 additions and 35 deletions

View File

@ -44,6 +44,8 @@ pub mod pattern;
pub use self::pattern::{Pattern, PatternRef}; pub use self::pattern::{Pattern, PatternRef};
/// Find the font closest matching the provided pattern. /// Find the font closest matching the provided pattern.
///
/// The returned pattern is the result of Pattern::render_prepare.
pub fn font_match( pub fn font_match(
config: &ConfigRef, config: &ConfigRef,
pattern: &mut PatternRef, pattern: &mut PatternRef,

View File

@ -24,7 +24,7 @@ use foreign_types::{ForeignType, ForeignTypeRef};
use super::ffi::FcResultMatch; use super::ffi::FcResultMatch;
use super::ffi::{FcPatternDestroy, FcPatternAddCharSet}; use super::ffi::{FcPatternDestroy, FcPatternAddCharSet};
use super::ffi::{FcPatternGetString, FcPatternCreate, FcPatternAddString}; 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::{FcChar8, FcPattern, FcDefaultSubstitute, FcConfigSubstitute};
use super::ffi::{FcFontRenderPrepare, FcPatternGetBool, FcBool}; use super::ffi::{FcFontRenderPrepare, FcPatternGetBool, FcBool};
@ -373,6 +373,17 @@ macro_rules! boolean_getter {
} }
impl PatternRef { 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 /// Add a string value to the pattern
/// ///
/// If the returned value is `true`, the value is added at the end of /// If the returned value is `true`, the value is added at the end of

View File

@ -16,8 +16,10 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::cmp::min; use std::cmp::min;
use std::path::PathBuf; use std::path::PathBuf;
use std::fmt;
use freetype::{self, Library}; use freetype::{self, Library};
use libc::c_uint;
pub mod fc; pub mod fc;
@ -27,6 +29,28 @@ use super::{FontDesc, RasterizedGlyph, Metrics, Size, FontKey, GlyphKey, Weight,
struct Face { struct Face {
ft_face: freetype::Face<'static>, ft_face: freetype::Face<'static>,
key: FontKey, 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. /// 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<FontKey, Error> { fn load_font(&mut self, desc: &FontDesc, _size: Size) -> Result<FontKey, Error> {
let face = Face { let face = self.get_face(desc)?;
ft_face: self.get_face(desc)?,
key: FontKey::next(),
};
let key = face.key; let key = face.key;
self.faces.insert(key, face); self.faces.insert(key, face);
Ok(key) Ok(key)
@ -124,7 +145,7 @@ impl IntoFontconfigType for Weight {
impl FreeTypeRasterizer { impl FreeTypeRasterizer {
/// Load a font face accoring to `FontDesc` /// Load a font face accoring to `FontDesc`
fn get_face(&mut self, desc: &FontDesc) -> Result<freetype::Face<'static>, Error> { fn get_face(&mut self, desc: &FontDesc) -> Result<Face, Error> {
match desc.style { match desc.style {
Style::Description { slant, weight } => { Style::Description { slant, weight } => {
// Match nearest font // Match nearest font
@ -142,7 +163,7 @@ impl FreeTypeRasterizer {
desc: &FontDesc, desc: &FontDesc,
slant: Slant, slant: Slant,
weight: Weight weight: Weight
) -> Result<freetype::Face<'static>, Error> { ) -> Result<Face, Error> {
let mut pattern = fc::Pattern::new(); let mut pattern = fc::Pattern::new();
pattern.add_family(&desc.name); pattern.add_family(&desc.name);
pattern.set_weight(weight.into_fontconfig_type()); pattern.set_weight(weight.into_fontconfig_type());
@ -151,30 +172,50 @@ impl FreeTypeRasterizer {
let font = fc::font_match(fc::Config::get_current(), &mut pattern) let font = fc::font_match(fc::Config::get_current(), &mut pattern)
.ok_or_else(|| Error::MissingFont(desc.to_owned()))?; .ok_or_else(|| Error::MissingFont(desc.to_owned()))?;
if let (Some(path), Some(index)) = (font.file(0), font.index().nth(0)) { self.face_from_pattern(&font)
return Ok(self.library.new_face(path, index)?); .and_then(|pattern| {
} pattern
.map(Ok)
Err(Error::MissingFont(desc.to_owned())) .unwrap_or_else(|| Err(Error::MissingFont(desc.to_owned())))
})
} }
fn get_specific_face( fn get_specific_face(
&mut self, &mut self,
desc: &FontDesc, desc: &FontDesc,
style: &str style: &str
) -> Result<freetype::Face<'static>, Error> { ) -> Result<Face, Error> {
let mut pattern = fc::Pattern::new(); let mut pattern = fc::Pattern::new();
pattern.add_family(&desc.name); pattern.add_family(&desc.name);
pattern.add_style(style); pattern.add_style(style);
let font = fc::font_match(fc::Config::get_current(), &mut pattern) let font = fc::font_match(fc::Config::get_current(), &mut pattern)
.ok_or_else(|| Error::MissingFont(desc.to_owned()))?; .ok_or_else(|| Error::MissingFont(desc.to_owned()))?;
if let (Some(path), Some(index)) = (font.file(0), font.index().nth(0)) { self.face_from_pattern(&font)
println!("got font path={:?}", path); .and_then(|pattern| {
Ok(self.library.new_face(path, index)?) pattern
} .map(Ok)
else { .unwrap_or_else(|| Err(Error::MissingFont(desc.to_owned())))
Err(Error::MissingFont(desc.to_owned())) })
}
fn face_from_pattern(&self, pattern: &fc::Pattern) -> Result<Option<Face>, 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 face = self.faces.get(&font_key).unwrap();
let index = face.ft_face.get_char_index(glyph_key.c as usize); 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 { unsafe {
let ft_lib = self.library.raw(); let ft_lib = self.library.raw();
freetype::ffi::FT_Library_SetLcdFilter( freetype::ffi::FT_Library_SetLcdFilter(ft_lib, face.lcd_filter);
ft_lib,
freetype::ffi::FT_LCD_FILTER_DEFAULT
);
} }
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())?; let (pixel_width, buf) = Self::normalize_buffer(&glyph.bitmap())?;
Ok(RasterizedGlyph { 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. /// 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. /// 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(); let config = fc::Config::get_current();
match fc::font_match(config, &mut pattern) { match fc::font_match(config, &mut pattern) {
Some(font) => { Some(pattern) => {
if let (Some(path), Some(index)) = (font.file(0), font.index().nth(0)) { if let (Some(path), Some(_)) = (pattern.file(0), pattern.index().nth(0)) {
match self.keys.get(&path) { match self.keys.get(&path) {
// We've previously loaded this font, so don't // We've previously loaded this font, so don't
// load it again. // load it again.
Some(&key) => { Some(&key) => {
debug!("Hit for font {:?}", path); debug!("Hit for font {:?}; no need to load.", path);
Ok(key) Ok(key)
}, },
None => { None => {
debug!("Miss for font {:?}", path); debug!("Miss for font {:?}; loading now.", path);
let face = Face { // Safe to unwrap the option since we've already checked for the path
ft_face: self.library.new_face(&path, index)?, // and index above.
key: FontKey::next(), let face = self.face_from_pattern(&pattern)?.unwrap();
};
let key = face.key; let key = face.key;
self.faces.insert(key, face); self.faces.insert(key, face);
self.keys.insert(path, key); self.keys.insert(path, key);