summaryrefslogtreecommitdiffstats
path: root/alacritty/src/renderer/text/builtin_font.rs
diff options
context:
space:
mode:
Diffstat (limited to 'alacritty/src/renderer/text/builtin_font.rs')
-rw-r--r--alacritty/src/renderer/text/builtin_font.rs823
1 files changed, 823 insertions, 0 deletions
diff --git a/alacritty/src/renderer/text/builtin_font.rs b/alacritty/src/renderer/text/builtin_font.rs
new file mode 100644
index 00000000..0922d3ef
--- /dev/null
+++ b/alacritty/src/renderer/text/builtin_font.rs
@@ -0,0 +1,823 @@
+//! 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).
+
+use std::{cmp, mem, ops};
+
+use crossfont::{BitmapBuffer, Metrics, RasterizedGlyph};
+
+use crate::config::ui_config::Delta;
+
+// Colors which are used for filling shade variants.
+const COLOR_FILL_ALPHA_STEP_1: Pixel = Pixel { _r: 192, _g: 192, _b: 192 };
+const COLOR_FILL_ALPHA_STEP_2: Pixel = Pixel { _r: 128, _g: 128, _b: 128 };
+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 };
+
+/// Returns the rasterized glyph if the character is part of the built-in font.
+pub fn builtin_glyph(
+ character: char,
+ metrics: &Metrics,
+ offset: &Delta<i8>,
+ glyph_offset: &Delta<i8>,
+) -> Option<RasterizedGlyph> {
+ let mut glyph = match character {
+ // Box drawing characters and block elements.
+ '\u{2500}'..='\u{259f}' => box_drawing(character, metrics, offset),
+ _ => return None,
+ };
+
+ // Since we want to ignore `glyph_offset` for the built-in font, subtract it to compensate its
+ // addition when loading glyphs in the renderer.
+ glyph.left -= glyph_offset.x as i32;
+ glyph.top -= glyph_offset.y as i32;
+
+ Some(glyph)
+}
+
+fn box_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 elemenets.
+ let stroke_size = cmp::max((width as f32 / 8.).round() as usize, 1);
+ let heavy_stroke_size = stroke_size * 2;
+
+ // Certain symbols require larger canvas than the cell itself, since for proper contiguous
+ // lines they require drawing on neighbour cells. So treat them specially early on and handle
+ // 'normal' characters later.
+ let mut canvas = match character {
+ // Diagonals: '╱', '╲', '╳'.
+ '\u{2571}'..='\u{2573}' => {
+ // Last coordinates.
+ let x_end = width as f32;
+ let mut y_end = height as f32;
+
+ let top = height as i32 + metrics.descent as i32 + stroke_size as i32;
+ let height = height + 2 * stroke_size;
+ let mut canvas = Canvas::new(width, height + 2 * stroke_size);
+
+ // The offset that we should take into account when drawing, since we've enlarged
+ // buffer vertically by twice of that amount.
+ let y_offset = stroke_size as f32;
+ y_end += y_offset;
+
+ let k = y_end / x_end;
+ let f_x = |x: f32, h: f32| -> f32 { -1. * k * x + h + y_offset };
+ let g_x = |x: f32, h: f32| -> f32 { k * x + h + y_offset };
+
+ let from_x = 0.;
+ let to_x = x_end + 1.;
+ for stroke_size in 0..2 * stroke_size {
+ let stroke_size = stroke_size as f32 / 2.;
+ if character == '\u{2571}' || character == '\u{2573}' {
+ let h = y_end - stroke_size as f32;
+ let from_y = f_x(from_x, h);
+ let to_y = f_x(to_x, h);
+ canvas.draw_line(from_x, from_y, to_x, to_y);
+ }
+ if character == '\u{2572}' || character == '\u{2573}' {
+ let from_y = g_x(from_x, stroke_size as f32);
+ let to_y = g_x(to_x, stroke_size as f32);
+ canvas.draw_line(from_x, from_y, to_x, to_y);
+ }
+ }
+
+ let buffer = BitmapBuffer::Rgb(canvas.into_raw());
+ return RasterizedGlyph {
+ character,
+ top,
+ left: 0,
+ height: height as i32,
+ width: width as i32,
+ buffer,
+ };
+ },
+ _ => Canvas::new(width, height),
+ };
+
+ match character {
+ // Horizontal dashes: '┄', '┅', '┈', '┉', '╌', '╍'.
+ '\u{2504}' | '\u{2505}' | '\u{2508}' | '\u{2509}' | '\u{254c}' | '\u{254d}' => {
+ let (num_gaps, stroke_size) = match character {
+ '\u{2504}' => (2, stroke_size),
+ '\u{2505}' => (2, heavy_stroke_size),
+ '\u{2508}' => (3, stroke_size),
+ '\u{2509}' => (3, heavy_stroke_size),
+ '\u{254c}' => (1, stroke_size),
+ '\u{254d}' => (1, heavy_stroke_size),
+ _ => unreachable!(),
+ };
+
+ let dash_gap_len = cmp::max(width / 8, 1);
+ let dash_len =
+ cmp::max(width.saturating_sub(dash_gap_len * num_gaps) / (num_gaps + 1), 1);
+ let y = canvas.y_center();
+ for gap in 0..=num_gaps {
+ let x = cmp::min(gap * (dash_len + dash_gap_len), width);
+ canvas.draw_h_line(x as f32, y, dash_len as f32, stroke_size);
+ }
+ },
+ // Vertical dashes: '┆', '┇', '┊', '┋', '╎', '╏'.
+ '\u{2506}' | '\u{2507}' | '\u{250a}' | '\u{250b}' | '\u{254e}' | '\u{254f}' => {
+ let (num_gaps, stroke_size) = match character {
+ '\u{2506}' => (2, stroke_size),
+ '\u{2507}' => (2, heavy_stroke_size),
+ '\u{250a}' => (3, stroke_size),
+ '\u{250b}' => (3, heavy_stroke_size),
+ '\u{254e}' => (1, stroke_size),
+ '\u{254f}' => (1, heavy_stroke_size),
+ _ => unreachable!(),
+ };
+
+ let dash_gap_len = cmp::max(height / 8, 1);
+ let dash_len =
+ cmp::max(height.saturating_sub(dash_gap_len * num_gaps) / (num_gaps + 1), 1);
+ let x = canvas.x_center();
+ for gap in 0..=num_gaps {
+ let y = cmp::min(gap * (dash_len + dash_gap_len), height);
+ canvas.draw_v_line(x, y as f32, dash_len as f32, stroke_size);
+ }
+ },
+ // Horizontal lines: '─', '━', '╴', '╶', '╸', '╺'.
+ // Vertical lines: '│', '┃', '╵', '╷', '╹', '╻'.
+ // Light and heavy line box components:
+ // '┌','┍','┎','┏','┐','┑','┒','┓','└','┕','┖','┗','┘','┙','┚','┛',├','┝','┞','┟','┠','┡',
+ // '┢','┣','┤','┥','┦','┧','┨','┩','┪','┫','┬','┭','┮','┯','┰','┱','┲','┳','┴','┵','┶','┷',
+ // '┸','┹','┺','┻','┼','┽','┾','┿','╀','╁','╂','╃','╄','╅','╆','╇','╈','╉','╊','╋'.
+ // Mixed light and heavy lines: '╼', '╽', '╾', '╿'.
+ '\u{2500}'..='\u{2503}' | '\u{250c}'..='\u{254b}' | '\u{2574}'..='\u{257f}' => {
+ // Left horizontal line.
+ let stroke_size_h1 = match character {
+ '\u{2500}' | '\u{2510}' | '\u{2512}' | '\u{2518}' | '\u{251a}' | '\u{2524}'
+ | '\u{2526}' | '\u{2527}' | '\u{2528}' | '\u{252c}' | '\u{252e}' | '\u{2530}'
+ | '\u{2532}' | '\u{2534}' | '\u{2536}' | '\u{2538}' | '\u{253a}' | '\u{253c}'
+ | '\u{253e}' | '\u{2540}' | '\u{2541}' | '\u{2542}' | '\u{2544}' | '\u{2546}'
+ | '\u{254a}' | '\u{2574}' | '\u{257c}' => stroke_size,
+ '\u{2501}' | '\u{2511}' | '\u{2513}' | '\u{2519}' | '\u{251b}' | '\u{2525}'
+ | '\u{2529}' | '\u{252a}' | '\u{252b}' | '\u{252d}' | '\u{252f}' | '\u{2531}'
+ | '\u{2533}' | '\u{2535}' | '\u{2537}' | '\u{2539}' | '\u{253b}' | '\u{253d}'
+ | '\u{253f}' | '\u{2543}' | '\u{2545}' | '\u{2547}' | '\u{2548}' | '\u{2549}'
+ | '\u{254b}' | '\u{2578}' | '\u{257e}' => heavy_stroke_size,
+ _ => 0,
+ };
+ // Right horizontal line.
+ let stroke_size_h2 = match character {
+ '\u{2500}' | '\u{250c}' | '\u{250e}' | '\u{2514}' | '\u{2516}' | '\u{251c}'
+ | '\u{251e}' | '\u{251f}' | '\u{2520}' | '\u{252c}' | '\u{252d}' | '\u{2530}'
+ | '\u{2531}' | '\u{2534}' | '\u{2535}' | '\u{2538}' | '\u{2539}' | '\u{253c}'
+ | '\u{253d}' | '\u{2540}' | '\u{2541}' | '\u{2542}' | '\u{2543}' | '\u{2545}'
+ | '\u{2549}' | '\u{2576}' | '\u{257e}' => stroke_size,
+ '\u{2501}' | '\u{250d}' | '\u{250f}' | '\u{2515}' | '\u{2517}' | '\u{251d}'
+ | '\u{2521}' | '\u{2522}' | '\u{2523}' | '\u{252e}' | '\u{252f}' | '\u{2532}'
+ | '\u{2533}' | '\u{2536}' | '\u{2537}' | '\u{253a}' | '\u{253b}' | '\u{253e}'
+ | '\u{253f}' | '\u{2544}' | '\u{2546}' | '\u{2547}' | '\u{2548}' | '\u{254a}'
+ | '\u{254b}' | '\u{257a}' | '\u{257c}' => heavy_stroke_size,
+ _ => 0,
+ };
+ // Top vertical line.
+ let stroke_size_v1 = match character {
+ '\u{2502}' | '\u{2514}' | '\u{2515}' | '\u{2518}' | '\u{2519}' | '\u{251c}'
+ | '\u{251d}' | '\u{251f}' | '\u{2522}' | '\u{2524}' | '\u{2525}' | '\u{2527}'
+ | '\u{252a}' | '\u{2534}' | '\u{2535}' | '\u{2536}' | '\u{2537}' | '\u{253c}'
+ | '\u{253d}' | '\u{253e}' | '\u{253f}' | '\u{2541}' | '\u{2545}' | '\u{2546}'
+ | '\u{2548}' | '\u{2575}' | '\u{257d}' => stroke_size,
+ '\u{2503}' | '\u{2516}' | '\u{2517}' | '\u{251a}' | '\u{251b}' | '\u{251e}'
+ | '\u{2520}' | '\u{2521}' | '\u{2523}' | '\u{2526}' | '\u{2528}' | '\u{2529}'
+ | '\u{252b}' | '\u{2538}' | '\u{2539}' | '\u{253a}' | '\u{253b}' | '\u{2540}'
+ | '\u{2542}' | '\u{2543}' | '\u{2544}' | '\u{2547}' | '\u{2549}' | '\u{254a}'
+ | '\u{254b}' | '\u{2579}' | '\u{257f}' => heavy_stroke_size,
+ _ => 0,
+ };
+ // Bottom vertical line.
+ let stroke_size_v2 = match character {
+ '\u{2502}' | '\u{250c}' | '\u{250d}' | '\u{2510}' | '\u{2511}' | '\u{251c}'
+ | '\u{251d}' | '\u{251e}' | '\u{2521}' | '\u{2524}' | '\u{2525}' | '\u{2526}'
+ | '\u{2529}' | '\u{252c}' | '\u{252d}' | '\u{252e}' | '\u{252f}' | '\u{253c}'
+ | '\u{253d}' | '\u{253e}' | '\u{253f}' | '\u{2540}' | '\u{2543}' | '\u{2544}'
+ | '\u{2547}' | '\u{2577}' | '\u{257f}' => stroke_size,
+ '\u{2503}' | '\u{250e}' | '\u{250f}' | '\u{2512}' | '\u{2513}' | '\u{251f}'
+ | '\u{2520}' | '\u{2522}' | '\u{2523}' | '\u{2527}' | '\u{2528}' | '\u{252a}'
+ | '\u{252b}' | '\u{2530}' | '\u{2531}' | '\u{2532}' | '\u{2533}' | '\u{2541}'
+ | '\u{2542}' | '\u{2545}' | '\u{2546}' | '\u{2548}' | '\u{2549}' | '\u{254a}'
+ | '\u{254b}' | '\u{257b}' | '\u{257d}' => heavy_stroke_size,
+ _ => 0,
+ };
+
+ let x_v = canvas.x_center();
+ let y_h = canvas.y_center();
+
+ let v_line_bounds_top = canvas.v_line_bounds(x_v, stroke_size_v1);
+ let v_line_bounds_bot = canvas.v_line_bounds(x_v, stroke_size_v2);
+ let h_line_bounds_left = canvas.h_line_bounds(y_h, stroke_size_h1);
+ let h_line_bounds_right = canvas.h_line_bounds(y_h, stroke_size_h2);
+
+ let size_h1 = cmp::max(v_line_bounds_top.1 as i32, v_line_bounds_bot.1 as i32) as f32;
+ let x_h = cmp::min(v_line_bounds_top.0 as i32, v_line_bounds_bot.0 as i32) as f32;
+ let size_h2 = width as f32 - x_h;
+
+ let size_v1 =
+ cmp::max(h_line_bounds_left.1 as i32, h_line_bounds_right.1 as i32) as f32;
+ let y_v = cmp::min(h_line_bounds_left.0 as i32, h_line_bounds_right.0 as i32) as f32;
+ let size_v2 = height as f32 - y_v;
+
+ // Left horizontal line.
+ canvas.draw_h_line(0., y_h, size_h1, stroke_size_h1);
+ // Right horizontal line.
+ canvas.draw_h_line(x_h, y_h, size_h2, stroke_size_h2);
+ // Top vertical line.
+ canvas.draw_v_line(x_v, 0., size_v1, stroke_size_v1);
+ // Bottom vertical line.
+ canvas.draw_v_line(x_v, y_v, size_v2, stroke_size_v2);
+ },
+ // Light and double line box components:
+ // '═','║','╒','╓','╔','╕','╖','╗','╘','╙','╚','╛','╜','╝','╞','╟','╠','╡','╢','╣','╤','╥',
+ // '╦','╧','╨','╩','╪','╫','╬'.
+ '\u{2550}'..='\u{256c}' => {
+ let v_lines = match character {
+ '\u{2552}' | '\u{2555}' | '\u{2558}' | '\u{255b}' | '\u{255e}' | '\u{2561}'
+ | '\u{2564}' | '\u{2567}' | '\u{256a}' => (canvas.x_center(), canvas.x_center()),
+ _ => {
+ let v_line_bounds = canvas.v_line_bounds(canvas.x_center(), stroke_size);
+ let left_line = cmp::max(v_line_bounds.0 as i32 - 1, 0) as f32;
+ let right_line = cmp::min(v_line_bounds.1 as i32 + 1, width as i32) as f32;
+
+ (left_line, right_line)
+ },
+ };
+ let h_lines = match character {
+ '\u{2553}' | '\u{2556}' | '\u{2559}' | '\u{255c}' | '\u{255f}' | '\u{2562}'
+ | '\u{2565}' | '\u{2568}' | '\u{256b}' => (canvas.y_center(), canvas.y_center()),
+ _ => {
+ let h_line_bounds = canvas.h_line_bounds(canvas.y_center(), stroke_size);
+ let top_line = cmp::max(h_line_bounds.0 as i32 - 1, 0) as f32;
+ let bottom_line = cmp::min(h_line_bounds.1 as i32 + 1, height as i32) as f32;
+
+ (top_line, bottom_line)
+ },
+ };
+
+ // Get bounds for each double line we could have.
+ let v_left_bounds = canvas.v_line_bounds(v_lines.0, stroke_size);
+ let v_right_bounds = canvas.v_line_bounds(v_lines.1, stroke_size);
+ let h_top_bounds = canvas.h_line_bounds(h_lines.0, stroke_size);
+ let h_bot_bounds = canvas.h_line_bounds(h_lines.1, stroke_size);
+
+ let height = height as f32;
+ let width = width as f32;
+
+ // Left horizontal part.
+ let (top_left_size, bot_left_size) = match character {
+ '\u{2550}' | '\u{256b}' => (canvas.x_center(), canvas.x_center()),
+ '\u{2555}'..='\u{2557}' => (v_right_bounds.1, v_left_bounds.1),
+ '\u{255b}'..='\u{255d}' => (v_left_bounds.1, v_right_bounds.1),
+ '\u{2561}'..='\u{2563}' | '\u{256a}' | '\u{256c}' => {
+ (v_left_bounds.1, v_left_bounds.1)
+ },
+ '\u{2564}'..='\u{2566}' => (canvas.x_center(), v_left_bounds.1),
+ '\u{2569}'..='\u{2569}' => (v_left_bounds.1, canvas.x_center()),
+ _ => (0., 0.),
+ };
+
+ // Right horizontal part.
+ let (top_right_x, bot_right_x, right_size) = match character {
+ '\u{2550}' | '\u{2565}' | '\u{256b}' => {
+ (canvas.x_center(), canvas.x_center(), width)
+ },
+ '\u{2552}'..='\u{2554}' | '\u{2568}' => (v_left_bounds.0, v_right_bounds.0, width),
+ '\u{2558}'..='\u{255a}' => (v_right_bounds.0, v_left_bounds.0, width),
+ '\u{255e}'..='\u{2560}' | '\u{256a}' | '\u{256c}' => {
+ (v_right_bounds.0, v_right_bounds.0, width)
+ },
+ '\u{2564}' | '\u{2566}' => (canvas.x_center(), v_right_bounds.0, width),
+ '\u{2567}' | '\u{2569}' => (v_right_bounds.0, canvas.x_center(), width),
+ _ => (0., 0., 0.),
+ };
+
+ // Top vertical part.
+ let (left_top_size, right_top_size) = match character {
+ '\u{2551}' | '\u{256a}' => (canvas.y_center(), canvas.y_center()),
+ '\u{2558}'..='\u{255c}' | '\u{2567}' | '\u{2568}' => {
+ (h_bot_bounds.1, h_top_bounds.1)
+ },
+ '\u{255d}' => (h_top_bounds.1, h_bot_bounds.1),
+ '\u{255e}'..='\u{2560}' => (canvas.y_center(), h_top_bounds.1),
+ '\u{2561}'..='\u{2563}' => (h_top_bounds.1, canvas.y_center()),
+ '\u{2569}' | '\u{256b}' | '\u{256c}' => (h_top_bounds.1, h_top_bounds.1),
+ _ => (0., 0.),
+ };
+
+ // Bottom vertical part.
+ let (left_bot_y, right_bot_y, bottom_size) = match character {
+ '\u{2551}' | '\u{256a}' => (canvas.y_center(), canvas.y_center(), height),
+ '\u{2552}'..='\u{2554}' => (h_top_bounds.0, h_bot_bounds.0, height),
+ '\u{2555}'..='\u{2557}' => (h_bot_bounds.0, h_top_bounds.0, height),
+ '\u{255e}'..='\u{2560}' => (canvas.y_center(), h_bot_bounds.0, height),
+ '\u{2561}'..='\u{2563}' => (h_bot_bounds.0, canvas.y_center(), height),
+ '\u{2564}'..='\u{2566}' | '\u{256b}' | '\u{256c}' => {
+ (h_bot_bounds.0, h_bot_bounds.0, height)
+ },
+ _ => (0., 0., 0.),
+ };
+
+ // Left horizontal line.
+ canvas.draw_h_line(0., h_lines.0, top_left_size, stroke_size);
+ canvas.draw_h_line(0., h_lines.1, bot_left_size, stroke_size);
+
+ // Right horizontal line.
+ canvas.draw_h_line(top_right_x, h_lines.0, right_size, stroke_size);
+ canvas.draw_h_line(bot_right_x, h_lines.1, right_size, stroke_size);
+
+ // Top vertical line.
+ canvas.draw_v_line(v_lines.0, 0., left_top_size, stroke_size);
+ canvas.draw_v_line(v_lines.1, 0., right_top_size, stroke_size);
+
+ // Bottom vertical line.
+ canvas.draw_v_line(v_lines.0, left_bot_y, bottom_size, stroke_size);
+ canvas.draw_v_line(v_lines.1, right_bot_y, bottom_size, stroke_size);
+ },
+ // Arcs: '╭', '╮', '╯', '╰'.
+ '\u{256d}' | '\u{256e}' | '\u{256f}' | '\u{2570}' => {
+ canvas.draw_ellipse_arc(stroke_size);
+
+ // Mirror `X` axis.
+ if character == '\u{256d}' || character == '\u{2570}' {
+ let center = canvas.x_center() as usize;
+
+ let extra_offset = if stroke_size % 2 == width % 2 { 0 } else { 1 };
+
+ let buffer = canvas.buffer_mut();
+ for y in 1..height {
+ let left = (y - 1) * width;
+ let right = y * width - 1;
+ if extra_offset != 0 {
+ buffer[right] = buffer[left];
+ }
+ for offset in 0..center {
+ buffer.swap(left + offset, right - offset - extra_offset);
+ }
+ }
+ }
+ // Mirror `Y` axis.
+ if character == '\u{256d}' || character == '\u{256e}' {
+ let center = canvas.y_center() as usize;
+
+ let extra_offset = if stroke_size % 2 == height % 2 { 0 } else { 1 };
+
+ let buffer = canvas.buffer_mut();
+ if extra_offset != 0 {
+ let bottom_row = (height - 1) * width;
+ for index in 0..width {
+ buffer[bottom_row + index] = buffer[index];
+ }
+ }
+ for offset in 1..=center {
+ let top_row = (offset - 1) * width;
+ let bottom_row = (height - offset - extra_offset) * width;
+ for index in 0..width {
+ buffer.swap(top_row + index, bottom_row + index);
+ }
+ }
+ }
+ },
+ // Parts of full block: '▀', '▁', '▂', '▃', '▄', '▅', '▆', '▇', '▔', '▉', '▊', '▋', '▌',
+ // '▍', '▎', '▏', '▐', '▕'.
+ '\u{2580}'..='\u{2587}' | '\u{2589}'..='\u{2590}' | '\u{2594}' | '\u{2595}' => {
+ let width = width as f32;
+ let height = height as f32;
+ let mut rect_width = match character {
+ '\u{2589}' => width * 7. / 8.,
+ '\u{258a}' => width * 6. / 8.,
+ '\u{258b}' => width * 5. / 8.,
+ '\u{258c}' => width * 4. / 8.,
+ '\u{258d}' => width * 3. / 8.,
+ '\u{258e}' => width * 2. / 8.,
+ '\u{258f}' => width * 1. / 8.,
+ '\u{2590}' => width * 4. / 8.,
+ '\u{2595}' => width * 1. / 8.,
+ _ => width,
+ };
+
+ let (mut rect_height, mut y) = match character {
+ '\u{2580}' => (height * 4. / 8., height * 8. / 8.),
+ '\u{2581}' => (height * 1. / 8., height * 1. / 8.),
+ '\u{2582}' => (height * 2. / 8., height * 2. / 8.),
+ '\u{2583}' => (height * 3. / 8., height * 3. / 8.),
+ '\u{2584}' => (height * 4. / 8., height * 4. / 8.),
+ '\u{2585}' => (height * 5. / 8., height * 5. / 8.),
+ '\u{2586}' => (height * 6. / 8., height * 6. / 8.),
+ '\u{2587}' => (height * 7. / 8., height * 7. / 8.),
+ '\u{2594}' => (height * 1. / 8., height * 8. / 8.),
+ _ => (height, height),
+ };
+
+ // Fix `y` coordinates.
+ y = height - y;
+
+ // Ensure that resulted glyph will be visible and also round sizes instead of straight
+ // flooring them.
+ rect_width = cmp::max(rect_width.round() as i32, 1) as f32;
+ rect_height = cmp::max(rect_height.round() as i32, 1) as f32;
+
+ let x = match character {
+ '\u{2590}' => canvas.x_center(),
+ '\u{2595}' => width as f32 - rect_width,
+ _ => 0.,
+ };
+
+ canvas.draw_rect(x, y, rect_width, rect_height, COLOR_FILL);
+ },
+ // Shades: '░', '▒', '▓', '█'.
+ '\u{2588}' | '\u{2591}' | '\u{2592}' | '\u{2593}' => {
+ let color = match character {
+ '\u{2588}' => COLOR_FILL,
+ '\u{2591}' => COLOR_FILL_ALPHA_STEP_3,
+ '\u{2592}' => COLOR_FILL_ALPHA_STEP_2,
+ '\u{2593}' => COLOR_FILL_ALPHA_STEP_1,
+ _ => unreachable!(),
+ };
+ canvas.fill(color);
+ },
+ // Quadrants: '▖', '▗', '▘', '▙', '▚', '▛', '▜', '▝', '▞', '▟'.
+ '\u{2596}'..='\u{259F}' => {
+ let (w_second, h_second) = match character {
+ '\u{2598}' | '\u{2599}' | '\u{259a}' | '\u{259b}' | '\u{259c}' => {
+ (canvas.x_center(), canvas.y_center())
+ },
+ _ => (0., 0.),
+ };
+ let (w_first, h_first) = match character {
+ '\u{259b}' | '\u{259c}' | '\u{259d}' | '\u{259e}' | '\u{259f}' => {
+ (canvas.x_center(), canvas.y_center())
+ },
+ _ => (0., 0.),
+ };
+ let (w_third, h_third) = match character {
+ '\u{2596}' | '\u{2599}' | '\u{259b}' | '\u{259e}' | '\u{259f}' => {
+ (canvas.x_center(), canvas.y_center())
+ },
+ _ => (0., 0.),
+ };
+ let (w_fourth, h_fourth) = match character {
+ '\u{2597}' | '\u{2599}' | '\u{259a}' | '\u{259c}' | '\u{259f}' => {
+ (canvas.x_center(), canvas.y_center())
+ },
+ _ => (0., 0.),
+ };
+
+ // Second quadrant.
+ canvas.draw_rect(0., 0., w_second, h_second, COLOR_FILL);
+ // First quadrant.
+ canvas.draw_rect(canvas.x_center(), 0., w_first, h_first, COLOR_FILL);
+ // Third quadrant.
+ canvas.draw_rect(0., canvas.y_center(), w_third, h_third, COLOR_FILL);
+ // Fourth quadrant.
+ canvas.draw_rect(canvas.x_center(), canvas.y_center(), w_fourth, h_fourth, COLOR_FILL);
+ },
+ _ => unreachable!(),
+ }
+
+ 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 }
+}
+
+#[repr(packed)]
+#[derive(Clone, Copy, Debug, Default)]
+struct Pixel {
+ _r: u8,
+ _g: u8,
+ _b: u8,
+}
+
+impl Pixel {
+ fn gray(color: u8) -> Self {
+ Self { _r: color, _g: color, _b: color }
+ }
+}
+
+impl ops::Add for Pixel {
+ type Output = Pixel;
+
+ fn add(self, rhs: Pixel) -> Self::Output {
+ let _r = self._r.saturating_add(rhs._r);
+ let _g = self._g.saturating_add(rhs._g);
+ let _b = self._b.saturating_add(rhs._b);
+ Pixel { _r, _g, _b }
+ }
+}
+
+impl ops::Div<u8> for Pixel {
+ type Output = Pixel;
+
+ fn div(self, rhs: u8) -> Self::Output {
+ let _r = self._r / rhs;
+ let _g = self._g / rhs;
+ let _b = self._b / rhs;
+ Pixel { _r, _g, _b }
+ }
+}
+
+/// Canvas which is used for simple line drawing operations.
+///
+/// The coordinate system is the following:
+///
+/// 0 x
+/// --------------→
+/// |
+/// |
+/// |
+/// |
+/// |
+/// |
+/// y↓
+struct Canvas {
+ /// Canvas width.
+ width: usize,
+
+ /// Canvas height.
+ height: usize,
+
+ /// Canvas buffer we draw on.
+ buffer: Vec<Pixel>,
+}
+
+impl Canvas {
+ /// Builds new `Canvas` for line drawing with the given `width` and `height` with default color.
+ fn new(width: usize, height: usize) -> Self {
+ let buffer = vec![Pixel::default(); width * height];
+ Self { width, height, buffer }
+ }
+
+ /// Vertical center of the `Canvas`.
+ fn y_center(&self) -> f32 {
+ self.height as f32 / 2.
+ }
+
+ /// Horizontal center of the `Canvas`.
+ fn x_center(&self) -> f32 {
+ self.width as f32 / 2.
+ }
+
+ /// Canvas underlying buffer for direct manipulation
+ fn buffer_mut(&mut self) -> &mut [Pixel] {
+ &mut self.buffer
+ }
+
+ /// Gives bounds for horizontal straight line on `y` with `stroke_size`.
+ fn h_line_bounds(&self, y: f32, stroke_size: usize) -> (f32, f32) {
+ let start_y = cmp::max((y - stroke_size as f32 / 2.) as i32, 0) as f32;
+ let end_y = cmp::min((y + stroke_size as f32 / 2.) as i32, self.height as i32) as f32;
+
+ (start_y, end_y)
+ }
+
+ /// Gives bounds for vertical straight line on `y` with `stroke_size`.
+ fn v_line_bounds(&self, x: f32, stroke_size: usize) -> (f32, f32) {
+ let start_x = cmp::max((x - stroke_size as f32 / 2.) as i32, 0) as f32;
+ let end_x = cmp::min((x + stroke_size as f32 / 2.) as i32, self.width as i32) as f32;
+
+ (start_x, end_x)
+ }
+
+ /// 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);
+ self.draw_rect(x, start_y as f32, size, (end_y - start_y) as f32, COLOR_FILL);
+ }
+
+ /// Draws a vertical straight line from (`x`, `y`) of `size` with the given `stroke_size`.
+ fn draw_v_line(&mut self, x: f32, y: f32, size: f32, stroke_size: usize) {
+ let (start_x, end_x) = self.v_line_bounds(x, stroke_size);
+ self.draw_rect(start_x as f32, y, (end_x - start_x) as f32, size, COLOR_FILL);
+ }
+
+ /// Draws a rect from the (`x`, `y`) of the given `width` and `height` using `color`.
+ fn draw_rect(&mut self, x: f32, y: f32, width: f32, height: f32, color: Pixel) {
+ let start_x = x as usize;
+ let end_x = cmp::min((x + width) as usize, self.width);
+ let start_y = y as usize;
+ let end_y = cmp::min((y + height) as usize, self.height);
+ for y in start_y..end_y {
+ let y = y * self.width;
+ self.buffer[start_x + y..end_x + y].fill(color);
+ }
+ }
+
+ /// Put pixel into buffer with the given color if the color is brighter than the one buffer
+ /// already has in place.
+ #[inline]
+ fn put_pixel(&mut self, x: f32, y: f32, color: Pixel) {
+ if x < 0. || y < 0. || x > self.width as f32 - 1. || y > self.height as f32 - 1. {
+ return;
+ }
+ let index = x as usize + y as usize * self.width;
+ if color._r > self.buffer[index]._r {
+ self.buffer[index] = color;
+ }
+ }
+
+ /// Xiaolin Wu's line drawing from (`from_x`, `from_y`) to (`to_x`, `to_y`).
+ fn draw_line(&mut self, mut from_x: f32, mut from_y: f32, mut to_x: f32, mut to_y: f32) {
+ let steep = (to_y - from_y).abs() > (to_x - from_x).abs();
+ if steep {
+ mem::swap(&mut from_x, &mut from_y);
+ mem::swap(&mut to_x, &mut to_y);
+ }
+ if from_x > to_x {
+ mem::swap(&mut from_x, &mut to_x);
+ mem::swap(&mut from_y, &mut to_y);
+ }
+
+ let delta_x = to_x - from_x;
+ let delta_y = to_y - from_y;
+ let gradient = if delta_x.abs() <= f32::EPSILON { 1. } else { delta_y / delta_x };
+
+ let x_end = f32::round(from_x);
+ let y_end = from_y + gradient * (x_end - from_x);
+ let x_gap = 1. - (from_x + 0.5).fract();
+
+ let xpxl1 = x_end;
+ let ypxl1 = y_end.trunc();
+
+ let color_1 = Pixel::gray(((1. - y_end.fract()) * x_gap * COLOR_FILL._r as f32) as u8);
+ let color_2 = Pixel::gray((y_end.fract() * x_gap * COLOR_FILL._r as f32) as u8);
+ if steep {
+ self.put_pixel(ypxl1, xpxl1, color_1);
+ self.put_pixel(ypxl1 + 1., xpxl1, color_2);
+ } else {
+ self.put_pixel(xpxl1, ypxl1, color_1);
+ self.put_pixel(xpxl1 + 1., ypxl1, color_2);
+ }
+
+ let mut intery = y_end + gradient;
+
+ let x_end = f32::round(to_x);
+ let y_end = to_y + gradient * (x_end - to_x);
+ let x_gap = (to_x + 0.5).fract();
+ let xpxl2 = x_end;
+ let ypxl2 = y_end.trunc();
+
+ let color_1 = Pixel::gray(((1. - y_end.fract()) * x_gap * COLOR_FILL._r as f32) as u8);
+ let color_2 = Pixel::gray((y_end.fract() * x_gap * COLOR_FILL._r as f32) as u8);
+ if steep {
+ self.put_pixel(ypxl2, xpxl2, color_1);
+ self.put_pixel(ypxl2 + 1., xpxl2, color_2);
+ } else {
+ self.put_pixel(xpxl2, ypxl2, color_1);
+ self.put_pixel(xpxl2, ypxl2 + 1., color_2);
+ }
+
+ if steep {
+ for x in xpxl1 as i32 + 1..xpxl2 as i32 {
+ let color_1 = Pixel::gray(((1. - intery.fract()) * COLOR_FILL._r as f32) as u8);
+ let color_2 = Pixel::gray((intery.fract() * COLOR_FILL._r as f32) as u8);
+ self.put_pixel(intery.trunc(), x as f32, color_1);
+ self.put_pixel(intery.trunc() + 1., x as f32, color_2);
+ intery += gradient;
+ }
+ } else {
+ for x in xpxl1 as i32 + 1..xpxl2 as i32 {
+ let color_1 = Pixel::gray(((1. - intery.fract()) * COLOR_FILL._r as f32) as u8);
+ let color_2 = Pixel::gray((intery.fract() * COLOR_FILL._r as f32) as u8);
+ self.put_pixel(x as f32, intery.trunc(), color_1);
+ self.put_pixel(x as f32, intery.trunc() + 1., color_2);
+ intery += gradient;
+ }
+ }
+ }
+
+ /// 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.
+ fn draw_ellipse_arc(&mut self, stroke_size: usize) {
+ fn colors_with_error(error: f32, max_transparancy: f32) -> (Pixel, Pixel) {
+ let transparancy = error * max_transparancy;
+ let alpha_1 = 1. - transparancy;
+ let alpha_2 = 1. - (max_transparancy - transparancy);
+ let color_1 = Pixel::gray((COLOR_FILL._r as f32 * alpha_1) as u8);
+ let color_2 = Pixel::gray((COLOR_FILL._r as f32 * alpha_2) as u8);
+ (color_1, color_2)
+ }
+
+ let h_line_bounds = self.h_line_bounds(self.y_center(), stroke_size);
+ let v_line_bounds = self.v_line_bounds(self.x_center(), stroke_size);
+ let h_line_bounds = (h_line_bounds.0 as usize, h_line_bounds.1 as usize);
+ let v_line_bounds = (v_line_bounds.0 as usize, v_line_bounds.1 as usize);
+ let max_transparancy = 0.5;
+
+ for (radius_y, radius_x) in (h_line_bounds.0..h_line_bounds.1)
+ .into_iter()
+ .zip((v_line_bounds.0..v_line_bounds.1).into_iter())
+ {
+ let radius_x = radius_x as f32;
+ let radius_y = radius_y as f32;
+ let radius_x2 = radius_x * radius_x;
+ let radius_y2 = radius_y * radius_y;
+ let quarter = f32::round(radius_x2 / f32::sqrt(radius_x2 + radius_y2)) as usize;
+
+ for x in 0..=quarter {
+ let x = x as f32;
+ let y = radius_y * f32::sqrt(1. - x * x / radius_x2);
+ let error = y.fract();
+
+ let (color_1, color_2) = colors_with_error(error, max_transparancy);
+
+ let x = x.clamp(0., radius_x);
+ let y_next = (y + 1.).clamp(0., h_line_bounds.1 as f32 - 1.);
+ let y = y.clamp(0., h_line_bounds.1 as f32 - 1.);
+
+ self.put_pixel(x, y, color_1);
+ self.put_pixel(x, y_next, color_2);
+ }
+
+ let quarter = f32::round(radius_y2 / f32::sqrt(radius_x2 + radius_y2)) as usize;
+ for y in 0..=quarter {
+ let y = y as f32;
+ let x = radius_x * f32::sqrt(1. - y * y / radius_y2);
+ let error = x - x.fract();
+
+ let (color_1, color_2) = colors_with_error(error, max_transparancy);
+
+ let x_next = (x + 1.).clamp(0., v_line_bounds.1 as f32 - 1.);
+ let x = x.clamp(0., v_line_bounds.1 as f32 - 1.);
+ let y = y.clamp(0., radius_y as f32);
+
+ self.put_pixel(x, y, color_1);
+ self.put_pixel(x_next, y, color_2);
+ }
+ }
+
+ // Ensure the part closer to edges is properly filled.
+ self.draw_h_line(0., self.y_center(), stroke_size as f32, stroke_size);
+ self.draw_v_line(self.x_center(), 0., stroke_size as f32, stroke_size);
+
+ // Fill the resulted arc, since it could have gaps in-between.
+ for y in 0..self.height {
+ let row = y * self.width;
+ let left = match self.buffer[row..row + self.width].iter().position(|p| p._r != 0) {
+ Some(left) => row + left,
+ _ => continue,
+ };
+ let right = match self.buffer[row..row + self.width].iter().rposition(|p| p._r != 0) {
+ Some(right) => row + right,
+ _ => continue,
+ };
+
+ for index in left + 1..right {
+ self.buffer[index] =
+ self.buffer[index] + self.buffer[index - 1] / 2 + self.buffer[index + 1] / 2;
+ }
+ }
+ }
+
+ /// Fills the `Canvas` with the given `Color`.
+ fn fill(&mut self, color: Pixel) {
+ self.buffer.fill(color);
+ }
+
+ /// Consumes `Canvas` and returns its underlying storage as raw byte vector.
+ fn into_raw(self) -> Vec<u8> {
+ // SAFETY This is safe since we use `repr(packed)` on `Pixel` struct for underlying storage
+ // of the `Canvas` buffer which consists of three u8 values.
+ unsafe {
+ let capacity = self.buffer.capacity() * mem::size_of::<Pixel>();
+ let len = self.buffer.len() * mem::size_of::<Pixel>();
+ let buf = self.buffer.as_ptr() as *mut u8;
+ mem::forget(self.buffer);
+ Vec::from_raw_parts(buf, len, capacity)
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crossfont::Metrics;
+
+ #[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());
+ }
+
+ for character in ('\u{2450}'..'\u{2500}').chain('\u{25a0}'..'\u{2600}') {
+ assert!(builtin_glyph(character, &metrics, &offset, &glyph_offset).is_none());
+ }
+ }