|  | @@ -0,0 +1,247 @@
 | 
	
		
			
				|  |  | +use config::Config;
 | 
	
		
			
				|  |  | +use freetype as ft;
 | 
	
		
			
				|  |  | +use fontconfig_sys::*;
 | 
	
		
			
				|  |  | +use ui::ext;
 | 
	
		
			
				|  |  | +use ui::ext::ConnectionExt;
 | 
	
		
			
				|  |  | +use ui::util;
 | 
	
		
			
				|  |  | +use xcb;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +use std::cell::RefCell;
 | 
	
		
			
				|  |  | +use std::collections::HashMap;
 | 
	
		
			
				|  |  | +use std::ffi::{CString, CStr};
 | 
	
		
			
				|  |  | +use std::mem;
 | 
	
		
			
				|  |  | +use std::ptr;
 | 
	
		
			
				|  |  | +use std::sync::{Arc, Once, ONCE_INIT};
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +static FC_INIT: Once = ONCE_INIT;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +struct Font {
 | 
	
		
			
				|  |  | +    pub id: xcb::render::Glyphset,
 | 
	
		
			
				|  |  | +    face: ft::Face<'static>,
 | 
	
		
			
				|  |  | +    char_widths: RefCell<HashMap<char, u16>>
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +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<u16> {
 | 
	
		
			
				|  |  | +        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<TextGroup<'a>>
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +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: Arc<xcb::Connection>,
 | 
	
		
			
				|  |  | +    library: ft::Library,
 | 
	
		
			
				|  |  | +    fonts: Vec<Font>
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +impl FontLoader {
 | 
	
		
			
				|  |  | +    pub fn new(conn: Arc<xcb::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: Arc<xcb::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<ft::Face<'static>> {
 | 
	
		
			
				|  |  | +        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() {
 | 
	
		
			
				|  |  | +            let (this_id, char_width) = self.glyphset_and_width_for_char(charcode).unwrap();
 | 
	
		
			
				|  |  | +            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
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 |