summaryrefslogtreecommitdiff
path: root/font
diff options
context:
space:
mode:
Diffstat (limited to 'font')
-rw-r--r--font/Cargo.lock193
-rw-r--r--font/Cargo.toml20
-rw-r--r--font/src/darwin/mod.rs380
-rw-r--r--font/src/ft/list_fonts.rs186
-rw-r--r--font/src/ft/mod.rs126
-rw-r--r--font/src/lib.rs94
6 files changed, 999 insertions, 0 deletions
diff --git a/font/Cargo.lock b/font/Cargo.lock
new file mode 100644
index 00000000..58622341
--- /dev/null
+++ b/font/Cargo.lock
@@ -0,0 +1,193 @@
+[root]
+name = "font"
+version = "0.1.0"
+dependencies = [
+ "core-foundation 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "core-foundation-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "core-graphics 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "core-text 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "euclid 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "freetype-rs 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "servo-fontconfig 0.2.0 (git+https://github.com/jwilm/rust-fontconfig)",
+]
+
+[[package]]
+name = "bitflags"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "core-foundation"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "core-foundation-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "core-graphics"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "core-foundation 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "core-text"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "core-foundation 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "core-graphics 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "euclid"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num-traits 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "expat-sys"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "make-cmd 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "freetype-rs"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "freetype-sys 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "freetype-sys"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libz-sys 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "gcc"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "heapsize"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "kernel32-sys"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "libz-sys"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "gcc 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "log"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "make-cmd"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "num-traits"
+version = "0.1.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "rustc-serialize"
+version = "0.3.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "serde"
+version = "0.7.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "servo-fontconfig"
+version = "0.2.0"
+source = "git+https://github.com/jwilm/rust-fontconfig#419135e5e1106ec0973dd4923bd9c70d8e438cc8"
+dependencies = [
+ "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "servo-fontconfig-sys 2.11.3 (git+https://github.com/jwilm/libfontconfig)",
+]
+
+[[package]]
+name = "servo-fontconfig-sys"
+version = "2.11.3"
+source = "git+https://github.com/jwilm/libfontconfig#618a52973d46e5cce4f054f6ee3bd2682167eee4"
+dependencies = [
+ "expat-sys 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "freetype-sys 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "winapi"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "winapi-build"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
diff --git a/font/Cargo.toml b/font/Cargo.toml
new file mode 100644
index 00000000..8345af73
--- /dev/null
+++ b/font/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+name = "font"
+version = "0.1.0"
+authors = ["Joe Wilm <joe@jwilm.com>"]
+description = "Font rendering using the best available solution per platform"
+license = "Apache-2.0"
+
+[dependencies]
+euclid = "0.6.8"
+
+[target.'cfg(not(target_os = "macos"))'.dependencies]
+servo-fontconfig = { git = "https://github.com/jwilm/rust-fontconfig" }
+freetype-rs = "0.9.0"
+libc = "0.2.11"
+
+[target.'cfg(target_os = "macos")'.dependencies]
+core-text = "1.1.1"
+core-foundation = "0.2.2"
+core-graphics = "0.3.2"
+core-foundation-sys = "0.2.2"
diff --git a/font/src/darwin/mod.rs b/font/src/darwin/mod.rs
new file mode 100644
index 00000000..844850e0
--- /dev/null
+++ b/font/src/darwin/mod.rs
@@ -0,0 +1,380 @@
+//! Font rendering based on CoreText
+//!
+//! TODO error handling... just search for unwrap.
+use std::collections::HashMap;
+use std::ops::Deref;
+use std::ptr;
+
+use core_foundation::base::TCFType;
+use core_foundation::string::{CFString, CFStringRef};
+use core_foundation::array::CFIndex;
+use core_foundation_sys::string::UniChar;
+use core_graphics::base::kCGImageAlphaNoneSkipFirst;
+use core_graphics::base::kCGImageAlphaPremultipliedLast;
+use core_graphics::color_space::CGColorSpace;
+use core_graphics::context::{CGContext, CGContextRef};
+use core_graphics::font::CGGlyph;
+use core_graphics::geometry::CGPoint;
+use core_text::font::{CTFont, new_from_descriptor as ct_new_from_descriptor};
+use core_text::font_collection::create_for_family;
+use core_text::font_collection::get_family_names as ct_get_family_names;
+use core_text::font_descriptor::kCTFontDefaultOrientation;
+use core_text::font_descriptor::kCTFontHorizontalOrientation;
+use core_text::font_descriptor::kCTFontVerticalOrientation;
+use core_text::font_descriptor::{CTFontDescriptor, CTFontDescriptorRef, CTFontOrientation};
+
+use euclid::point::Point2D;
+use euclid::rect::Rect;
+use euclid::size::Size2D;
+
+use super::{FontDesc, RasterizedGlyph, Metrics};
+
+/// Font descriptor
+///
+/// The descriptor provides data about a font and supports creating a font.
+#[derive(Debug)]
+pub struct Descriptor {
+ family_name: String,
+ font_name: String,
+ style_name: String,
+ display_name: String,
+ font_path: String,
+
+ ct_descriptor: CTFontDescriptor
+}
+
+/// Rasterizer, the main type exported by this package
+///
+/// Given a fontdesc, can rasterize fonts.
+pub struct Rasterizer {
+ fonts: HashMap<FontDesc, Font>,
+ device_pixel_ratio: f32,
+}
+
+impl Rasterizer {
+ pub fn new(dpi_x: f32, dpi_y: f32, device_pixel_ratio: f32) -> Rasterizer {
+ println!("device_pixel_ratio: {}", device_pixel_ratio);
+ Rasterizer {
+ fonts: HashMap::new(),
+ device_pixel_ratio: device_pixel_ratio,
+ }
+ }
+
+ pub fn metrics(&mut self, desc: &FontDesc, size: f32) -> Metrics {
+ let scaled_size = self.device_pixel_ratio * size;
+ self.get_font(desc, scaled_size).unwrap().metrics()
+ }
+
+ fn get_font(&mut self, desc: &FontDesc, size: f32) -> Option<Font> {
+ if let Some(font) = self.fonts.get(desc) {
+ return Some(font.clone());
+ }
+
+ let descriptors = descriptors_for_family(&desc.name[..]);
+ for descriptor in descriptors {
+ if descriptor.style_name == desc.style {
+ // Found the font we want
+ let font = descriptor.to_font(size as _);
+ self.fonts.insert(desc.to_owned(), font.clone());
+ return Some(font);
+ }
+ }
+
+ None
+ }
+
+ pub fn get_glyph(&mut self, desc: &FontDesc, size: f32, c: char) -> RasterizedGlyph {
+ let scaled_size = self.device_pixel_ratio * size;
+ let glyph = self.get_font(desc, scaled_size).unwrap().get_glyph(c, scaled_size as _);
+
+ glyph
+ }
+}
+
+/// Specifies the intended rendering orientation of the font for obtaining glyph metrics
+#[derive(Debug)]
+pub enum FontOrientation {
+ Default = kCTFontDefaultOrientation as isize,
+ Horizontal = kCTFontHorizontalOrientation as isize,
+ Vertical = kCTFontVerticalOrientation as isize,
+}
+
+impl Default for FontOrientation {
+ fn default() -> FontOrientation {
+ FontOrientation::Default
+ }
+}
+
+/// A font
+#[derive(Debug, Clone)]
+pub struct Font {
+ ct_font: CTFont
+}
+
+unsafe impl Send for Font {}
+
+/// List all family names
+pub fn get_family_names() -> Vec<String> {
+ // CFArray of CFStringRef
+ let names = ct_get_family_names();
+ let mut owned_names = Vec::new();
+
+ for name in names.iter() {
+ let family: CFString = unsafe { TCFType::wrap_under_get_rule(name as CFStringRef) };
+ owned_names.push(format!("{}", family));
+ }
+
+ owned_names
+}
+
+/// Get descriptors for family name
+pub fn descriptors_for_family(family: &str) -> Vec<Descriptor> {
+ let mut out = Vec::new();
+
+ let ct_collection = match create_for_family(family) {
+ Some(c) => c,
+ None => return out,
+ };
+
+ // CFArray of CTFontDescriptorRef (i think)
+ let descriptors = ct_collection.get_descriptors();
+ for descriptor in descriptors.iter() {
+ let desc: CTFontDescriptor = unsafe {
+ TCFType::wrap_under_get_rule(descriptor as CTFontDescriptorRef)
+ };
+ out.push(Descriptor {
+ family_name: desc.family_name(),
+ font_name: desc.font_name(),
+ style_name: desc.style_name(),
+ display_name: desc.display_name(),
+ font_path: desc.font_path(),
+ ct_descriptor: desc,
+ });
+ }
+
+ out
+}
+
+impl Descriptor {
+ /// Create a Font from this descriptor
+ pub fn to_font(&self, pt_size: f64) -> Font {
+ let ct_font = ct_new_from_descriptor(&self.ct_descriptor, pt_size);
+ Font {
+ ct_font: ct_font
+ }
+ }
+}
+
+impl Deref for Font {
+ type Target = CTFont;
+
+ #[inline]
+ fn deref(&self) -> &Self::Target {
+ &self.ct_font
+ }
+}
+
+impl Font {
+ /// The the bounding rect of a glyph
+ pub fn bounding_rect_for_glyph(&self, orientation: FontOrientation, index: u32) -> Rect<f64> {
+ let cg_rect = self.ct_font.get_bounding_rects_for_glyphs(orientation as CTFontOrientation,
+ &[index as CGGlyph]);
+
+ Rect::new(
+ Point2D::new(cg_rect.origin.x, cg_rect.origin.y),
+ Size2D::new(cg_rect.size.width, cg_rect.size.height),
+ )
+ }
+
+ pub fn metrics(&self) -> Metrics {
+ let average_advance = self.glyph_advance('0');
+
+ let ascent = self.ct_font.ascent() as f64;
+ let descent = self.ct_font.descent() as f64;
+ let leading = self.ct_font.leading() as f64;
+ let line_height = (ascent + descent + leading + 0.5).floor();
+
+ Metrics {
+ average_advance: average_advance,
+ line_height: line_height,
+ }
+ }
+
+ fn glyph_advance(&self, character: char) -> f64 {
+ let index = self.glyph_index(character).unwrap();
+
+ let indices = [index as CGGlyph];
+
+ self.ct_font.get_advances_for_glyphs(FontOrientation::Default as _,
+ &indices[0],
+ ptr::null_mut(),
+ 1)
+ }
+
+ pub fn get_glyph(&self, character: char, size: f64) -> RasterizedGlyph {
+ let glyph_index = match self.glyph_index(character) {
+ Some(i) => i,
+ None => {
+ // TODO refactor this
+ return RasterizedGlyph {
+ c: ' ',
+ width: 0,
+ height: 0,
+ top: 0,
+ left: 0,
+ buf: Vec::new()
+ };
+ }
+ };
+
+ let bounds = self.bounding_rect_for_glyph(Default::default(), glyph_index);
+
+ let rasterized_left = bounds.origin.x.floor() as i32;
+ let rasterized_width =
+ (bounds.origin.x - (rasterized_left as f64) + bounds.size.width).ceil() as u32;
+ let rasterized_descent = (-bounds.origin.y).ceil() as i32;
+ let rasterized_ascent = (bounds.size.height + bounds.origin.y).ceil() as i32;
+ let rasterized_height = (rasterized_descent + rasterized_ascent) as u32;
+
+ if rasterized_width == 0 || rasterized_height == 0 {
+ return RasterizedGlyph {
+ c: ' ',
+ width: 0,
+ height: 0,
+ top: 0,
+ left: 0,
+ buf: Vec::new()
+ };
+ }
+
+ let mut cg_context = CGContext::create_bitmap_context(rasterized_width as usize,
+ rasterized_height as usize,
+ 8, // bits per component
+ rasterized_width as usize * 4,
+ &CGColorSpace::create_device_rgb(),
+ kCGImageAlphaNoneSkipFirst);
+
+ cg_context.set_allows_font_smoothing(true);
+ cg_context.set_should_smooth_fonts(true);
+ cg_context.set_allows_font_subpixel_quantization(true);
+ cg_context.set_should_subpixel_quantize_fonts(true);
+ cg_context.set_rgb_fill_color(1.0, 1.0, 1.0, 1.0);
+
+ let rasterization_origin = CGPoint {
+ x: -rasterized_left as f64,
+ y: rasterized_descent as f64,
+ };
+
+ self.ct_font.draw_glyphs(&[glyph_index as CGGlyph],
+ &[rasterization_origin],
+ cg_context.clone());
+
+ let rasterized_area = (rasterized_width * rasterized_height) as usize;
+ let rasterized_pixels = cg_context.data().to_vec();
+ let buf = rasterized_pixels.into_iter()
+ .enumerate()
+ .filter(|&(index, _)| (index % 4) != 0)
+ .map(|(_, val)| val)
+ .collect::<Vec<_>>();
+
+ RasterizedGlyph {
+ c: character,
+ left: rasterized_left,
+ top: (bounds.size.height + bounds.origin.y).ceil() as i32,
+ width: rasterized_width as i32,
+ height: rasterized_height as i32,
+ buf: buf,
+ }
+ }
+
+ fn glyph_index(&self, character: char) -> Option<u32> {
+ let chars = [character as UniChar];
+ let mut glyphs = [0 as CGGlyph];
+
+ let res = self.ct_font.get_glyphs_for_characters(&chars[0], &mut glyphs[0], 1 as CFIndex);
+
+ if res {
+ Some(glyphs[0] as u32)
+ } else {
+ None
+ }
+ }
+}
+
+/// Additional methods needed to render fonts for Alacritty
+///
+/// TODO upstream these into core_graphics crate
+trait CGContextExt {
+ fn set_allows_font_subpixel_quantization(&self, bool);
+ fn set_should_subpixel_quantize_fonts(&self, bool);
+}
+
+impl CGContextExt for CGContext {
+ fn set_allows_font_subpixel_quantization(&self, allows: bool) {
+ unsafe {
+ CGContextSetAllowsFontSubpixelQuantization(self.as_concrete_TypeRef(), allows);
+ }
+ }
+
+ fn set_should_subpixel_quantize_fonts(&self, should: bool) {
+ unsafe {
+ CGContextSetShouldSubpixelQuantizeFonts(self.as_concrete_TypeRef(), should);
+ }
+ }
+}
+
+#[link(name = "ApplicationServices", kind = "framework")]
+extern {
+ fn CGContextSetAllowsFontSubpixelQuantization(c: CGContextRef, allows: bool);
+ fn CGContextSetShouldSubpixelQuantizeFonts(c: CGContextRef, should: bool);
+}
+
+#[cfg(test)]
+mod tests {
+ #[test]
+ fn get_family_names() {
+ let names = super::get_family_names();
+ assert!(names.contains(&String::from("Menlo")));
+ assert!(names.contains(&String::from("Monaco")));
+ }
+
+ #[test]
+ fn get_descriptors_and_build_font() {
+ let list = super::descriptors_for_family("Menlo");
+ assert!(!list.is_empty());
+ println!("{:?}", list);
+
+ // Check to_font
+ let fonts = list.iter()
+ .map(|desc| desc.to_font(72.))
+ .collect::<Vec<_>>();
+
+ for font in fonts {
+ // Check deref
+ println!("family: {}", font.family_name());
+
+ // Get a glyph
+ for c in &['a', 'b', 'c', 'd'] {
+ let glyph = font.get_glyph(*c, 72.);
+
+ // Debug the glyph.. sigh
+ for row in 0..glyph.height {
+ for col in 0..glyph.width {
+ let index = ((glyph.width * 3 * row) + (col * 3)) as usize;
+ let value = glyph.buf[index];
+ let c = match value {
+ 0...50 => ' ',
+ 51...100 => '.',
+ 101...150 => '~',
+ 151...200 => '*',
+ 201...255 => '#',
+ _ => unreachable!()
+ };
+ print!("{}", c);
+ }
+ print!("\n");
+ }
+ }
+ }
+ }
+}
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")
+ }
+}
diff --git a/font/src/lib.rs b/font/src/lib.rs
new file mode 100644
index 00000000..ba632bee
--- /dev/null
+++ b/font/src/lib.rs
@@ -0,0 +1,94 @@
+//! Compatibility layer for different font engines
+//!
+//! This module is developed as part of Alacritty; Alacritty does not include Windows support
+//! as a goal at this time, and neither does this module.
+//!
+//! CoreText is used on Mac OS.
+//! FreeType is used on everything that's not Mac OS.
+
+#[cfg(not(target_os = "macos"))]
+extern crate fontconfig;
+#[cfg(not(target_os = "macos"))]
+extern crate freetype;
+#[cfg(not(target_os = "macos"))]
+extern crate libc;
+
+#[cfg(target_os = "macos")]
+extern crate core_text;
+#[cfg(target_os = "macos")]
+extern crate core_foundation;
+#[cfg(target_os = "macos")]
+extern crate core_foundation_sys;
+#[cfg(target_os = "macos")]
+extern crate core_graphics;
+
+extern crate euclid;
+
+use std::fmt;
+
+// If target isn't macos, reexport everything from ft
+#[cfg(not(target_os = "macos"))]
+mod ft;
+#[cfg(not(target_os = "macos"))]
+pub use ft::*;
+
+// If target is macos, reexport everything from darwin
+#[cfg(target_os = "macos")]
+mod darwin;
+#[cfg(target_os = "macos")]
+pub use darwin::*;
+
+#[derive(Clone, PartialEq, Eq, Hash)]
+pub struct FontDesc {
+ name: String,
+ style: String,
+}
+
+impl FontDesc {
+ pub fn new<S>(name: S, style: S) -> FontDesc
+ where S: Into<String>
+ {
+ FontDesc {
+ name: name.into(),
+ style: style.into()
+ }
+ }
+}
+
+pub struct RasterizedGlyph {
+ pub c: char,
+ pub width: i32,
+ pub height: i32,
+ pub top: i32,
+ pub left: i32,
+ pub buf: Vec<u8>,
+}
+
+struct BufDebugger<'a>(&'a [u8]);
+
+impl<'a> fmt::Debug for BufDebugger<'a> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("GlyphBuffer")
+ .field("len", &self.0.len())
+ .field("bytes", &self.0)
+ .finish()
+ }
+}
+
+impl fmt::Debug for RasterizedGlyph {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("RasterizedGlyph")
+ .field("c", &self.c)
+ .field("width", &self.width)
+ .field("height", &self.height)
+ .field("top", &self.top)
+ .field("left", &self.left)
+ .field("buf", &BufDebugger(&self.buf[..]))
+ .finish()
+ }
+}
+
+pub struct Metrics {
+ pub average_advance: f64,
+ pub line_height: f64,
+}