diff --git a/font/src/darwin/mod.rs b/font/src/darwin/mod.rs index 31eb1292..4db8dad1 100644 --- a/font/src/darwin/mod.rs +++ b/font/src/darwin/mod.rs @@ -43,7 +43,7 @@ use euclid::point::Point2D; use euclid::rect::Rect; use euclid::size::Size2D; -use super::{FontDesc, RasterizedGlyph, Metrics}; +use super::{FontDesc, RasterizedGlyph, Metrics, FontKey, GlyphKey}; pub mod cg_color; use self::cg_color::{CGColorRef, CGColor}; @@ -52,6 +52,10 @@ pub mod byte_order; use self::byte_order::kCGBitmapByteOrder32Host; use self::byte_order::extract_rgb; +use super::Size; + +static FONT_LOAD_ERROR: &'static str = "font specified by FontKey has been loaded"; + /// Font descriptor /// /// The descriptor provides data about a font and supports creating a font. @@ -70,7 +74,7 @@ pub struct Descriptor { /// /// Given a fontdesc, can rasterize fonts. pub struct Rasterizer { - fonts: HashMap, + fonts: HashMap, device_pixel_ratio: f32, } @@ -83,22 +87,35 @@ impl Rasterizer { } } - 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() + /// Get metrics for font specified by FontKey + /// + /// # Panics + /// + /// If FontKey was not generated by `load_font`, this method will panic. + pub fn metrics(&self, key: FontKey, _size: Size) -> Metrics { + // NOTE size is not needed here since the font loaded already contains + // it. It's part of the API due to platform differences. + let font = self.fonts.get(&key).expect(FONT_LOAD_ERROR); + font.metrics() } - fn get_font(&mut self, desc: &FontDesc, size: f32) -> Option { - if let Some(font) = self.fonts.get(desc) { - return Some(font.clone()); - } + pub fn load_font(&mut self, desc: &FontDesc, size: Size) -> Option { + self.get_font(desc, size) + .map(|font| { + let key = FontKey::next(); + self.fonts.insert(key, font); + key + }) + } + + fn get_font(&mut self, desc: &FontDesc, size: Size) -> Option { 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()); + let scaled_size = size.as_f32_pts() as f64 * self.device_pixel_ratio as f64; + let font = descriptor.to_font(scaled_size); return Some(font); } } @@ -106,11 +123,18 @@ impl Rasterizer { 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 _); + /// Get rasterized glyph for given glyph key + /// + /// # Panics + /// + /// Panics if the FontKey specified in GlyphKey was not generated from `load_font` + pub fn get_glyph(&mut self, glyph: &GlyphKey) -> RasterizedGlyph { + let scaled_size = self.device_pixel_ratio * glyph.size.as_f32_pts(); - glyph + self.fonts + .get(&glyph.font_key) + .expect(FONT_LOAD_ERROR) + .get_glyph(glyph.c, scaled_size as _) } } @@ -181,8 +205,8 @@ pub fn descriptors_for_family(family: &str) -> Vec { 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); + pub fn to_font(&self, size: f64) -> Font { + let ct_font = ct_new_from_descriptor(&self.ct_descriptor, size); let cg_font = ct_font.copy_to_CGFont(); Font { ct_font: ct_font, diff --git a/font/src/lib.rs b/font/src/lib.rs index a42020d8..74563b1a 100644 --- a/font/src/lib.rs +++ b/font/src/lib.rs @@ -19,6 +19,7 @@ //! //! CoreText is used on Mac OS. //! FreeType is used on everything that's not Mac OS. +#![feature(integer_atomics)] #[cfg(not(target_os = "macos"))] extern crate fontconfig; @@ -38,6 +39,7 @@ extern crate euclid; extern crate libc; use std::fmt; +use std::sync::atomic::{AtomicU32, ATOMIC_U32_INIT, Ordering}; // If target isn't macos, reexport everything from ft #[cfg(not(target_os = "macos"))] @@ -51,7 +53,7 @@ mod darwin; #[cfg(target_os = "macos")] pub use darwin::*; -#[derive(Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct FontDesc { name: String, style: String, @@ -68,6 +70,54 @@ impl FontDesc { } } +/// Identifier for a Font for use in maps/etc +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub struct FontKey { + token: u32, +} + +impl FontKey { + /// Get next font key for given size + /// + /// The generated key will be globally unique + pub fn next() -> FontKey { + static TOKEN: AtomicU32 = ATOMIC_U32_INIT; + + FontKey { + token: TOKEN.fetch_add(1, Ordering::SeqCst), + } + } +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub struct GlyphKey { + pub c: char, + pub font_key: FontKey, + pub size: Size, +} + +/// Font size stored as integer +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub struct Size(i32); + +impl Size { + /// Scale factor between font "Size" type and point size + #[inline] + pub fn factor() -> f32 { + 2.0 + } + + /// Create a new `Size` from a f32 size in points + pub fn new(size: f32) -> Size { + Size((size * Size::factor()) as i32) + } + + /// Get the f32 size in points + pub fn as_f32_pts(self) -> f32 { + self.0 as f32 / Size::factor() + } +} + pub struct RasterizedGlyph { pub c: char, pub width: i32, diff --git a/src/config.rs b/src/config.rs index 426a3381..30bbf1b5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -8,7 +8,9 @@ use std::fs; use std::io::{self, Read}; use std::path::{Path, PathBuf}; +use font::Size; use serde_yaml; +use serde; /// Top-level config type #[derive(Debug, Deserialize, Default)] @@ -220,6 +222,47 @@ impl FontOffset { } } +trait DeserializeFromF32 : Sized { + fn deserialize_from_f32(&mut D) -> ::std::result::Result + where D: serde::de::Deserializer; +} + +impl DeserializeFromF32 for Size { + fn deserialize_from_f32(deserializer: &mut D) -> ::std::result::Result + where D: serde::de::Deserializer + { + use std::marker::PhantomData; + + struct FloatVisitor<__D> { + _marker: PhantomData<__D>, + } + + impl<__D> ::serde::de::Visitor for FloatVisitor<__D> + where __D: ::serde::de::Deserializer + { + type Value = f32; + + fn visit_f32(&mut self, value: f32) -> ::std::result::Result + where E: ::serde::de::Error + { + Ok(value) + } + + fn visit_str(&mut self, value: &str) -> ::std::result::Result + where E: ::serde::de::Error + { + // FIXME serde-yaml visits a str for real numbers. + // https://github.com/dtolnay/serde-yaml/issues/24 + Ok(value.parse::().expect("size must be float")) + } + } + + deserializer + .deserialize_f32(FloatVisitor::{ _marker: PhantomData }) + .map(|v| Size::new(v)) + } +} + /// Font config /// /// Defaults are provided at the level of this struct per platform, but not per @@ -234,8 +277,15 @@ pub struct Font { /// Font style style: String, - /// Font size in points - size: f32, + /// Bold font style + bold_style: Option, + + /// Italic font style + italic_style: Option, + + // Font size in points + #[serde(deserialize_with="DeserializeFromF32::deserialize_from_f32")] + size: Size, /// Extra spacing per character offset: FontOffset, @@ -254,9 +304,25 @@ impl Font { &self.style[..] } + /// Get italic font style; assumes same family + #[inline] + pub fn italic_style(&self) -> Option<&str> { + self.italic_style + .as_ref() + .map(|s| s.as_str()) + } + + /// Get bold font style; assumes same family + #[inline] + pub fn bold_style(&self) -> Option<&str> { + self.bold_style + .as_ref() + .map(|s| s.as_str()) + } + /// Get the font size in points #[inline] - pub fn size(&self) -> f32 { + pub fn size(&self) -> Size { self.size } @@ -273,7 +339,9 @@ impl Default for Font { Font { family: String::from("Menlo"), style: String::from("Regular"), - size: 11.0, + size: Size::new(11.0), + bold_style: Some(String::from("Bold")), + italic_style: Some(String::from("Italic")), offset: FontOffset { x: 0.0, y: 0.0 @@ -288,7 +356,9 @@ impl Default for Font { Font { family: String::from("DejaVu Sans Mono"), style: String::from("Book"), - size: 11.0, + size: Size::new(11.0), + bold_style: Some(String::from("Bold")), + italic_style: Some(String::from("Italic")), offset: FontOffset { // TODO should improve freetype metrics... shouldn't need such // drastic offsets for the default! diff --git a/src/main.rs b/src/main.rs index af4cc312..ef02af51 100644 --- a/src/main.rs +++ b/src/main.rs @@ -57,7 +57,6 @@ use std::sync::{mpsc, Arc}; use sync::PriorityMutex; use config::Config; -use font::FontDesc; use meter::Meter; use renderer::{QuadRenderer, GlyphCache}; use term::Term; @@ -126,10 +125,28 @@ fn main() { gl::Enable(gl::MULTISAMPLE); } - let desc = FontDesc::new(font.family(), font.style()); - let mut rasterizer = font::Rasterizer::new(dpi.x(), dpi.y(), dpr); + let rasterizer = font::Rasterizer::new(dpi.x(), dpi.y(), dpr); - let metrics = rasterizer.metrics(&desc, font.size()); + // Create renderer + let mut renderer = QuadRenderer::new(width, height); + + // Initialize glyph cache + let mut glyph_cache = { + println!("Initializing glyph cache"); + let init_start = ::std::time::Instant::now(); + + let cache = renderer.with_loader(|mut api| { + GlyphCache::new(rasterizer, &config, &mut api) + }); + + let stop = init_start.elapsed(); + let stop_f = stop.as_secs() as f64 + stop.subsec_nanos() as f64 / 1_000_000_000f64; + println!("Finished initializing glyph cache in {}", stop_f); + + cache + }; + + let metrics = glyph_cache.font_metrics(); let cell_width = (metrics.average_advance + font.offset().x() as f64) as u32; let cell_height = (metrics.line_height + font.offset().y() as f64) as u32; @@ -140,7 +157,6 @@ fn main() { let mut reader = terminal.tty().reader(); let writer = terminal.tty().writer(); - let mut glyph_cache = GlyphCache::new(rasterizer, desc, font.size()); let (tx, rx) = mpsc::channel(); unsafe { @@ -192,23 +208,6 @@ fn main() { let window = Arc::new(window); let window_ref = window.clone(); - // Create renderer - let mut renderer = QuadRenderer::new(width, height); - - // Initialize glyph cache - { - let init_start = ::std::time::Instant::now(); - println!("Initializing glyph cache"); - let terminal = term_ref.lock_high(); - renderer.with_api(terminal.size_info(), |mut api| { - glyph_cache.init(&mut api); - }); - - let stop = init_start.elapsed(); - let stop_f = stop.as_secs() as f64 + stop.subsec_nanos() as f64 / 1_000_000_000f64; - println!("Finished initializing glyph cache in {}", stop_f); - } - let mut input_processor = input::Processor::new(); 'main_loop: loop { @@ -282,7 +281,7 @@ fn main() { renderer.with_api(&size_info, |mut api| { // Draw the grid api.render_grid(&terminal.render_grid(), &mut glyph_cache); - }) + }); } // Draw render timer diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index fec6cf85..23157456 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -22,13 +22,14 @@ use std::sync::Arc; use std::sync::atomic::{Ordering, AtomicBool}; use cgmath; +use font::{self, Rasterizer, RasterizedGlyph, FontDesc, GlyphKey, FontKey}; use gl::types::*; use gl; -use grid::Grid; use notify::{Watcher as WatcherApi, RecommendedWatcher as Watcher, op}; -use term::{self, cell, Cell}; -use font::{Rasterizer, RasterizedGlyph, FontDesc}; +use config::Config; +use grid::Grid; +use term::{self, cell, Cell}; use super::Rgb; @@ -84,58 +85,110 @@ pub struct Glyph { /// representations of the same code point. pub struct GlyphCache { /// Cache of buffered glyphs - cache: HashMap, + cache: HashMap, /// Rasterizer for loading new glyphs rasterizer: Rasterizer, - /// Font description - desc: FontDesc, + /// regular font + font_key: FontKey, - /// Font Size - size: f32, + /// italic font + italic_key: FontKey, + + /// bold font + bold_key: FontKey, + + /// font size + font_size: font::Size, } impl GlyphCache { - pub fn new(rasterizer: Rasterizer, desc: FontDesc, font_size: f32) -> GlyphCache { - GlyphCache { + pub fn new(mut rasterizer: Rasterizer, config: &Config, loader: &mut L) -> GlyphCache + where L: LoadGlyph + { + let font = config.font(); + let size = font.size(); + + // Load regular font + let regular_desc = FontDesc::new(font.family(), font.style()); + let regular = rasterizer.load_font(®ular_desc, size).expect("regular font load ok"); + + // Load bold font + let bold_style = font.bold_style().unwrap_or("Bold"); + let bold_desc = FontDesc::new(font.family(), bold_style); + + let bold = if bold_desc == regular_desc { + regular.clone() + } else { + rasterizer.load_font(&bold_desc, size).unwrap_or_else(|| regular.clone()) + }; + + // Load italic font + let italic_style = font.italic_style().unwrap_or("Italic"); + let italic_desc = FontDesc::new(font.family(), italic_style); + + let italic = if italic_desc == regular_desc { + regular.clone() + } else { + rasterizer.load_font(&italic_desc, size) + .unwrap_or_else(|| regular.clone()) + }; + + let mut cache = GlyphCache { cache: HashMap::new(), rasterizer: rasterizer, - desc: desc, - size: font_size, + font_size: font.size(), + font_key: regular.clone(), + bold_key: bold.clone(), + italic_key: italic.clone(), + }; + + macro_rules! load_glyphs_for_font { + ($font:expr) => { + for i in 32u8...128u8 { + cache.load_and_cache_glyph(GlyphKey { + font_key: $font, + c: i as char, + size: font.size() + }, loader); + } + } } + + load_glyphs_for_font!(regular); + load_glyphs_for_font!(bold); + load_glyphs_for_font!(italic); + + cache } - pub fn init(&mut self, loader: &mut L) - where L: LoadGlyph - { - for i in 32u8...128u8 { - self.load_and_cache_glyph(i as char, loader); - } + pub fn font_metrics(&self) -> font::Metrics { + self.rasterizer.metrics(self.font_key, self.font_size) } - fn load_and_cache_glyph(&mut self, c: char, loader: &mut L) + fn load_and_cache_glyph(&mut self, glyph_key: GlyphKey, loader: &mut L) where L: LoadGlyph { - let rasterized = self.rasterizer.get_glyph(&self.desc, self.size, c); + let rasterized = self.rasterizer.get_glyph(&glyph_key); let glyph = loader.load_glyph(&rasterized); - self.cache.insert(c, glyph); + self.cache.insert(glyph_key, glyph); } - pub fn get(&mut self, c: char, loader: &mut L) -> Option<&Glyph> + pub fn get(&mut self, glyph_key: &GlyphKey, loader: &mut L) -> Option<&Glyph> where L: LoadGlyph { // Return glyph if it's already loaded // hi borrowck { - if self.cache.contains_key(&c) { - return self.cache.get(&c); + if self.cache.contains_key(glyph_key) { + return self.cache.get(glyph_key); } } // Rasterize and load the glyph - self.load_and_cache_glyph(c, loader); - self.cache.get(&c) + self.load_and_cache_glyph(glyph_key.to_owned(), loader); + self.cache.get(&glyph_key) } } @@ -187,6 +240,12 @@ pub struct RenderApi<'a> { program: &'a mut ShaderProgram, } +#[derive(Debug)] +pub struct LoaderApi<'a> { + active_tex: &'a mut GLuint, + atlas: &'a mut Vec, +} + #[derive(Debug)] pub struct PackedVertex { x: f32, @@ -436,8 +495,8 @@ impl QuadRenderer { renderer } - pub fn with_api(&mut self, props: &term::SizeInfo, mut func: F) - where F: FnMut(RenderApi) + pub fn with_api(&mut self, props: &term::SizeInfo, func: F) -> T + where F: FnOnce(RenderApi) -> T { if self.should_reload.load(Ordering::Relaxed) { self.reload_shaders(props.width as u32, props.height as u32); @@ -453,7 +512,7 @@ impl QuadRenderer { gl::ActiveTexture(gl::TEXTURE0); } - func(RenderApi { + let res = func(RenderApi { active_tex: &mut self.active_tex, batch: &mut self.batch, atlas: &mut self.atlas, @@ -467,6 +526,21 @@ impl QuadRenderer { self.program.deactivate(); } + + res + } + + pub fn with_loader(&mut self, func: F) -> T + where F: FnOnce(LoaderApi) -> T + { + unsafe { + gl::ActiveTexture(gl::TEXTURE0); + } + + func(LoaderApi { + active_tex: &mut self.active_tex, + atlas: &mut self.atlas, + }) } pub fn reload_shaders(&mut self, width: u32, height: u32) { @@ -545,7 +619,13 @@ impl<'a> RenderApi<'a> { let mut col = 100.0; for c in s.chars() { - if let Some(glyph) = glyph_cache.get(c, self) { + let glyph_key = GlyphKey { + font_key: glyph_cache.font_key, + size: glyph_cache.font_size, + c: c + }; + + if let Some(glyph) = glyph_cache.get(&glyph_key, self) { let cell = Cell { c: c, fg: *color, @@ -587,8 +667,23 @@ impl<'a> RenderApi<'a> { continue; } - // Add cell to batch if the glyph is laoded - if let Some(glyph) = glyph_cache.get(cell.c, self) { + // Get font key for cell + // FIXME this is super inefficient. + let mut font_key = glyph_cache.font_key; + if cell.flags.contains(cell::BOLD) { + font_key = glyph_cache.bold_key; + } else if cell.flags.contains(cell::ITALIC) { + font_key = glyph_cache.italic_key; + } + + let glyph_key = GlyphKey { + font_key: font_key, + size: glyph_cache.font_size, + c: cell.c + }; + + // Add cell to batch if glyph available + if let Some(glyph) = glyph_cache.get(&glyph_key, self) { self.add_render_item(i as f32, j as f32, cell, glyph); } } @@ -596,11 +691,30 @@ impl<'a> RenderApi<'a> { } } +impl<'a> LoadGlyph for LoaderApi<'a> { + /// Load a glyph into a texture atlas + /// + /// If the current atlas is full, a new one will be created. + fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph { + // At least one atlas is guaranteed to be in the `self.atlas` list; thus the unwrap. + match self.atlas.last_mut().unwrap().insert(rasterized, &mut self.active_tex) { + Ok(glyph) => glyph, + Err(_) => { + let atlas = Atlas::new(ATLAS_SIZE); + *self.active_tex = 0; // Atlas::new binds a texture. Ugh this is sloppy. + self.atlas.push(atlas); + self.load_glyph(rasterized) + } + } + } +} + impl<'a> LoadGlyph for RenderApi<'a> { /// Load a glyph into a texture atlas /// /// If the current atlas is full, a new one will be created. fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph { + // At least one atlas is guaranteed to be in the `self.atlas` list; thus the unwrap. match self.atlas.last_mut().unwrap().insert(rasterized, &mut self.active_tex) { Ok(glyph) => glyph, Err(_) => { @@ -656,7 +770,7 @@ impl ShaderProgram { assert!($uniform != gl::INVALID_OPERATION as i32); }; ( $( $uniform:expr ),* ) => { - $( assert_uniform_valid!($uniform) )* + $( assert_uniform_valid!($uniform); )* }; }