diff --git a/font/src/darwin/mod.rs b/font/src/darwin/mod.rs index 7f01f57..ca784ae 100644 --- a/font/src/darwin/mod.rs +++ b/font/src/darwin/mod.rs @@ -430,10 +430,25 @@ impl Font { let leading = self.ct_font.leading() as f64; let line_height = (ascent + descent + leading + 0.5).floor(); + // Strikeout and underline metrics + // CoreText doesn't provide strikeout so we provide our own + let underline_position = + (self.ct_font.underline_position() - descent) + .round() as f32; + let underline_thickness = self.ct_font.underline_thickness() + .round() + .max(1.) as f32; + let strikeout_position = (line_height as f32 / 2. - descent as f32).round(); + let strikeout_thickness = underline_thickness; + Metrics { average_advance, line_height, descent: -(self.ct_font.descent() as f32), + underline_position, + underline_thickness, + strikeout_position, + strikeout_thickness, } } diff --git a/font/src/ft/mod.rs b/font/src/ft/mod.rs index 1d3f64b..03e8bb3 100644 --- a/font/src/ft/mod.rs +++ b/font/src/ft/mod.rs @@ -18,6 +18,7 @@ use std::cmp::min; use std::path::PathBuf; use std::fmt; +use freetype::tt_os2::TrueTypeOS2Table; use freetype::{self, Library}; use libc::c_uint; @@ -86,15 +87,50 @@ impl ::Rasterize for FreeTypeRasterizer { } fn metrics(&self, key: FontKey, _size: Size) -> Result { + let face = self.faces + .get(&key) + .ok_or(Error::FontNotLoaded)?; let full = self.full_metrics(key)?; let height = (full.size_metrics.height / 64) as f64; let descent = (full.size_metrics.descender / 64) as f32; + // Get underline position and thickness in device pixels + let x_scale = full.size_metrics.x_scale as f32 / 65536.0; + let underline_position = + (f32::from(face.ft_face.underline_position()) * x_scale / 64.).round(); + let underline_thickness = + (f32::from(face.ft_face.underline_thickness()) * x_scale / 64.) + .round() + .max(1.); + + // Get strikeout position and thickness in device pixels + let (strikeout_position, strikeout_thickness) = + match TrueTypeOS2Table::from_face(&mut face.ft_face.clone()) + { + Some(os2) => { + let strikeout_position = + (f32::from(os2.y_strikeout_position()) * x_scale / 64.).round(); + let strikeout_thickness = + (f32::from(os2.y_strikeout_size()) * x_scale / 64.).round(); + (strikeout_position, strikeout_thickness) + }, + _ => { + // Fallback if font doesn't provide info about strikeout + trace!("No strikeout data available for font, using fallback."); + let strikeout_position = height as f32 / 2. + descent; + (strikeout_position, underline_thickness) + }, + }; + Ok(Metrics { average_advance: full.cell_width, line_height: height, descent, + underline_position, + underline_thickness, + strikeout_position, + strikeout_thickness, }) } diff --git a/font/src/lib.rs b/font/src/lib.rs index 1db0821..fdba4b8 100644 --- a/font/src/lib.rs +++ b/font/src/lib.rs @@ -336,6 +336,10 @@ pub struct Metrics { pub average_advance: f64, pub line_height: f64, pub descent: f32, + pub underline_position: f32, + pub underline_thickness: f32, + pub strikeout_position: f32, + pub strikeout_thickness: f32, } pub trait Rasterize { diff --git a/font/src/rusttype/mod.rs b/font/src/rusttype/mod.rs index dea5815..2d28a8b 100644 --- a/font/src/rusttype/mod.rs +++ b/font/src/rusttype/mod.rs @@ -33,10 +33,25 @@ impl crate::Rasterize for RustTypeRasterizer { .ok_or(Error::MissingGlyph)? .scaled(scale) .h_metrics(); + + let line_height = f64::from(vmetrics.ascent - vmetrics.descent + vmetrics.line_gap); + let average_advance = f64::from(hmetrics.advance_width); + let descent = vmetrics.descent; + + // Strikeout and underline metrics + // RustType doesn't support these, so we make up our own + let thickness = (descent / 5.).round(); + let underline_position = descent / 2. + thickness / 2.; + let strikeout_position = (line_height as f32 / 2. - descent).round(); + Ok(Metrics { - descent: vmetrics.descent, - average_advance: f64::from(hmetrics.advance_width), - line_height: f64::from(vmetrics.ascent - vmetrics.descent + vmetrics.line_gap), + descent, + average_advance, + line_height, + underline_position, + underline_thickness: thickness, + strikeout_position, + strikeout_thickness: thickness, }) } diff --git a/src/ansi.rs b/src/ansi.rs index db8022e..29b1a45 100644 --- a/src/ansi.rs +++ b/src/ansi.rs @@ -642,7 +642,7 @@ pub enum Attr { Reverse, /// Do not display characters Hidden, - /// Strikethrough text + /// Strikeout text Strike, /// Cancel bold CancelBold, @@ -658,7 +658,7 @@ pub enum Attr { CancelReverse, /// Cancel text hiding CancelHidden, - /// Cancel strike through + /// Cancel strikeout CancelStrike, /// Set indexed foreground color Foreground(Color), diff --git a/src/display.rs b/src/display.rs index 120cd1d..b1575c5 100644 --- a/src/display.rs +++ b/src/display.rs @@ -25,6 +25,7 @@ use crate::config::Config; use font::{self, Rasterize}; use crate::meter::Meter; use crate::renderer::{self, GlyphCache, QuadRenderer}; +use crate::renderer::lines::Lines; use crate::term::{Term, SizeInfo, RenderableCell}; use crate::sync::FairMutex; use crate::window::{self, Window}; @@ -410,19 +411,27 @@ impl Display { { let glyph_cache = &mut self.glyph_cache; + let metrics = glyph_cache.font_metrics(); + let mut cell_line_rects = Lines::new(&metrics, &size_info); // Draw grid { let _sampler = self.meter.sampler(); self.renderer.with_api(config, &size_info, |mut api| { - // Draw the grid - api.render_cells(grid_cells.iter(), glyph_cache); + // Iterate over all non-empty cells in the grid + for cell in grid_cells { + // Update underline/strikeout + cell_line_rects.update_lines(&cell); + + // Draw the cell + api.render_cell(cell, glyph_cache); + } }); } // Draw rectangles - self.renderer.draw_rects(config, &size_info, visual_bell_intensity); + self.renderer.draw_rects(config, &size_info, visual_bell_intensity, cell_line_rects); // Draw render timer if self.render_timer { @@ -490,4 +499,3 @@ impl Display { self.window().set_ime_spot(LogicalPosition::from((nspot_x, nspot_y))); } } - diff --git a/src/renderer/lines.rs b/src/renderer/lines.rs new file mode 100644 index 0000000..556dcb0 --- /dev/null +++ b/src/renderer/lines.rs @@ -0,0 +1,143 @@ +// 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 std::collections::HashMap; + +use crate::term::{ SizeInfo, RenderableCell}; +use crate::term::cell::Flags; +use crate::renderer::Rect; +use font::Metrics; +use crate::Rgb; + +/// Lines for underline and strikeout. +pub struct Lines<'a> { + inner: Vec<(Rect, Rgb)>, + last_starts: HashMap>, + last_cell: Option, + metrics: &'a Metrics, + size: &'a SizeInfo, +} + +impl<'a> Lines<'a> { + pub fn new(metrics: &'a Metrics, size: &'a SizeInfo) -> Self { + let mut last_starts = HashMap::new(); + last_starts.insert(Flags::UNDERLINE, None); + last_starts.insert(Flags::STRIKEOUT, None); + + Self { + inner: Vec::new(), + last_cell: None, + last_starts, + metrics, + size, + } + } + + /// Convert the stored lines to rectangles for the renderer. + pub fn rects(mut self) -> Vec<(Rect, Rgb)> { + // If there's still a line pending, draw it until the last cell + for (flag, start_cell) in self.last_starts.iter_mut() { + if let Some(start) = start_cell { + self.inner.push( + create_rect( + &start, + &self.last_cell.unwrap(), + *flag, + &self.metrics, + &self.size, + ) + ); + } + } + + self.inner + } + + /// Update the stored lines with the next cell info. + pub fn update_lines(&mut self, cell: &RenderableCell) { + for (flag, start_cell) in self.last_starts.iter_mut() { + let flag = *flag; + *start_cell = match *start_cell { + // Check for end if line is present + Some(ref mut start) => { + // No change in line + if cell.line == start.line && cell.flags.contains(flag) && cell.fg == start.fg { + continue; + } + + self.inner.push( + create_rect( + &start, + &self.last_cell.unwrap(), + flag, + &self.metrics, + &self.size, + ) + ); + + // Start a new line if the flag is present + if cell.flags.contains(flag) { + Some(*cell) + } else { + None + } + } + // Check for new start of line + None => if cell.flags.contains(flag) { + Some(*cell) + } else { + None + }, + }; + } + + self.last_cell = Some(*cell); + } +} + +/// Create a rectangle between two cells. +fn create_rect( + start: &RenderableCell, + end: &RenderableCell, + flag: Flags, + metrics: &Metrics, + size: &SizeInfo, +) -> (Rect, Rgb) { + let start_x = start.column.0 as f32 * size.cell_width; + let end_x = (end.column.0 + 1) as f32 * size.cell_width; + let width = end_x - start_x; + + let (position, height) = match flag { + Flags::UNDERLINE => (metrics.underline_position, metrics.underline_thickness), + Flags::STRIKEOUT => (metrics.strikeout_position, metrics.strikeout_thickness), + _ => unimplemented!("Invalid flag for cell line drawing specified"), + }; + + let cell_bottom = (start.line.0 as f32 + 1.) * size.cell_height; + let baseline = cell_bottom + metrics.descent; + + let mut y = baseline - position - height / 2.; + let max_y = cell_bottom - height; + if y > max_y { + y = max_y; + } + + let rect = Rect::new( + start_x + size.padding_x, + y + size.padding_y, + width, + height, + ); + + (rect, start.fg) +} diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index c05301c..42a1748 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -23,16 +23,19 @@ use std::time::Duration; use cgmath; use fnv::FnvHasher; +use glutin::dpi::PhysicalSize; use font::{self, FontDesc, FontKey, GlyphKey, Rasterize, RasterizedGlyph, Rasterizer}; +use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; + use crate::gl::types::*; use crate::gl; use crate::index::{Column, Line, RangeInclusive}; -use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; use crate::Rgb; - use crate::config::{self, Config, Delta}; use crate::term::{self, cell, RenderableCell}; -use glutin::dpi::PhysicalSize; +use crate::renderer::lines::Lines; + +pub mod lines; // Shader paths for live reload static TEXT_SHADER_F_PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/res/text.f.glsl"); @@ -699,6 +702,7 @@ impl QuadRenderer { config: &Config, props: &term::SizeInfo, visual_bell_intensity: f64, + cell_line_rects: Lines, ) { // Swap to rectangle rendering program unsafe { @@ -725,6 +729,11 @@ impl QuadRenderer { let rect = Rect::new(0., 0., props.width, props.height); self.render_rect(&rect, color, visual_bell_intensity as f32, props); + // Draw underlines and strikeouts + for cell_line_rect in cell_line_rects.rects() { + self.render_rect(&cell_line_rect.0, cell_line_rect.1, 255., props); + } + // Deactivate rectangle program again unsafe { // Reset blending strategy @@ -902,7 +911,8 @@ impl QuadRenderer { } } -struct Rect { +#[derive(Debug, Copy, Clone)] +pub struct Rect { x: T, y: T, width: T, @@ -910,7 +920,7 @@ struct Rect { } impl Rect { - fn new(x: T, y: T, width: T, height: T) -> Self { + pub fn new(x: T, y: T, width: T, height: T) -> Self { Rect { x, y, width, height } } } @@ -998,7 +1008,9 @@ impl<'a> RenderApi<'a> { }) .collect::>(); - self.render_cells(cells.iter(), glyph_cache); + for cell in cells { + self.render_cell(cell, glyph_cache); + } } #[inline] @@ -1016,74 +1028,52 @@ impl<'a> RenderApi<'a> { } } - pub fn render_cells<'b, I>( - &mut self, - cells: I, - glyph_cache: &mut GlyphCache - ) - where I: Iterator - { - for cell in cells { - // Get font key for cell - // FIXME this is super inefficient. - let font_key = if cell.flags.contains(cell::Flags::BOLD) { - glyph_cache.bold_key - } else if cell.flags.contains(cell::Flags::ITALIC) { - glyph_cache.italic_key - } else { - glyph_cache.font_key - }; + pub fn render_cell(&mut self, cell: RenderableCell, glyph_cache: &mut GlyphCache) { + // Get font key for cell + // FIXME this is super inefficient. + let font_key = if cell.flags.contains(cell::Flags::BOLD) { + glyph_cache.bold_key + } else if cell.flags.contains(cell::Flags::ITALIC) { + glyph_cache.italic_key + } else { + glyph_cache.font_key + }; - // Don't render text of HIDDEN cells - let mut chars = if cell.flags.contains(cell::Flags::HIDDEN) { - [' '; cell::MAX_ZEROWIDTH_CHARS + 1] - } else { - cell.chars - }; + // Don't render text of HIDDEN cells + let mut chars = if cell.flags.contains(cell::Flags::HIDDEN) { + [' '; cell::MAX_ZEROWIDTH_CHARS + 1] + } else { + cell.chars + }; - // Render tabs as spaces in case the font doesn't support it - if chars[0] == '\t' { - chars[0] = ' '; - } + // Render tabs as spaces in case the font doesn't support it + if chars[0] == '\t' { + chars[0] = ' '; + } - let mut glyph_key = GlyphKey { - font_key, - size: glyph_cache.font_size, - c: chars[0], - }; + let mut glyph_key = GlyphKey { + font_key, + size: glyph_cache.font_size, + c: chars[0], + }; - // Add cell to batch - let glyph = glyph_cache.get(glyph_key, self); - self.add_render_item(&cell, glyph); + // Add cell to batch + let glyph = glyph_cache.get(glyph_key, self); + self.add_render_item(&cell, glyph); - // Render zero-width characters - for c in (&chars[1..]).iter().filter(|c| **c != ' ') { - glyph_key.c = *c; - let mut glyph = *glyph_cache.get(glyph_key, self); + // Render zero-width characters + for c in (&chars[1..]).iter().filter(|c| **c != ' ') { + glyph_key.c = *c; + let mut glyph = *glyph_cache.get(glyph_key, self); - // The metrics of zero-width characters are based on rendering - // the character after the current cell, with the anchor at the - // right side of the preceding character. Since we render the - // zero-width characters inside the preceding character, the - // anchor has been moved to the right by one cell. - glyph.left += glyph_cache.metrics.average_advance as f32; + // The metrics of zero-width characters are based on rendering + // the character after the current cell, with the anchor at the + // right side of the preceding character. Since we render the + // zero-width characters inside the preceding character, the + // anchor has been moved to the right by one cell. + glyph.left += glyph_cache.metrics.average_advance as f32; - self.add_render_item(&cell, &glyph); - } - - // FIXME This is a super hacky way to do underlined text. During - // a time crunch to release 0.1, this seemed like a really - // easy, clean hack. - if cell.flags.contains(cell::Flags::UNDERLINE) { - let glyph_key = GlyphKey { - font_key, - size: glyph_cache.font_size, - c: '_', - }; - - let underscore = glyph_cache.get(glyph_key, self); - self.add_render_item(&cell, underscore); - } + self.add_render_item(&cell, &glyph); } } } diff --git a/src/term/cell.rs b/src/term/cell.rs index bd56148..5d3b703 100644 --- a/src/term/cell.rs +++ b/src/term/cell.rs @@ -23,16 +23,17 @@ pub const MAX_ZEROWIDTH_CHARS: usize = 5; bitflags! { #[derive(Serialize, Deserialize)] pub struct Flags: u16 { - const INVERSE = 0b0_0000_0001; - const BOLD = 0b0_0000_0010; - const ITALIC = 0b0_0000_0100; - const UNDERLINE = 0b0_0000_1000; - const WRAPLINE = 0b0_0001_0000; - const WIDE_CHAR = 0b0_0010_0000; - const WIDE_CHAR_SPACER = 0b0_0100_0000; - const DIM = 0b0_1000_0000; - const DIM_BOLD = 0b0_1000_0010; - const HIDDEN = 0b1_0000_0000; + const INVERSE = 0b00_0000_0001; + const BOLD = 0b00_0000_0010; + const ITALIC = 0b00_0000_0100; + const UNDERLINE = 0b00_0000_1000; + const WRAPLINE = 0b00_0001_0000; + const WIDE_CHAR = 0b00_0010_0000; + const WIDE_CHAR_SPACER = 0b00_0100_0000; + const DIM = 0b00_1000_0000; + const DIM_BOLD = 0b00_1000_0010; + const HIDDEN = 0b01_0000_0000; + const STRIKEOUT = 0b10_0000_0000; } } @@ -117,7 +118,7 @@ impl Cell { (self.c == ' ' || self.c == '\t') && self.extra[0] == ' ' && self.bg == Color::Named(NamedColor::Background) - && !self.flags.intersects(Flags::INVERSE | Flags::UNDERLINE) + && !self.flags.intersects(Flags::INVERSE | Flags::UNDERLINE | Flags::STRIKEOUT) } #[inline] diff --git a/src/term/mod.rs b/src/term/mod.rs index fd2fcf8..bb65fba 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -419,7 +419,7 @@ impl<'a> RenderableCellsIter<'a> { } } -#[derive(Debug)] +#[derive(Copy, Clone, Debug)] pub struct RenderableCell { /// A _Display_ line (not necessarily an _Active_ line) pub line: Line, @@ -1965,6 +1965,8 @@ impl ansi::Handler for Term { Attr::CancelUnderline => self.cursor.template.flags.remove(cell::Flags::UNDERLINE), Attr::Hidden => self.cursor.template.flags.insert(cell::Flags::HIDDEN), Attr::CancelHidden => self.cursor.template.flags.remove(cell::Flags::HIDDEN), + Attr::Strike => self.cursor.template.flags.insert(cell::Flags::STRIKEOUT), + Attr::CancelStrike => self.cursor.template.flags.remove(cell::Flags::STRIKEOUT), _ => { debug!("Term got unhandled attr: {:?}", attr); }