1
0
Fork 0
mirror of https://github.com/alacritty/alacritty.git synced 2025-07-31 22:03:40 -04:00

Render underline/strikethrough as rects

Support for strikethrough has been added by inserting and removing a
`STRIKE_THROUGH` flag on the cell.

Now all strikethrough and underline drawing is also done through the
rectangle renderer. So no glyphs are used to render underlines and
strikethrough.
The position is taken from the font metrics and should be accurate for
linux, however is not yet tested on macos.

It works by checking the underline state for each cell and then drawing
from the start until the last position whenever an underline ended. This
adds a few checks even if no underline is rendered but I was not able to
measure any significant performance impact.

Fixes jwilm/alacritty#806.
Fixes jwilm/alacritty#31.
This commit is contained in:
Christian Duerr 2018-02-01 19:56:46 +01:00
parent b86c1ff9ed
commit 6428ce28b4
No known key found for this signature in database
GPG key ID: 85CDAE3C164BA7B4
7 changed files with 200 additions and 62 deletions

View file

@ -422,11 +422,15 @@ impl Font {
let descent = self.ct_font.descent() as f64;
let leading = self.ct_font.leading() as f64;
let line_height = (ascent + descent + leading + 0.5).floor();
let underline_position = self.ct_font.underline_position() as f32;
let underline_thickness = self.ct_font.underline_thickness() as f32;
Metrics {
average_advance: average_advance,
line_height: line_height,
descent: -(self.ct_font.descent() as f32),
underline_position,
underline_thickness,
}
}

View file

@ -86,15 +86,24 @@ impl ::Rasterize for FreeTypeRasterizer {
}
fn metrics(&self, key: FontKey) -> Result<Metrics, Error> {
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;
let underline_position =
(f32::from(face.ft_face.underline_position()) / 64.).round();
let underline_thickness =
(f32::from(face.ft_face.underline_thickness()) / 64.).round();
Ok(Metrics {
average_advance: full.cell_width,
line_height: height,
descent: descent,
underline_position,
underline_thickness,
})
}

View file

