aboutsummaryrefslogtreecommitdiff
path: root/alacritty/src/renderer/text
diff options
context:
space:
mode:
Diffstat (limited to 'alacritty/src/renderer/text')
-rw-r--r--alacritty/src/renderer/text/atlas.rs273
-rw-r--r--alacritty/src/renderer/text/builtin_font.rs823
-rw-r--r--alacritty/src/renderer/text/gles2.rs478
-rw-r--r--alacritty/src/renderer/text/glsl3.rs460
-rw-r--r--alacritty/src/renderer/text/glyph_cache.rs322
-rw-r--r--alacritty/src/renderer/text/mod.rs202
6 files changed, 2558 insertions, 0 deletions
diff --git a/alacritty/src/renderer/text/atlas.rs b/alacritty/src/renderer/text/atlas.rs
new file mode 100644
index 00000000..97c8b0b4
--- /dev/null
+++ b/alacritty/src/renderer/text/atlas.rs
@@ -0,0 +1,273 @@
+use std::ptr;
+
+use crossfont::{BitmapBuffer, RasterizedGlyph};
+
+use crate::gl;
+use crate::gl::types::*;
+
+use super::Glyph;
+
+/// Size of the Atlas.
+pub const ATLAS_SIZE: i32 = 1024;
+
+/// Manages a single texture atlas.
+///
+/// The strategy for filling an atlas looks roughly like this:
+///
+/// ```text
+/// (width, height)
+/// ┌─────┬─────┬─────┬─────┬─────┐
+/// │ 10 │ │ │ │ │ <- Empty spaces; can be filled while
+/// │ │ │ │ │ │ glyph_height < height - row_baseline
+/// ├─────┼─────┼─────┼─────┼─────┤
+/// │ 5 │ 6 │ 7 │ 8 │ 9 │
+/// │ │ │ │ │ │
+/// ├─────┼─────┼─────┼─────┴─────┤ <- Row height is tallest glyph in row; this is
+/// │ 1 │ 2 │ 3 │ 4 │ used as the baseline for the following row.
+/// │ │ │ │ │ <- Row considered full when next glyph doesn't
+/// └─────┴─────┴─────┴───────────┘ fit in the row.
+/// (0, 0) x->
+/// ```
+#[derive(Debug)]
+pub struct Atlas {
+ /// Texture id for this atlas.
+ id: GLuint,
+
+ /// Width of atlas.
+ width: i32,
+
+ /// Height of atlas.
+ height: i32,
+
+ /// Left-most free pixel in a row.
+ ///
+ /// This is called the extent because it is the upper bound of used pixels
+ /// in a row.
+ row_extent: i32,
+
+ /// Baseline for glyphs in the current row.
+ row_baseline: i32,
+
+ /// Tallest glyph in current row.
+ ///
+ /// This is used as the advance when end of row is reached.
+ row_tallest: i32,
+}
+
+/// Error that can happen when inserting a texture to the Atlas.
+pub enum AtlasInsertError {
+ /// Texture atlas is full.
+ Full,
+
+ /// The glyph cannot fit within a single texture.
+ GlyphTooLarge,
+}
+
+impl Atlas {
+ pub fn new(size: i32) -> Self {
+ let mut id: GLuint = 0;
+ unsafe {
+ gl::PixelStorei(gl::UNPACK_ALIGNMENT, 1);
+ gl::GenTextures(1, &mut id);
+ gl::BindTexture(gl::TEXTURE_2D, id);
+ // Use RGBA texture for both normal and emoji glyphs, since it has no performance
+ // impact.
+ gl::TexImage2D(
+ gl::TEXTURE_2D,
+ 0,
+ gl::RGBA as i32,
+ size,
+ size,
+ 0,
+ gl::RGBA,
+ gl::UNSIGNED_BYTE,
+ ptr::null(),
+ );
+
+ gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32);
+ gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32);
+ gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32);
+ gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32);
+
+ gl::BindTexture(gl::TEXTURE_2D, 0);
+ }
+
+ Self { id, width: size, height: size, row_extent: 0, row_baseline: 0, row_tallest: 0 }
+ }
+
+ pub fn clear(&mut self) {
+ self.row_extent = 0;
+ self.row_baseline = 0;
+ self.row_tallest = 0;
+ }
+
+ /// Insert a RasterizedGlyph into the texture atlas.
+ pub fn insert(
+ &mut self,
+ glyph: &RasterizedGlyph,
+ active_tex: &mut u32,
+ ) -> Result<Glyph, AtlasInsertError> {
+ if glyph.width > self.width || glyph.height > self.height {
+ return Err(AtlasInsertError::GlyphTooLarge);
+ }
+
+ // If there's not enough room in current row, go onto next one.
+ if !self.room_in_row(glyph) {
+ self.advance_row()?;
+ }
+
+ // If there's still not room, there's nothing that can be done here..
+ if !self.room_in_row(glyph) {
+ return Err(AtlasInsertError::Full);
+ }
+
+ // There appears to be room; load the glyph.
+ Ok(self.insert_inner(glyph, active_tex))
+ }
+
+ /// Insert the glyph without checking for room.
+ ///
+ /// Internal function for use once atlas has been checked for space. GL
+ /// errors could still occur at this point if we were checking for them;
+ /// hence, the Result.
+ fn insert_inner(&mut self, glyph: &RasterizedGlyph, active_tex: &mut u32) -> Glyph {
+ let offset_y = self.row_baseline;
+ let offset_x = self.row_extent;
+ let height = glyph.height as i32;
+ let width = glyph.width as i32;
+ let multicolor;
+
+ unsafe {
+ gl::BindTexture(gl::TEXTURE_2D, self.id);
+
+ // Load data into OpenGL.
+ let (format, buffer) = match &glyph.buffer {
+ BitmapBuffer::Rgb(buffer) => {
+ multicolor = false;
+ (gl::RGB, buffer)
+ },
+ BitmapBuffer::Rgba(buffer) => {
+ multicolor = true;
+ (gl::RGBA, buffer)
+ },
+ };
+
+ gl::TexSubImage2D(
+ gl::TEXTURE_2D,
+ 0,
+ offset_x,
+ offset_y,
+ width,
+ height,
+ format,
+ gl::UNSIGNED_BYTE,
+ buffer.as_ptr() as *const _,
+ );
+
+ gl::BindTexture(gl::TEXTURE_2D, 0);
+ *active_tex = 0;
+ }
+
+ // Update Atlas state.
+ self.row_extent = offset_x + width;
+ if height > self.row_tallest {
+ self.row_tallest = height;
+ }
+
+ // Generate UV coordinates.
+ let uv_bot = offset_y as f32 / self.height as f32;
+ let uv_left = offset_x as f32 / self.width as f32;
+ let uv_height = height as f32 / self.height as f32;
+ let uv_width = width as f32 / self.width as f32;
+
+ Glyph {
+ tex_id: self.id,
+ multicolor,
+ top: glyph.top as i16,
+ left: glyph.left as i16,
+ width: width as i16,
+ height: height as i16,
+ uv_bot,
+ uv_left,
+ uv_width,
+ uv_height,
+ }
+ }
+
+ /// Check if there's room in the current row for given glyph.
+ pub fn room_in_row(&self, raw: &RasterizedGlyph) -> bool {
+ let next_extent = self.row_extent + raw.width as i32;
+ let enough_width = next_extent <= self.width;
+ let enough_height = (raw.height as i32) < (self.height - self.row_baseline);
+
+ enough_width && enough_height
+ }
+
+ /// Mark current row as finished and prepare to insert into the next row.
+ pub fn advance_row(&mut self) -> Result<(), AtlasInsertError> {
+ let advance_to = self.row_baseline + self.row_tallest;
+ if self.height - advance_to <= 0 {
+ return Err(AtlasInsertError::Full);
+ }
+
+ self.row_baseline = advance_to;
+ self.row_extent = 0;
+ self.row_tallest = 0;
+
+ Ok(())
+ }
+
+ /// Load a glyph into a texture atlas.
+ ///
+ /// If the current atlas is full, a new one will be created.
+ #[inline]
+ pub fn load_glyph(
+ active_tex: &mut GLuint,
+ atlas: &mut Vec<Atlas>,
+ current_atlas: &mut usize,
+ rasterized: &RasterizedGlyph,
+ ) -> Glyph {
+ // At least one atlas is guaranteed to be in the `self.atlas` list; thus
+ // the unwrap.
+ match atlas[*current_atlas].insert(rasterized, active_tex) {
+ Ok(glyph) => glyph,
+ Err(AtlasInsertError::Full) => {
+ *current_atlas += 1;
+ if *current_atlas == atlas.len() {
+ let new = Atlas::new(ATLAS_SIZE);
+ *active_tex = 0; // Atlas::new binds a texture. Ugh this is sloppy.
+ atlas.push(new);
+ }
+ Atlas::load_glyph(active_tex, atlas, current_atlas, rasterized)
+ },
+ Err(AtlasInsertError::GlyphTooLarge) => Glyph {
+ tex_id: atlas[*current_atlas].id,
+ multicolor: false,
+ top: 0,
+ left: 0,
+ width: 0,
+ height: 0,
+ uv_bot: 0.,
+ uv_left: 0.,
+ uv_width: 0.,
+ uv_height: 0.,
+ },
+ }
+ }
+
+ #[inline]
+ pub fn clear_atlas(atlas: &mut Vec<Atlas>, current_atlas: &mut usize) {
+ for atlas in atlas.iter_mut() {
+ atlas.clear();
+ }
+ *current_atlas = 0;
+ }
+}
+
+impl Drop for Atlas {
+ fn drop(&mut self) {
+ unsafe {
+ gl::DeleteTextures(1, &self.id);
+ }
+ }
+}
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());
+ }
+ }
+}
diff --git a/alacritty/src/renderer/text/gles2.rs b/alacritty/src/renderer/text/gles2.rs
new file mode 100644
index 00000000..9b161081
--- /dev/null
+++ b/alacritty/src/renderer/text/gles2.rs
@@ -0,0 +1,478 @@
+use std::mem::size_of;
+use std::ptr;
+
+use crossfont::RasterizedGlyph;
+use log::info;
+
+use alacritty_terminal::term::cell::Flags;
+use alacritty_terminal::term::SizeInfo;
+
+use crate::display::content::RenderableCell;
+use crate::gl;
+use crate::gl::types::*;
+use crate::renderer::shader::{ShaderProgram, ShaderVersion};
+use crate::renderer::{cstr, Error};
+
+use super::atlas::{Atlas, ATLAS_SIZE};
+use super::{
+ Glyph, LoadGlyph, LoaderApi, TextRenderApi, TextRenderBatch, TextRenderer, TextShader,
+};
+
+// Shader source.
+static TEXT_SHADER_F: &str = include_str!("../../../res/gles2/text.f.glsl");
+static TEXT_SHADER_V: &str = include_str!("../../../res/gles2/text.v.glsl");
+
+#[derive(Debug)]
+pub struct Gles2Renderer {
+ program: TextShaderProgram,
+ vao: GLuint,
+ vbo: GLuint,
+ ebo: GLuint,
+ atlas: Vec<Atlas>,
+ batch: Batch,
+ current_atlas: usize,
+ active_tex: GLuint,
+}
+
+impl Gles2Renderer {
+ pub fn new() -> Result<Self, Error> {
+ info!("Using OpenGL ES 2.0 renderer");
+
+ let program = TextShaderProgram::new(ShaderVersion::Gles2)?;
+ let mut vao: GLuint = 0;
+ let mut vbo: GLuint = 0;
+ let mut ebo: GLuint = 0;
+
+ let mut vertex_indices = Vec::with_capacity(BATCH_MAX / 4 * 6);
+ for index in 0..(BATCH_MAX / 4) as u16 {
+ let index = index * 4;
+ vertex_indices.push(index);
+ vertex_indices.push(index + 1);
+ vertex_indices.push(index + 3);
+
+ vertex_indices.push(index + 1);
+ vertex_indices.push(index + 2);
+ vertex_indices.push(index + 3);
+ }
+
+ unsafe {
+ gl::Enable(gl::BLEND);
+
+ gl::DepthMask(gl::FALSE);
+
+ gl::GenVertexArrays(1, &mut vao);
+ gl::GenBuffers(1, &mut ebo);
+ gl::GenBuffers(1, &mut vbo);
+ gl::BindVertexArray(vao);
+
+ // Elements buffer.
+ gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo);
+ gl::BufferData(
+ gl::ELEMENT_ARRAY_BUFFER,
+ (vertex_indices.capacity() * size_of::<u16>()) as isize,
+ vertex_indices.as_ptr() as *const _,
+ gl::STATIC_DRAW,
+ );
+
+ // Vertex buffer.
+ gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
+ gl::BufferData(
+ gl::ARRAY_BUFFER,
+ (BATCH_MAX * size_of::<TextVertex>()) as isize,
+ ptr::null(),
+ gl::STREAM_DRAW,
+ );
+
+ let mut index = 0;
+ let mut size = 0;
+
+ macro_rules! add_attr {
+ ($count:expr, $gl_type:expr, $type:ty) => {
+ gl::VertexAttribPointer(
+ index,
+ $count,
+ $gl_type,
+ gl::FALSE,
+ size_of::<TextVertex>() as i32,
+ size as *const _,
+ );
+ gl::EnableVertexAttribArray(index);
+
+ #[allow(unused_assignments)]
+ {
+ size += $count * size_of::<$type>();
+ index += 1;
+ }
+ };
+ }
+
+ // Cell coords.
+ add_attr!(2, gl::SHORT, i16);
+
+ // Glyph coords.
+ add_attr!(2, gl::SHORT, i16);
+
+ // UV.
+ add_attr!(2, gl::FLOAT, u32);
+
+ // Color and bitmap color.
+ //
+ // These are packed together because of an OpenGL driver issue on macOS, which caused a
+ // `vec3(u8)` text color and a `u8` for glyph color to cause performance regressions.
+ add_attr!(4, gl::UNSIGNED_BYTE, u8);
+
+ // Background color.
+ add_attr!(4, gl::UNSIGNED_BYTE, u8);
+
+ // Cleanup.
+ gl::BindVertexArray(0);
+ gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
+ gl::BindBuffer(gl::ARRAY_BUFFER, 0);
+ }
+
+ Ok(Self {
+ program,
+ vao,
+ vbo,
+ ebo,
+ atlas: vec![Atlas::new(ATLAS_SIZE)],
+ batch: Batch::new(),
+ current_atlas: 0,
+ active_tex: 0,
+ })
+ }
+}
+
+impl Drop for Gles2Renderer {
+ fn drop(&mut self) {
+ unsafe {
+ gl::DeleteBuffers(1, &self.vbo);
+ gl::DeleteBuffers(1, &self.ebo);
+ gl::DeleteVertexArrays(1, &self.vao);
+ }
+ }
+}
+
+impl<'a> TextRenderer<'a> for Gles2Renderer {
+ type RenderApi = RenderApi<'a>;
+ type RenderBatch = Batch;
+ type Shader = TextShaderProgram;
+
+ fn program(&self) -> &Self::Shader {
+ &self.program
+ }
+
+ fn with_api<'b: 'a, F, T>(&'b mut self, _: &'b SizeInfo, func: F) -> T
+ where
+ F: FnOnce(Self::RenderApi) -> T,
+ {
+ unsafe {
+ gl::UseProgram(self.program.id());
+ gl::BindVertexArray(self.vao);
+ gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.ebo);
+ gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo);
+ gl::ActiveTexture(gl::TEXTURE0);
+ }
+
+ let res = func(RenderApi {
+ active_tex: &mut self.active_tex,
+ batch: &mut self.batch,
+ atlas: &mut self.atlas,
+ current_atlas: &mut self.current_atlas,
+ program: &mut self.program,
+ });
+
+ unsafe {
+ gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
+ gl::BindBuffer(gl::ARRAY_BUFFER, 0);
+ gl::BindVertexArray(0);
+
+ gl::UseProgram(0);
+ }
+
+ res
+ }
+
+ fn loader_api(&mut self) -> LoaderApi<'_> {
+ LoaderApi {
+ active_tex: &mut self.active_tex,
+ atlas: &mut self.atlas,
+ current_atlas: &mut self.current_atlas,
+ }
+ }
+}
+
+/// Maximum items to be drawn in a batch.
+///
+/// We use the closest number to `u16::MAX` dividable by 4 (amount of vertices we push for a glyph),
+/// since it's the maximum possible index in `glDrawElements` in gles2.
+const BATCH_MAX: usize = (u16::MAX - u16::MAX % 4) as usize;
+
+#[derive(Debug)]
+pub struct Batch {
+ tex: GLuint,
+ vertices: Vec<TextVertex>,
+}
+
+impl Batch {
+ fn new() -> Self {
+ Self { tex: 0, vertices: Vec::with_capacity(BATCH_MAX) }
+ }
+
+ #[inline]
+ fn len(&self) -> usize {
+ self.vertices.len()
+ }
+
+ #[inline]
+ fn capacity(&self) -> usize {
+ BATCH_MAX
+ }
+
+ #[inline]
+ fn size(&self) -> usize {
+ self.len() * size_of::<TextVertex>()
+ }
+
+ #[inline]
+ fn clear(&mut self) {
+ self.vertices.clear();
+ }
+}
+
+impl TextRenderBatch for Batch {
+ #[inline]
+ fn tex(&self) -> GLuint {
+ self.tex
+ }
+
+ #[inline]
+ fn full(&self) -> bool {
+ self.capacity() == self.len()
+ }
+
+ #[inline]
+ fn is_empty(&self) -> bool {
+ self.len() == 0
+ }
+
+ fn add_item(&mut self, cell: &RenderableCell, glyph: &Glyph, size_info: &SizeInfo) {
+ if self.is_empty() {
+ self.tex = glyph.tex_id;
+ }
+
+ // Calculate the cell position.
+ let x = cell.point.column.0 as i16 * size_info.cell_width() as i16;
+ let y = cell.point.line as i16 * size_info.cell_height() as i16;
+
+ // Calculate the glyph position.
+ let glyph_x = cell.point.column.0 as i16 * size_info.cell_width() as i16 + glyph.left;
+ let glyph_y = (cell.point.line + 1) as i16 * size_info.cell_height() as i16 - glyph.top;
+
+ let colored = if glyph.multicolor { 1 } else { 0 };
+ let is_wide = if cell.flags.contains(Flags::WIDE_CHAR) { 2 } else { 1 };
+
+ let mut vertex = TextVertex {
+ x,
+ y: y + size_info.cell_height() as i16,
+
+ glyph_x,
+ glyph_y: glyph_y + glyph.height,
+
+ u: glyph.uv_left,
+ v: glyph.uv_bot + glyph.uv_height,
+ r: cell.fg.r,
+ g: cell.fg.g,
+ b: cell.fg.b,
+ colored,
+ bg_r: cell.bg.r,
+ bg_g: cell.bg.g,
+ bg_b: cell.bg.b,
+ bg_a: (cell.bg_alpha * 255.0) as u8,
+ };
+
+ self.vertices.push(vertex);
+
+ vertex.y = y;
+ vertex.glyph_y = glyph_y;
+ vertex.u = glyph.uv_left;
+ vertex.v = glyph.uv_bot;
+ self.vertices.push(vertex);
+
+ vertex.x = x + is_wide * size_info.cell_width() as i16;
+ vertex.glyph_x = glyph_x + glyph.width;
+ vertex.u = glyph.uv_left + glyph.uv_width;
+ vertex.v = glyph.uv_bot;
+ self.vertices.push(vertex);
+
+ vertex.x = x + is_wide * size_info.cell_width() as i16;
+ vertex.y = y + size_info.cell_height() as i16;
+ vertex.glyph_x = glyph_x + glyph.width;
+ vertex.glyph_y = glyph_y + glyph.height;
+ vertex.u = glyph.uv_left + glyph.uv_width;
+ vertex.v = glyph.uv_bot + glyph.uv_height;
+ self.vertices.push(vertex);
+ }
+}
+
+#[derive(Debug)]
+pub struct RenderApi<'a> {
+ active_tex: &'a mut GLuint,
+ batch: &'a mut Batch,
+ atlas: &'a mut Vec<Atlas>,
+ current_atlas: &'a mut usize,
+ program: &'a mut TextShaderProgram,
+}
+
+impl<'a> Drop for RenderApi<'a> {
+ fn drop(&mut self) {
+ if !self.batch.is_empty() {
+ self.render_batch();
+ }
+ }
+}
+
+impl<'a> LoadGlyph for RenderApi<'a> {
+ fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph {
+ Atlas::load_glyph(self.active_tex, self.atlas, self.current_atlas, rasterized)
+ }
+
+ fn clear(&mut self) {
+ Atlas::clear_atlas(self.atlas, self.current_atlas)
+ }
+}
+
+impl<'a> TextRenderApi<Batch> for RenderApi<'a> {
+ fn batch(&mut self) -> &mut Batch {
+ self.batch
+ }
+
+ fn render_batch(&mut self) {
+ unsafe {
+ gl::BufferSubData(
+ gl::ARRAY_BUFFER,
+ 0,
+ self.batch.size() as isize,
+ self.batch.vertices.as_ptr() as *const _,
+ );
+ }
+
+ if *self.active_tex != self.batch.tex() {
+ unsafe {
+ gl::BindTexture(gl::TEXTURE_2D, self.batch.tex());
+ }
+ *self.active_tex = self.batch.tex();
+ }
+
+ unsafe {
+ let num_indices = (self.batch.len() / 4 * 6) as i32;
+
+ // The rendering is inspired by
+ // https://github.com/servo/webrender/blob/master/webrender/doc/text-rendering.md.
+
+ // Draw background.
+ self.program.set_rendering_pass(RenderingPass::Background);
+ gl::BlendFunc(gl::ONE, gl::ZERO);
+ gl::DrawElements(gl::TRIANGLES, num_indices, gl::UNSIGNED_SHORT, ptr::null());
+
+ // First text rendering pass.
+ self.program.set_rendering_pass(RenderingPass::SubpixelPass1);
+ gl::BlendFuncSeparate(gl::ZERO, gl::ONE_MINUS_SRC_COLOR, gl::ZERO, gl::ONE);
+ gl::DrawElements(gl::TRIANGLES, num_indices, gl::UNSIGNED_SHORT, ptr::null());
+
+ // Second text rendering pass.
+ self.program.set_rendering_pass(RenderingPass::SubpixelPass2);
+ gl::BlendFuncSeparate(gl::ONE_MINUS_DST_ALPHA, gl::ONE, gl::ZERO, gl::ONE);
+ gl::DrawElements(gl::TRIANGLES, num_indices, gl::UNSIGNED_SHORT, ptr::null());
+
+ // Third pass.
+ self.program.set_rendering_pass(RenderingPass::SubpixelPass3);
+ gl::BlendFuncSeparate(gl::ONE, gl::ONE, gl::ONE, gl::ONE_MINUS_SRC_ALPHA);
+ gl::DrawElements(gl::TRIANGLES, num_indices, gl::UNSIGNED_SHORT, ptr::null());
+ }
+
+ self.batch.clear();
+ }
+}
+
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+struct TextVertex {
+ // Cell coordinates.
+ x: i16,
+ y: i16,
+
+ // Glyph coordinates.
+ glyph_x: i16,
+ glyph_y: i16,
+
+ // Offsets into Atlas.
+ u: f32,
+ v: f32,
+
+ // Color.
+ r: u8,
+ g: u8,
+ b: u8,
+
+ // Whether the glyph is colored.
+ colored: u8,
+
+ // Background color.
+ bg_r: u8,
+ bg_g: u8,
+ bg_b: u8,
+ bg_a: u8,
+}
+
+// NOTE: These flags must be in sync with their usage in the gles2/text.*.glsl shaders.
+#[repr(u8)]
+enum RenderingPass {
+ Background = 0,
+ SubpixelPass1 = 1,
+ SubpixelPass2 = 2,
+ SubpixelPass3 = 3,
+}
+
+#[derive(Debug)]
+pub struct TextShaderProgram {
+ /// Shader program.
+ program: ShaderProgram,
+
+ /// Projection scale and offset uniform.
+ u_projection: GLint,
+
+ /// Rendering pass.
+ ///
+ /// The rendering is split into 4 passes. One is used for the background and the rest to
+ /// perform subpixel text rendering according to
+ /// https://github.com/servo/webrender/blob/master/webrender/doc/text-rendering.md.
+ ///
+ /// Rendering is split into three passes.
+ u_rendering_pass: GLint,
+}
+
+impl TextShaderProgram {
+ pub fn new(shader_version: ShaderVersion) -> Result<Self, Error> {
+ let program = ShaderProgram::new(shader_version, TEXT_SHADER_V, TEXT_SHADER_F)?;
+ Ok(Self {
+ u_projection: program.get_uniform_location(cstr!("projection"))?,
+ u_rendering_pass: program.get_uniform_location(cstr!("renderingPass"))?,
+ program,
+ })
+ }
+
+ fn set_rendering_pass(&self, rendering_pass: RenderingPass) {
+ unsafe { gl::Uniform1i(self.u_rendering_pass, rendering_pass as i32) }
+ }
+}
+
+impl TextShader for TextShaderProgram {
+ fn id(&self) -> GLuint {
+ self.program.id()
+ }
+
+ fn projection_uniform(&self) -> GLint {
+ self.u_projection
+ }
+}
diff --git a/alacritty/src/renderer/text/glsl3.rs b/alacritty/src/renderer/text/glsl3.rs
new file mode 100644
index 00000000..6701cf3d
--- /dev/null
+++ b/alacritty/src/renderer/text/glsl3.rs
@@ -0,0 +1,460 @@
+use std::mem::size_of;
+use std::ptr;
+
+use crossfont::RasterizedGlyph;
+use log::info;
+
+use alacritty_terminal::term::cell::Flags;
+use alacritty_terminal::term::SizeInfo;
+
+use crate::display::content::RenderableCell;
+use crate::gl;
+use crate::gl::types::*;
+use crate::renderer::shader::{ShaderProgram, ShaderVersion};
+use crate::renderer::{cstr, Error};
+
+use super::atlas::{Atlas, ATLAS_SIZE};
+use super::{
+ Glyph, LoadGlyph, LoaderApi, RenderingGlyphFlags, TextRenderApi, TextRenderBatch, TextRenderer,
+ TextShader,
+};
+
+// Shader source.
+static TEXT_SHADER_F: &str = include_str!("../../../res/glsl3/text.f.glsl");
+static TEXT_SHADER_V: &str = include_str!("../../../res/glsl3/text.v.glsl");
+
+/// Maximum items to be drawn in a batch.
+const BATCH_MAX: usize = 0x1_0000;
+
+#[derive(Debug)]
+pub struct Glsl3Renderer {
+ program: TextShaderProgram,
+ vao: GLuint,
+ ebo: GLuint,
+ vbo_instance: GLuint,
+ atlas: Vec<Atlas>,
+ current_atlas: usize,
+ active_tex: GLuint,
+ batch: Batch,
+}
+
+impl Glsl3Renderer {
+ pub fn new() -> Result<Self, Error> {
+ info!("Using OpenGL 3.3 renderer");
+
+ let program = TextShaderProgram::new(ShaderVersion::Glsl3)?;
+ let mut vao: GLuint = 0;
+ let mut ebo: GLuint = 0;
+ let mut vbo_instance: GLuint = 0;
+
+ unsafe {
+ gl::Enable(gl::BLEND);
+ gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR);
+ gl::Enable(gl::MULTISAMPLE);
+
+ // Disable depth mask, as the renderer never uses depth tests.
+ gl::DepthMask(gl::FALSE);
+
+ gl::GenVertexArrays(1, &mut vao);
+ gl::GenBuffers(1, &mut ebo);
+ gl::GenBuffers(1, &mut vbo_instance);
+ gl::BindVertexArray(vao);
+
+ // ---------------------
+ // Set up element buffer
+ // ---------------------
+ let indices: [u32; 6] = [0, 1, 3, 1, 2, 3];
+
+ gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo);
+ gl::BufferData(
+ gl::ELEMENT_ARRAY_BUFFER,
+ (6 * size_of::<u32>()) as isize,
+ indices.as_ptr() as *const _,
+ gl::STATIC_DRAW,
+ );
+
+ // ----------------------------
+ // Setup vertex instance buffer
+ // ----------------------------
+ gl::BindBuffer(gl::ARRAY_BUFFER, vbo_instance);
+ gl::BufferData(
+ gl::ARRAY_BUFFER,
+ (BATCH_MAX * size_of::<InstanceData>()) as isize,
+ ptr::null(),
+ gl::STREAM_DRAW,
+ );
+
+ let mut index = 0;
+ let mut size = 0;
+
+ macro_rules! add_attr {
+ ($count:expr, $gl_type:expr, $type:ty) => {
+ gl::VertexAttribPointer(
+ index,
+ $count,
+ $gl_type,
+ gl::FALSE,
+ size_of::<InstanceData>() as i32,
+ size as *const _,
+ );
+ gl::EnableVertexAttribArray(index);
+ gl::VertexAttribDivisor(index, 1);
+
+ #[allow(unused_assignments)]
+ {
+ size += $count * size_of::<$type>();
+ index += 1;
+ }
+ };
+ }
+
+ // Coords.
+ add_attr!(2, gl::UNSIGNED_SHORT, u16);
+
+ // Glyph offset and size.
+ add_attr!(4, gl::SHORT, i16);
+
+ // UV offset.
+ add_attr!(4, gl::FLOAT, f32);
+
+ // 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` cell flags to increase the rendering time by a
+ // huge margin.
+ add_attr!(4, gl::UNSIGNED_BYTE, u8);
+
+ // Background color.
+ add_attr!(4, gl::UNSIGNED_BYTE, u8);
+
+ // Cleanup.
+ gl::BindVertexArray(0);
+ gl::BindBuffer(gl::ARRAY_BUFFER, 0);
+ gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
+ }
+
+ Ok(Self {
+ program,
+ vao,
+ ebo,
+ vbo_instance,
+ atlas: vec![Atlas::new(ATLAS_SIZE)],
+ current_atlas: 0,
+ active_tex: 0,
+ batch: Batch::new(),
+ })
+ }
+}
+
+impl<'a> TextRenderer<'a> for Glsl3Renderer {
+ type RenderApi = RenderApi<'a>;
+ type RenderBatch = Batch;
+ type Shader = TextShaderProgram;
+
+ fn with_api<'b: 'a, F, T>(&'b mut self, size_info: &'b SizeInfo, func: F) -> T
+ where
+ F: FnOnce(Self::RenderApi) -> T,
+ {
+ unsafe {
+ gl::UseProgram(self.program.id());
+ self.program.set_term_uniforms(size_info);
+
+ gl::BindVertexArray(self.vao);
+ gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.ebo);
+ gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo_instance);
+ gl::ActiveTexture(gl::TEXTURE0);
+ }
+
+ let res = func(RenderApi {
+ active_tex: &mut self.active_tex,
+ batch: &mut self.batch,
+ atlas: &mut self.atlas,
+ current_atlas: &mut self.current_atlas,
+ program: &mut self.program,
+ });
+
+ unsafe {
+ gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
+ gl::BindBuffer(gl::ARRAY_BUFFER, 0);
+ gl::BindVertexArray(0);
+
+ gl::UseProgram(0);
+ }
+
+ res
+ }
+
+ fn program(&self) -> &Self::Shader {
+ &self.program
+ }
+
+ fn loader_api(&mut self) -> LoaderApi<'_> {
+ LoaderApi {
+ active_tex: &mut self.active_tex,
+ atlas: &mut self.atlas,
+ current_atlas: &mut self.current_atlas,
+ }
+ }
+}
+
+impl Drop for Glsl3Renderer {
+ fn drop(&mut self) {
+ unsafe {
+ gl::DeleteBuffers(1, &self.vbo_instance);
+ gl::DeleteBuffers(1, &self.ebo);
+ gl::DeleteVertexArrays(1, &self.vao);
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct RenderApi<'a> {
+ active_tex: &'a mut GLuint,
+ batch: &'a mut Batch,
+ atlas: &'a mut Vec<Atlas>,
+ current_atlas: &'a mut usize,
+ program: &'a mut TextShaderProgram,
+}
+
+impl<'a> TextRenderApi<Batch> for RenderApi<'a> {
+ fn batch(&mut self) -> &mut Batch {
+ self.batch
+ }
+
+ fn render_batch(&mut self) {
+ unsafe {
+ gl::BufferSubData(
+ gl::ARRAY_BUFFER,
+ 0,
+ self.batch.size() as isize,
+ self.batch.instances.as_ptr() as *const _,
+ );
+ }
+
+ // Bind texture if necessary.
+ if *self.active_tex != self.batch.tex() {
+ unsafe {
+ gl::BindTexture(gl::TEXTURE_2D, self.batch.tex());
+ }
+ *self.active_tex = self.batch.tex();
+ }
+
+ unsafe {
+ self.program.set_background_pass(true);
+ gl::DrawElementsInstanced(
+ gl::TRIANGLES,
+ 6,
+ gl::UNSIGNED_INT,
+ ptr::null(),
+ self.batch.len() as GLsizei,
+ );
+ self.program.set_background_pass(false);
+ gl::DrawElementsInstanced(
+ gl::TRIANGLES,
+ 6,
+ gl::UNSIGNED_INT,
+ ptr::null(),
+ self.batch.len() as GLsizei,
+ );
+ }
+
+ self.batch.clear();
+ }
+}
+
+impl<'a> LoadGlyph for RenderApi<'a> {
+ fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph {
+ Atlas::load_glyph(self.active_tex, self.atlas, self.current_atlas, rasterized)
+ }
+
+ fn clear(&mut self) {
+ Atlas::clear_atlas(self.atlas, self.current_atlas)
+ }
+}
+
+impl<'a> Drop for RenderApi<'a> {
+ fn drop(&mut self) {
+ if !self.batch.is_empty() {
+ self.render_batch();
+ }
+ }
+}
+
+#[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,
+
+ // Cell flags like multicolor or fullwidth character.
+ cell_flags: RenderingGlyphFlags,
+
+ // Background color.
+ bg_r: u8,
+ bg_g: u8,
+ bg_b: u8,
+ bg_a: u8,
+}
+
+#[derive(Debug, Default)]
+pub struct Batch {
+ tex: GLuint,
+ instances: Vec<InstanceData>,
+}
+
+impl TextRenderBatch for Batch {
+ #[inline]
+ fn tex(&self) -> GLuint {
+ self.tex
+ }
+
+ #[inline]
+ fn full(&self) -> bool {
+ self.capacity() == self.len()
+ }
+
+ #[inline]
+ fn is_empty(&self) -> bool {
+ self.len() == 0
+ }
+
+ fn add_item(&mut self, cell: &RenderableCell, glyph: &Glyph, _: &SizeInfo) {
+ if self.is_empty() {
+ 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.point.column.0 as u16,
+ row: cell.point.line as u16,
+
+ top: glyph.top,
+ left: glyph.left,
+ width: glyph.width,
+ height: glyph.height,
+
+ uv_bot: glyph.uv_bot,
+ uv_left: glyph.uv_left,
+ uv_width: glyph.uv_width,
+ uv_height: glyph.uv_height,
+
+ 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,
+ });
+ }
+}
+
+impl Batch {
+ #[inline]
+ pub fn new() -> Self {
+ Self { tex: 0, instances: Vec::with_capacity(BATCH_MAX) }
+ }
+
+ #[inline]
+ pub fn len(&self) -> usize {
+ self.instances.len()
+ }
+
+ #[inline]
+ pub fn capacity(&self) -> usize {
+ BATCH_MAX
+ }
+
+ #[inline]
+ pub fn size(&self) -> usize {
+ self.len() * size_of::<InstanceData>()
+ }
+
+ pub fn clear(&mut self) {
+ self.tex = 0;
+ self.instances.clear();
+ }
+}
+
+/// Text drawing program.
+///
+/// Uniforms are prefixed with "u", and vertex attributes are prefixed with "a".
+#[derive(Debug)]
+pub struct TextShaderProgram {
+ /// Shader program.
+ program: ShaderProgram,
+
+ /// Projection scale and offset uniform.
+ u_projection: GLint,
+
+ /// Cell dimensions (pixels).
+ u_cell_dim: GLint,
+
+ /// Background pass flag.
+ ///
+ /// Rendering is split into two passes; 1 for backgrounds, and one for text.
+ u_background: GLint,
+}
+
+impl TextShaderProgram {
+ pub fn new(shader_version: ShaderVersion) -> Result<TextShaderProgram, Error> {
+ let program = ShaderProgram::new(shader_version, TEXT_SHADER_V, TEXT_SHADER_F)?;
+ Ok(Self {
+ u_projection: program.get_uniform_location(cstr!("projection"))?,
+ u_cell_dim: program.get_uniform_location(cstr!("cellDim"))?,
+ u_background: program.get_uniform_location(cstr!("backgroundPass"))?,
+ program,
+ })
+ }
+
+ fn set_term_uniforms(&self, props: &SizeInfo) {
+ unsafe {
+ gl::Uniform2f(self.u_cell_dim, props.cell_width(), props.cell_height());
+ }
+ }
+
+ fn set_background_pass(&self, background_pass: bool) {
+ let value = if background_pass { 1 } else { 0 };
+
+ unsafe {
+ gl::Uniform1i(self.u_background, value);
+ }
+ }
+}
+
+impl TextShader for TextShaderProgram {
+ fn id(&self) -> GLuint {
+ self.program.id()
+ }
+
+ fn projection_uniform(&self) -> GLint {
+ self.u_projection
+ }
+}
diff --git a/alacritty/src/renderer/text/glyph_cache.rs b/alacritty/src/renderer/text/glyph_cache.rs
new file mode 100644
index 00000000..c75cad7a
--- /dev/null
+++ b/alacritty/src/renderer/text/glyph_cache.rs
@@ -0,0 +1,322 @@
+use std::collections::HashMap;
+use std::hash::BuildHasherDefault;
+
+use crossfont::{
+ Error as RasterizerError, FontDesc, FontKey, GlyphKey, Metrics, Rasterize, RasterizedGlyph,
+ Rasterizer, Size, Slant, Style, Weight,
+};
+use fnv::FnvHasher;
+use log::{error, info};
+use unicode_width::UnicodeWidthChar;
+
+use crate::config::font::{Font, FontDescription};
+use crate::config::ui_config::Delta;
+use crate::gl::types::*;
+
+use super::builtin_font;
+
+/// `LoadGlyph` allows for copying a rasterized glyph into graphics memory.
+pub trait LoadGlyph {
+ /// Load the rasterized glyph into GPU memory.
+ fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph;
+
+ /// Clear any state accumulated from previous loaded glyphs.
+ ///
+ /// This can, for instance, be used to reset the texture Atlas.
+ fn clear(&mut self);
+}
+
+#[derive(Copy, Clone, Debug)]
+pub struct Glyph {
+ pub tex_id: GLuint,
+ pub multicolor: bool,
+ pub top: i16,
+ pub left: i16,
+ pub width: i16,
+ pub height: i16,
+ pub uv_bot: f32,
+ pub uv_left: f32,
+ pub uv_width: f32,
+ pub uv_height: f32,
+}
+
+/// Naïve glyph cache.
+///
+/// Currently only keyed by `char`, and thus not possible to hold different
+/// representations of the same code point.
+pub struct GlyphCache {
+ /// Cache of buffered glyphs.
+ cache: HashMap<GlyphKey, Glyph, BuildHasherDefault<FnvHasher>>,
+
+ /// Rasterizer for loading new glyphs.
+ rasterizer: Rasterizer,
+
+ /// Regular font.
+ pub font_key: FontKey,
+
+ /// Bold font.
+ pub bold_key: FontKey,
+
+ /// Italic font.
+ pub italic_key: FontKey,
+
+ /// Bold italic font.
+ pub bold_italic_key: FontKey,
+
+ /// Font size.
+ pub font_size: crossfont::Size,
+
+ /// Font offset.
+ font_offset: Delta<i8>,
+
+ /// Glyph offset.
+ glyph_offset: Delta<i8>,
+
+ /// Font metrics.
+ metrics: Metrics,
+
+ /// Whether to use the built-in font for box drawing characters.
+ builtin_box_drawing: bool,
+}
+
+impl GlyphCache {
+ pub fn new(mut rasterizer: Rasterizer, font: &Font) -> Result<GlyphCache, crossfont::Error> {
+ let (regular, bold, italic, bold_italic) = Self::compute_font_keys(font, &mut rasterizer)?;
+
+ // Need to load at least one glyph for the face before calling metrics.
+ // The glyph requested here ('m' at the time of writing) has no special
+ // meaning.
+ rasterizer.get_glyph(GlyphKey { font_key: regular, character: 'm', size: font.size() })?;
+
+ let metrics = rasterizer.metrics(regular, font.size())?;
+
+ Ok(Self {
+ cache: HashMap::default(),
+ rasterizer,
+ font_size: font.size(),
+ font_key: regular,
+ bold_key: bold,
+ italic_key: italic,
+ bold_italic_key: bold_italic,
+ font_offset: font.offset,
+ glyph_offset: font.glyph_offset,
+ metrics,
+ builtin_box_drawing: font.builtin_box_drawing,
+ })
+ }
+
+ fn load_glyphs_for_font<L: LoadGlyph>(&mut self, font: FontKey, loader: &mut L) {
+ let size = self.font_size;
+
+ // Cache all ascii characters.
+ for i in 32u8..=126u8 {
+ self.get(GlyphKey { font_key: font, character: i as char, size }, loader, true);
+ }
+ }
+
+ /// Computes font keys for (Regular, Bold, Italic, Bold Italic).
+ fn compute_font_keys(
+ font: &Font,
+ rasterizer: &mut Rasterizer,
+ ) -> Result<(FontKey, FontKey, FontKey, FontKey), crossfont::Error> {
+ let size = font.size();
+
+ // Load regular font.
+ let regular_desc = Self::make_desc(font.normal(), Slant::Normal, Weight::Normal);
+
+ let regular = Self::load_regular_font(rasterizer, &regular_desc, size)?;
+
+ // Helper to load a description if it is not the `regular_desc`.
+ let mut load_or_regular = |desc: FontDesc| {
+ if desc == regular_desc {
+ regular
+ } else {
+ rasterizer.load_font(&desc, size).unwrap_or(regular)
+ }
+ };
+
+ // Load bold font.
+ let bold_desc = Self::make_desc(&font.bold(), Slant::Normal, Weight::Bold);
+
+ let bold = load_or_regular(bold_desc);
+
+ // Load italic font.
+ let italic_desc = Self::make_desc(&font.italic(), Slant::Italic, Weight::Normal);
+
+ let italic = load_or_regular(italic_desc);
+
+ // Load bold italic font.
+ let bold_italic_desc = Self::make_desc(&font.bold_italic(), Slant::Italic, Weight::Bold);
+
+ let bold_italic = load_or_regular(bold_italic_desc);
+
+ Ok((regular, bold, italic, bold_italic))
+ }
+
+ fn load_regular_font(
+ rasterizer: &mut Rasterizer,
+ description: &FontDesc,
+ size: Size,
+ ) -> Result<FontKey, crossfont::Error> {
+ match rasterizer.load_font(description, size) {
+ Ok(font) => Ok(font),
+ Err(err) => {
+ error!("{}", err);
+
+ let fallback_desc =
+ Self::make_desc(Font::default().normal(), Slant::Normal, Weight::Normal);
+ rasterizer.load_font(&fallback_desc, size)
+ },
+ }
+ }
+
+ fn make_desc(desc: &FontDescription, slant: Slant, weight: Weight) -> FontDesc {
+ let style = if let Some(ref spec) = desc.style {
+ Style::Specific(spec.to_owned())
+ } else {
+ Style::Description { slant, weight }
+ };
+ FontDesc::new(desc.family.clone(), style)
+ }
+
+ /// Get a glyph from the font.
+ ///
+ /// If the glyph has never been loaded before, it will be rasterized and inserted into the
+ /// cache.
+ ///
+ /// # Errors
+ ///
+ /// This will fail when the glyph could not be rasterized. Usually this is due to the glyph
+ /// not being present in any font.
+ pub fn get<L: ?Sized>(
+ &mut self,
+ glyph_key: GlyphKey,
+ loader: &mut L,
+ show_missing: bool,
+ ) -> Glyph
+ where
+ L: LoadGlyph,
+ {
+ // Try to load glyph from cache.
+ if let Some(glyph) = self.cache.get(&glyph_key) {
+ return *glyph;
+ };
+
+ // Rasterize the glyph using the built-in font for special characters or the user's font
+ // for everything else.
+ let rasterized = self
+ .builtin_box_drawing
+ .then(|| {
+ builtin_font::builtin_glyph(
+ glyph_key.character,
+ &self.metrics,
+ &self.font_offset,
+ &self.glyph_offset,
+ )
+ })
+ .flatten()
+ .map_or_else(|| self.rasterizer.get_glyph(glyph_key), Ok);
+
+ let glyph = match rasterized {
+ Ok(rasterized) => self.load_glyph(loader, rasterized),
+ // Load fallback glyph.
+ Err(RasterizerError::MissingGlyph(rasterized)) if show_missing => {
+ // Use `\0` as "missing" glyph to cache it only once.
+ let missing_key = GlyphKey { character: '\0', ..glyph_key };
+ if let Some(glyph) = self.cache.get(&missing_key) {
+ *glyph
+ } else {
+ // If no missing glyph was loaded yet, insert it as `\0`.
+ let glyph = self.load_glyph(loader, rasterized);
+ self.cache.insert(missing_key, glyph);
+
+ glyph
+ }
+ },
+ Err(_) => self.load_glyph(loader, Default::default()),
+ };
+
+ // Cache rasterized glyph.
+ *self.cache.entry(glyph_key).or_insert(glyph)
+ }
+
+ /// Load glyph into the atlas.
+ ///
+ /// This will apply all transforms defined for the glyph cache to the rasterized glyph before
+ pub fn load_glyph<L: ?Sized>(&self, loader: &mut L, mut glyph: RasterizedGlyph) -> Glyph
+ where
+ L: LoadGlyph,
+ {
+ glyph.left += i32::from(self.glyph_offset.x);
+ glyph.top += i32::from(self.glyph_offset.y);
+ glyph.top -= self.metrics.descent as i32;
+
+ // The metrics of zero-width characters are based on rendering
+ // the character after the current cell, with the anchor at the
+ // right side of the preceding character. Since we render the
+ // zero-width characters inside the preceding character, the
+ // anchor has been moved to the right by one cell.
+ if glyph.character.width() == Some(0) {
+ glyph.left += self.metrics.average_advance as i32;
+ }
+
+ // Add glyph to cache.
+ loader.load_glyph(&glyph)
+ }
+
+ /// Clear currently cached data in both GL and the registry.
+ pub fn clear_glyph_cache<L: LoadGlyph>(&mut self, loader: &mut L) {
+ loader.clear();
+ self.cache = HashMap::default();
+
+ self.load_common_glyphs(loader);
+ }
+
+ pub fn update_font_size<L: LoadGlyph>(
+ &mut self,
+ font: &Font,
+ scale_factor: f64,
+ loader: &mut L,
+ ) -> Result<(), crossfont::Error> {
+ // Update dpi scaling.
+ self.rasterizer.update_dpr(scale_factor as f32);
+ self.font_offset = font.offset;
+
+ // Recompute font keys.
+ let (regular, bold, italic, bold_italic) =
+ Self::compute_font_keys(font, &mut self.rasterizer)?;
+
+ self.rasterizer.get_glyph(GlyphKey {
+ font_key: regular,
+ character: 'm',
+ size: font.size(),
+ })?;
+ let metrics = self.rasterizer.metrics(regular, font.size())?;
+
+ info!("Font size changed to {:?} with scale factor of {}", font.size(), scale_factor);
+
+ self.font_size = font.size();
+ self.font_key = regular;
+ self.bold_key = bold;
+ self.italic_key = italic;
+ self.bold_italic_key = bold_italic;
+ self.metrics = metrics;
+ self.builtin_box_drawing = font.builtin_box_drawing;
+
+ self.clear_glyph_cache(loader);
+
+ Ok(())
+ }
+
+ pub fn font_metrics(&self) -> crossfont::Metrics {
+ self.metrics
+ }
+
+ /// Prefetch glyphs that are almost guaranteed to be loaded anyways.
+ pub fn load_common_glyphs<L: LoadGlyph>(&mut self, loader: &mut L) {
+ self.load_glyphs_for_font(self.font_key, loader);
+ self.load_glyphs_for_font(self.bold_key, loader);
+ self.load_glyphs_for_font(self.italic_key, loader);
+ self.load_glyphs_for_font(self.bold_italic_key, loader);
+ }
+}
diff --git a/alacritty/src/renderer/text/mod.rs b/alacritty/src/renderer/text/mod.rs
new file mode 100644
index 00000000..05ac59e8
--- /dev/null
+++ b/alacritty/src/renderer/text/mod.rs
@@ -0,0 +1,202 @@
+use bitflags::bitflags;
+use crossfont::{GlyphKey, RasterizedGlyph};
+
+use alacritty_terminal::term::cell::Flags;
+use alacritty_terminal::term::SizeInfo;
+
+use crate::display::content::RenderableCell;
+use crate::gl;
+use crate::gl::types::*;
+
+mod atlas;
+mod builtin_font;
+mod gles2;
+mod glsl3;
+pub mod glyph_cache;
+
+use atlas::Atlas;
+pub use gles2::Gles2Renderer;
+pub use glsl3::Glsl3Renderer;
+pub use glyph_cache::GlyphCache;
+use glyph_cache::{Glyph, LoadGlyph};
+
+// 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;
+ }
+}
+
+pub trait TextRenderer<'a> {
+ type Shader: TextShader;
+ type RenderBatch: TextRenderBatch;
+ type RenderApi: TextRenderApi<Self::RenderBatch>;
+
+ /// Get loader API for the renderer.
+ fn loader_api(&mut self) -> LoaderApi<'_>;
+
+ /// Draw cells.
+ fn draw_cells<'b: 'a, I: Iterator<Item = RenderableCell>>(
+ &'b mut self,
+ size_info: &'b SizeInfo,
+ glyph_cache: &'a mut GlyphCache,
+ cells: I,
+ ) {
+ self.with_api(size_info, |mut api| {
+ for cell in cells {
+ api.draw_cell(cell, glyph_cache, size_info);
+ }
+ })
+ }
+
+ fn with_api<'b: 'a, F, T>(&'b mut self, size_info: &'b SizeInfo, func: F) -> T
+ where
+ F: FnOnce(Self::RenderApi) -> T;
+
+ fn program(&self) -> &Self::Shader;
+
+ /// Resize the text rendering.
+ fn resize(&self, size: &SizeInfo) {
+ unsafe {
+ let program = self.program();
+ gl::UseProgram(program.id());
+ update_projection(program.projection_uniform(), size);
+ gl::UseProgram(0);
+ }
+ }
+
+ /// Invoke renderer with the loader.
+ fn with_loader<F: FnOnce(LoaderApi<'_>) -> T, T>(&mut self, func: F) -> T {
+ unsafe {
+ gl::ActiveTexture(gl::TEXTURE0);
+ }
+
+ func(self.loader_api())
+ }
+}
+
+pub trait TextRenderBatch {
+ /// Check if `Batch` is empty.
+ fn is_empty(&self) -> bool;
+
+ /// Check whether the `Batch` is full.
+ fn full(&self) -> bool;
+
+ /// Get texture `Batch` is using.
+ fn tex(&self) -> GLuint;
+
+ /// Add item to the batch.
+ fn add_item(&mut self, cell: &RenderableCell, glyph: &Glyph, size_info: &SizeInfo);
+}
+
+pub trait TextRenderApi<T: TextRenderBatch>: LoadGlyph {
+ /// Get `Batch` the api is using.
+ fn batch(&mut self) -> &mut T;
+
+ /// Render the underlying data.
+ fn render_batch(&mut self);
+
+ /// Add item to the rendering queue.
+ #[inline]
+ fn add_render_item(&mut self, cell: &RenderableCell, glyph: &Glyph, size_info: &SizeInfo) {
+ // Flush batch if tex changing.
+ if !self.batch().is_empty() && self.batch().tex() != glyph.tex_id {
+ self.render_batch();
+ }
+
+ self.batch().add_item(cell, glyph, size_info);
+
+ // Render batch and clear if it's full.
+ if self.batch().full() {
+ self.render_batch();
+ }
+ }
+
+ /// Draw cell.
+ fn draw_cell(
+ &mut self,
+ mut cell: RenderableCell,
+ glyph_cache: &mut GlyphCache,
+ size_info: &SizeInfo,
+ ) {
+ // Get font key for cell.
+ let font_key = match cell.flags & Flags::BOLD_ITALIC {
+ Flags::BOLD_ITALIC => glyph_cache.bold_italic_key,
+ Flags::ITALIC => glyph_cache.italic_key,
+ Flags::BOLD => glyph_cache.bold_key,
+ _ => glyph_cache.font_key,
+ };
+
+ // Ignore hidden cells and render tabs as spaces to prevent font issues.
+ let hidden = cell.flags.contains(Flags::HIDDEN);
+ if cell.character == '\t' || hidden {
+ cell.character = ' ';
+ }
+
+ let mut glyph_key =
+ GlyphKey { font_key, size: glyph_cache.font_size, character: cell.character };
+
+ // Add cell to batch.
+ let glyph = glyph_cache.get(glyph_key, self, true);
+ self.add_render_item(&cell, &glyph, size_info);
+
+ // Render visible zero-width characters.
+ if let Some(zerowidth) = cell.zerowidth.take().filter(|_| !hidden) {
+ for character in zerowidth {
+ glyph_key.character = character;
+ let glyph = glyph_cache.get(glyph_key, self, false);
+ self.add_render_item(&cell, &glyph, size_info);
+ }
+ }
+ }
+}
+
+pub trait TextShader {
+ fn id(&self) -> GLuint;
+
+ /// Id of the projection uniform.
+ fn projection_uniform(&self) -> GLint;
+}
+
+#[derive(Debug)]
+pub struct LoaderApi<'a> {
+ active_tex: &'a mut GLuint,
+ atlas: &'a mut Vec<Atlas>,
+ current_atlas: &'a mut usize,
+}
+
+impl<'a> LoadGlyph for LoaderApi<'a> {
+ fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph {
+ Atlas::load_glyph(self.active_tex, self.atlas, self.current_atlas, rasterized)
+ }
+
+ fn clear(&mut self) {
+ Atlas::clear_atlas(self.atlas, self.current_atlas)
+ }
+}
+
+fn update_projection(u_projection: GLint, size: &SizeInfo) {
+ let width = size.width();
+ let height = size.height();
+ let padding_x = size.padding_x();
+ let padding_y = size.padding_y();
+
+ // Bounds check.
+ if (width as u32) < (2 * padding_x as u32) || (height as u32) < (2 * padding_y as u32) {
+ return;
+ }
+
+ // Compute scale and offset factors, from pixel to ndc space. Y is inverted.
+ // [0, width - 2 * padding_x] to [-1, 1]
+ // [height - 2 * padding_y, 0] to [-1, 1]
+ let scale_x = 2. / (width - 2. * padding_x);
+ let scale_y = -2. / (height - 2. * padding_y);
+ let offset_x = -1.;
+ let offset_y = 1.;
+
+ unsafe {
+ gl::Uniform4f(u_projection, offset_x, offset_y, scale_x, scale_y);
+ }
+}