aboutsummaryrefslogtreecommitdiff
path: root/font/src/ft
diff options
context:
space:
mode:
Diffstat (limited to 'font/src/ft')
-rw-r--r--font/src/ft/list_fonts.rs186
-rw-r--r--font/src/ft/mod.rs126
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")
+ }
+}