|
@@ -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
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|