aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--font/src/darwin/mod.rs60
-rw-r--r--font/src/lib.rs52
-rw-r--r--src/config.rs80
-rw-r--r--src/main.rs45
-rw-r--r--src/renderer/mod.rs180
5 files changed, 337 insertions, 80 deletions
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<FontDesc, Font>,
+ fonts: HashMap<FontKey, Font>,
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<Font> {
- if let Some(font) = self.fonts.get(desc) {
- return Some(font.clone());
- }
+ pub fn load_font(&mut self, desc: &FontDesc, size: Size) -> Option<FontKey> {
+ 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<Font> {
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 _);
-
- glyph
+ /// 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();
+
+ 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<Descriptor> {
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<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(&regular_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); )*
};
}