use config::Config; use freetype as ft; use fontconfig_sys::*; use ui::ext; use ui::ext::ConnectionExt; use ui::util; use ui::x11; use xcb; use std::cell::RefCell; use std::collections::HashMap; use std::ffi::{CString, CStr}; use std::mem; use std::ptr; use std::sync::{Once, ONCE_INIT}; static FC_INIT: Once = ONCE_INIT; struct Font { pub id: xcb::render::Glyphset, face: ft::Face<'static>, char_widths: RefCell> } impl Font { pub fn new(conn: &xcb::Connection, face: ft::Face<'static>) -> Font { let format = conn.get_pict_format(ext::PictFormat::ARGB32); let id = conn.generate_id(); xcb::render::create_glyph_set(conn, id, format); Font { id: id, face: face, char_widths: RefCell::new(HashMap::new()) } } pub fn load_glyph(&self, conn: &xcb::Connection, charcode: char) -> Option { if self.char_widths.borrow().contains_key(&charcode) { return self.char_widths.borrow().get(&charcode).map(|r| *r) } if self.face.get_char_index(charcode as usize) == 0 { return None } match self.face.load_char(charcode as usize, ft::face::RENDER | ft::face::TARGET_LCD) { Ok(_) => { let glyph = self.face.glyph(); let bitmap = glyph.bitmap(); let pixel_width = (bitmap.width() / 3) as usize; let pixel_height = bitmap.rows() as usize; let info = xcb::render::Glyphinfo::new( pixel_width as u16, pixel_height as u16, -glyph.bitmap_left() as i16, glyph.bitmap_top() as i16, (glyph.advance().x / 64) as i16, (glyph.advance().y / 64) as i16 ); let stride = bitmap.width() as usize + (pixel_width & 3); // round up to 4 somehow let source = bitmap.buffer(); let pixels = pixel_height * pixel_width; let mut buf = Vec::with_capacity(pixels * 4); for i in 0..pixel_height { let offset = i * stride; for j in 0..pixel_width { let offset = offset + j * 3; buf.push(source[offset+2]); buf.push(source[offset+1]); buf.push(source[offset]); buf.push(0); } } let width = (glyph.advance().x / 64) as u16; xcb::render::add_glyphs(conn, self.id, &[charcode as u32], &[info], buf.as_slice()); self.char_widths.borrow_mut().insert(charcode, width); Some(width) }, _ => None } } pub fn baseline_offset(&self, height: u16) -> i16 { self.face.load_char('X' as usize, ft::face::LoadFlag::empty()).unwrap(); let ascender = self.face.glyph().metrics().horiBearingY / 64; (height as i16 - ascender as i16) / 2 } } pub struct TextGroup<'a> { pub text: &'a str, pub glyphset: xcb::render::Glyphset } pub struct RenderableText<'a> { pub width: u16, groups: Vec> } impl<'a> RenderableText<'a> { pub fn render(&self, conn: &xcb::Connection, pen: xcb::render::Picture, pic: xcb::render::Picture, x: u16, y: u16) { let mut cmds = util::GlyphCmd::new(); let mut default_glyphset = 0; cmds.set_position(x, y); for group in self.groups.iter() { default_glyphset = group.glyphset; cmds.set_glyphset(group.glyphset); cmds.add_text(group.text); } xcb::render::composite_glyphs_32( conn, xcb::render::PICT_OP_OVER as u8, pen, pic, 0, default_glyphset, 0, 0, cmds.as_ref() ); } } pub struct FontLoader { conn: x11::Connection, library: ft::Library, fonts: Vec } impl FontLoader { pub fn new(conn: x11::Connection) -> Self { FC_INIT.call_once(|| unsafe { assert_eq!(FcInit(), 1); }); let library = ft::Library::init().unwrap(); unsafe { ft::ffi::FT_Library_SetLcdFilter(library.raw(), ft::ffi::FT_LCD_FILTER_DEFAULT); }; FontLoader { conn: conn, library: library, fonts: vec![] } } pub fn from_config(conn: x11::Connection, cfg: &Config) -> Self { let val = cfg.lookup("bar.fonts").unwrap(); let fonts = val.as_slice().unwrap(); let fonts = fonts.iter().flat_map(|elem| elem.as_str()); let mut font_loader = Self::new(conn); for font in fonts { font_loader.load(font); } font_loader } fn find_face(&self, name: &str) -> Option> { unsafe { let slice = CString::new(name.to_string()).unwrap(); let pattern = FcNameParse(slice.as_ptr() as *const u8); FcConfigSubstitute(ptr::null_mut(), pattern, FcMatchPattern); FcDefaultSubstitute(pattern); let mut result: FcResult = mem::uninitialized(); let font = FcFontMatch(ptr::null_mut(), pattern, &mut result); if !font.is_null() { let field = CString::new("file").unwrap(); let mut ret: *mut FcChar8 = mem::uninitialized(); if FcPatternGetString(font, field.as_ptr(), 0, &mut ret) == FcResultMatch { let file = CStr::from_ptr(ret as *const i8); let path = file.to_string_lossy().to_string(); let face = self.library.new_face(&path, 0).unwrap(); let field = CString::new("dpi").unwrap(); let mut dpi = 0.0; if FcPatternGetDouble(font, field.as_ptr(), 0, &mut dpi) != FcResultMatch { dpi = 75.0; } let field = CString::new("pixelsize").unwrap(); let mut pixel_size = 0.0; if FcPatternGetDouble(font, field.as_ptr(), 0, &mut pixel_size) == FcResultMatch { face.set_pixel_sizes((96.0 / dpi * pixel_size) as u32, 0).unwrap(); } return Some(face) } } None } } pub fn load(&mut self, name: &str) { if let Some(face) = self.find_face(name) { let font = Font::new(&self.conn, face); self.fonts.push(font); } else { panic!("Could not load font {}", name); } } pub fn default_offset(&self, height: u16) -> i16 { self.fonts[0].baseline_offset(height) } fn glyphset_and_width_for_char(&self, charcode: char) -> Option<(xcb::render::Glyphset, u16)> { for font in self.fonts.iter() { if let Some(width) = font.load_glyph(&self.conn, charcode) { return Some((font.id, width)) } } None } pub fn create_renderable_text<'a>(&self, string: &'a str) -> RenderableText<'a> { let mut ret = vec![]; let mut prev = 0; let mut id = 0; let mut width = 0; for (i, charcode) in string.char_indices() { if let Some((this_id, char_width)) = self.glyphset_and_width_for_char(charcode) { width += char_width; if i == 0 { id = this_id; } else if id != this_id { ret.push(TextGroup { text: &string[prev..i], glyphset: id }); id = this_id; prev = i; } } } ret.push(TextGroup { text: &string[prev..string.len()], glyphset: id }); RenderableText { width: width, groups: ret } } }