mirror of
https://github.com/wez/wezterm.git
synced 2024-12-22 12:51:31 +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:
parent
a3b25324b2
commit
0597684ebd
@ -876,6 +876,9 @@ pub struct Config {
|
||||
#[serde(default = "default_true")]
|
||||
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
|
||||
#[serde(default)]
|
||||
pub window_padding: WindowPadding,
|
||||
|
@ -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: [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)
|
||||
* 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
|
||||
|
||||
|
13
docs/config/lua/config/custom_block_glyphs.md
Normal file
13
docs/config/lua/config/custom_block_glyphs.md
Normal 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
12
test-data/blocks.py
Executable 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)
|
||||
|
@ -10,6 +10,7 @@ use anyhow::{anyhow, Context};
|
||||
use config::{configuration, AllowSquareGlyphOverflow, TextStyle};
|
||||
use euclid::num::Zero;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Range;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use termwiz::image::ImageData;
|
||||
@ -123,12 +124,104 @@ struct LineKey {
|
||||
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> {
|
||||
glyph_cache: HashMap<GlyphKey, Rc<CachedGlyph<T>>>,
|
||||
pub atlas: Atlas<T>,
|
||||
fonts: Rc<FontConfiguration>,
|
||||
image_cache: HashMap<usize, Sprite<T>>,
|
||||
line_glyphs: HashMap<LineKey, Sprite<T>>,
|
||||
block_glyphs: HashMap<BlockKey, Sprite<T>>,
|
||||
metrics: RenderMetrics,
|
||||
}
|
||||
|
||||
@ -149,6 +242,7 @@ impl GlyphCache<ImageTexture> {
|
||||
atlas,
|
||||
metrics: metrics.clone(),
|
||||
line_glyphs: HashMap::new(),
|
||||
block_glyphs: HashMap::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -176,6 +270,7 @@ impl GlyphCache<SrgbTexture2d> {
|
||||
atlas,
|
||||
metrics: metrics.clone(),
|
||||
line_glyphs: HashMap::new(),
|
||||
block_glyphs: HashMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
@ -184,6 +279,7 @@ impl GlyphCache<SrgbTexture2d> {
|
||||
self.image_cache.clear();
|
||||
self.glyph_cache.clear();
|
||||
self.line_glyphs.clear();
|
||||
self.block_glyphs.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@ -349,6 +445,144 @@ impl<T: Texture2d> GlyphCache<T> {
|
||||
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>> {
|
||||
let mut buffer = Image::new(
|
||||
self.metrics.cell_size.width as usize,
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::glium::texture::SrgbTexture2d;
|
||||
use crate::glyphcache::{CachedGlyph, GlyphCache};
|
||||
use crate::glyphcache::{BlockKey, CachedGlyph, GlyphCache};
|
||||
use crate::shapecache::*;
|
||||
use crate::termwindow::{BorrowedShapeCacheKey, MappedQuads, RenderState, ScrollHit, ShapedInfo};
|
||||
use ::window::bitmaps::atlas::OutOfTextureSpace;
|
||||
@ -779,6 +779,25 @@ impl super::TermWindow {
|
||||
continue;
|
||||
}
|
||||
|
||||
if self.config.custom_block_glyphs {
|
||||
if let Some(block) = BlockKey::from_cell(¶ms.line.cells()[cell_idx]) {
|
||||
self.populate_block_quad(
|
||||
block,
|
||||
gl_state,
|
||||
quads,
|
||||
cell_idx,
|
||||
¶ms,
|
||||
hsv,
|
||||
cursor_shape,
|
||||
glyph_color,
|
||||
underline_color,
|
||||
bg_color,
|
||||
white_space,
|
||||
)?;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let texture = glyph
|
||||
.texture
|
||||
.as_ref()
|
||||
@ -889,6 +908,51 @@ impl super::TermWindow {
|
||||
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
|
||||
pub fn populate_image_quad(
|
||||
&self,
|
||||
|
Loading…
Reference in New Issue
Block a user