Hide "missing" glyp for zerowidth character

This patch prevents missing zerowidth glyphs from obscuring the rendered
glyph of a cell.

The missing glyph itself is also consistently loaded and displayed on
all platforms. It is initialized once together with the ascii symbols
and then written to the atlas only once for every cached missing glyph.

Co-authored-by: Christian Duerr <contact@christianduerr.com>
This commit is contained in:
Kirill Chibisov 2020-12-24 01:28:41 +03:00 committed by GitHub
parent f19cbca9b4
commit fdc10d270e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 123 additions and 80 deletions

View File

@ -22,6 +22,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Nonexistent config imports are ignored instead of raising an error - Nonexistent config imports are ignored instead of raising an error
- Value for disabling logging with `config.log_level` is `Off` instead of `None` - Value for disabling logging with `config.log_level` is `Off` instead of `None`
- Missing glyph symbols are no longer drawn for zerowidth characters
### Fixed ### Fixed
@ -43,6 +44,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Alacritty not discarding invalid escape sequences starting with ESC - Alacritty not discarding invalid escape sequences starting with ESC
- Crash due to clipboard not being properly released on Wayland - Crash due to clipboard not being properly released on Wayland
- Shadow artifacts when resizing transparent windows on macOS - Shadow artifacts when resizing transparent windows on macOS
- Missing glyph symbols not being rendered for missing glyphs on macOS and Windows
### Removed ### Removed

23
Cargo.lock generated
View File

