Przeglądaj źródła

Implement text rendering

Thomas Dy 8 lat temu
rodzic
commit
039d312209
7 zmienionych plików z 437 dodań i 11 usunięć
  1. 3 1
      Cargo.toml
  2. 8 5
      src/main.rs
  3. 49 0
      src/ui/ext.rs
  4. 235 0
      src/ui/font.rs
  5. 6 2
      src/ui/mod.rs
  6. 62 3
      src/ui/panel.rs
  7. 74 0
      src/ui/util.rs

+ 3 - 1
Cargo.toml

@@ -9,7 +9,9 @@ toml = "0.1"
 mpd = "0.0.11"
 chan = "0.1"
 chan-signal = "0.1"
+freetype-rs = "0.11"
+silverknife-fontconfig-sys = "0.1"
 
 [dependencies.xcb]
 version = "0.7.5"
-features = [ "thread" ]
+features = [ "render", "thread" ]

+ 8 - 5
src/main.rs

@@ -3,9 +3,12 @@ extern crate xcb;
 extern crate chan;
 extern crate chan_signal;
 
+extern crate freetype;
+extern crate fontconfig_sys;
+
 //mod sensors;
 //mod comm;
-//mod config;
+mod config;
 //mod bspwm;
 //mod bar;
 //mod tray;
@@ -17,7 +20,7 @@ mod ui;
 
 //use comm::{Channel, Message};
 //use config::Config;
-//use std::env;
+use std::env;
 //use std::sync::Arc;
 //use std::sync::mpsc;
 use std::process;
@@ -25,8 +28,8 @@ use std::process;
 
 fn main() {
     let signal = chan_signal::notify(&[chan_signal::Signal::INT, chan_signal::Signal::TERM]);
-//    let config_path = env::args().nth(1).unwrap_or("./panel.toml".to_string());
-//    let cfg = config::load(&config_path);
+    let config_path = env::args().nth(1).unwrap_or("./panel.toml".to_string());
+    let cfg = config::load(&config_path);
 //
 //    let mut topbar = bar::Bar::new(true, &cfg);
 //    let _tray = tray::Tray::new();
@@ -52,7 +55,7 @@ fn main() {
 //        ));
 //    }
 
-    process::exit(ui::ui_main(signal));
+    process::exit(ui::ui_main(signal, &cfg));
 }
 
 //fn make_thread(tx: &Channel, cfg: &Arc<Config>, func: fn(&Channel, &Config) -> ()) {

+ 49 - 0
src/ui/ext.rs

@@ -0,0 +1,49 @@
+use xcb;
+
+pub enum PictFormat {
+    RGB24,
+    ARGB32
+}
+
+fn format_rgb24(format: &xcb::render::Pictforminfo) -> bool {
+    let depth = format.depth();
+    let dformat = format.direct();
+    depth == 24
+        && dformat.red_mask() == 255
+        && dformat.green_mask() == 255
+        && dformat.blue_mask() == 255
+        && dformat.red_shift() == 16
+        && dformat.green_shift() == 8
+        && dformat.blue_shift() == 0
+}
+
+fn format_argb32(format: &xcb::render::Pictforminfo) -> bool {
+    let depth = format.depth();
+    let dformat = format.direct();
+    depth == 32
+        && dformat.alpha_mask() == 255
+        && dformat.red_mask() == 255
+        && dformat.green_mask() == 255
+        && dformat.blue_mask() == 255
+        && dformat.alpha_shift() == 24
+        && dformat.red_shift() == 16
+        && dformat.green_shift() == 8
+        && dformat.blue_shift() == 0
+}
+
+pub trait ConnectionExt {
+    fn get_pict_format(&self, format: PictFormat) -> xcb::render::Pictformat;
+}
+
+impl ConnectionExt for xcb::Connection {
+    fn get_pict_format(&self, format: PictFormat) -> xcb::render::Pictformat {
+        let cookie = xcb::render::query_pict_formats(self);
+        let reply = cookie.get_reply().unwrap();
+        let spec: fn(&xcb::render::Pictforminfo) -> bool = match format {
+            PictFormat::RGB24 => format_rgb24,
+            PictFormat::ARGB32 => format_argb32
+        };
+        let format = reply.formats().find(spec).expect("Could not find PictFormat");
+        format.id()
+    }
+}

+ 235 - 0
src/ui/font.rs

@@ -0,0 +1,235 @@
+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![]
+        }
+    }
+
+    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
+        }
+    }
+}

