diff options
Diffstat (limited to 'font/src/ft')
-rw-r--r-- | font/src/ft/list_fonts.rs | 186 | ||||
-rw-r--r-- | font/src/ft/mod.rs | 126 |
2 files changed, 312 insertions, 0 deletions
diff --git a/font/src/ft/list_fonts.rs b/font/src/ft/list_fonts.rs new file mode 100644 index 00000000..f171f57e --- /dev/null +++ b/font/src/ft/list_fonts.rs @@ -0,0 +1,186 @@ +use std::collections::HashMap; +use std::ffi::{CStr, CString}; +use std::fmt; +use std::path::PathBuf; +use std::ptr; +use std::str::from_utf8; + +use libc::{c_char, c_int}; + +use fontconfig::fontconfig::{FcConfigGetCurrent, FcConfigGetFonts, FcSetSystem}; +use fontconfig::fontconfig::{FcPatternGetString, FcPatternCreate, FcPatternAddString}; +use fontconfig::fontconfig::{FcPatternGetInteger}; +use fontconfig::fontconfig::{FcObjectSetCreate, FcObjectSetAdd}; +use fontconfig::fontconfig::{FcResultMatch, FcFontSetList}; +use fontconfig::fontconfig::{FcChar8}; +use fontconfig::fontconfig::{FcFontSetDestroy, FcPatternDestroy, FcObjectSetDestroy}; + +unsafe fn fc_char8_to_string(fc_str: *mut FcChar8) -> String { + from_utf8(CStr::from_ptr(fc_str as *const c_char).to_bytes()).unwrap().to_owned() +} + +fn list_families() -> Vec<String> { + let mut families = Vec::new(); + unsafe { + // https://www.freedesktop.org/software/fontconfig/fontconfig-devel/fcconfiggetcurrent.html + let config = FcConfigGetCurrent(); // *mut FcConfig + + // https://www.freedesktop.org/software/fontconfig/fontconfig-devel/fcconfiggetfonts.html + let font_set = FcConfigGetFonts(config, FcSetSystem); // *mut FcFontSet + + let nfont = (*font_set).nfont as isize; + for i in 0..nfont { + let font = (*font_set).fonts.offset(i); // *mut FcPattern + let id = 0 as c_int; + let mut family: *mut FcChar8 = ptr::null_mut(); + let mut format: *mut FcChar8 = ptr::null_mut(); + + let result = FcPatternGetString(*font, + b"fontformat\0".as_ptr() as *mut c_char, + id, + &mut format); + + if result != FcResultMatch { + continue; + } + + let format = fc_char8_to_string(format); + + if format != "TrueType" && format != "CFF" { + continue + } + + let mut id = 0; + while FcPatternGetString(*font, b"family\0".as_ptr() as *mut c_char, id, &mut family) == FcResultMatch { + let safe_family = fc_char8_to_string(family); + id += 1; + families.push(safe_family); + } + } + } + + families.sort(); + families.dedup(); + families +} + +#[derive(Debug)] +pub struct Variant { + style: String, + file: PathBuf, + index: isize, +} + +impl Variant { + #[inline] + pub fn path(&self) -> &::std::path::Path { + self.file.as_path() + } + + #[inline] + pub fn index(&self) -> isize { + self.index + } +} + +#[derive(Debug)] +pub struct Family { + name: String, + variants: HashMap<String, Variant>, +} + +impl fmt::Display for Family { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + try!(write!(f, "{}: ", self.name)); + for (k, _v) in &self.variants { + try!(write!(f, "{}, ", k)); + } + + Ok(()) + } +} + +impl Family { + #[inline] + pub fn variants(&self) -> &HashMap<String, Variant> { + &self.variants + } +} + +static FILE: &'static [u8] = b"file\0"; +static FAMILY: &'static [u8] = b"family\0"; +static INDEX: &'static [u8] = b"index\0"; +static STYLE: &'static [u8] = b"style\0"; + +pub fn get_family_info(family: String) -> Family { + + let mut members = Vec::new(); + + unsafe { + let config = FcConfigGetCurrent(); // *mut FcConfig + let mut font_set = FcConfigGetFonts(config, FcSetSystem); // *mut FcFontSet + + let pattern = FcPatternCreate(); + let family_name = CString::new(&family[..]).unwrap(); + let family_name = family_name.as_ptr(); + + // Add family name to pattern. Use this for searching. + FcPatternAddString(pattern, FAMILY.as_ptr() as *mut c_char, family_name as *mut FcChar8); + + // Request filename, style, and index for each variant in family + let object_set = FcObjectSetCreate(); // *mut FcObjectSet + FcObjectSetAdd(object_set, FILE.as_ptr() as *mut c_char); + FcObjectSetAdd(object_set, INDEX.as_ptr() as *mut c_char); + FcObjectSetAdd(object_set, STYLE.as_ptr() as *mut c_char); + + let variants = FcFontSetList(config, &mut font_set, 1 /* nsets */, pattern, object_set); + let num_variant = (*variants).nfont as isize; + + for i in 0..num_variant { + let font = (*variants).fonts.offset(i); + let mut file: *mut FcChar8 = ptr::null_mut(); + assert_eq!(FcPatternGetString(*font, FILE.as_ptr() as *mut c_char, 0, &mut file), + FcResultMatch); + let file = fc_char8_to_string(file); + + let mut style: *mut FcChar8 = ptr::null_mut(); + assert_eq!(FcPatternGetString(*font, STYLE.as_ptr() as *mut c_char, 0, &mut style), + FcResultMatch); + let style = fc_char8_to_string(style); + + let mut index = 0 as c_int; + assert_eq!(FcPatternGetInteger(*font, INDEX.as_ptr() as *mut c_char, 0, &mut index), + FcResultMatch); + + members.push(Variant { + style: style, + file: PathBuf::from(file), + index: index as isize, + }); + } + + FcFontSetDestroy(variants); + FcPatternDestroy(pattern); + FcObjectSetDestroy(object_set); + } + + Family { + name: family, + variants: members.into_iter().map(|v| (v.style.clone(), v)).collect() + } +} + +pub fn get_font_families() -> HashMap<String, Family> { + list_families().into_iter() + .map(|family| (family.clone(), get_family_info(family))) + .collect() +} + +#[cfg(test)] +mod tests { + #[test] + fn get_font_families() { + let families = super::get_font_families(); + assert!(!families.is_empty()); + } +} diff --git a/font/src/ft/mod.rs b/font/src/ft/mod.rs new file mode 100644 index 00000000..f288cda5 --- /dev/null +++ b/font/src/ft/mod.rs @@ -0,0 +1,126 @@ +//! Rasterization powered by FreeType and FontConfig +use std::collections::HashMap; + +use freetype::Library; +use freetype::Face; +use freetype; + +mod list_fonts; + +use self::list_fonts::{Family, get_font_families}; +use super::{FontDesc, RasterizedGlyph, Metrics}; + +/// Rasterizes glyphs for a single font face. +pub struct Rasterizer { + faces: HashMap<FontDesc, Face<'static>>, + library: Library, + system_fonts: HashMap<String, Family>, + dpi_x: u32, + dpi_y: u32, + dpr: f32, +} + +#[inline] +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 Rasterizer { + pub fn new(dpi_x: f32, dpi_y: f32, device_pixel_ratio: f32) -> Rasterizer { + let library = Library::init().unwrap(); + + Rasterizer { + system_fonts: get_font_families(), + faces: HashMap::new(), + library: library, + dpi_x: dpi_x as u32, + dpi_y: dpi_y as u32, + dpr: device_pixel_ratio, + } + } + + pub fn metrics(&mut self, desc: &FontDesc, size: f32) -> Metrics { + let face = self.get_face(&desc).unwrap(); + + let scale_size = self.dpr as f64 * size as f64; + + let em_size = face.em_size() as f64; + let w = face.max_advance_width() as f64; + let h = (face.ascender() - face.descender() + face.height()) as f64; + + let w_scale = w * scale_size / em_size; + let h_scale = h * scale_size / em_size; + + Metrics { + average_advance: w_scale, + line_height: h_scale, + } + } + + fn get_face(&mut self, desc: &FontDesc) -> Option<Face<'static>> { + if let Some(face) = self.faces.get(desc) { + return Some(face.clone()); + } + + if let Some(font) = self.system_fonts.get(&desc.name[..]) { + if let Some(variant) = font.variants().get(&desc.style[..]) { + let face = self.library.new_face(variant.path(), variant.index()) + .expect("TODO handle new_face error"); + + self.faces.insert(desc.to_owned(), face); + return Some(self.faces.get(desc).unwrap().clone()); + } + } + + None + } + + pub fn get_glyph(&mut self, desc: &FontDesc, size: f32, c: char) -> RasterizedGlyph { + let face = self.get_face(desc).expect("TODO handle get_face error"); + face.set_char_size(to_freetype_26_6(size * self.dpr), 0, self.dpi_x, self.dpi_y).unwrap(); + face.load_char(c as usize, freetype::face::TARGET_LIGHT).unwrap(); + let glyph = face.glyph(); + glyph.render_glyph(freetype::render_mode::RenderMode::Lcd).unwrap(); + + unsafe { + let ft_lib = self.library.raw(); + freetype::ffi::FT_Library_SetLcdFilter(ft_lib, freetype::ffi::FT_LCD_FILTER_DEFAULT); + } + + let bitmap = glyph.bitmap(); + let buf = bitmap.buffer(); + let pitch = bitmap.pitch() as usize; + + let mut packed = Vec::with_capacity((bitmap.rows() * bitmap.width()) as usize); + for i in 0..bitmap.rows() { + let start = (i as usize) * pitch; + let stop = start + bitmap.width() as usize; + packed.extend_from_slice(&buf[start..stop]); + } + + RasterizedGlyph { + c: c, + top: glyph.bitmap_top(), + left: glyph.bitmap_left(), + width: glyph.bitmap().width() / 3, + height: glyph.bitmap().rows(), + buf: packed, + } + } +} + +unsafe impl Send for Rasterizer {} + +#[cfg(test)] +mod tests { + use ::FontDesc; + + fn font_desc() -> FontDesc { + FontDesc::new("Ubuntu Mono", "Regular") + } +} |