@ -519,9 +519,9 @@ dependencies = [
[[package]] [[package]]
name = "crossfont" name = "crossfont"
version = "0.1.1" version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f4b217779694a81b60a4174f121697c9bd7550cb9a6d7e970051bea1b13dbc0" checksum = "108946cf5228893bf470564410639b87a53f959d937f1c57bbc2a6603d98e812"
dependencies = [ dependencies = [
"cocoa 0.24.0", "cocoa 0.24.0",
"core-foundation 0.9.1", "core-foundation 0.9.1",
@ -529,7 +529,6 @@ dependencies = [
"core-graphics 0.22.1", "core-graphics 0.22.1",
"core-text", "core-text",
"dwrote", "dwrote",
"euclid",
"foreign-types 0.5.0", "foreign-types 0.5.0",
"freetype-rs", "freetype-rs",
"libc", "libc",
@ -670,15 +669,6 @@ dependencies = [
"termcolor", "termcolor",
] ]
[[package]]
name = "euclid"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5337024b8293bdce5265dc9570ef6e608a34bfacbbc87fe1a5dcb5f1dac2f4e2"
dependencies = [
"num-traits",
]
[[package]] [[package]]
name = "expat-sys" name = "expat-sys"
version = "2.1.6" version = "2.1.6"
@ -1327,15 +1317,6 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "num-traits"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "num_enum" name = "num_enum"
version = "0.4.3" version = "0.4.3"

View File

@ -28,7 +28,7 @@ serde_json = "1"
glutin = { version = "0.26.0", default-features = false, features = ["serde"] } glutin = { version = "0.26.0", default-features = false, features = ["serde"] }
notify = "4" notify = "4"
parking_lot = "0.11.0" parking_lot = "0.11.0"
crossfont = { version = "0.1.0", features = ["force_system_fontconfig"] } crossfont = { version = "0.2.0", features = ["force_system_fontconfig"] }
urlocator = "0.1.3" urlocator = "0.1.3"
copypasta = { version = "0.7.0", default-features = false } copypasta = { version = "0.7.0", default-features = false }
libc = "0.2" libc = "0.2"

View File

@ -37,39 +37,39 @@ pub fn get_cursor_glyph(
/// Return a custom underline cursor character. /// Return a custom underline cursor character.
pub fn get_underline_cursor_glyph(width: usize, line_width: usize) -> RasterizedGlyph { pub fn get_underline_cursor_glyph(width: usize, line_width: usize) -> RasterizedGlyph {
// Create a new rectangle, the height is relative to the font width. // Create a new rectangle, the height is relative to the font width.
let buf = vec![255u8; width * line_width * 3]; let buffer = BitmapBuffer::RGB(vec![255u8; width * line_width * 3]);
// Create a custom glyph with the rectangle data attached to it. // Create a custom glyph with the rectangle data attached to it.
RasterizedGlyph { RasterizedGlyph {
c: ' ', character: ' ',
top: line_width as i32, top: line_width as i32,
left: 0, left: 0,
height: line_width as i32, height: line_width as i32,
width: width as i32, width: width as i32,
buf: BitmapBuffer::RGB(buf), buffer,
} }
} }
/// Return a custom beam cursor character. /// Return a custom beam cursor character.
pub fn get_beam_cursor_glyph(height: usize, line_width: usize) -> RasterizedGlyph { pub fn get_beam_cursor_glyph(height: usize, line_width: usize) -> RasterizedGlyph {
// Create a new rectangle that is at least one pixel wide // Create a new rectangle that is at least one pixel wide
let buf = vec![255u8; line_width * height * 3]; let buffer = BitmapBuffer::RGB(vec![255u8; line_width * height * 3]);
// Create a custom glyph with the rectangle data attached to it // Create a custom glyph with the rectangle data attached to it
RasterizedGlyph { RasterizedGlyph {
c: ' ', character: ' ',
top: height as i32, top: height as i32,
left: 0, left: 0,
height: height as i32, height: height as i32,
width: line_width as i32, width: line_width as i32,
buf: BitmapBuffer::RGB(buf), buffer,
} }
} }
/// Returns a custom box cursor character. /// Returns a custom box cursor character.
pub fn get_box_cursor_glyph(height: usize, width: usize, line_width: usize) -> RasterizedGlyph { pub fn get_box_cursor_glyph(height: usize, width: usize, line_width: usize) -> RasterizedGlyph {
// Create a new box outline rectangle. // Create a new box outline rectangle.
let mut buf = Vec::with_capacity(width * height * 3); let mut buffer = Vec::with_capacity(width * height * 3);
for y in 0..height { for y in 0..height {
for x in 0..width { for x in 0..width {
if y < line_width if y < line_width
@ -77,36 +77,36 @@ pub fn get_box_cursor_glyph(height: usize, width: usize, line_width: usize) -> R
|| x < line_width || x < line_width
|| x >= width - line_width || x >= width - line_width
{ {
buf.append(&mut vec![255u8; 3]); buffer.append(&mut vec![255u8; 3]);
} else { } else {
buf.append(&mut vec![0u8; 3]); buffer.append(&mut vec![0u8; 3]);
} }
} }
} }
// Create a custom glyph with the rectangle data attached to it. // Create a custom glyph with the rectangle data attached to it.
RasterizedGlyph { RasterizedGlyph {
c: ' ', character: ' ',
top: height as i32, top: height as i32,
left: 0, left: 0,
height: height as i32, height: height as i32,
width: width as i32, width: width as i32,
buf: BitmapBuffer::RGB(buf), buffer: BitmapBuffer::RGB(buffer),
} }
} }
/// Return a custom block cursor character. /// Return a custom block cursor character.
pub fn get_block_cursor_glyph(height: usize, width: usize) -> RasterizedGlyph { pub fn get_block_cursor_glyph(height: usize, width: usize) -> RasterizedGlyph {
// Create a completely filled glyph. // Create a completely filled glyph.
let buf = vec![255u8; width * height * 3]; let buffer = BitmapBuffer::RGB(vec![255u8; width * height * 3]);
// Create a custom glyph with the rectangle data attached to it. // Create a custom glyph with the rectangle data attached to it.
RasterizedGlyph { RasterizedGlyph {
c: ' ', character: ' ',
top: height as i32, top: height as i32,
left: 0, left: 0,
height: height as i32, height: height as i32,
width: width as i32, width: width as i32,
buf: BitmapBuffer::RGB(buf), buffer,
} }
} }

View File

@ -1,3 +1,4 @@
use std::collections::hash_map::Entry;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
use std::hash::BuildHasherDefault; use std::hash::BuildHasherDefault;
@ -7,11 +8,12 @@ use std::ptr;
use bitflags::bitflags; use bitflags::bitflags;
use crossfont::{ use crossfont::{
BitmapBuffer, FontDesc, FontKey, GlyphKey, Rasterize, RasterizedGlyph, Rasterizer, Size, Slant, BitmapBuffer, Error as RasterizerError, FontDesc, FontKey, GlyphKey, Rasterize,
Style, Weight, RasterizedGlyph, Rasterizer, Size, Slant, Style, Weight,
}; };
use fnv::FnvHasher; use fnv::FnvHasher;
use log::{error, info}; use log::{debug, error, info};
use unicode_width::UnicodeWidthChar;
use alacritty_terminal::config::Cursor; use alacritty_terminal::config::Cursor;
use alacritty_terminal::index::{Column, Line}; use alacritty_terminal::index::{Column, Line};
@ -92,7 +94,7 @@ pub struct TextShaderProgram {
u_background: GLint, u_background: GLint,
} }
#[derive(Copy, Debug, Clone)] #[derive(Copy, Clone, Debug)]
pub struct Glyph { pub struct Glyph {
tex_id: GLuint, tex_id: GLuint,
multicolor: bool, multicolor: bool,
@ -156,7 +158,7 @@ impl GlyphCache {
// Need to load at least one glyph for the face before calling metrics. // 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 ('m' at the time of writing) has no special
// meaning. // meaning.
rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size() })?; rasterizer.get_glyph(GlyphKey { font_key: regular, character: 'm', size: font.size() })?;
let metrics = rasterizer.metrics(regular, font.size())?; let metrics = rasterizer.metrics(regular, font.size())?;
@ -180,8 +182,13 @@ impl GlyphCache {
fn load_glyphs_for_font<L: LoadGlyph>(&mut self, font: FontKey, loader: &mut L) { fn load_glyphs_for_font<L: LoadGlyph>(&mut self, font: FontKey, loader: &mut L) {
let size = self.font_size; let size = self.font_size;
// Cache the "missing glyph" character.
self.cache_glyph(GlyphKey { font_key: font, character: '\0', size }, loader);
// Cache all ascii characters.
for i in 32u8..=126u8 { for i in 32u8..=126u8 {
self.get(GlyphKey { font_key: font, c: i as char, size }, loader); self.cache_glyph(GlyphKey { font_key: font, character: i as char, size }, loader);
} }
} }
@ -250,23 +257,71 @@ impl GlyphCache {
FontDesc::new(desc.family.clone(), style) FontDesc::new(desc.family.clone(), style)
} }
pub fn get<L>(&mut self, glyph_key: GlyphKey, loader: &mut L) -> &Glyph /// Get a glyph from the font.
///
/// If the glyph has never been loaded before, it will be rasterized and inserted into the
/// cache.
///
/// # Errors
///
/// This will fail when the glyph could not be rasterized. Usually this is due to the glyph
/// not being present in any font.
fn get<L>(&mut self, glyph_key: GlyphKey, loader: &mut L) -> Result<&Glyph, RasterizerError>
where where
L: LoadGlyph, L: LoadGlyph,
{ {
let glyph_offset = self.glyph_offset; match self.cache.entry(glyph_key) {
let rasterizer = &mut self.rasterizer; // Glyph was loaded from cache.
let metrics = &self.metrics; Entry::Occupied(entry) => Ok(entry.into_mut()),
self.cache.entry(glyph_key).or_insert_with(|| { // Try to rasterize the glyph if it's uncached.
let mut rasterized = Entry::Vacant(entry) => match self.rasterizer.get_glyph(glyph_key) {
rasterizer.get_glyph(glyph_key).unwrap_or_else(|_| Default::default()); // Add rasterized glyph to the cache.
Ok(mut rasterized) => {
rasterized.left += i32::from(self.glyph_offset.x);
rasterized.top += i32::from(self.glyph_offset.y);
rasterized.top -= self.metrics.descent as i32;
rasterized.left += i32::from(glyph_offset.x); // The metrics of zero-width characters are based on rendering
rasterized.top += i32::from(glyph_offset.y); // the character after the current cell, with the anchor at the
rasterized.top -= metrics.descent as i32; // 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.
if glyph_key.character.width() == Some(0) {
rasterized.left += self.metrics.average_advance as i32;
}
loader.load_glyph(&rasterized) Ok(entry.insert(loader.load_glyph(&rasterized)))
}) },
// Propagate rasterization failure.
Err(err) => Err(err),
},
}
}
/// Load a glyph into the cache.
///
/// This will always insert a new glyph in the cache, even if the rasterization returned a
/// "missing" glyph or failed completely.
fn cache_glyph<L>(&mut self, glyph_key: GlyphKey, loader: &mut L)
where
L: LoadGlyph,
{
let rasterized = match self.rasterizer.get_glyph(glyph_key) {
Ok(mut rasterized) | Err(RasterizerError::MissingGlyph(mut rasterized)) => {
rasterized.left += i32::from(self.glyph_offset.x);
rasterized.top += i32::from(self.glyph_offset.y);
rasterized.top -= self.metrics.descent as i32;
rasterized
},
// Use empty glyph as fallback.
Err(err) => {
debug!("{}", err);
Default::default()
},
};
// Cache rasterized glyph.
self.cache.insert(glyph_key, loader.load_glyph(&rasterized));
} }
/// Clear currently cached data in both GL and the registry. /// Clear currently cached data in both GL and the registry.
@ -291,7 +346,11 @@ impl GlyphCache {
let (regular, bold, italic, bold_italic) = let (regular, bold, italic, bold_italic) =
Self::compute_font_keys(font, &mut self.rasterizer)?; Self::compute_font_keys(font, &mut self.rasterizer)?;
self.rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size() })?; self.rasterizer.get_glyph(GlyphKey {
font_key: regular,
character: 'm',
size: font.size(),
})?;
let metrics = self.rasterizer.metrics(regular, font.size())?; let metrics = self.rasterizer.metrics(regular, font.size())?;
info!("Font size changed to {:?} with DPR of {}", font.size(), dpr); info!("Font size changed to {:?} with DPR of {}", font.size(), dpr);
@ -325,7 +384,7 @@ impl GlyphCache {
let mut rasterizer = crossfont::Rasterizer::new(dpr as f32, font.use_thin_strokes)?; let mut rasterizer = crossfont::Rasterizer::new(dpr as f32, font.use_thin_strokes)?;
let regular_desc = GlyphCache::make_desc(&font.normal(), Slant::Normal, Weight::Normal); let regular_desc = GlyphCache::make_desc(&font.normal(), Slant::Normal, Weight::Normal);
let regular = Self::load_regular_font(&mut rasterizer, &regular_desc, font.size())?; let regular = Self::load_regular_font(&mut rasterizer, &regular_desc, font.size())?;
rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size() })?; rasterizer.get_glyph(GlyphKey { font_key: regular, character: 'm', size: font.size() })?;
rasterizer.metrics(regular, font.size()) rasterizer.metrics(regular, font.size())
} }
@ -822,7 +881,7 @@ impl<'a> RenderApi<'a> {
} }
pub fn render_cell(&mut self, mut cell: RenderableCell, glyph_cache: &mut GlyphCache) { pub fn render_cell(&mut self, mut cell: RenderableCell, glyph_cache: &mut GlyphCache) {
let (mut c, zerowidth) = match cell.inner { let (mut character, zerowidth) = match cell.inner {
RenderableCellContent::Cursor(cursor_key) => { RenderableCellContent::Cursor(cursor_key) => {
// Raw cell pixel buffers like cursors don't need to go through font lookup. // Raw cell pixel buffers like cursors don't need to go through font lookup.
let metrics = glyph_cache.metrics; let metrics = glyph_cache.metrics;
@ -852,30 +911,31 @@ impl<'a> RenderApi<'a> {
// Ignore hidden cells and render tabs as spaces to prevent font issues. // Ignore hidden cells and render tabs as spaces to prevent font issues.
let hidden = cell.flags.contains(Flags::HIDDEN); let hidden = cell.flags.contains(Flags::HIDDEN);
if c == '\t' || hidden { if character == '\t' || hidden {
c = ' '; character = ' ';
} }
let mut glyph_key = GlyphKey { font_key, size: glyph_cache.font_size, c }; let mut glyph_key = GlyphKey { font_key, size: glyph_cache.font_size, character };
// Add cell to batch. // Add cell to batch.
let glyph = glyph_cache.get(glyph_key, self); match glyph_cache.get(glyph_key, self) {
self.add_render_item(&cell, glyph); Ok(glyph) => self.add_render_item(&cell, glyph),
// Insert the "missing" glyph for this key into the cache on error.
Err(_) => {
let missing_key = GlyphKey { character: '\0', ..glyph_key };
let glyph = *glyph_cache.get(missing_key, self).expect("no 'missing' glyph");
glyph_cache.cache.insert(glyph_key, glyph);
self.add_render_item(&cell, &glyph)
},
}
// Render visible zero-width characters. // Render visible zero-width characters.
if let Some(zerowidth) = zerowidth.filter(|_| !hidden) { if let Some(zerowidth) = zerowidth.filter(|_| !hidden) {
for c in zerowidth { for character in zerowidth.iter() {
glyph_key.c = c; glyph_key.character = *character;
let mut glyph = *glyph_cache.get(glyph_key, self); if let Ok(glyph) = glyph_cache.get(glyph_key, self) {
self.add_render_item(&cell, &glyph);
// 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 i16;
self.add_render_item(&cell, &glyph);
} }
} }
} }
@ -1314,14 +1374,14 @@ impl Atlas {
gl::BindTexture(gl::TEXTURE_2D, self.id); gl::BindTexture(gl::TEXTURE_2D, self.id);
// Load data into OpenGL. // Load data into OpenGL.
let (format, buf) = match &glyph.buf { let (format, buffer) = match &glyph.buffer {
BitmapBuffer::RGB(buf) => { BitmapBuffer::RGB(buffer) => {
multicolor = false; multicolor = false;
(gl::RGB, buf) (gl::RGB, buffer)
}, },
BitmapBuffer::RGBA(buf) => { BitmapBuffer::RGBA(buffer) => {
multicolor = true; multicolor = true;
(gl::RGBA, buf) (gl::RGBA, buffer)
}, },
}; };
@ -1334,7 +1394,7 @@ impl Atlas {
height, height,
format, format,
gl::UNSIGNED_BYTE, gl::UNSIGNED_BYTE,
buf.as_ptr() as *const _, buffer.as_ptr() as *const _,
); );
gl::BindTexture(gl::TEXTURE_2D, 0); gl::BindTexture(gl::TEXTURE_2D, 0);