Fix wide characters being cut off

Fixes #791.
This commit is contained in:
Kirill Chibisov 2020-11-17 17:49:05 +03:00 committed by GitHub
parent 8b10e5e778
commit 9724418d35
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 112 additions and 57 deletions

View File

@ -7,6 +7,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## 0.7.0-dev
### Fixed
- Wide characters sometimes being cut off
## 0.6.0
### Packaging

1
Cargo.lock generated
View File

@ -26,6 +26,7 @@ name = "alacritty"
version = "0.7.0-dev"
dependencies = [
"alacritty_terminal",
"bitflags",
"clap",
"copypasta",
"crossfont",

View File

@ -29,6 +29,7 @@ urlocator = "0.1.3"
copypasta = { version = "0.7.0", default-features = false }
libc = "0.2"
unicode-width = "0.1"
bitflags = "1"
[build-dependencies]
gl_generator = "0.14.0"

View File

@ -9,31 +9,31 @@ layout(location = 0, index = 1) out vec4 alphaMask;
uniform sampler2D mask;
void main()
{
#define COLORED 2
void main() {
if (backgroundPass != 0) {
if (bg.a == 0.0)
if (bg.a == 0.0) {
discard;
}
alphaMask = vec4(1.0);
color = vec4(bg.rgb, 1.0);
} else {
if (fg.a != 0.0) {
// Color glyphs, like emojis.
vec4 glyphColor = texture(mask, TexCoords);
alphaMask = vec4(glyphColor.a);
} else if ((int(fg.a) & COLORED) != 0) {
// Color glyphs, like emojis.
vec4 glyphColor = texture(mask, TexCoords);
alphaMask = vec4(glyphColor.a);
// Revert alpha premultiplication.
if (glyphColor.a != 0) {
glyphColor.rgb = vec3(glyphColor.rgb / glyphColor.a);
}
color = vec4(glyphColor.rgb, 1.0);
} else {
// Regular text glyphs.
vec3 textColor = texture(mask, TexCoords).rgb;
alphaMask = vec4(textColor, textColor.r);
color = vec4(fg.rgb, 1.0);
// Revert alpha premultiplication.
if (glyphColor.a != 0) {
glyphColor.rgb = vec3(glyphColor.rgb / glyphColor.a);
}
color = vec4(glyphColor.rgb, 1.0);
} else {
// Regular text glyphs.
vec3 textColor = texture(mask, TexCoords).rgb;
alphaMask = vec4(textColor, textColor.r);
color = vec4(fg.rgb, 1.0);
}
}

View File

@ -1,18 +1,20 @@
#version 330 core
// Cell properties.
layout (location = 0) in vec2 gridCoords;
layout(location = 0) in vec2 gridCoords;
// Glyph properties.
layout (location = 1) in vec4 glyph;
layout(location = 1) in vec4 glyph;
// uv mapping.
layout (location = 2) in vec4 uv;
layout(location = 2) in vec4 uv;
// Text foreground rgb packed together with multicolor flag.
layout (location = 3) in vec4 textColor;
// Text foreground rgb packed together with cell flags. textColor.a
// are the bitflags; consult RenderingGlyphFlags in renderer/mod.rs
// for the possible values.
layout(location = 3) in vec4 textColor;
// Background color.
layout (location = 4) in vec4 backgroundColor;
layout(location = 4) in vec4 backgroundColor;
out vec2 TexCoords;
flat out vec4 fg;
@ -24,9 +26,9 @@ uniform vec4 projection;
uniform int backgroundPass;
#define WIDE_CHAR 1
void main()
{
void main() {
vec2 projectionOffset = projection.xy;
vec2 projectionScale = projection.zw;
@ -39,8 +41,14 @@ void main()
vec2 cellPosition = cellDim * gridCoords;
if (backgroundPass != 0) {
vec2 finalPosition = cellPosition + cellDim * position;
gl_Position = vec4(projectionOffset + projectionScale * finalPosition, 0.0, 1.0);
vec2 backgroundDim = cellDim;
if ((int(textColor.a) & WIDE_CHAR) != 0) {
// Update wide char x dimension so it'll cover the following spacer.
backgroundDim.x *= 2;
}
vec2 finalPosition = cellPosition + backgroundDim * position;
gl_Position =
vec4(projectionOffset + projectionScale * finalPosition, 0.0, 1.0);
TexCoords = vec2(0, 0);
} else {
@ -49,7 +57,8 @@ void main()
glyphOffset.y = cellDim.y - glyphOffset.y;
vec2 finalPosition = cellPosition + glyphSize * position + glyphOffset;
gl_Position = vec4(projectionOffset + projectionScale * finalPosition, 0.0, 1.0);
gl_Position =
vec4(projectionOffset + projectionScale * finalPosition, 0.0, 1.0);
vec2 uvOffset = uv.xy;
vec2 uvSize = uv.zw;

View File

@ -9,6 +9,7 @@ use std::ptr;
use std::sync::mpsc;
use std::time::Duration;
use bitflags::bitflags;
use crossfont::{
BitmapBuffer, FontDesc, FontKey, GlyphKey, Rasterize, RasterizedGlyph, Rasterizer, Size, Slant,
Style, Weight,
@ -123,7 +124,7 @@ pub struct RectShaderProgram {
#[derive(Copy, Debug, Clone)]
pub struct Glyph {
tex_id: GLuint,
multicolor: u8,
multicolor: bool,
top: i16,
left: i16,
width: i16,
@ -359,30 +360,46 @@ impl GlyphCache {
}
}
// NOTE: These flags must be in sync with their usage in the text.*.glsl shaders.
bitflags! {
#[repr(C)]
struct RenderingGlyphFlags: u8 {
const WIDE_CHAR = 0b0000_0001;
const COLORED = 0b0000_0010;
}
}
#[derive(Debug)]
#[repr(C)]
struct InstanceData {
// Coords.
col: u16,
row: u16,
// Glyph offset.
left: i16,
top: i16,
// Glyph size.
width: i16,
height: i16,
// UV offset.
uv_left: f32,
uv_bot: f32,
// uv scale.
uv_width: f32,
uv_height: f32,
// Color.
r: u8,
g: u8,
b: u8,
// Flag indicating that a glyph uses multiple colors; like an Emoji.
multicolor: u8,
// Cell flags like multicolor or fullwidth character.
cell_flags: RenderingGlyphFlags,
// Background color.
bg_r: u8,
bg_g: u8,
@ -441,6 +458,10 @@ impl Batch {
self.tex = glyph.tex_id;
}
let mut cell_flags = RenderingGlyphFlags::empty();
cell_flags.set(RenderingGlyphFlags::COLORED, glyph.multicolor);
cell_flags.set(RenderingGlyphFlags::WIDE_CHAR, cell.flags.contains(Flags::WIDE_CHAR));
self.instances.push(InstanceData {
col: cell.column.0 as u16,
row: cell.line.0 as u16,
@ -458,12 +479,12 @@ impl Batch {
r: cell.fg.r,
g: cell.fg.g,
b: cell.fg.b,
cell_flags,
bg_r: cell.bg.r,
bg_g: cell.bg.g,
bg_b: cell.bg.b,
bg_a: (cell.bg_alpha * 255.0) as u8,
multicolor: glyph.multicolor,
});
}
@ -586,10 +607,10 @@ impl QuadRenderer {
// UV offset.
add_attr!(4, gl::FLOAT, f32);
// Color and multicolor flag.
// Color and cell flags.
//
// These are packed together because of an OpenGL driver issue on macOS, which caused a
// `vec3(u8)` text color and a `u8` multicolor flag to increase the rendering time by a
// `vec3(u8)` text color and a `u8` cell flags to increase the rendering time by a
// huge margin.
add_attr!(4, gl::UNSIGNED_BYTE, u8);
@ -1067,7 +1088,7 @@ fn load_glyph(
},
Err(AtlasInsertError::GlyphTooLarge) => Glyph {
tex_id: atlas[*current_atlas].id,
multicolor: 0,
multicolor: false,
top: 0,
left: 0,
width: 0,
@ -1581,7 +1602,7 @@ impl Atlas {
Glyph {
tex_id: self.id,
multicolor: multicolor as u8,
multicolor,
top: glyph.top as i16,
left: glyph.left as i16,
width: width as i16,

View File

@ -162,6 +162,12 @@ impl RenderLines {
return;
}
// Include wide char spacer if the current cell is a wide char.
let mut end: Point = cell.into();
if cell.flags.contains(Flags::WIDE_CHAR) {
end.col += 1;
}
// Check if there's an active line.
if let Some(line) = self.inner.get_mut(&flag).and_then(|lines| lines.last_mut()) {
if cell.fg == line.color
@ -169,13 +175,13 @@ impl RenderLines {
&& cell.line == line.end.line
{
// Update the length of the line.
line.end = cell.into();
line.end = end;
return;
}
}
// Start new line if there currently is none.
let line = RenderLine { start: cell.into(), end: cell.into(), color: cell.fg };
let line = RenderLine { start: cell.into(), end, color: cell.fg };
match self.inner.get_mut(&flag) {
Some(lines) => lines.push(line),
None => {

View File

@ -79,7 +79,12 @@ impl Urls {
};
let point: Point = cell.into();
let end = point;
let mut end = point;
// Include the following wide char spacer.
if cell.flags.contains(Flags::WIDE_CHAR) {
end.col += 1;
}
// Reset URL when empty cells have been skipped.
if point != Point::default() && Some(point.sub(num_cols, 1)) != self.last_point {
@ -88,8 +93,8 @@ impl Urls {
self.last_point = Some(end);
// Extend current state if a wide char spacer is encountered.
if cell.flags.intersects(Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER) {
// Extend current state if a leading wide char spacer is encountered.
if cell.flags.intersects(Flags::LEADING_WIDE_CHAR_SPACER) {
if let UrlLocation::Url(_, mut end_offset) = self.state {
if end_offset != 0 {
end_offset += 1;
@ -252,4 +257,24 @@ mod tests {
assert_eq!(urls.urls[2].start().col, Column(17));
assert_eq!(urls.urls[2].end().col, Column(21));
}
#[test]
fn wide_urls() {
let input = text_to_cells("test https://こんにちは (http:여보세요) ing");
let num_cols = input.len() + 9;
let mut urls = Urls::new();
for cell in input {
urls.update(Column(num_cols), &cell);
}
assert_eq!(urls.urls.len(), 2);
assert_eq!(urls.urls[0].start().col, Column(5));
assert_eq!(urls.urls[0].end().col, Column(17));
assert_eq!(urls.urls[1].start().col, Column(20));
assert_eq!(urls.urls[1].end().col, Column(28));
}
}

View File

@ -165,8 +165,8 @@ impl<'a, C> Iterator for RenderableCellsIter<'a, C> {
let cell = self.inner.next()?;
let cell = RenderableCell::new(self, cell);
// Skip empty cells.
if !cell.is_empty() {
// Skip empty cells and wide char spacers.
if !cell.is_empty() && !cell.flags.contains(Flags::WIDE_CHAR_SPACER) {
return Some(cell);
}
}
@ -282,18 +282,6 @@ impl<'a, C> RenderableCellsIter<'a, C> {
// Check line-wrapping, leading spacer.
|| (self.grid[buffer_prev].flags.contains(Flags::LEADING_WIDE_CHAR_SPACER)
&& selection.contains(prev.col, prev.line))
} else if cell.flags.contains(Flags::WIDE_CHAR_SPACER) {
// Check if spacer's wide char is selected.
let prev = point.sub(num_cols, 1);
let buffer_prev = self.grid.visible_to_buffer(prev);
if self.grid[buffer_prev].flags.contains(Flags::WIDE_CHAR) {
// Check previous cell for trailing spacer.
self.is_selected(prev)
} else {
// Check next cell for line-wrapping, leading spacer.
self.is_selected(point.add(num_cols, 1))
}
} else {
false
}