mirror of
https://github.com/wez/wezterm.git
synced 2024-11-22 22:42:48 +03:00
revise ligature render / cursor intersection
This puts to final rest #478, wherein ligatured glyphs that span cells would render portions of the glyph with the wrong fg color, where wrong was usually the bg color and cause the glyph to turn invisible when cursoring through the ligature. The approach used here is to divide the glyph into 7 discrete strips where each strip either intersects with the cursor, the selection, or neither. That allows us to render each strip with the appropriate foreground color. This change simplifies some of the logic and allows some other code to be removed, so that feels good! As is tradition with these renderer changes, there's a good chance that I overlooked something in testing and that the metrics or alignment might be slightly off for some font/text combo. Let's see how this goes! refs: #784 refs: #478 refs: #1617
This commit is contained in:
parent
1e32ccbd2f
commit
bd47979292
@ -41,20 +41,15 @@ impl CellCluster {
|
||||
|
||||
/// Compute the list of CellClusters from a set of visible cells.
|
||||
/// The input is typically the result of calling `Line::visible_cells()`.
|
||||
/// The cursor_idx is passed in order to force clusters to break around
|
||||
/// the cursor position, essentially forcing ligatures to be disabled
|
||||
/// around the cursor boundary.
|
||||
pub fn make_cluster<'a>(
|
||||
hint: usize,
|
||||
iter: impl Iterator<Item = (usize, &'a Cell)>,
|
||||
cursor_idx: Option<usize>,
|
||||
bidi_hint: Option<ParagraphDirectionHint>,
|
||||
) -> Vec<CellCluster> {
|
||||
let mut last_cluster = None;
|
||||
let mut clusters = Vec::new();
|
||||
let mut whitespace_run = 0;
|
||||
let mut only_whitespace = false;
|
||||
let mut last_was_cursor = false;
|
||||
|
||||
for (cell_idx, c) in iter {
|
||||
let presentation = c.presentation();
|
||||
@ -67,10 +62,6 @@ impl CellCluster {
|
||||
Cow::Borrowed(c.attrs())
|
||||
};
|
||||
|
||||
let is_cursor_boundary = bidi_hint.is_none() && Some(cell_idx) == cursor_idx;
|
||||
let was_cursor = last_was_cursor;
|
||||
last_was_cursor = is_cursor_boundary;
|
||||
|
||||
last_cluster = match last_cluster.take() {
|
||||
None => {
|
||||
// Start new cluster
|
||||
@ -86,11 +77,7 @@ impl CellCluster {
|
||||
))
|
||||
}
|
||||
Some(mut last) => {
|
||||
if is_cursor_boundary
|
||||
|| was_cursor
|
||||
|| last.attrs != *normalized_attr
|
||||
|| last.presentation != presentation
|
||||
{
|
||||
if last.attrs != *normalized_attr || last.presentation != presentation {
|
||||
// Flush pending cluster and start a new one
|
||||
clusters.push(last);
|
||||
|
||||
|
@ -799,17 +799,8 @@ impl Line {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn cluster(
|
||||
&self,
|
||||
cursor_idx: Option<usize>,
|
||||
bidi_hint: Option<ParagraphDirectionHint>,
|
||||
) -> Vec<CellCluster> {
|
||||
CellCluster::make_cluster(
|
||||
self.cells.len(),
|
||||
self.visible_cells(),
|
||||
cursor_idx,
|
||||
bidi_hint,
|
||||
)
|
||||
pub fn cluster(&self, bidi_hint: Option<ParagraphDirectionHint>) -> Vec<CellCluster> {
|
||||
CellCluster::make_cluster(self.cells.len(), self.visible_cells(), bidi_hint)
|
||||
}
|
||||
|
||||
pub fn cells(&self) -> &[Cell] {
|
||||
|
@ -684,7 +684,7 @@ pub fn run_ls_fonts(config: config::ConfigHandle, cmd: &LsFontsCommand) -> anyho
|
||||
|
||||
if let Some(text) = &cmd.text {
|
||||
let line = Line::from_text(text, &CellAttributes::default(), SEQ_ZERO);
|
||||
let cell_clusters = line.cluster(None, bidi_hint);
|
||||
let cell_clusters = line.cluster(bidi_hint);
|
||||
for cluster in cell_clusters {
|
||||
let style = font_config.match_style(&config, &cluster.attrs);
|
||||
let font = font_config.resolve_font(style)?;
|
||||
|
@ -153,7 +153,7 @@ mod test {
|
||||
{
|
||||
let line = Line::from_text(text, &CellAttributes::default(), SEQ_ZERO);
|
||||
eprintln!("{:?}", line);
|
||||
let cell_clusters = line.cluster(None, None);
|
||||
let cell_clusters = line.cluster(None);
|
||||
assert_eq!(cell_clusters.len(), 1);
|
||||
let cluster = &cell_clusters[0];
|
||||
let presentation_width = PresentationWidth::with_cluster(&cluster);
|
||||
@ -295,7 +295,7 @@ mod test {
|
||||
let style = TextStyle::default();
|
||||
let font = fonts.resolve_font(&style).unwrap();
|
||||
let line = Line::from_text(&text, &CellAttributes::default(), SEQ_ZERO);
|
||||
let cell_clusters = line.cluster(None, None);
|
||||
let cell_clusters = line.cluster(None);
|
||||
let cluster = &cell_clusters[0];
|
||||
let presentation_width = PresentationWidth::with_cluster(&cluster);
|
||||
|
||||
|
@ -258,7 +258,7 @@ impl Element {
|
||||
pub fn with_line(font: &Rc<LoadedFont>, line: &Line, palette: &ColorPalette) -> Self {
|
||||
let mut content = vec![];
|
||||
|
||||
for cluster in line.cluster(None, None) {
|
||||
for cluster in line.cluster(None) {
|
||||
let child =
|
||||
Element::new(font, ElementContent::Text(cluster.text)).colors(ElementColors {
|
||||
border: BorderColor::default(),
|
||||
|
@ -39,7 +39,6 @@ use wezterm_font::units::{IntPixelLength, PixelLength};
|
||||
use wezterm_font::{ClearShapeCache, GlyphInfo, LoadedFont};
|
||||
use wezterm_term::color::{ColorAttribute, ColorPalette, RgbColor};
|
||||
use wezterm_term::{CellAttributes, Line, StableRowIndex};
|
||||
use window::bitmaps::atlas::SpriteSlice;
|
||||
use window::bitmaps::Texture2d;
|
||||
use window::color::LinearRgba;
|
||||
|
||||
@ -154,9 +153,8 @@ pub struct RenderScreenLineOpenGLParams<'a> {
|
||||
}
|
||||
|
||||
pub struct ComputeCellFgBgParams<'a> {
|
||||
pub cell_idx: usize,
|
||||
pub selected: bool,
|
||||
pub cursor: Option<&'a StableCursorPosition>,
|
||||
pub selection: &'a Range<usize>,
|
||||
pub fg_color: LinearRgba,
|
||||
pub bg_color: LinearRgba,
|
||||
pub palette: &'a ColorPalette,
|
||||
@ -1726,11 +1724,10 @@ impl super::TermWindow {
|
||||
let cell_width = params.render_metrics.cell_size.width as f32;
|
||||
let cell_height = params.render_metrics.cell_size.height as f32;
|
||||
let pos_y = (self.dimensions.pixel_height as f32 / -2.) + params.top_pixel_y;
|
||||
let gl_x = self.dimensions.pixel_width as f32 / -2.;
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
let mut last_cell_idx = 0;
|
||||
|
||||
let cell_clusters;
|
||||
|
||||
let cursor_idx = if params.pane.is_some()
|
||||
@ -1773,14 +1770,32 @@ impl super::TermWindow {
|
||||
CellAttributes::blank(),
|
||||
termwiz::surface::SEQ_ZERO,
|
||||
);
|
||||
cell_clusters = line.cluster(cursor_idx, bidi_hint);
|
||||
cell_clusters = line.cluster(bidi_hint);
|
||||
composition_width = unicode_column_width(composing, None);
|
||||
&cell_clusters
|
||||
} else {
|
||||
cell_clusters = params.line.cluster(cursor_idx, bidi_hint);
|
||||
cell_clusters = params.line.cluster(bidi_hint);
|
||||
&cell_clusters
|
||||
};
|
||||
|
||||
let cursor_range = if composition_width > 0 {
|
||||
params.cursor.x..params.cursor.x + composition_width
|
||||
} else if params.stable_line_idx == Some(params.cursor.y) {
|
||||
params.cursor.x
|
||||
..params.cursor.x
|
||||
+ params
|
||||
.line
|
||||
.cells()
|
||||
.get(params.cursor.x)
|
||||
.map(|c| c.width())
|
||||
.unwrap_or(1)
|
||||
} else {
|
||||
0..0
|
||||
};
|
||||
|
||||
let cursor_range_pixels = params.left_pixel_x + cursor_range.start as f32 * cell_width
|
||||
..params.left_pixel_x + cursor_range.end as f32 * cell_width;
|
||||
|
||||
let shaped = self.cluster_and_shape(&to_shape, ¶ms)?;
|
||||
|
||||
let bounding_rect = euclid::rect(
|
||||
@ -1797,6 +1812,20 @@ impl super::TermWindow {
|
||||
}
|
||||
}
|
||||
|
||||
if params.line.is_reverse() {
|
||||
let mut quad = self.filled_rectangle(
|
||||
&mut layers[0],
|
||||
euclid::rect(
|
||||
params.left_pixel_x,
|
||||
params.top_pixel_y,
|
||||
params.pixel_width,
|
||||
cell_height,
|
||||
),
|
||||
params.foreground,
|
||||
)?;
|
||||
quad.set_hsv(hsv);
|
||||
}
|
||||
|
||||
// Make a pass to compute background colors.
|
||||
// Need to consider:
|
||||
// * background when it is not the default color
|
||||
@ -1855,23 +1884,74 @@ impl super::TermWindow {
|
||||
// Render the selection background color.
|
||||
// This always uses a physical x position, regardles of the line
|
||||
// direction.
|
||||
if !params.selection.is_empty() {
|
||||
let selection_pixel_range = if !params.selection.is_empty() {
|
||||
let start = params.left_pixel_x + (params.selection.start as f32 * cell_width);
|
||||
let width = (params.selection.end - params.selection.start) as f32 * cell_width;
|
||||
let mut quad = self.filled_rectangle(
|
||||
&mut layers[0],
|
||||
euclid::rect(
|
||||
params.left_pixel_x + (params.selection.start as f32 * cell_width),
|
||||
params.top_pixel_y,
|
||||
(params.selection.end - params.selection.start) as f32 * cell_width,
|
||||
cell_height,
|
||||
),
|
||||
euclid::rect(start, params.top_pixel_y, width, cell_height),
|
||||
params.selection_bg,
|
||||
)?;
|
||||
|
||||
quad.set_hsv(hsv);
|
||||
}
|
||||
|
||||
let directional_selection = phys(params.selection.start, num_cols, direction)
|
||||
..phys(params.selection.end, num_cols, direction);
|
||||
start..start + width
|
||||
} else {
|
||||
0.0..0.0
|
||||
};
|
||||
|
||||
// Consider cursor
|
||||
if !cursor_range.is_empty() {
|
||||
let ComputeCellFgBgResult {
|
||||
fg_color: _glyph_color,
|
||||
bg_color: _bg_color,
|
||||
cursor_shape,
|
||||
cursor_border_color,
|
||||
} = self.compute_cell_fg_bg(ComputeCellFgBgParams {
|
||||
cursor: Some(params.cursor),
|
||||
selected: false,
|
||||
fg_color: params.foreground,
|
||||
bg_color: params.default_bg,
|
||||
palette: params.palette,
|
||||
is_active_pane: params.is_active,
|
||||
config: params.config,
|
||||
selection_fg: params.selection_fg,
|
||||
selection_bg: params.selection_bg,
|
||||
cursor_fg: params.cursor_fg,
|
||||
cursor_bg: params.cursor_bg,
|
||||
cursor_border_color: params.cursor_border_color,
|
||||
pane: params.pane,
|
||||
});
|
||||
let pos_x = (self.dimensions.pixel_width as f32 / -2.)
|
||||
+ params.left_pixel_x
|
||||
+ (phys(params.cursor.x, num_cols, direction) as f32 * cell_width);
|
||||
|
||||
if cursor_shape.is_some() {
|
||||
let mut quad = layers[0].allocate()?;
|
||||
quad.set_position(
|
||||
pos_x,
|
||||
pos_y,
|
||||
pos_x + (cursor_range.end - cursor_range.start) as f32 * cell_width,
|
||||
pos_y + cell_height,
|
||||
);
|
||||
quad.set_hsv(hsv);
|
||||
quad.set_has_color(false);
|
||||
|
||||
quad.set_texture(
|
||||
gl_state
|
||||
.glyph_cache
|
||||
.borrow_mut()
|
||||
.cursor_sprite(
|
||||
cursor_shape,
|
||||
¶ms.render_metrics,
|
||||
(cursor_range.end - cursor_range.start) as u8,
|
||||
)?
|
||||
.texture_coords(),
|
||||
);
|
||||
|
||||
quad.set_fg_color(cursor_border_color);
|
||||
}
|
||||
}
|
||||
|
||||
let mut overlay_images = vec![];
|
||||
|
||||
@ -1887,6 +1967,7 @@ impl super::TermWindow {
|
||||
let style_params = &item.style;
|
||||
let cluster = &item.cluster;
|
||||
let glyph_info = &item.glyph_info;
|
||||
let images = cluster.attrs.images().unwrap_or_else(|| vec![]);
|
||||
|
||||
// TODO: remember logical/visual mapping for selection
|
||||
#[allow(unused_variables)]
|
||||
@ -1915,299 +1996,216 @@ impl super::TermWindow {
|
||||
let top = cell_height + params.render_metrics.descender.get() as f32
|
||||
- (glyph.y_offset + glyph.bearing_y).get() as f32;
|
||||
|
||||
// We use this to remember the `left` offset value to use for glyph_idx > 0
|
||||
let mut slice_left = 0.;
|
||||
|
||||
// Iterate each cell that comprises this glyph. There is usually
|
||||
// a single cell per glyph but combining characters, ligatures
|
||||
// and emoji can be 2 or more cells wide.
|
||||
// Conversely, a combining glyph can produce an entry that is zero
|
||||
// cells wide and which combines with a neighboring glyph that we
|
||||
// (will|did) emit in another iteration of the glyph_info loop
|
||||
// so we must ensure that we iterate and observe that!
|
||||
for glyph_idx in 0..info.pos.num_cells.max(1) as usize {
|
||||
let cell_idx = visual_cell_idx + glyph_idx;
|
||||
|
||||
if cell_idx >= num_cols {
|
||||
// terminal line data is wider than the window.
|
||||
// This happens for example while live resizing the window
|
||||
// smaller than the terminal.
|
||||
break;
|
||||
}
|
||||
|
||||
last_cell_idx = visual_cell_idx;
|
||||
|
||||
let in_composition = composition_width > 0
|
||||
&& visual_cell_idx >= params.cursor.x
|
||||
&& visual_cell_idx <= params.cursor.x + composition_width;
|
||||
|
||||
let ComputeCellFgBgResult {
|
||||
fg_color: glyph_color,
|
||||
bg_color,
|
||||
cursor_shape,
|
||||
cursor_border_color,
|
||||
} = self.compute_cell_fg_bg(ComputeCellFgBgParams {
|
||||
// We pass the visual_cell_idx instead of the cell_idx when
|
||||
// computing the cursor/background color because we may
|
||||
// have a series of ligatured glyphs that compose over the
|
||||
// top of each other to form a double-wide grapheme cell.
|
||||
// If we use cell_idx here we could render half of that
|
||||
// in the cursor colors (good) and the other half in
|
||||
// the text colors, which is bad because we get a half
|
||||
// reversed, half not glyph and that is hard to read
|
||||
// against the cursor background.
|
||||
// When we cluster, we guarantee that the ligatures are
|
||||
// broken around the cursor boundary, and clustering
|
||||
// guarantees that different colors are broken out as
|
||||
// well, so this assumption is probably good in all
|
||||
// cases!
|
||||
// <https://github.com/wez/wezterm/issues/478>
|
||||
cell_idx: visual_cell_idx,
|
||||
cursor: if params.stable_line_idx == Some(params.cursor.y)
|
||||
&& (in_composition || params.cursor.x == visual_cell_idx)
|
||||
{
|
||||
Some(params.cursor)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
selection: &directional_selection,
|
||||
fg_color: style_params.fg_color,
|
||||
bg_color: style_params.bg_color,
|
||||
palette: params.palette,
|
||||
is_active_pane: params.is_active,
|
||||
config: params.config,
|
||||
selection_fg: params.selection_fg,
|
||||
selection_bg: params.selection_bg,
|
||||
cursor_fg: params.cursor_fg,
|
||||
cursor_bg: params.cursor_bg,
|
||||
cursor_border_color: params.cursor_border_color,
|
||||
pane: params.pane,
|
||||
});
|
||||
|
||||
let pos_x = params.left_pixel_x
|
||||
+ cluster_x_pos
|
||||
+ if params.use_pixel_positioning {
|
||||
(glyph.x_offset + glyph.bearing_x).get() as f32
|
||||
} else {
|
||||
glyph_idx as f32 * cell_width
|
||||
};
|
||||
|
||||
if pos_x > params.left_pixel_x + params.pixel_width {
|
||||
log::info!(
|
||||
"breaking on overflow {} > {} + {}",
|
||||
pos_x,
|
||||
params.left_pixel_x,
|
||||
params.pixel_width
|
||||
);
|
||||
break;
|
||||
}
|
||||
let pos_x = (self.dimensions.pixel_width as f32 / -2.) + pos_x;
|
||||
|
||||
let pixel_width = glyph.x_advance.get() as f32;
|
||||
|
||||
// Note: in use_pixel_positioning mode, we draw backgrounds
|
||||
// for glyph_idx == 0 based on the whole glyph advance, rather than
|
||||
// for each of the cells.
|
||||
|
||||
if cursor_shape.is_some() {
|
||||
if glyph_idx == 0 {
|
||||
// We'd like to render the cursor with the cell width
|
||||
// so that double-wide cells look more reasonable.
|
||||
// If we have a cursor shape, compute the intended cursor
|
||||
// width. We only use that if we're the first cell that
|
||||
// comprises this glyph; if for some reason the cursor position
|
||||
// is in the middle of a glyph we just use a single cell.
|
||||
let cursor_width = params
|
||||
.line
|
||||
.cells()
|
||||
.get(cell_idx)
|
||||
.map(|c| c.width() as u8)
|
||||
// skip in weird situations, like a combining character
|
||||
// "e⃗" where we have two glyphs and one of them has
|
||||
// num_cells=0.
|
||||
.unwrap_or(0);
|
||||
|
||||
if cursor_width > 0 {
|
||||
let mut quad = layers[0].allocate()?;
|
||||
quad.set_position(
|
||||
pos_x,
|
||||
pos_y,
|
||||
pos_x
|
||||
+ if params.use_pixel_positioning {
|
||||
pixel_width
|
||||
} else {
|
||||
cursor_width as f32 * cell_width
|
||||
},
|
||||
pos_y + cell_height,
|
||||
);
|
||||
quad.set_hsv(hsv);
|
||||
quad.set_has_color(false);
|
||||
|
||||
quad.set_texture(
|
||||
gl_state
|
||||
.glyph_cache
|
||||
.borrow_mut()
|
||||
.cursor_sprite(
|
||||
cursor_shape,
|
||||
¶ms.render_metrics,
|
||||
cursor_width,
|
||||
)?
|
||||
.texture_coords(),
|
||||
);
|
||||
|
||||
quad.set_fg_color(cursor_border_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let images = cluster.attrs.images().unwrap_or_else(|| vec![]);
|
||||
|
||||
for glyph_idx in 0..info.pos.num_cells as usize {
|
||||
for img in &images {
|
||||
if img.z_index() < 0 {
|
||||
self.populate_image_quad(
|
||||
&img,
|
||||
gl_state,
|
||||
&mut layers[0],
|
||||
cell_idx,
|
||||
visual_cell_idx + glyph_idx,
|
||||
¶ms,
|
||||
hsv,
|
||||
glyph_color,
|
||||
style_params.fg_color,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Underlines
|
||||
if style_params.underline_tex_rect != params.white_space {
|
||||
if !params.use_pixel_positioning || glyph_idx == 0 {
|
||||
let mut quad = layers[0].allocate()?;
|
||||
quad.set_position(
|
||||
pos_x,
|
||||
pos_y,
|
||||
pos_x
|
||||
+ if params.use_pixel_positioning {
|
||||
pixel_width
|
||||
} else {
|
||||
cell_width
|
||||
},
|
||||
pos_y + cell_height,
|
||||
);
|
||||
quad.set_hsv(hsv);
|
||||
quad.set_has_color(false);
|
||||
{
|
||||
// First, resolve this glyph to a texture
|
||||
let mut texture = glyph.texture.as_ref().cloned();
|
||||
|
||||
quad.set_texture(style_params.underline_tex_rect);
|
||||
quad.set_fg_color(style_params.underline_color);
|
||||
}
|
||||
}
|
||||
|
||||
let mut did_custom = false;
|
||||
|
||||
if self.config.custom_block_glyphs && glyph_idx == 0 {
|
||||
if let Some(cell) = params.line.cells().get(cell_idx) {
|
||||
if self.config.custom_block_glyphs {
|
||||
if let Some(cell) = params.line.cells().get(visual_cell_idx) {
|
||||
if let Some(block) = BlockKey::from_cell(cell) {
|
||||
if glyph_color != bg_color {
|
||||
self.populate_block_quad(
|
||||
block,
|
||||
gl_state,
|
||||
&mut layers[0],
|
||||
pos_x,
|
||||
¶ms,
|
||||
hsv,
|
||||
glyph_color,
|
||||
)?;
|
||||
}
|
||||
did_custom = true;
|
||||
texture.replace(
|
||||
gl_state
|
||||
.glyph_cache
|
||||
.borrow_mut()
|
||||
.cached_block(block, ¶ms.render_metrics)?,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !did_custom {
|
||||
if let Some(texture) = glyph.texture.as_ref() {
|
||||
if glyph_color != bg_color || glyph.has_color {
|
||||
if params.use_pixel_positioning {
|
||||
// When use_pixel_positioning is in effect, we simply
|
||||
// draw the entire glyph at once.
|
||||
if glyph_idx == 0 {
|
||||
let mut quad = layers[1].allocate()?;
|
||||
let pos_y = params.top_pixel_y
|
||||
+ self.dimensions.pixel_height as f32 / -2.0
|
||||
+ top;
|
||||
quad.set_position(
|
||||
pos_x,
|
||||
pos_y,
|
||||
pos_x + texture.coords.size.width as f32,
|
||||
pos_y + texture.coords.size.height as f32,
|
||||
);
|
||||
quad.set_fg_color(glyph_color);
|
||||
quad.set_texture(texture.texture_coords());
|
||||
quad.set_hsv(if glyph.brightness_adjust != 1.0 {
|
||||
let hsv =
|
||||
hsv.unwrap_or_else(|| HsbTransform::default());
|
||||
Some(HsbTransform {
|
||||
brightness: hsv.brightness
|
||||
* glyph.brightness_adjust,
|
||||
..hsv
|
||||
})
|
||||
} else {
|
||||
hsv
|
||||
});
|
||||
quad.set_has_color(glyph.has_color);
|
||||
}
|
||||
} else {
|
||||
let left = info.pos.x_offset.get() as f32 + info.pos.bearing_x;
|
||||
let slice = SpriteSlice {
|
||||
cell_idx: glyph_idx,
|
||||
num_cells: info.pos.num_cells as usize,
|
||||
cell_width: params.render_metrics.cell_size.width as usize,
|
||||
scale: glyph.scale as f32,
|
||||
left_offset: left,
|
||||
};
|
||||
if let Some(texture) = texture {
|
||||
// TODO: clipping, but we can do that based on pixels
|
||||
|
||||
let pixel_rect = slice.pixel_rect(texture);
|
||||
let texture_rect =
|
||||
texture.texture.to_texture_coords(pixel_rect);
|
||||
let pos_x = cluster_x_pos
|
||||
+ params.left_pixel_x
|
||||
+ if params.use_pixel_positioning {
|
||||
(glyph.x_offset + glyph.bearing_x).get() as f32
|
||||
} else {
|
||||
0.
|
||||
};
|
||||
|
||||
let left = if glyph_idx == 0 { left } else { slice_left };
|
||||
let bottom =
|
||||
(pixel_rect.size.height as f32 * glyph.scale as f32) + top
|
||||
- params.render_metrics.cell_size.height as f32;
|
||||
let right = pixel_rect.size.width as f32 + left
|
||||
- params.render_metrics.cell_size.width as f32;
|
||||
if pos_x > params.pixel_width {
|
||||
log::info!(
|
||||
"breaking on overflow {} > {} + {}",
|
||||
pos_x,
|
||||
params.left_pixel_x,
|
||||
params.pixel_width
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
// Save the `right` position; we'll use it for the `left` adjust for
|
||||
// the next slice that comprises this glyph.
|
||||
// This is important because some glyphs (eg: 현재 브랜치) can have
|
||||
// fractional advance/offset positions that leave one half slightly
|
||||
// out of alignment with the other if we were to simply force the
|
||||
// `left` value to be 0 when glyph_idx > 0.
|
||||
slice_left = right;
|
||||
// We need to conceptually slice this texture into
|
||||
// up into strips that consider the cursor and selection
|
||||
// background ranges. For ligatures that span cells, we'll
|
||||
// need to explicitly render each strip independently so that
|
||||
// we can set its foreground color to the appropriate color
|
||||
// for the cursor/selection/regular background upon which
|
||||
// it will be drawn.
|
||||
|
||||
let mut quad = layers[1].allocate()?;
|
||||
quad.set_position(
|
||||
pos_x + left,
|
||||
pos_y + top,
|
||||
pos_x + cell_width + right,
|
||||
pos_y + cell_height + bottom,
|
||||
);
|
||||
quad.set_fg_color(glyph_color);
|
||||
quad.set_texture(texture_rect);
|
||||
quad.set_hsv(if glyph.brightness_adjust != 1.0 {
|
||||
let hsv = hsv.unwrap_or_else(|| HsbTransform::default());
|
||||
Some(HsbTransform {
|
||||
brightness: hsv.brightness * glyph.brightness_adjust,
|
||||
..hsv
|
||||
})
|
||||
} else {
|
||||
hsv
|
||||
});
|
||||
quad.set_has_color(glyph.has_color);
|
||||
}
|
||||
/// Computes the intersection between r1 and r2.
|
||||
/// It may be empty.
|
||||
fn intersection(r1: &Range<f32>, r2: &Range<f32>) -> Range<f32> {
|
||||
let start = r1.start.max(r2.start);
|
||||
let end = r1.end.min(r2.end);
|
||||
if end > start {
|
||||
start..end
|
||||
} else {
|
||||
// Empty
|
||||
start..start
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for img in images {
|
||||
/// Assess range `r` relative to `within`. If `r` intersects
|
||||
/// `within` then return the 3 ranges that are subsets of `r`
|
||||
/// which are to the left of `within`, intersecting `within`
|
||||
/// and to the right of `within`.
|
||||
/// If `r` and `within` do not intersect, returns `r` and
|
||||
/// two empty ranges.
|
||||
/// If `r` is itself an empty range, all returned ranges
|
||||
/// will be empty.
|
||||
fn range3(
|
||||
r: &Range<f32>,
|
||||
within: &Range<f32>,
|
||||
) -> (Range<f32>, Range<f32>, Range<f32>) {
|
||||
if r.is_empty() {
|
||||
return (r.clone(), r.clone(), r.clone());
|
||||
}
|
||||
let i = intersection(r, within);
|
||||
if i.is_empty() {
|
||||
return (r.clone(), i.clone(), i.clone());
|
||||
}
|
||||
|
||||
let left = if i.start > r.start {
|
||||
r.start..i.start
|
||||
} else {
|
||||
r.start..r.start
|
||||
};
|
||||
|
||||
let right = if i.end < r.end {
|
||||
i.end..r.end
|
||||
} else {
|
||||
r.end..r.end
|
||||
};
|
||||
|
||||
(left, i, right)
|
||||
}
|
||||
|
||||
let adjust = (glyph.x_offset + glyph.bearing_x).get() as f32;
|
||||
let texture_range =
|
||||
pos_x + adjust..pos_x + adjust + texture.coords.size.width as f32;
|
||||
|
||||
// First bucket the ranges according to cursor position
|
||||
let (left, mid, right) = range3(&texture_range, &cursor_range_pixels);
|
||||
// Then sub-divide the non-cursor ranges according to selection
|
||||
let (la, lb, lc) = range3(&left, &selection_pixel_range);
|
||||
let (ra, rb, rc) = range3(&right, &selection_pixel_range);
|
||||
|
||||
// and render each of these strips
|
||||
for range in [la, lb, lc, mid, ra, rb, rc] {
|
||||
if range.start == range.end {
|
||||
continue;
|
||||
}
|
||||
|
||||
let is_cursor = cursor_range_pixels.contains(&range.start);
|
||||
let selected =
|
||||
!is_cursor && selection_pixel_range.contains(&range.start);
|
||||
|
||||
let ComputeCellFgBgResult {
|
||||
fg_color: glyph_color,
|
||||
bg_color,
|
||||
cursor_shape: _,
|
||||
cursor_border_color: _,
|
||||
} = self.compute_cell_fg_bg(ComputeCellFgBgParams {
|
||||
cursor: if is_cursor { Some(params.cursor) } else { None },
|
||||
selected,
|
||||
fg_color: style_params.fg_color,
|
||||
bg_color: style_params.bg_color,
|
||||
palette: params.palette,
|
||||
is_active_pane: params.is_active,
|
||||
config: params.config,
|
||||
selection_fg: params.selection_fg,
|
||||
selection_bg: params.selection_bg,
|
||||
cursor_fg: params.cursor_fg,
|
||||
cursor_bg: params.cursor_bg,
|
||||
cursor_border_color: params.cursor_border_color,
|
||||
pane: params.pane,
|
||||
});
|
||||
|
||||
if glyph_color == bg_color {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Underlines
|
||||
if style_params.underline_tex_rect != params.white_space {
|
||||
let mut quad = layers[0].allocate()?;
|
||||
quad.set_position(
|
||||
gl_x + range.start,
|
||||
pos_y,
|
||||
gl_x + range.end,
|
||||
pos_y + cell_height,
|
||||
);
|
||||
quad.set_hsv(hsv);
|
||||
quad.set_has_color(false);
|
||||
quad.set_texture(style_params.underline_tex_rect);
|
||||
quad.set_fg_color(style_params.underline_color);
|
||||
}
|
||||
|
||||
let pixel_rect = euclid::rect(
|
||||
texture.coords.origin.x + (range.start - (pos_x + adjust)) as isize,
|
||||
texture.coords.origin.y,
|
||||
(range.end - range.start) as isize,
|
||||
texture.coords.size.height,
|
||||
);
|
||||
|
||||
let texture_rect = texture.texture.to_texture_coords(pixel_rect);
|
||||
|
||||
let mut quad = layers[1].allocate()?;
|
||||
quad.set_position(
|
||||
gl_x + range.start,
|
||||
pos_y + top,
|
||||
gl_x + range.end,
|
||||
pos_y + top + texture.coords.size.height as f32,
|
||||
);
|
||||
quad.set_fg_color(glyph_color);
|
||||
quad.set_texture(texture_rect);
|
||||
quad.set_hsv(if glyph.brightness_adjust != 1.0 {
|
||||
let hsv = hsv.unwrap_or_else(|| HsbTransform::default());
|
||||
Some(HsbTransform {
|
||||
brightness: hsv.brightness * glyph.brightness_adjust,
|
||||
..hsv
|
||||
})
|
||||
} else {
|
||||
hsv
|
||||
});
|
||||
quad.set_has_color(glyph.has_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for glyph_idx in 0..info.pos.num_cells as usize {
|
||||
for img in &images {
|
||||
if img.z_index() >= 0 {
|
||||
overlay_images.push((cell_idx, img, glyph_color));
|
||||
overlay_images.push((
|
||||
visual_cell_idx + glyph_idx,
|
||||
img.clone(),
|
||||
style_params.fg_color,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2245,112 +2243,7 @@ impl super::TermWindow {
|
||||
)?;
|
||||
}
|
||||
|
||||
// If the clusters don't extend to the full physical width of the display,
|
||||
// we have a little bit more work to do to ensure that we correctly paint:
|
||||
// * Reversed background
|
||||
// * Cursor
|
||||
let right_fill_start = Instant::now();
|
||||
if last_cell_idx < num_cols {
|
||||
if params.line.is_reverse() {
|
||||
let mut quad = self.filled_rectangle(
|
||||
&mut layers[0],
|
||||
euclid::rect(
|
||||
params.left_pixel_x
|
||||
+ (phys(last_cell_idx, num_cols, direction) as f32 * cell_width),
|
||||
params.top_pixel_y,
|
||||
(num_cols - last_cell_idx) as f32 * cell_width,
|
||||
cell_height,
|
||||
),
|
||||
params.foreground,
|
||||
)?;
|
||||
quad.set_hsv(hsv);
|
||||
}
|
||||
|
||||
if params.stable_line_idx == Some(params.cursor.y)
|
||||
&& ((params.cursor.x > last_cell_idx) || shaped.is_empty())
|
||||
{
|
||||
// Compute the cursor fg/bg
|
||||
let ComputeCellFgBgResult {
|
||||
fg_color: _glyph_color,
|
||||
bg_color,
|
||||
cursor_shape,
|
||||
cursor_border_color,
|
||||
} = self.compute_cell_fg_bg(ComputeCellFgBgParams {
|
||||
cell_idx: params.cursor.x,
|
||||
cursor: Some(params.cursor),
|
||||
selection: &directional_selection,
|
||||
fg_color: params.foreground,
|
||||
bg_color: params.default_bg,
|
||||
palette: params.palette,
|
||||
is_active_pane: params.is_active,
|
||||
config: params.config,
|
||||
selection_fg: params.selection_fg,
|
||||
selection_bg: params.selection_bg,
|
||||
cursor_fg: params.cursor_fg,
|
||||
cursor_bg: params.cursor_bg,
|
||||
cursor_border_color: params.cursor_border_color,
|
||||
pane: params.pane,
|
||||
});
|
||||
|
||||
let pos_x = (self.dimensions.pixel_width as f32 / -2.)
|
||||
+ params.left_pixel_x
|
||||
+ (phys(params.cursor.x, num_cols, direction) as f32 * cell_width);
|
||||
|
||||
let overflow = pos_x > params.left_pixel_x + params.pixel_width;
|
||||
|
||||
if overflow {
|
||||
log::info!(
|
||||
"breaking on overflow {} > {} + {}",
|
||||
pos_x,
|
||||
params.left_pixel_x,
|
||||
params.pixel_width
|
||||
);
|
||||
} else {
|
||||
if bg_color != LinearRgba::TRANSPARENT {
|
||||
// Avoid poking a transparent hole underneath the cursor
|
||||
let mut quad = self.filled_rectangle(
|
||||
&mut layers[2],
|
||||
euclid::rect(
|
||||
params.left_pixel_x
|
||||
+ phys(params.cursor.x, num_cols, direction) as f32
|
||||
* cell_width,
|
||||
params.top_pixel_y,
|
||||
cell_width,
|
||||
cell_height,
|
||||
),
|
||||
bg_color,
|
||||
)?;
|
||||
quad.set_hsv(hsv);
|
||||
}
|
||||
{
|
||||
let mut quad = layers[2].allocate()?;
|
||||
quad.set_position(pos_x, pos_y, pos_x + cell_width, pos_y + cell_height);
|
||||
|
||||
quad.set_has_color(false);
|
||||
quad.set_hsv(hsv);
|
||||
|
||||
quad.set_texture(
|
||||
gl_state
|
||||
.glyph_cache
|
||||
.borrow_mut()
|
||||
.cursor_sprite(cursor_shape, ¶ms.render_metrics, 1)?
|
||||
.texture_coords(),
|
||||
);
|
||||
quad.set_fg_color(cursor_border_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
metrics::histogram!(
|
||||
"render_screen_line_opengl.right_fill",
|
||||
right_fill_start.elapsed()
|
||||
);
|
||||
metrics::histogram!("render_screen_line_opengl", start.elapsed());
|
||||
log::trace!(
|
||||
"right fill {} -> elapsed {:?}",
|
||||
num_cols.saturating_sub(last_cell_idx),
|
||||
right_fill_start.elapsed()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -2465,8 +2358,6 @@ impl super::TermWindow {
|
||||
}
|
||||
|
||||
pub fn compute_cell_fg_bg(&self, params: ComputeCellFgBgParams) -> ComputeCellFgBgResult {
|
||||
let selected = params.selection.contains(¶ms.cell_idx);
|
||||
|
||||
if params.cursor.is_some() {
|
||||
if let Some(intensity) = self.get_intensity_if_bell_target_ringing(
|
||||
params.pane.expect("cursor only set if pane present"),
|
||||
@ -2530,13 +2421,13 @@ impl super::TermWindow {
|
||||
}
|
||||
}
|
||||
|
||||
// This logic figures out whether the cursor is visible or not.
|
||||
// If the cursor is explicitly hidden then it is obviously not
|
||||
// visible.
|
||||
// If the cursor is set to a blinking mode then we are visible
|
||||
// depending on the current time.
|
||||
let (cursor_shape, visibility) = match params.cursor {
|
||||
Some(cursor) if cursor.visibility == CursorVisibility::Visible => {
|
||||
// This logic figures out whether the cursor is visible or not.
|
||||
// If the cursor is explicitly hidden then it is obviously not
|
||||
// visible.
|
||||
// If the cursor is set to a blinking mode then we are visible
|
||||
// depending on the current time.
|
||||
let shape = params
|
||||
.config
|
||||
.default_cursor_style
|
||||
@ -2597,43 +2488,47 @@ impl super::TermWindow {
|
||||
|
||||
let focused_and_active = self.focused.is_some() && params.is_active_pane;
|
||||
|
||||
let (fg_color, bg_color, cursor_bg) =
|
||||
match (selected, focused_and_active, cursor_shape, visibility) {
|
||||
// Selected text overrides colors
|
||||
(true, _, _, CursorVisibility::Hidden) => {
|
||||
(params.selection_fg, params.selection_bg, params.cursor_bg)
|
||||
let (fg_color, bg_color, cursor_bg) = match (
|
||||
params.selected,
|
||||
focused_and_active,
|
||||
cursor_shape,
|
||||
visibility,
|
||||
) {
|
||||
// Selected text overrides colors
|
||||
(true, _, _, CursorVisibility::Hidden) => {
|
||||
(params.selection_fg, params.selection_bg, params.cursor_bg)
|
||||
}
|
||||
// block Cursor cell overrides colors
|
||||
(
|
||||
_,
|
||||
true,
|
||||
CursorShape::BlinkingBlock | CursorShape::SteadyBlock,
|
||||
CursorVisibility::Visible,
|
||||
) => {
|
||||
if self.config.force_reverse_video_cursor {
|
||||
(params.bg_color, params.fg_color, params.fg_color)
|
||||
} else {
|
||||
(params.cursor_fg, params.cursor_bg, params.cursor_bg)
|
||||
}
|
||||
// block Cursor cell overrides colors
|
||||
(
|
||||
_,
|
||||
true,
|
||||
CursorShape::BlinkingBlock | CursorShape::SteadyBlock,
|
||||
CursorVisibility::Visible,
|
||||
) => {
|
||||
if self.config.force_reverse_video_cursor {
|
||||
(params.bg_color, params.fg_color, params.fg_color)
|
||||
} else {
|
||||
(params.cursor_fg, params.cursor_bg, params.cursor_bg)
|
||||
}
|
||||
}
|
||||
(
|
||||
_,
|
||||
true,
|
||||
CursorShape::BlinkingUnderline
|
||||
| CursorShape::SteadyUnderline
|
||||
| CursorShape::BlinkingBar
|
||||
| CursorShape::SteadyBar,
|
||||
CursorVisibility::Visible,
|
||||
) => {
|
||||
if self.config.force_reverse_video_cursor {
|
||||
(params.fg_color, params.bg_color, params.fg_color)
|
||||
} else {
|
||||
(params.fg_color, params.bg_color, params.cursor_bg)
|
||||
}
|
||||
(
|
||||
_,
|
||||
true,
|
||||
CursorShape::BlinkingUnderline
|
||||
| CursorShape::SteadyUnderline
|
||||
| CursorShape::BlinkingBar
|
||||
| CursorShape::SteadyBar,
|
||||
CursorVisibility::Visible,
|
||||
) => {
|
||||
if self.config.force_reverse_video_cursor {
|
||||
(params.fg_color, params.bg_color, params.fg_color)
|
||||
} else {
|
||||
(params.fg_color, params.bg_color, params.cursor_bg)
|
||||
}
|
||||
}
|
||||
// Normally, render the cell as configured (or if the window is unfocused)
|
||||
_ => (params.fg_color, params.bg_color, params.cursor_border_color),
|
||||
};
|
||||
}
|
||||
// Normally, render the cell as configured (or if the window is unfocused)
|
||||
_ => (params.fg_color, params.bg_color, params.cursor_border_color),
|
||||
};
|
||||
|
||||
ComputeCellFgBgResult {
|
||||
fg_color,
|
||||
|
@ -175,74 +175,3 @@ where
|
||||
self.texture.to_texture_coords(self.coords)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a vertical slice through a sprite.
|
||||
/// These are used to handle multi-cell wide glyphs.
|
||||
/// Each cell is nominally `cell_width` wide but font metrics
|
||||
/// may result in the glyphs being wider than this.
|
||||
#[derive(Debug)]
|
||||
pub struct SpriteSlice {
|
||||
/// This is glyph X out of num_cells
|
||||
pub cell_idx: usize,
|
||||
/// How many cells comprise this glyph
|
||||
pub num_cells: usize,
|
||||
/// The nominal width of each cell
|
||||
pub cell_width: usize,
|
||||
/// The glyph will be scaled from sprite pixels down to
|
||||
/// cell pixels by this factor.
|
||||
pub scale: f32,
|
||||
/// The font metrics will adjust the left-most pixel
|
||||
/// by this amount. This causes the width of cell 0
|
||||
/// to be adjusted by this same amount.
|
||||
pub left_offset: f32,
|
||||
}
|
||||
|
||||
impl SpriteSlice {
|
||||
pub fn pixel_rect<T: Texture2d>(&self, sprite: &Sprite<T>) -> Rect {
|
||||
let width = self.slice_width(sprite) as isize;
|
||||
let left = self.left_pix(sprite) as isize;
|
||||
|
||||
Rect::new(
|
||||
Point::new(sprite.coords.origin.x + left, sprite.coords.origin.y),
|
||||
Size::new(width, sprite.coords.size.height),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the scaled offset to the left most pixel in a slice.
|
||||
/// This is 0 for the first slice and increases by the slice_width
|
||||
/// as we work through the slices.
|
||||
pub fn left_pix<T: Texture2d>(&self, sprite: &Sprite<T>) -> f32 {
|
||||
let width = sprite.coords.size.width as f32 * self.scale;
|
||||
if self.num_cells == 1 || self.cell_idx == 0 {
|
||||
0.0
|
||||
} else {
|
||||
// Width of the first cell
|
||||
let cell_0 = width.min((self.cell_width as f32) - self.left_offset);
|
||||
|
||||
// Width of all the preceding cells, except the first.
|
||||
let prev = self.cell_width * (self.cell_idx - 1);
|
||||
cell_0 + prev as f32
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the (scaled) pixel width of a slice.
|
||||
/// This is nominally the cell_width but can be modified by being the first
|
||||
/// or last in a sequence of potentially oversized sprite slices.
|
||||
pub fn slice_width<T: Texture2d>(&self, sprite: &Sprite<T>) -> f32 {
|
||||
let width = sprite.coords.size.width as f32 * self.scale;
|
||||
|
||||
if self.num_cells == 1 {
|
||||
width
|
||||
} else if self.cell_idx == 0 {
|
||||
// The first slice can extend (or recede) to the left based
|
||||
// on the slice.left_offset value.
|
||||
width.min((self.cell_width as f32) - self.left_offset)
|
||||
} else if self.cell_idx == self.num_cells - 1 {
|
||||
width - self.left_pix(sprite)
|
||||
} else {
|
||||
// somewhere in the middle of the sequence, the width is
|
||||
// simply the cell_width
|
||||
self.cell_width as f32
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user