alacritty/alacritty/src/renderer/text/mod.rs

203 lines
5.6 KiB
Rust

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