@ -314,6 +314,8 @@ pub struct Metrics {
pub average_advance: f64,
pub line_height: f64,
pub descent: f32,
pub underline_position: f32,
pub underline_thickness: f32,
}
pub trait Rasterize {

View file

@ -21,11 +21,11 @@ use parking_lot::{MutexGuard};
use Rgb;
use cli;
use config::Config;
use font::{self, Rasterize};
use font::{self, Rasterize, Metrics};
use meter::Meter;
use renderer::{self, GlyphCache, QuadRenderer};
use renderer::{self, GlyphCache, QuadRenderer, Rect};
use selection::Selection;
use term::{Term, SizeInfo};
use term::{cell, Term, SizeInfo, RenderableCell};
use window::{self, Size, Pixels, Window, SetInnerSize};
@ -346,6 +346,8 @@ impl Display {
{
let glyph_cache = &mut self.glyph_cache;
let metrics = glyph_cache.font_metrics();
let mut cell_line_rects = Vec::new();
// Draw grid
{
@ -363,17 +365,74 @@ impl Display {
api.clear(background_color);
}
// Draw the grid
api.render_cells(
terminal.renderable_cells(config, selection, window_focused),
glyph_cache,
);
// Store underline/strikethrough information beyond current cell
let mut last_cell = None;
let mut start_underline: Option<RenderableCell> = None;
let mut start_strikethrough: Option<RenderableCell> = None;
// Iterate over all non-empty cells in the grid
for cell in terminal.renderable_cells(config, selection, window_focused) {
// Check if there is a new underline
if let Some(underline) = calculate_cell_line_state(
cell,
&mut start_underline,
&last_cell,
&metrics,
&size_info,
cell::Flags::UNDERLINE,
) {
cell_line_rects.push(underline);
}
// Check if there is a new strikethrough
if let Some(strikethrough) = calculate_cell_line_state(
cell,
&mut start_strikethrough,
&last_cell,
&metrics,
&size_info,
cell::Flags::STRIKE_THROUGH,
) {
cell_line_rects.push(strikethrough);
}
// Change the last checked cell for underline/strikethrough
last_cell = Some(cell);
// Draw the cell
api.render_cell(cell, glyph_cache);
}
// If underline hasn't been reset, draw until the last cell
if let Some(start) = start_underline {
cell_line_rects.push(
cell_line_rect(
&start,
&last_cell.unwrap(),
&metrics, &size_info,
cell::Flags::UNDERLINE
)
);
}
// If strikethrough hasn't been reset, draw until the last cell
if let Some(start) = start_strikethrough {
let flag = cell::Flags::STRIKE_THROUGH;
cell_line_rects.push(
cell_line_rect(
&start,
&last_cell.unwrap(),
&metrics, &size_info,
cell::Flags::STRIKE_THROUGH
)
);
}
});
}
// Draw rectangles
let visual_bell_intensity = terminal.visual_bell.intensity();
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 {
@ -423,3 +482,75 @@ impl Display {
self.window().send_xim_spot(nspot_x, nspot_y);
}
}
// Check if the underline/strikethrough state has changed
// This returns a new rectangle whenever an underline/strikethrough ends
fn calculate_cell_line_state(
cell: RenderableCell,
start_cell_line: &mut Option<RenderableCell>,
last_cell: &Option<RenderableCell>,
metrics: &Metrics,
size_info: &SizeInfo,
flag: cell::Flags,
) -> Option<(Rect<u32>, Rgb)> {
match *start_cell_line {
// If line is already started, check for end
Some(start) => {
// No change in line
if cell.line == start.line
&& cell.flags.contains(flag)
&& cell.fg == start.fg
{
return None;
}
// Check if we need to start a new line
// because the cell color has changed
*start_cell_line = if cell.fg != start.fg && cell.flags.contains(flag) {
// Start a new line
Some(cell)
} else {
// Disable line
None
};
return Some(
cell_line_rect(&start, &last_cell.unwrap(), metrics, size_info, flag)
);
},
// Check for new start of line
None => if cell.flags.contains(flag) {
*start_cell_line = Some(cell);
},
}
None
}
// Create a colored rectangle for an underline/strikethrough based on two cells
fn cell_line_rect(
start: &RenderableCell,
end: &RenderableCell,
metrics: &Metrics,
size: &SizeInfo,
flag: cell::Flags,
) -> (Rect<u32>, Rgb) {
let x = (start.column.0 as f64 * metrics.average_advance) as u32;
let end_x = ((end.column.0 + 1) as f64 * metrics.average_advance) as u32;
let width = end_x - x;
let y = match flag {
cell::Flags::UNDERLINE => {
((start.line.0 as f32 + 1.) * metrics.line_height as f32 + metrics.descent - metrics.underline_position) as u32
},
cell::Flags::STRIKE_THROUGH => {
((start.line.0 as f32 + 0.5) * metrics.line_height as f32) as u32
},
_ => panic!("Invalid flag for cell line drawing specified"),
};
let height = metrics.underline_thickness as u32;
let rect = Rect::new(x + size.padding_x as u32, y + size.padding_y as u32, width, height);
let color = start.fg;
(rect, color)
}

View file

@ -668,7 +668,8 @@ impl QuadRenderer {
&mut self,
config: &Config,
props: &term::SizeInfo,
visual_bell_intensity: f64)
visual_bell_intensity: f64,
cell_line_rects: Vec<(Rect<u32>, Rgb)>)
{
// Swap to rectangle rendering program
unsafe {
@ -701,6 +702,11 @@ impl QuadRenderer {
let rect = Rect::new(0, 0, *size.width, *size.height);
self.render_rect(&rect, color, visual_bell_intensity as f32, size);
// Draw underlines and strikethroughs
for cell_line_rect in cell_line_rects {
self.render_rect(&cell_line_rect.0, cell_line_rect.1, 255., size);
}
// Deactivate rectangle program again
unsafe {
// Reset blending strategy
@ -865,7 +871,8 @@ impl QuadRenderer {
}
}
struct Rect<T> {
#[derive(Debug, Copy, Clone)]
pub struct Rect<T> {
x: T,
y: T,
width: T,
@ -873,7 +880,7 @@ struct Rect<T> {
}
impl<T> Rect<T> {
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 }
}
}
@ -943,7 +950,9 @@ impl<'a> RenderApi<'a> {
})
.collect::<Vec<_>>();
self.render_cells(cells.into_iter(), glyph_cache);
for cell in cells {
self.render_cell(cell, glyph_cache);
}
}
#[inline]
@ -961,48 +970,27 @@ impl<'a> RenderApi<'a> {
}
}
pub fn render_cells<I>(
&mut self,
cells: I,
glyph_cache: &mut GlyphCache
)
where I: Iterator<Item=RenderableCell>
pub fn render_cell(&mut self, cell: RenderableCell, glyph_cache: &mut GlyphCache)
{
for cell in cells {
// Get font key for cell
// FIXME this is super inefficient.
let mut font_key = glyph_cache.font_key;
if cell.flags.contains(cell::Flags::BOLD) {
font_key = glyph_cache.bold_key;
} else if cell.flags.contains(cell::Flags::ITALIC) {
font_key = glyph_cache.italic_key;
}
// Get font key for cell
// FIXME this is super inefficient.
let mut font_key = glyph_cache.font_key;
if cell.flags.contains(cell::Flags::BOLD) {
font_key = glyph_cache.bold_key;
} else if cell.flags.contains(cell::Flags::ITALIC) {
font_key = glyph_cache.italic_key;
}
let glyph_key = GlyphKey {
font_key: font_key,
size: glyph_cache.font_size,
c: cell.c
};
let glyph_key = GlyphKey {
font_key: font_key,
size: glyph_cache.font_size,
c: cell.c
};
// Add cell to batch
{
let glyph = glyph_cache.get(&glyph_key, self);
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: font_key,
size: glyph_cache.font_size,
c: '_'
};
let underscore = glyph_cache.get(&glyph_key, self);
self.add_render_item(&cell, underscore);
}
// Add cell to batch
{
let glyph = glyph_cache.get(&glyph_key, self);
self.add_render_item(&cell, glyph);
}
}
}

