Skip to content

Commit

Permalink
Use builtin font to draw powerline symbols
Browse files Browse the repository at this point in the history
In addition to box drawing it was decided to also draw powerline
symbols, since those are quite common and rather simple to draw with
present box drawing infra.
  • Loading branch information
kchibisov committed Nov 11, 2023
1 parent 7ea927f commit 4a26667
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 15 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Synchronized updates now use `CSI 2026` instead of legacy `DCS` variant
- In mouse mode with `Shift` pressed, mouse bindings without `Shift` are only triggered
if no exact binding (i.e. one with `Shift`) is found.
- Use built-in font for powerline symbols from `U+E0B0` to `U+E0B3`

### Fixed

Expand Down
201 changes: 187 additions & 14 deletions alacritty/src/renderer/text/builtin_font.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! Hand-rolled drawing of unicode [box drawing](http://www.unicode.org/charts/PDF/U2500.pdf)
//! and [block elements](https://www.unicode.org/charts/PDF/U2580.pdf).
//! and [block elements](https://www.unicode.org/charts/PDF/U2580.pdf), and also powerline symbols.

use std::{cmp, mem, ops};

Expand All @@ -25,6 +25,8 @@ pub fn builtin_glyph(
let mut glyph = match character {
// 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),
_ => return None,
};

Expand Down Expand Up @@ -495,6 +497,125 @@ 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 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;

// 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());

// 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 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;
}

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`.

let mut y1 = 0;
for row in (0..height / 2).rev() {
if buffer[row * width]._r != 0 {
y1 = row;
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;
}
}
}

// Some glyphs are just flipped versions of others, so just flip them.
if character == '\u{e0b2}' || character == '\u{e0b3}' {
canvas.flip_horizontal();
}

let top = height as i32 + metrics.descent as i32;
let buffer = BitmapBuffer::Rgb(canvas.into_raw());
RasterizedGlyph {
character,
top,
left: 0,
height: height as i32,
width: width as i32,
buffer,
advance: (width as i32, height as i32),
}
}

#[repr(packed)]
#[derive(Clone, Copy, Debug, Default)]
struct Pixel {
Expand Down Expand Up @@ -593,6 +714,16 @@ impl Canvas {
(start_x, end_x)
}

/// Flip horizontally.
fn flip_horizontal(&mut self) {
for row in 0..self.height {
for col in 0..self.width / 2 {
let index = row * self.width;
self.buffer.swap(index + col, index + self.width - col - 1)
}
}
}

/// Draws a horizontal straight line from (`x`, `y`) of `size` with the given `stroke_size`.
fn draw_h_line(&mut self, x: f32, y: f32, size: f32, stroke_size: usize) {
let (start_y, end_y) = self.h_line_bounds(y, stroke_size);
Expand Down Expand Up @@ -700,6 +831,33 @@ 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.
Expand Down Expand Up @@ -807,29 +965,44 @@ mod tests {
use super::*;
use crossfont::Metrics;

// Dummy metrics values to test builtin glyphs coverage.
const METRICS: Metrics = Metrics {
average_advance: 6.,
line_height: 16.,
descent: 4.,
underline_position: 2.,
underline_thickness: 2.,
strikeout_position: 2.,
strikeout_thickness: 2.,
};

#[test]
fn builtin_line_drawing_glyphs_coverage() {
// Dummy metrics values to test built-in glyphs coverage.
let metrics = Metrics {
average_advance: 6.,
line_height: 16.,
descent: 4.,
underline_position: 2.,
underline_thickness: 2.,
strikeout_position: 2.,
strikeout_thickness: 2.,
};

let offset = Default::default();
let glyph_offset = Default::default();

// Test coverage of box drawing characters.
for character in '\u{2500}'..='\u{259f}' {
assert!(builtin_glyph(character, &metrics, &offset, &glyph_offset).is_some());
assert!(builtin_glyph(character, &METRICS, &offset, &glyph_offset).is_some());
}

for character in ('\u{2450}'..'\u{2500}').chain('\u{25a0}'..'\u{2600}') {
assert!(builtin_glyph(character, &metrics, &offset, &glyph_offset).is_none());
assert!(builtin_glyph(character, &METRICS, &offset, &glyph_offset).is_none());
}
}

#[test]
fn builtin_powerline_glyphs_coverage() {
let offset = Default::default();
let glyph_offset = Default::default();

// Test coverage of box drawing characters.
for character in '\u{e0b0}'..='\u{e0b3}' {
assert!(builtin_glyph(character, &METRICS, &offset, &glyph_offset).is_some());
}

for character in ('\u{e0a0}'..'\u{e0b0}').chain('\u{e0b4}'..'\u{e0c0}') {
assert!(builtin_glyph(character, &METRICS, &offset, &glyph_offset).is_none());
}
}
}
3 changes: 2 additions & 1 deletion extra/man/alacritty.5.scd
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,8 @@ macOS: _{ family = "Menlo", style = "Regular" }_
*builtin_box_drawing* <boolean>

When _true_, Alacritty will use a custom built-in font for box drawing
characters (Unicode points _U+2500_ - _U+259f_).
characters (Unicode points _U+2500_ - _U+259F_) and powerline symbols
(Unicode points _U+E0B0_ - _U+E0B3_).

Default: _true_

Expand Down

0 comments on commit 4a26667

Please sign in to comment.