diff options
Diffstat (limited to 'font')
-rw-r--r-- | font/Cargo.lock | 193 | ||||
-rw-r--r-- | font/Cargo.toml | 20 | ||||
-rw-r--r-- | font/src/darwin/mod.rs | 380 | ||||
-rw-r--r-- | font/src/ft/list_fonts.rs | 186 | ||||
-rw-r--r-- | font/src/ft/mod.rs | 126 | ||||
-rw-r--r-- | font/src/lib.rs | 94 |
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, +} |