use std::collections::HashMap; use std::mem; use crossfont::Metrics; use alacritty_terminal::grid::Dimensions; use alacritty_terminal::index::{Column, Point}; use alacritty_terminal::term::cell::Flags; use alacritty_terminal::term::color::Rgb; use alacritty_terminal::term::SizeInfo; use crate::display::content::RenderableCell; use crate::gl; use crate::gl::types::*; use crate::renderer; #[derive(Debug, Copy, Clone)] pub struct RenderRect { pub x: f32, pub y: f32, pub width: f32, pub height: f32, pub color: Rgb, pub alpha: f32, } impl RenderRect { pub fn new(x: f32, y: f32, width: f32, height: f32, color: Rgb, alpha: f32) -> Self { RenderRect { x, y, width, height, color, alpha } } } #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct RenderLine { pub start: Point, pub end: Point, pub color: Rgb, } impl RenderLine { pub fn rects(&self, flag: Flags, metrics: &Metrics, size: &SizeInfo) -> Vec { let mut rects = Vec::new(); let mut start = self.start; while start.line < self.end.line { let end = Point::new(start.line, size.last_column()); Self::push_rects(&mut rects, metrics, size, flag, start, end, self.color); start = Point::new(start.line + 1, Column(0)); } Self::push_rects(&mut rects, metrics, size, flag, start, self.end, self.color); rects } /// Push all rects required to draw the cell's line. fn push_rects( rects: &mut Vec, metrics: &Metrics, size: &SizeInfo, flag: Flags, start: Point, end: Point, color: Rgb, ) { match flag { Flags::UNDERCURL => { Self::push_undercurl_rects( rects, size, metrics.descent, start, end, metrics.underline_position + 1., metrics.underline_thickness, color) } Flags::DOUBLE_UNDERLINE => { // Position underlines so each one has 50% of descent available. let top_pos = 0.25 * metrics.descent; let bottom_pos = 0.75 * metrics.descent; rects.push(Self::create_rect( size, metrics.descent, start, end, top_pos, metrics.underline_thickness, color, )); rects.push(Self::create_rect( size, metrics.descent, start, end, bottom_pos, metrics.underline_thickness, color, )); }, Flags::UNDERLINE => { rects.push(Self::create_rect( size, metrics.descent, start, end, metrics.underline_position + 1., metrics.underline_thickness, color, )); }, Flags::OVERLINE => { let start_x = start.column.0 as f32 * size.cell_width(); let end_x = (end.column.0 + 1) as f32 * size.cell_width(); rects.push(RenderRect::new( start_x + size.padding_x(), (start.line as f32 * size.cell_height()) + (size.cell_height() / 8.), end_x - start_x, metrics.underline_thickness, color, 1., )); }, Flags::STRIKEOUT => { rects.push(Self::create_rect( size, metrics.descent, start, end, metrics.strikeout_position, metrics.strikeout_thickness, color, )); }, Flags::DOTTED_UNDERLINE => { Self::push_dotted_underline_rects( rects, size, metrics.descent, start, end, metrics.underline_position + 1., metrics.underline_thickness, color) } _ => unimplemented!("Invalid flag for cell line drawing specified"), }; } fn push_undercurl_rects( rects: &mut Vec, size: &SizeInfo, descent: f32, start: Point, end: Point, position: f32, mut thickness: f32, color: Rgb, ) { let start_x = start.column.0 as f32 * size.cell_width(); let end_x = (end.column.0 + 1) as f32 * size.cell_width(); // Make sure lines are always visible. thickness = thickness.max(1.); let line_bottom = (start.line as f32 + 1.) * size.cell_height(); let baseline = line_bottom + descent; let mut y = (baseline - position - thickness / 2.).ceil(); let max_y = line_bottom - thickness; if y > max_y { y = max_y; } let period_div = 1.5 * (2. * std::f32::consts::PI) / size.cell_width(); let amplitude_mul = size.cell_height() / 10.; let mut x = start_x; let mut idx = 0; while x < end_x { let altr = [1., 0., -1., 0.][idx % 4]; let fl = (x * period_div).sin() * amplitude_mul; let al = (fl.round() - fl).abs(); rects.push(RenderRect::new( x + size.padding_x(), y + size.padding_y() + fl, 1., thickness, color, (1. - al).powf(2.), )); let fl = (x * period_div).sin() * amplitude_mul - 0.5; let al = (fl.round() - fl).abs(); rects.push(RenderRect::new( x + size.padding_x(), y + size.padding_y() + fl, 1., thickness, color, (1. - al).powf(2.), )); let fl = (x * period_div).sin() * amplitude_mul + 0.5; let al = (fl.round() - fl).abs(); rects.push(RenderRect::new( x + size.padding_x(), y + size.padding_y() + fl, 1., thickness, color, (1. - al).powf(2.), )); x += 1.; idx += 1; } } fn push_dotted_underline_rects( rects: &mut Vec, size: &SizeInfo, descent: f32, start: Point, end: Point, position: f32, mut thickness: f32, color: Rgb, ) { let start_x = start.column.0 as f32 * size.cell_width(); let end_x = (end.column.0 + 1) as f32 * size.cell_width(); // Make sure lines are always visible. thickness = thickness.max(1.); let line_bottom = (start.line as f32 + 1.) * size.cell_height(); let baseline = line_bottom + descent; let mut y = (baseline - position - thickness / 2.).ceil(); let max_y = line_bottom - thickness; if y > max_y { y = max_y; } let mut x = start_x; let mut idx = 0; while x < end_x { if idx % 4 == 0 { let rect = RenderRect::new( x + size.padding_x(), y + size.padding_y(), thickness + 1., thickness + 1., color, 1., ); rects.push(rect); } x += 1.; idx += 1; } } /// Create a line's rect at a position relative to the baseline. fn create_rect( size: &SizeInfo, descent: f32, start: Point, end: Point, position: f32, mut thickness: f32, color: Rgb, ) -> RenderRect { let start_x = start.column.0 as f32 * size.cell_width(); let end_x = (end.column.0 + 1) as f32 * size.cell_width(); let width = end_x - start_x; // Make sure lines are always visible. thickness = thickness.max(1.); let line_bottom = (start.line as f32 + 1.) * size.cell_height(); let baseline = line_bottom + descent; let mut y = (baseline - position - thickness / 2.).ceil(); let max_y = line_bottom - thickness; if y > max_y { y = max_y; } RenderRect::new( start_x + size.padding_x(), y + size.padding_y(), width, thickness, color, 1., ) } } /// Lines for underline and strikeout. #[derive(Default)] pub struct RenderLines { inner: HashMap>, } impl RenderLines { #[inline] pub fn new() -> Self { Self::default() } #[inline] pub fn rects(&self, metrics: &Metrics, size: &SizeInfo) -> Vec { self.inner .iter() .flat_map(|(flag, lines)| { lines.iter().flat_map(move |line| line.rects(*flag, metrics, size)) }) .collect() } /// Update the stored lines with the next cell info. #[inline] pub fn update(&mut self, cell: &RenderableCell) { self.update_flag(cell, Flags::UNDERLINE); self.update_flag(cell, Flags::DOUBLE_UNDERLINE); self.update_flag(cell, Flags::STRIKEOUT); self.update_flag(cell, Flags::UNDERCURL); self.update_flag(cell, Flags::OVERLINE); self.update_flag(cell, Flags::DOTTED_UNDERLINE); } /// Update the lines for a specific flag. fn update_flag(&mut self, cell: &RenderableCell, flag: Flags) { if !cell.flags.contains(flag) { return; } // Include wide char spacer if the current cell is a wide char. let mut end = cell.point; if cell.flags.contains(Flags::WIDE_CHAR) { end.column += 1; } // Check if there's an active line. if let Some(line) = self.inner.get_mut(&flag).and_then(|lines| lines.last_mut()) { if cell.fg == line.color && cell.point.column == line.end.column + 1 && cell.point.line == line.end.line { // Update the length of the line. line.end = end; return; } } // Start new line if there currently is none. let line = RenderLine { start: cell.point, end, color: cell.sp }; match self.inner.get_mut(&flag) { Some(lines) => lines.push(line), None => { self.inner.insert(flag, vec![line]); }, } } } /// Shader sources for rect rendering program. static RECT_SHADER_F: &str = include_str!("../../res/rect.f.glsl"); static RECT_SHADER_V: &str = include_str!("../../res/rect.v.glsl"); #[repr(C)] #[derive(Debug, Clone, Copy)] struct Vertex { // Normalized screen coordinates. x: f32, y: f32, // Color. r: u8, g: u8, b: u8, a: u8, } #[derive(Debug)] pub struct RectRenderer { // GL buffer objects. vao: GLuint, vbo: GLuint, program: RectShaderProgram, vertices: Vec, } impl RectRenderer { pub fn new() -> Result { let mut vao: GLuint = 0; let mut vbo: GLuint = 0; let program = RectShaderProgram::new()?; unsafe { // Allocate buffers. gl::GenVertexArrays(1, &mut vao); gl::GenBuffers(1, &mut vbo); gl::BindVertexArray(vao); // VBO binding is not part of VAO itself, but VBO binding is stored in attributes. gl::BindBuffer(gl::ARRAY_BUFFER, vbo); let mut attribute_offset = 0; // Position. gl::VertexAttribPointer( 0, 2, gl::FLOAT, gl::FALSE, mem::size_of::() as i32, attribute_offset as *const _, ); gl::EnableVertexAttribArray(0); attribute_offset += mem::size_of::() * 2; // Color. gl::VertexAttribPointer( 1, 4, gl::UNSIGNED_BYTE, gl::TRUE, mem::size_of::() as i32, attribute_offset as *const _, ); gl::EnableVertexAttribArray(1); // Reset buffer bindings. gl::BindVertexArray(0); gl::BindBuffer(gl::ARRAY_BUFFER, 0); } Ok(Self { vao, vbo, program, vertices: Vec::new() }) } pub fn draw(&mut self, size_info: &SizeInfo, rects: Vec) { unsafe { // Bind VAO to enable vertex attribute slots. gl::BindVertexArray(self.vao); // Bind VBO only once for buffer data upload only. gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo); gl::UseProgram(self.program.id); } let half_width = size_info.width() / 2.; let half_height = size_info.height() / 2.; // Build rect vertices vector. self.vertices.clear(); for rect in &rects { self.add_rect(half_width, half_height, rect); } unsafe { // Upload accumulated vertices. gl::BufferData( gl::ARRAY_BUFFER, (self.vertices.len() * mem::size_of::()) as isize, self.vertices.as_ptr() as *const _, gl::STREAM_DRAW, ); // Draw all vertices as list of triangles. gl::DrawArrays(gl::TRIANGLES, 0, self.vertices.len() as i32); // Disable program. gl::UseProgram(0); // Reset buffer bindings to nothing. gl::BindBuffer(gl::ARRAY_BUFFER, 0); gl::BindVertexArray(0); } } fn add_rect(&mut self, half_width: f32, half_height: f32, rect: &RenderRect) { // Calculate rectangle vertices positions in normalized device coordinates. // NDC range from -1 to +1, with Y pointing up. let x = rect.x / half_width - 1.0; let y = -rect.y / half_height + 1.0; let width = rect.width / half_width; let height = rect.height / half_height; let Rgb { r, g, b } = rect.color; let a = (rect.alpha * 255.) as u8; // Make quad vertices. let quad = [ Vertex { x, y, r, g, b, a }, Vertex { x, y: y - height, r, g, b, a }, Vertex { x: x + width, y, r, g, b, a }, Vertex { x: x + width, y: y - height, r, g, b, a }, ]; // Append the vertices to form two triangles. self.vertices.push(quad[0]); self.vertices.push(quad[1]); self.vertices.push(quad[2]); self.vertices.push(quad[2]); self.vertices.push(quad[3]); self.vertices.push(quad[1]); } } impl Drop for RectRenderer { fn drop(&mut self) { unsafe { gl::DeleteBuffers(1, &self.vbo); gl::DeleteVertexArrays(1, &self.vao); } } } /// Rectangle drawing program. #[derive(Debug)] pub struct RectShaderProgram { /// Program id. id: GLuint, } impl RectShaderProgram { pub fn new() -> Result { let vertex_shader = renderer::create_shader(gl::VERTEX_SHADER, RECT_SHADER_V)?; let fragment_shader = renderer::create_shader(gl::FRAGMENT_SHADER, RECT_SHADER_F)?; let program = renderer::create_program(vertex_shader, fragment_shader)?; unsafe { gl::DeleteShader(fragment_shader); gl::DeleteShader(vertex_shader); gl::UseProgram(program); } let shader = Self { id: program }; unsafe { gl::UseProgram(0) } Ok(shader) } } impl Drop for RectShaderProgram { fn drop(&mut self) { unsafe { gl::DeleteProgram(self.id); } } }