1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-22 21:01:36 +03:00

Render custom block glyphs

As explained in the docs included in this commit, ideally this
wouldn't be needed, but due to a long-standing hinting bug in
freetype <https://gitlab.freedesktop.org/freetype/freetype/-/issues/761>
it seems most expedient to just render our own block glyphs,
so that's what this does!

refs: #433
This commit is contained in:
Wez Furlong 2021-02-28 09:43:26 -08:00
parent a3b25324b2
commit 0597684ebd
6 changed files with 328 additions and 1 deletions

View File

@ -876,6 +876,9 @@ pub struct Config {
#[serde(default = "default_true")] #[serde(default = "default_true")]
pub prefer_egl: bool, pub prefer_egl: bool,
#[serde(default = "default_true")]
pub custom_block_glyphs: bool,
/// Controls the amount of padding to use around the terminal cell area /// Controls the amount of padding to use around the terminal cell area
#[serde(default)] #[serde(default)]
pub window_padding: WindowPadding, pub window_padding: WindowPadding,

View File

@ -36,6 +36,7 @@ brief notes about them may accumulate here.
* New: added `--config name=value` options to `wezterm`, `wezterm-gui` and `wezterm-mux-server`. The `--front-end`, `--font-locator`, `--font-rasterizer` and `--font-shaper` CLI options have been removed in favor of this new mechanism. * New: added `--config name=value` options to `wezterm`, `wezterm-gui` and `wezterm-mux-server`. The `--front-end`, `--font-locator`, `--font-rasterizer` and `--font-shaper` CLI options have been removed in favor of this new mechanism.
* New: [window:set_config_overrides](config/lua/window/set_config_overrides.md) method that can be used to override GUI related configuration options on a per-window basis. Click through to see examples of dynamically toggling ligatures and window opacity. [#469](https://github.com/wez/wezterm/issues/469) [#329](https://github.com/wez/wezterm/issues/329) * New: [window:set_config_overrides](config/lua/window/set_config_overrides.md) method that can be used to override GUI related configuration options on a per-window basis. Click through to see examples of dynamically toggling ligatures and window opacity. [#469](https://github.com/wez/wezterm/issues/469) [#329](https://github.com/wez/wezterm/issues/329)
* Fixed: line-based mouse selection (default: triple click) now extends forwards to include wrapped lines. [#466](https://github.com/wez/wezterm/issues/466) * Fixed: line-based mouse selection (default: triple click) now extends forwards to include wrapped lines. [#466](https://github.com/wez/wezterm/issues/466)
* New: introduced [custom_block_glyphs](config/lua/config/custom_block_glyphs.md) option to ensure that block glyphs don't have gaps. [#433](https://github.com/wez/wezterm/issues/433)
### 20210203-095643-70a364eb ### 20210203-095643-70a364eb

View File

@ -0,0 +1,13 @@
## `custom_block_glyphs = true`
*Since: nightly builds only*
When set to `true` (the default), WezTerm will compute its own idea of what the
[U2580](https://www.unicode.org/charts/PDF/U2580.pdf) unicode block elements
range, instead of using glyphs resolved from a font.
Ideally this option wouldn't exist, but it is present to work around a [hinting issue in freetype](https://gitlab.freedesktop.org/freetype/freetype/-/issues/761).
You can set this to `false` to use the block characters provided by your font selection.

12
test-data/blocks.py Executable file
View File

@ -0,0 +1,12 @@
#!/usr/bin/env python3
# Print out the unicde block range
s = ""
for b in range(0x2580, 0x25A0):
s += chr(b) + " "
print(s)
print(s)
print(s)

View File

@ -10,6 +10,7 @@ use anyhow::{anyhow, Context};
use config::{configuration, AllowSquareGlyphOverflow, TextStyle}; use config::{configuration, AllowSquareGlyphOverflow, TextStyle};
use euclid::num::Zero; use euclid::num::Zero;
use std::collections::HashMap; use std::collections::HashMap;
use std::ops::Range;
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
use termwiz::image::ImageData; use termwiz::image::ImageData;
@ -123,12 +124,104 @@ struct LineKey {
overline: bool, overline: bool,
} }
bitflags::bitflags! {
pub struct Quadrant: u8{
const UPPER_LEFT = 1<<1;
const UPPER_RIGHT = 1<<2;
const LOWER_LEFT = 1<<3;
const LOWER_RIGHT = 1<<4;
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub enum BlockAlpha {
/// 100%
Full,
/// 75%
Dark,
/// 50%
Medium,
/// 25%
Light,
}
/// Represents a Block Element glyph, decoded from
/// <https://en.wikipedia.org/wiki/Block_Elements>
/// <https://www.unicode.org/charts/PDF/U2580.pdf>
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub enum BlockKey {
/// Number of 1/8ths in the upper half
Upper(u8),
/// Number of 1/8ths in the lower half
Lower(u8),
/// Number of 1/8ths in the left half
Left(u8),
/// Number of 1/8ths in the right half
Right(u8),
/// Full block with alpha level
Full(BlockAlpha),
/// A combination of quadrants
Quadrants(Quadrant),
}
impl BlockKey {
pub fn from_char(c: char) -> Option<Self> {
let c = c as u32;
Some(match c {
// Upper half block
0x2580 => Self::Upper(4),
// Lower 1..7 eighths
0x2581..=0x2587 => Self::Lower((c - 0x2580) as u8),
0x2588 => Self::Full(BlockAlpha::Full),
// Left 7..1 eighths
0x2589..=0x258f => Self::Left((0x2590 - c) as u8),
// Right half
0x2590 => Self::Right(4),
0x2591 => Self::Full(BlockAlpha::Light),
0x2592 => Self::Full(BlockAlpha::Medium),
0x2593 => Self::Full(BlockAlpha::Dark),
0x2594 => Self::Upper(1),
0x2595 => Self::Right(1),
0x2596 => Self::Quadrants(Quadrant::LOWER_LEFT),
0x2597 => Self::Quadrants(Quadrant::LOWER_RIGHT),
0x2598 => Self::Quadrants(Quadrant::UPPER_LEFT),
0x2599 => {
Self::Quadrants(Quadrant::UPPER_LEFT | Quadrant::LOWER_LEFT | Quadrant::LOWER_RIGHT)
}
0x259a => Self::Quadrants(Quadrant::UPPER_LEFT | Quadrant::LOWER_RIGHT),
0x259b => {
Self::Quadrants(Quadrant::UPPER_LEFT | Quadrant::UPPER_RIGHT | Quadrant::LOWER_LEFT)
}
0x259c => Self::Quadrants(
Quadrant::UPPER_LEFT | Quadrant::UPPER_RIGHT | Quadrant::LOWER_RIGHT,
),
0x259d => Self::Quadrants(Quadrant::UPPER_RIGHT),
0x259e => Self::Quadrants(Quadrant::UPPER_RIGHT | Quadrant::LOWER_LEFT),
0x259f => Self::Quadrants(
Quadrant::UPPER_RIGHT | Quadrant::LOWER_LEFT | Quadrant::LOWER_RIGHT,
),
_ => return None,
})
}
pub fn from_cell(cell: &termwiz::cell::Cell) -> Option<Self> {
let mut chars = cell.str().chars();
let first_char = chars.next()?;
if chars.next().is_some() {
None
} else {
Self::from_char(first_char)
}
}
}
pub struct GlyphCache<T: Texture2d> { pub struct GlyphCache<T: Texture2d> {
glyph_cache: HashMap<GlyphKey, Rc<CachedGlyph<T>>>, glyph_cache: HashMap<GlyphKey, Rc<CachedGlyph<T>>>,
pub atlas: Atlas<T>, pub atlas: Atlas<T>,
fonts: Rc<FontConfiguration>, fonts: Rc<FontConfiguration>,
image_cache: HashMap<usize, Sprite<T>>, image_cache: HashMap<usize, Sprite<T>>,
line_glyphs: HashMap<LineKey, Sprite<T>>, line_glyphs: HashMap<LineKey, Sprite<T>>,
block_glyphs: HashMap<BlockKey, Sprite<T>>,
metrics: RenderMetrics, metrics: RenderMetrics,
} }
@ -149,6 +242,7 @@ impl GlyphCache<ImageTexture> {
atlas, atlas,
metrics: metrics.clone(), metrics: metrics.clone(),
line_glyphs: HashMap::new(), line_glyphs: HashMap::new(),
block_glyphs: HashMap::new(),
}) })
} }
} }
@ -176,6 +270,7 @@ impl GlyphCache<SrgbTexture2d> {
atlas, atlas,
metrics: metrics.clone(), metrics: metrics.clone(),
line_glyphs: HashMap::new(), line_glyphs: HashMap::new(),
block_glyphs: HashMap::new(),
}) })
} }
@ -184,6 +279,7 @@ impl GlyphCache<SrgbTexture2d> {
self.image_cache.clear(); self.image_cache.clear();
self.glyph_cache.clear(); self.glyph_cache.clear();
self.line_glyphs.clear(); self.line_glyphs.clear();
self.block_glyphs.clear();
} }
} }
@ -349,6 +445,144 @@ impl<T: Texture2d> GlyphCache<T> {
Ok(sprite) Ok(sprite)
} }
fn block_sprite(&mut self, block: BlockKey) -> anyhow::Result<Sprite<T>> {
let mut buffer = Image::new(
self.metrics.cell_size.width as usize,
self.metrics.cell_size.height as usize,
);
let black = ::window::color::Color::rgba(0, 0, 0, 0);
let white = ::window::color::Color::rgb(0xff, 0xff, 0xff);
let cell_rect = Rect::new(Point::new(0, 0), self.metrics.cell_size);
let y_eighth = (self.metrics.cell_size.height / 8) as usize;
let x_eighth = (self.metrics.cell_size.width / 8) as usize;
buffer.clear_rect(cell_rect, black);
let draw_horizontal = |buffer: &mut Image, y: usize| {
buffer.draw_line(
Point::new(cell_rect.origin.x, cell_rect.origin.y + y as isize),
Point::new(
cell_rect.origin.x + self.metrics.cell_size.width,
cell_rect.origin.y + y as isize,
),
white,
Operator::Source,
);
};
let draw_vertical = |buffer: &mut Image, x: usize| {
buffer.draw_line(
Point::new(cell_rect.origin.x + x as isize, cell_rect.origin.y),
Point::new(
cell_rect.origin.x + x as isize,
cell_rect.origin.y + self.metrics.cell_size.height,
),
white,
Operator::Source,
);
};
let draw_quad = |buffer: &mut Image, x: Range<usize>, y: Range<usize>| {
for y in y {
buffer.draw_line(
Point::new(
cell_rect.origin.x + x.start as isize,
cell_rect.origin.y + y as isize,
),
Point::new(
cell_rect.origin.x + x.end as isize,
cell_rect.origin.y + y as isize,
),
white,
Operator::Source,
);
}
};
match block {
BlockKey::Upper(num) => {
for n in 0..usize::from(num) {
for a in 0..y_eighth {
draw_horizontal(&mut buffer, (n * y_eighth) + a);
}
}
}
BlockKey::Lower(num) => {
for n in 0..usize::from(num) {
let y = (self.metrics.cell_size.height - 1) as usize - (n * y_eighth);
for a in 0..y_eighth {
draw_horizontal(&mut buffer, y + a);
}
}
}
BlockKey::Left(num) => {
for n in 0..usize::from(num) {
for a in 0..x_eighth {
draw_vertical(&mut buffer, (n * x_eighth) + a);
}
}
}
BlockKey::Right(num) => {
for n in 0..usize::from(num) {
let x = (self.metrics.cell_size.width - 1) as usize - (n * x_eighth);
for a in 0..x_eighth {
draw_vertical(&mut buffer, x + a);
}
}
}
BlockKey::Full(alpha) => {
let alpha = match alpha {
BlockAlpha::Full => 0xff,
BlockAlpha::Dark => (0.75 * 255.) as u8,
BlockAlpha::Medium => (0.5 * 255.) as u8,
BlockAlpha::Light => (0.25 * 255.) as u8,
};
let fill = ::window::color::Color::with_linear_rgba_u8(alpha, alpha, alpha, alpha);
buffer.clear_rect(cell_rect, fill);
}
BlockKey::Quadrants(quads) => {
if quads.contains(Quadrant::UPPER_LEFT) {
draw_quad(&mut buffer, 0..(x_eighth * 4), 0..(y_eighth * 4));
}
if quads.contains(Quadrant::UPPER_RIGHT) {
draw_quad(
&mut buffer,
(x_eighth * 4)..(x_eighth * 8),
0..(y_eighth * 4),
);
}
if quads.contains(Quadrant::LOWER_LEFT) {
draw_quad(
&mut buffer,
0..(x_eighth * 4),
(y_eighth * 4)..(y_eighth * 8),
);
}
if quads.contains(Quadrant::LOWER_RIGHT) {
draw_quad(
&mut buffer,
(x_eighth * 4)..(x_eighth * 8),
(y_eighth * 4)..(y_eighth * 8),
);
}
}
}
let sprite = self.atlas.allocate(&buffer)?;
self.block_glyphs.insert(block, sprite.clone());
Ok(sprite)
}
pub fn cached_block(&mut self, block: BlockKey) -> anyhow::Result<Sprite<T>> {
if let Some(s) = self.block_glyphs.get(&block) {
return Ok(s.clone());
}
self.block_sprite(block)
}
fn line_sprite(&mut self, key: LineKey) -> anyhow::Result<Sprite<T>> { fn line_sprite(&mut self, key: LineKey) -> anyhow::Result<Sprite<T>> {
let mut buffer = Image::new( let mut buffer = Image::new(
self.metrics.cell_size.width as usize, self.metrics.cell_size.width as usize,

View File

@ -1,5 +1,5 @@
use crate::glium::texture::SrgbTexture2d; use crate::glium::texture::SrgbTexture2d;
use crate::glyphcache::{CachedGlyph, GlyphCache}; use crate::glyphcache::{BlockKey, CachedGlyph, GlyphCache};
use crate::shapecache::*; use crate::shapecache::*;
use crate::termwindow::{BorrowedShapeCacheKey, MappedQuads, RenderState, ScrollHit, ShapedInfo}; use crate::termwindow::{BorrowedShapeCacheKey, MappedQuads, RenderState, ScrollHit, ShapedInfo};
use ::window::bitmaps::atlas::OutOfTextureSpace; use ::window::bitmaps::atlas::OutOfTextureSpace;
@ -779,6 +779,25 @@ impl super::TermWindow {
continue; continue;
} }
if self.config.custom_block_glyphs {
if let Some(block) = BlockKey::from_cell(&params.line.cells()[cell_idx]) {
self.populate_block_quad(
block,
gl_state,
quads,
cell_idx,
&params,
hsv,
cursor_shape,
glyph_color,
underline_color,
bg_color,
white_space,
)?;
continue;
}
}
let texture = glyph let texture = glyph
.texture .texture
.as_ref() .as_ref()
@ -889,6 +908,51 @@ impl super::TermWindow {
Ok(()) Ok(())
} }
pub fn populate_block_quad(
&self,
block: BlockKey,
gl_state: &RenderState,
quads: &mut MappedQuads,
cell_idx: usize,
params: &RenderScreenLineOpenGLParams,
hsv: Option<config::HsbTransform>,
cursor_shape: Option<CursorShape>,
glyph_color: Color,
underline_color: Color,
bg_color: Color,
white_space: TextureRect,
) -> anyhow::Result<()> {
let sprite = gl_state
.glyph_cache
.borrow_mut()
.cached_block(block)?
.texture_coords();
let mut quad =
match quads.cell(cell_idx + params.pos.left, params.line_idx + params.pos.top) {
Ok(quad) => quad,
Err(_) => return Ok(()),
};
quad.set_hsv(hsv);
quad.set_fg_color(glyph_color);
quad.set_underline_color(underline_color);
quad.set_bg_color(bg_color);
quad.set_texture(sprite);
quad.set_texture_adjust(0., 0., 0., 0.);
quad.set_underline(white_space);
quad.set_has_color(false);
quad.set_cursor(
gl_state
.util_sprites
.cursor_sprite(cursor_shape)
.texture_coords(),
);
quad.set_cursor_color(params.cursor_border_color);
Ok(())
}
/// Render iTerm2 style image attributes /// Render iTerm2 style image attributes
pub fn populate_image_quad( pub fn populate_image_quad(
&self, &self,