+ 6 - 2
src/ui/mod.rs

@@ -1,15 +1,19 @@
+mod font;
 mod panel;
+mod ext;
 mod tray;
+mod util;
 mod x11;
 
 use chan;
 use chan_signal;
+use config::Config;
 
 use std::thread;
 
 pub const SIZE: u16 = 20;
 
-pub fn ui_main(signal: chan::Receiver<chan_signal::Signal>) -> i32 {
+pub fn ui_main(signal: chan::Receiver<chan_signal::Signal>, cfg: &Config) -> i32 {
     if let Some(conn) = x11::Connection::new() {
         let (tx, rx) = chan::sync(0);
         {
@@ -24,7 +28,7 @@ pub fn ui_main(signal: chan::Receiver<chan_signal::Signal>) -> i32 {
             });
         }
 
-        let panel = panel::Panel::new(conn);
+        let panel = panel::Panel::new(conn, cfg);
         panel.create();
 
         loop {

+ 62 - 3
src/ui/panel.rs

@@ -1,4 +1,8 @@
+use config::Config;
 use xcb;
+use ui::ext;
+use ui::ext::ConnectionExt;
+use ui::font;
 use ui::tray;
 use ui::x11;
 
@@ -10,16 +14,31 @@ pub struct Panel {
     pub window: xcb::Window,
     pub width: u16,
     tray: RefCell<Option<tray::Tray>>,
+    fonts: font::FontLoader,
+    picture: xcb::render::Picture,
     finishing: RefCell<bool>
 }
 
 impl Panel {
-    pub fn new(conn: x11::Connection) -> Rc<Panel> {
+    pub fn new(conn: x11::Connection, cfg: &Config) -> Rc<Panel> {
+        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 = font::FontLoader::new(conn.clone_connection());
+        for font in fonts {
+            font_loader.load(font);
+        }
+
+        let window = conn.generate_id();
+        let picture = conn.generate_id();
+
         let panel = Rc::new(Panel {
-            window: conn.generate_id(),
+            window: window,
             width: conn.default_screen().width,
+            fonts: font_loader,
             conn: conn,
             tray: RefCell::new(None),
+            picture: picture,
             finishing: RefCell::new(false)
         });
         let tray = tray::Tray::new(Rc::downgrade(&panel));
@@ -43,7 +62,7 @@ impl Panel {
             xcb::COPY_FROM_PARENT,
             &[
                 (xcb::CW_BACK_PIXEL, 0xFF000000),
-                (xcb::CW_EVENT_MASK, xcb::EVENT_MASK_PROPERTY_CHANGE | xcb::EVENT_MASK_STRUCTURE_NOTIFY)
+                (xcb::CW_EVENT_MASK, xcb::EVENT_MASK_EXPOSURE | xcb::EVENT_MASK_PROPERTY_CHANGE | xcb::EVENT_MASK_STRUCTURE_NOTIFY)
             ]
         );
         self.set_property(
@@ -53,6 +72,10 @@ impl Panel {
             &[conn.atom(x11::_NET_WM_WINDOW_TYPE_DOCK)]
         );
         xcb::map_window(conn, self.window);
+        xcb::render::create_picture(&conn, self.picture, self.window, conn.get_pict_format(ext::PictFormat::RGB24), &[
+            (xcb::render::CP_POLY_EDGE, xcb::render::POLY_EDGE_SMOOTH),
+            (xcb::render::CP_POLY_MODE, xcb::render::POLY_MODE_IMPRECISE)
+        ]);
         conn.flush();
     }
 
@@ -70,6 +93,9 @@ impl Panel {
 
     pub fn handle_event(&self, event: xcb::GenericEvent) -> bool {
         let tray = self.tray.borrow_mut().as_mut().unwrap().handle_event(&event);
+        if event.response_type() == xcb::EXPOSE {
+            self.draw_text("Hello 世界!");
+        }
         if self.is_finishing() && event.response_type() == xcb::DESTROY_NOTIFY {
             let event: &xcb::DestroyNotifyEvent = xcb::cast_event(&event);
             if event.window() == self.window {
@@ -79,6 +105,39 @@ impl Panel {
         tray
     }
 
+    pub fn create_pen(&self, r: u16, g: u16, b: u16, a: u16) -> xcb::render::Picture {
+        let color = xcb::render::Color::new(r, g, b, a);
+        let format = self.conn.get_pict_format(ext::PictFormat::ARGB32);
+        let pixmap = self.conn.generate_id();
+        xcb::create_pixmap(&self.conn, 32, pixmap, self.window, 1, 1);
+        let picture = self.conn.generate_id();
+        xcb::render::create_picture(
+            &self.conn,
+            picture,
+            pixmap,
+            format,
+            &[(xcb::render::CP_REPEAT, xcb::render::REPEAT_NORMAL)]
+        );
+        xcb::render::fill_rectangles(
+            &self.conn,
+            xcb::render::PICT_OP_OVER as u8,
+            picture,
+            color,
+            &[xcb::Rectangle::new(0, 0, 1, 1)]
+        );
+        xcb::free_pixmap(&self.conn, pixmap);
+        self.conn.flush();
+        picture
+    }
+
+    pub fn draw_text(&self, name: &str) {
+        let pen = self.create_pen(0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF);
+        let text = self.fonts.create_renderable_text(name);
+        let baseline = 20 - self.fonts.default_offset(20);
+        text.render(&self.conn, pen, self.picture, 0, baseline as u16);
+        self.conn.flush();
+    }
+
     pub fn is_finishing(&self) -> bool {
         *self.finishing.borrow()
     }

+ 74 - 0
src/ui/util.rs

@@ -0,0 +1,74 @@
+use xcb;
+
+use std::mem;
+
+#[repr(C)]
+struct FontHeader {
+    marker: u8,
+    padding: [u8; 7],
+    id: u32
+}
+
+#[repr(C)]
+struct ELTHeader {
+    len: u32,
+    x: u16,
+    y: u16
+}
+
+pub struct GlyphCmd {
+    buf: Vec<u8>
+}
+
+impl GlyphCmd {
+    pub fn new() -> Self {
+        GlyphCmd { buf: vec![] }
+    }
+
+    pub fn set_glyphset(&mut self, id: xcb::render::Glyphset) {
+        let header = FontHeader {
+            marker: 0xFF,
+            padding: [0; 7],
+            id: id
+        };
+        let bytes: [u8; 12] = unsafe {
+            mem::transmute(header)
+        };
+        self.buf.extend_from_slice(&bytes);
+    }
+
+    pub fn set_position(&mut self, x: u16, y: u16) {
+        self.add_text_at("", x, y);
+    }
+
+    pub fn add_text(&mut self, string: &str) {
+        self.add_text_at(string, 0, 0);
+    }
+
+    pub fn add_text_at(&mut self, string: &str, x: u16, y: u16) {
+        let mut count = 0;
+        let mut temp = vec![];
+        for charcode in string.chars() {
+            count += 1;
+            let bytes: [u8; 4] = unsafe {
+                mem::transmute(charcode)
+            };
+            temp.extend_from_slice(&bytes);
+        }
+        let header = ELTHeader {
+            len: count,
+            x: x,
+            y: y
+        };
+        let bytes: [u8; 8] = unsafe {
+            mem::transmute(header)
+        };
+        self.buf.extend_from_slice(&bytes);
+        self.buf.extend_from_slice(&temp);
+    }
+
+    pub fn as_ref(&self) -> &[u8] {
+        self.buf.as_slice()
+    }
+}
+