diff options
author | Joe Wilm <joe@jwilm.com> | 2016-08-12 13:38:41 -0500 |
---|---|---|
committer | Joe Wilm <joe@jwilm.com> | 2016-08-12 19:30:14 -0500 |
commit | b70394170c0fa9cd4e4be8150121a698a426a069 (patch) | |
tree | 41d84240a4e58df201fbd7f4f984c3814c7cb055 /src | |
parent | 874719580ce3e12f09d01a9a53c5e64205ef5b5d (diff) | |
download | alacritty-b70394170c0fa9cd4e4be8150121a698a426a069.tar.gz alacritty-b70394170c0fa9cd4e4be8150121a698a426a069.zip |
Support bold/italic font rendering on macOS
This patch adds support for rendering italic fonts and bold fonts.
The `font` crate has a couple of new paradigms to support this: font
keys and glyph keys. `FontKey` is a lightweight (4 byte) identifier for
a font loaded out of the rasterizer. This replaces `FontDesc` for
rasterizing glyphs from a loaded font. `FontDesc` had the problem that
it contained two strings, and the glyph cache needs to store a copy of
the font key for every loaded glyph. `GlyphKey` is now passed to the
glyph rasterization method instead of a simple `char`. `GlyphKey`
contains information including font, size, and the character.
The rasterizer APIs do not define what happens when loading the same
font from a `FontDesc` more than once. It is assumed that the
application will track the resulting `FontKey` instead of asking the
font to be loaded multiple times.
Diffstat (limited to 'src')
-rw-r--r-- | src/config.rs | 80 | ||||
-rw-r--r-- | src/main.rs | 45 | ||||
-rw-r--r-- | src/renderer/mod.rs | 180 |
3 files changed, 244 insertions, 61 deletions
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<D>(&mut D) -> ::std::result::Result<Self, D::Error> + where D: serde::de::Deserializer; +} + +impl DeserializeFromF32 for Size { + fn deserialize_from_f32<D>(deserializer: &mut D) -> ::std::result::Result<Self, D::Error> + 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<E>(&mut self, value: f32) -> ::std::result::Result<Self::Value, E> + where E: ::serde::de::Error + { + Ok(value) + } + + fn visit_str<E>(&mut self, value: &str) -> ::std::result::Result<Self::Value, E> + 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::<f32>().expect("size must be float")) + } + } + + deserializer + .deserialize_f32(FloatVisitor::<D>{ _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<String>, + + /// Italic font style + italic_style: Option<String>, + + // 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<char, Glyph>, + cache: HashMap<GlyphKey, Glyph>, /// 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<L>(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<L>(&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<L>(&mut self, c: char, loader: &mut L) + fn load_and_cache_glyph<L>(&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<L>(&mut self, c: char, loader: &mut L) -> Option<&Glyph> + pub fn get<L>(&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) } } @@ -188,6 +241,12 @@ pub struct RenderApi<'a> { } #[derive(Debug)] +pub struct LoaderApi<'a> { + active_tex: &'a mut GLuint, + atlas: &'a mut Vec<Atlas>, +} + +#[derive(Debug)] pub struct PackedVertex { x: f32, y: f32, @@ -436,8 +495,8 @@ impl QuadRenderer { renderer } - pub fn with_api<F>(&mut self, props: &term::SizeInfo, mut func: F) - where F: FnMut(RenderApi) + pub fn with_api<F, T>(&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<F, T>(&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); )* }; } |