From 038899c5b3a0909a4a00116820c0d3619c1b22ed Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Tue, 20 Feb 2018 18:23:06 +0100 Subject: [PATCH] WIP --- src/config/font.rs | 212 +++++++++++++++++++++++++++++++ src/{config.rs => config/mod.rs} | 194 ++-------------------------- src/font.rs | 43 +++++++ src/renderer/mod.rs | 114 +++-------------- 4 files changed, 283 insertions(+), 280 deletions(-) create mode 100644 src/config/font.rs rename src/{config.rs => config/mod.rs} (91%) create mode 100644 src/font.rs diff --git a/src/config/font.rs b/src/config/font.rs new file mode 100644 index 00000000..2d3ae310 --- /dev/null +++ b/src/config/font.rs @@ -0,0 +1,212 @@ +// Copyright 2016 Joe Wilm, The Alacritty Project Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use serde_yaml; +use serde::{de, Deserialize}; + +use config::{Delta, failure_default}; +use font::{Size, FontKey, Slant}; + +// Global and local font configuration +#[derive(Deserialize, Debug)] +pub struct FontConfiguration { + #[serde(default, deserialize_with="failure_default")] + options: GlobalOptions, + #[serde(default, deserialize_with="deserialize_font_collection")] + fonts: Vec, +} + +impl FontConfiguration { + pub fn font_by_char(&self, c: char, weight: Slant) -> FontKey { + for font in fonts { + let options = font.options(); + + // Skip font if font slant does not match requested slant + if options.unwrap_or(self.options as Options).style.unwrap_or(self.options.style) != weight { + continue; + } + + let is_match = match options { + Some(options) => { + for range in options.ranges { + if range.start < c && range.end > c { + return true; + } + } + false + }, + None => { + true + }, + }; + + if is_match { + // TODO: Check if this font contains the char + } + } + } +} + +impl Default for FontConfiguration { + fn default() -> Self { + Self { + fonts: vec!(Font::default()), + ..Default::default() + } + } +} + +// Information about a font +#[derive(Deserialize, Debug)] +pub struct Font { + family: String, + #[serde(deserialize_with="failure_default")] + options: Option, +} + +// Default font config in case of failure or missing config +impl Default for Font { + #[cfg(target_os = "macos")] + fn default() -> Self { + Font { + family: "Menlo".into(), + options: None, + } + } + + #[cfg(not(target_os = "macos"))] + fn default() -> Self { + Font { + family: "monospace".into(), + options: None, + } + } +} + +// Options for a font +#[derive(Deserialize, Debug)] +pub struct Options { + #[serde(deserialize_with="deserialize_size")] + size: Option, + #[serde(deserialize_with="failure_default")] + thin_strokes: Option, + #[serde(deserialize_with="failure_default")] + antialias: Option, + #[serde(deserialize_with="failure_default")] + hinting: Option, + #[serde(deserialize_with="failure_default")] + style: Option, + #[serde(deserialize_with="failure_default")] + offset: Option, + #[serde(deserialize_with="failure_default")] + ranges: Vec, +} + +impl Default for Options { + fn default() -> Self { + Options { + size: None, + thin_strokes: None, + antialias: None, + hinting: None, + style: None, + offset: None, + ranges: Vec::new(), + } + } +} + +#[derive(Deserialize, Debug)] +pub struct GlobalOptions(Options); +impl Default for GlobalOptions { + fn default() -> Self { + GlobalOptions(Options { + size: Some(Size::new(12.0)), + thin_strokes: Some(true), + antialias: Some(AntiAlias::LCD), + hinting: Some(true), + style: Some("normal".into()), + offset: Some(Delta::default()), + ranges: Vec::new(), + }) + } +} + +// AntiAliasing settings for fonts +#[derive(Deserialize, Debug)] +pub enum AntiAlias { + LCD, + LCDV, + GRAY, + DISABLED, +} + +// Range for which a specific font should be used +#[derive(Deserialize, Debug)] +pub struct FontRange { + #[serde(deserialize_with="failure_default")] + start: char, + #[serde(deserialize_with="failure_default")] + end: char, +} + +// Deserialize the font vector +fn deserialize_font_collection<'a, D>(deserializer: D) + -> ::std::result::Result, D::Error> + where D: de::Deserializer<'a>, +{ + // Deserialize vector as generic yaml value + let mut value = match serde_yaml::Value::deserialize(deserializer) { + Ok(value) => value, + Err(err) => { + eprintln!("problem with config: {}; Using default fonts", err); + return Ok(vec!(Font::default())); + }, + }; + + // Get value as sequence + let sequence = match value.as_sequence_mut() { + Some(sequence) => sequence, + None => return Ok(vec!(Font::default())), + }; + + // Deserialize each element in the sequence + let mut font_collection = Vec::new(); + for i in 0..sequence.len() { + match Font::deserialize(sequence.remove(i)) { + Ok(font) => font_collection.push(font), + // TODO: Print line or something like that? + Err(err) => eprintln!("problem with config: Malformed font; Skipping"), + } + } + + // Return defaults if collection contains no font + if font_collection.is_empty() { + Ok(vec!(Font::default())) + } else { + Ok(font_collection) + } +} + +// Deserialize font size +fn deserialize_size<'a, D>(deserializer: D) + -> ::std::result::Result, D::Error> + where D: de::Deserializer<'a>, +{ + match f32::deserialize(deserializer) { + Ok(value) => Ok(Some(Size::new(value))), + _ => { + Ok(None) + }, + } +} diff --git a/src/config.rs b/src/config/mod.rs similarity index 91% rename from src/config.rs rename to src/config/mod.rs index be967150..b3400b7d 100644 --- a/src/config.rs +++ b/src/config/mod.rs @@ -3,6 +3,8 @@ //! Alacritty reads from a config file at startup to determine various runtime //! parameters including font family and style, font size, etc. In the future, //! the config file will also hold user and platform specific keybindings. +pub mod font; + use std::borrow::Cow; use std::{env, fmt}; use std::fs::{self, File}; @@ -14,15 +16,15 @@ use std::time::Duration; use std::collections::HashMap; use ::Rgb; -use font::Size; use serde_yaml; -use serde::{self, de, Deserialize}; +use serde::{de, Deserialize}; use serde::de::Error as SerdeError; use serde::de::{Visitor, MapAccess, Unexpected}; use notify::{Watcher, watcher, DebouncedEvent, RecursiveMode}; use glutin::ModifiersState; +use self::font::FontConfiguration; use input::{Action, Binding, MouseBinding, KeyBinding}; use index::{Line, Column}; use ansi::CursorStyle; @@ -326,7 +328,7 @@ pub struct Config { /// Font configuration #[serde(default, deserialize_with = "failure_default")] - font: Font, + font: FontConfiguration, /// Should show render timer #[serde(default, deserialize_with = "failure_default")] @@ -453,7 +455,7 @@ fn default_true_bool<'a, D>(deserializer: D) -> ::std::result::Result(deserializer: D) +pub fn failure_default<'a, D, T>(deserializer: D) -> ::std::result::Result where D: de::Deserializer<'a>, T: Deserialize<'a> + Default @@ -467,10 +469,8 @@ fn failure_default<'a, D, T>(deserializer: D) } } -#[cfg(not(target_os="macos"))] -static DEFAULT_ALACRITTY_CONFIG: &'static str = include_str!("../alacritty.yml"); -#[cfg(target_os="macos")] -static DEFAULT_ALACRITTY_CONFIG: &'static str = include_str!("../alacritty_macos.yml"); +static DEFAULT_ALACRITTY_CONFIG: &'static str = + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/alacritty.yml")); impl Default for Config { fn default() -> Self { @@ -1295,12 +1295,6 @@ impl Config { self.draw_bold_text_with_bright_colors } - /// Get font config - #[inline] - pub fn font(&self) -> &Font { - &self.font - } - /// Get window dimensions #[inline] pub fn dimensions(&self) -> Dimensions { @@ -1325,11 +1319,6 @@ impl Config { self.render_timer } - #[inline] - pub fn use_thin_strokes(&self) -> bool { - self.font.use_thin_strokes - } - /// show cursor as inverted #[inline] pub fn custom_cursor_colors(&self) -> bool { @@ -1464,173 +1453,6 @@ impl Default for Delta { } } -trait DeserializeSize : Sized { - fn deserialize<'a, D>(D) -> ::std::result::Result - where D: serde::de::Deserializer<'a>; -} - -impl DeserializeSize for Size { - fn deserialize<'a, D>(deserializer: D) -> ::std::result::Result - where D: serde::de::Deserializer<'a> - { - use std::marker::PhantomData; - - struct NumVisitor<__D> { - _marker: PhantomData<__D>, - } - - impl<'a, __D> Visitor<'a> for NumVisitor<__D> - where __D: serde::de::Deserializer<'a> - { - type Value = f64; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("f64 or u64") - } - - fn visit_f64(self, value: f64) -> ::std::result::Result - where E: ::serde::de::Error - { - Ok(value) - } - - fn visit_u64(self, value: u64) -> ::std::result::Result - where E: ::serde::de::Error - { - Ok(value as f64) - } - } - - let size = deserializer - .deserialize_any(NumVisitor::{ _marker: PhantomData }) - .map(|v| Size::new(v as _)); - - // Use font size 12 as fallback - match size { - Ok(size) => Ok(size), - Err(err) => { - eprintln!("problem with config: {}; Using size 12", err); - Ok(Size::new(12.)) - }, - } - } -} - -/// Font config -/// -/// Defaults are provided at the level of this struct per platform, but not per -/// field in this struct. It might be nice in the future to have defaults for -/// each value independently. Alternatively, maybe erroring when the user -/// doesn't provide complete config is Ok. -#[derive(Debug, Deserialize, Clone)] -pub struct Font { - /// Font family - pub normal: FontDescription, - - #[serde(default="default_italic_desc")] - pub italic: FontDescription, - - #[serde(default="default_bold_desc")] - pub bold: FontDescription, - - // Font size in points - #[serde(deserialize_with="DeserializeSize::deserialize")] - pub size: Size, - - /// Extra spacing per character - #[serde(default, deserialize_with = "failure_default")] - offset: Delta, - - /// Glyph offset within character cell - #[serde(default, deserialize_with = "failure_default")] - glyph_offset: Delta, - - #[serde(default="true_bool", deserialize_with = "default_true_bool")] - use_thin_strokes: bool -} - -fn default_bold_desc() -> FontDescription { - Font::default().bold -} - -fn default_italic_desc() -> FontDescription { - Font::default().italic -} - -/// Description of a single font -#[derive(Debug, Deserialize, Clone)] -pub struct FontDescription { - pub family: String, - pub style: Option, -} - -impl FontDescription { - fn new_with_family>(family: S) -> FontDescription { - FontDescription { - family: family.into(), - style: None, - } - } -} - -impl Font { - /// Get the font size in points - #[inline] - pub fn size(&self) -> Size { - self.size - } - - /// Get offsets to font metrics - #[inline] - pub fn offset(&self) -> &Delta { - &self.offset - } - - /// Get cell offsets for glyphs - #[inline] - pub fn glyph_offset(&self) -> &Delta { - &self.glyph_offset - } - - /// Get a font clone with a size modification - pub fn with_size(self, size: Size) -> Font { - Font { - size, - .. self - } - } -} - -#[cfg(target_os = "macos")] -impl Default for Font { - fn default() -> Font { - Font { - normal: FontDescription::new_with_family("Menlo"), - bold: FontDescription::new_with_family("Menlo"), - italic: FontDescription::new_with_family("Menlo"), - size: Size::new(11.0), - use_thin_strokes: true, - offset: Default::default(), - glyph_offset: Default::default() - } - } -} - -#[cfg(any(target_os = "linux",target_os = "freebsd"))] -impl Default for Font { - fn default() -> Font { - Font { - normal: FontDescription::new_with_family("monospace"), - bold: FontDescription::new_with_family("monospace"), - italic: FontDescription::new_with_family("monospace"), - size: Size::new(11.0), - use_thin_strokes: false, - offset: Default::default(), - glyph_offset: Default::default() - } - } -} - pub struct Monitor { _thread: ::std::thread::JoinHandle<()>, rx: mpsc::Receiver, diff --git a/src/font.rs b/src/font.rs new file mode 100644 index 00000000..5d80b97c --- /dev/null +++ b/src/font.rs @@ -0,0 +1,43 @@ +// Copyright 2016 Joe Wilm, The Alacritty Project Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use config::Delta; +use font::Size; + +struct Font { + family: String, + options: Option, +} + +enum AntiAlias { + LCD, + LCDV, + GRAY, + // TODO: Maybe change the name so it's not confused with Rust's None? + NONE, +} + +struct Options { + size: Option, + thin_strokes: Option, + antialias: Option, + hinting: Option, + style: Option, + offset: Option, + range: Option, +} + +struct FontRange { + start: char, + end: char, +} diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index c0e4a9f3..9119802e 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -30,6 +30,7 @@ use index::{Line, Column, RangeInclusive}; use notify::{Watcher, watcher, RecursiveMode, DebouncedEvent}; use config::{self, Config, Delta}; +use config::font::FontConfiguration; use term::{self, cell, RenderableCell}; use window::{Size, Pixels}; @@ -152,124 +153,47 @@ pub struct GlyphCache { /// Rasterizer for loading new glyphs rasterizer: Rasterizer, - /// regular font - font_key: FontKey, - - /// italic font - italic_key: FontKey, - - /// bold font - bold_key: FontKey, - - /// font size - font_size: font::Size, - - /// glyph offset - glyph_offset: Delta, + /// Font configuration with all fonts + font_config: FontConfiguration, + /// Font metrics like glyph width/height metrics: ::font::Metrics, } impl GlyphCache { pub fn new( mut rasterizer: Rasterizer, - font: &config::Font, + font_config: FontConfiguration, loader: &mut L ) -> Result where L: LoadGlyph { - let (regular, bold, italic) = Self::compute_font_keys(font, &mut rasterizer)?; - // Need to load at least one glyph for the face before calling metrics. - // The glyph requested here ('m' at the time of writing) has no special + // The glyph requested here ('0' at the time of writing) has no special // meaning. - rasterizer.get_glyph(&GlyphKey { font_key: regular, c: 'm', size: font.size() })?; + let primary_font = font_config.font_by_char('0'); + rasterizer.get_glyph(&GlyphKey { font_key: primary_font, c: '0', size: primary_font.size() })?; let metrics = rasterizer.metrics(regular)?; let mut cache = GlyphCache { cache: HashMap::default(), - rasterizer: rasterizer, - font_size: font.size(), - font_key: regular, - bold_key: bold, - italic_key: italic, - glyph_offset: *font.glyph_offset(), - metrics: metrics + metrics, + rasterizer, + font_config, }; - cache.load_glyphs_for_font(regular, loader); - cache.load_glyphs_for_font(bold, loader); - cache.load_glyphs_for_font(italic, loader); + // TODO: Load set of standard glyphs + // cache.load_glyphs_for_font(regular, loader); + // cache.load_glyphs_for_font(bold, loader); + // cache.load_glyphs_for_font(italic, loader); Ok(cache) } - fn load_glyphs_for_font( - &mut self, - font: FontKey, - loader: &mut L, - ) { - let size = self.font_size; - for i in RangeInclusive::new(32u8, 128u8) { - self.get(&GlyphKey { - font_key: font, - c: i as char, - size: size - }, loader); - } - } - - /// Computes font keys for (Regular, Bold, Italic) - fn compute_font_keys( - font: &config::Font, - rasterizer: &mut Rasterizer - ) -> Result<(FontKey, FontKey, FontKey), font::Error> { - let size = font.size(); - - // Load regular font - let regular_desc = Self::make_desc(&font.normal, font::Slant::Normal, font::Weight::Normal); - - let regular = rasterizer - .load_font(®ular_desc, size)?; - - // helper to load a description if it is not the regular_desc - let mut load_or_regular = |desc:FontDesc| { - if desc == regular_desc { - regular - } else { - rasterizer.load_font(&desc, size).unwrap_or_else(|_| regular) - } - }; - - // Load bold font - let bold_desc = Self::make_desc(&font.bold, font::Slant::Normal, font::Weight::Bold); - - let bold = load_or_regular(bold_desc); - - // Load italic font - let italic_desc = Self::make_desc(&font.italic, font::Slant::Italic, font::Weight::Normal); - - let italic = load_or_regular(italic_desc); - - Ok((regular, bold, italic)) - } - - fn make_desc( - desc: &config::FontDescription, - slant: font::Slant, - weight: font::Weight, - ) -> FontDesc { - let style = if let Some(ref spec) = desc.style { - font::Style::Specific(spec.to_owned()) - } else { - font::Style::Description {slant:slant, weight:weight} - }; - FontDesc::new(&desc.family[..], style) - } - pub fn font_metrics(&self) -> font::Metrics { + let primary_font = self.font_config.font_by_char('0'); self.rasterizer - .metrics(self.font_key) + .metrics(primary_font) .expect("metrics load since font is loaded at glyph cache creation") } @@ -292,9 +216,11 @@ impl GlyphCache { loader.load_glyph(&rasterized) }) } + + // TODO pub fn update_font_size( &mut self, - font: &config::Font, + font: &FontConfiguration, size: font::Size, loader: &mut L ) -> Result<(), font::Error> {