View file

@ -18,15 +18,16 @@ use index::Column;
bitflags! {
#[derive(Serialize, Deserialize)]
pub struct Flags: u32 {
const INVERSE = 0b0000_0001;
const BOLD = 0b0000_0010;
const ITALIC = 0b0000_0100;
const UNDERLINE = 0b0000_1000;
const WRAPLINE = 0b0001_0000;
const WIDE_CHAR = 0b0010_0000;
const WIDE_CHAR_SPACER = 0b0100_0000;
const DIM = 0b1000_0000;
const DIM_BOLD = 0b1000_0010;
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 STRIKE_THROUGH = 0b1_0000_0000;
}
}
@ -103,7 +104,7 @@ impl Cell {
pub fn is_empty(&self) -> bool {
self.c == ' ' &&
self.bg == Color::Named(NamedColor::Background) &&
!self.flags.intersects(Flags::INVERSE | Flags::UNDERLINE)
!self.flags.intersects(Flags::INVERSE | Flags::UNDERLINE | Flags::STRIKE_THROUGH)
}
#[inline]

View file

@ -317,6 +317,7 @@ impl<'a> RenderableCellsIter<'a> {
}
}
#[derive(Copy, Clone)]
pub struct RenderableCell {
pub line: Line,
pub column: Column,
@ -1808,6 +1809,8 @@ impl ansi::Handler for Term {
Attr::CancelItalic => self.cursor.template.flags.remove(cell::Flags::ITALIC),
Attr::Underscore => self.cursor.template.flags.insert(cell::Flags::UNDERLINE),
Attr::CancelUnderline => self.cursor.template.flags.remove(cell::Flags::UNDERLINE),
Attr::Strike => self.cursor.template.flags.insert(cell::Flags::STRIKE_THROUGH),
Attr::CancelStrike => self.cursor.template.flags.remove(cell::Flags::STRIKE_THROUGH),
_ => {
debug!("Term got unhandled attr: {:?}", attr);
}