1
0
Fork 0
mirror of https://github.com/alacritty/alacritty.git synced 2025-04-14 17:53:03 -04:00

Simplify powerline drawing algorithm

Iterate over points in line instead of drawing it right away
and then finding it in the buffer.

Fixes: 4a26667060 (Use builtin font to draw powerline symbols)
This commit is contained in:
Kirill Chibisov 2023-11-19 15:21:20 +04:00 committed by GitHub
parent d83d5af2b5
commit f8d9f5f6ef
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -15,6 +15,11 @@ const COLOR_FILL_ALPHA_STEP_3: Pixel = Pixel { _r: 64, _g: 64, _b: 64 };
/// Default color used for filling.
const COLOR_FILL: Pixel = Pixel { _r: 255, _g: 255, _b: 255 };
const POWERLINE_TRIANGLE_LTR: char = '\u{e0b0}';
const POWERLINE_ARROW_LTR: char = '\u{e0b1}';
const POWERLINE_TRIANGLE_RTL: char = '\u{e0b2}';
const POWERLINE_ARROW_RTL: char = '\u{e0b3}';
/// Returns the rasterized glyph if the character is part of the built-in font.
pub fn builtin_glyph(
character: char,
@ -26,7 +31,9 @@ pub fn builtin_glyph(
// Box drawing characters and block elements.
'\u{2500}'..='\u{259f}' => box_drawing(character, metrics, offset),
// Powerline symbols: '','','',''
'\u{e0b0}'..='\u{e0b3}' => powerline_drawing(character, metrics, offset),
POWERLINE_TRIANGLE_LTR..=POWERLINE_ARROW_RTL => {
powerline_drawing(character, metrics, offset)
},
_ => return None,
};
@ -42,8 +49,7 @@ fn box_drawing(character: char, metrics: &Metrics, offset: &Delta<i8>) -> Raster
// Ensure that width and height is at least one.
let height = (metrics.line_height as i32 + offset.y as i32).max(1) as usize;
let width = (metrics.average_advance as i32 + offset.x as i32).max(1) as usize;
// Use one eight of the cell width, since this is used as a step size for block elements.
let stroke_size = cmp::max((width as f32 / 8.).round() as usize, 1);
let stroke_size = calculate_stroke_size(width);
let heavy_stroke_size = stroke_size * 2;
// Certain symbols require larger canvas than the cell itself, since for proper contiguous
@ -500,106 +506,50 @@ fn box_drawing(character: char, metrics: &Metrics, offset: &Delta<i8>) -> Raster
fn powerline_drawing(character: char, metrics: &Metrics, offset: &Delta<i8>) -> RasterizedGlyph {
let height = (metrics.line_height as i32 + offset.y as i32) as usize;
let width = (metrics.average_advance as i32 + offset.x as i32) as usize;
// Use one eight of the cell width, since this is used as a step size for block elements.
let stroke_size = cmp::max((width as f32 / 8.).round() as usize, 1) as f32;
let extra_thickness = calculate_stroke_size(width) as i32 - 1;
let mut canvas = Canvas::new(width, height);
let y_center = (height - 1) as f32 / 2.;
// Start with offset `1` and draw until the intersection of the f(x) = x + 1 and
// g(x) = H - x + 1 lines. The intersection happens when f(x) = g(x), which is at
// x = H/2 (`y_center`).
let from_y = 1;
let x_end = y_center.floor();
let y_end = (height - from_y - 1) as f32;
let slope = 1;
let top_y = 1;
let bottom_y = height as i32 - top_y - 1;
// Pick the start point outside of the canvas to even-out the start.
let from_x = 0.;
let to_x = x_end;
canvas.draw_line_grid(from_x, from_y as f32, to_x, y_center.floor());
canvas.draw_line_grid(from_x, y_end, to_x, y_center.ceil());
// Start with offset `1` and draw until the intersection of the f(x) = slope * x + 1 and
// g(x) = H - slope * x - 1 lines. The intersection happens when f(x) = g(x), which is at
// x = (H - 2) / (2 * slope).
let x_intersection = (height as i32 + 1) / 2 - 1;
// For regular arrows we handle thickness by drawing 2 angle arrows and then just filling
// the contents between them.
if (character == '\u{e0b1}' || character == '\u{e0b3}') && stroke_size > 1. {
// The default line is of stroke size 1, so the 0.5 is computed by subtracting 1 from
// stroke_size and then adding 0.5 to to put the target in the center of the cell.
let to_x = x_end - stroke_size;
canvas.draw_line_grid(from_x, from_y as f32 + stroke_size, to_x, y_center.floor());
canvas.draw_line_grid(from_x, y_end - stroke_size, to_x, y_center.ceil());
}
let top_line = (0..x_intersection).map(|x| line_equation(slope, x, top_y));
let bottom_line = (0..x_intersection).map(|x| line_equation(-slope, x, bottom_y));
let buffer = canvas.buffer_mut();
if character == '\u{e0b0}' || character == '\u{e0b2}' {
for row in from_y..height - from_y {
let row_offset = row * width;
for index in 1..width {
let index = row_offset + index;
if buffer[index - 1]._r > buffer[index]._r && buffer[index]._r == 0 {
break;
}
// Inner lines to make arrows thicker.
let mut top_inner_line = (0..x_intersection - extra_thickness)
.map(|x| line_equation(slope, x, top_y + extra_thickness));
let mut bottom_inner_line = (0..x_intersection - extra_thickness)
.map(|x| line_equation(-slope, x, bottom_y - extra_thickness));
buffer[index - 1] = COLOR_FILL;
}
}
} else if stroke_size > 1. {
// Find the bottom/top most points of extra line we draw, so we can properly set the
// `start`.
// NOTE: top_line and bottom_line have the same amount of iterations.
for (p1, p2) in top_line.zip(bottom_line) {
if character == POWERLINE_TRIANGLE_LTR || character == POWERLINE_TRIANGLE_RTL {
canvas.draw_rect(0., p1.1, p1.0 + 1., 1., COLOR_FILL);
canvas.draw_rect(0., p2.1, p2.0 + 1., 1., COLOR_FILL);
} else if character == POWERLINE_ARROW_LTR || character == POWERLINE_ARROW_RTL {
let p3 = top_inner_line.next().unwrap_or(p2);
let p4 = bottom_inner_line.next().unwrap_or(p1);
let mut y1 = 0;
for row in (0..height / 2).rev() {
if buffer[row * width]._r != 0 {
y1 = row;
// If we can't fit the entire arrow in the cell, we cut off the tip of the arrow by
// drawing a rectangle between the two lines.
if p1.0 as usize + 1 == width {
canvas.draw_rect(p1.0, p1.1, 1., p2.1 - p1.1 + 1., COLOR_FILL);
break;
}
}
let mut y2 = height / 2;
for row in height / 2..height {
if buffer[row * width]._r != 0 {
y2 = row;
break;
}
}
for row in from_y..height - from_y {
let row_offset = row * width;
// Find the point on the inner line.
let mut start = 0;
if row >= y1 && row <= y2 {
for base_index in 0..width - 1 {
let index = row_offset + base_index;
if buffer[index]._r != 0 {
start = base_index + 1;
break;
}
}
}
// Find the point on the outer line.
let mut end = 0;
for base_index in (1..width).rev() {
let index = row_offset + base_index;
if buffer[index]._r != 0 {
end = base_index - 1;
break;
}
}
if (row == y1 || row == y2) && start == end {
start = 0;
}
// Fill the canvas between inner and outer points in the row.
for index in start..=end {
let index = row_offset + index;
buffer[index] = COLOR_FILL;
} else {
canvas.draw_rect(p1.0, p1.1, 1., p3.1 - p1.1 + 1., COLOR_FILL);
canvas.draw_rect(p4.0, p4.1, 1., p2.1 - p4.1 + 1., COLOR_FILL);
}
}
}
// Some glyphs are just flipped versions of others, so just flip them.
if character == '\u{e0b2}' || character == '\u{e0b3}' {
if character == POWERLINE_TRIANGLE_RTL || character == POWERLINE_ARROW_RTL {
canvas.flip_horizontal();
}
@ -831,33 +781,6 @@ impl Canvas {
}
}
/// WalkGrid line drawing from (`from_x`, `from_y`) to (`to_x`, `to_y`).
fn draw_line_grid(&mut self, from_x: f32, from_y: f32, to_x: f32, to_y: f32) {
let dx = (to_x - from_x).trunc();
let nx = dx.abs();
let dy = (to_y - from_y).trunc();
let ny = dy.abs();
let sign_x = dx.signum();
let sign_y = dy.signum();
let mut point = (from_x.trunc(), from_y.trunc());
let mut ix = 0.;
let mut iy = 0.;
while ix <= nx && iy <= ny {
self.put_pixel(point.0, point.1, COLOR_FILL);
if (0.5 + ix) / nx < (0.5 + iy) / ny {
point.0 += sign_x;
ix += 1.;
} else {
point.1 += sign_y;
iy += 1.;
}
}
}
/// Draws a part of an ellipse centered in `(0., 0.)` with `self.x_center()` and `self.y_center`
/// vertex and co-vertex respectively using a given `stroke` in the bottom-right quadrant of the
/// `Canvas` coordinate system.
@ -960,6 +883,17 @@ impl Canvas {
}
}
/// Compute line width.
fn calculate_stroke_size(cell_width: usize) -> usize {
// Use one eight of the cell width, since this is used as a step size for block elements.
cmp::max((cell_width as f32 / 8.).round() as usize, 1)
}
/// `f(x) = slope * x + offset` equation.
fn line_equation(slope: i32, x: i32, offset: i32) -> (f32, f32) {
(x as f32, (slope * x + offset) as f32)
}
#[cfg(test)]
mod tests {
use super::*;