From 79b19176eeb57fbd6b137160afd6bc9f5518ad33 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Tue, 10 Dec 2019 01:12:44 +0300 Subject: [PATCH] Add support for colored emojis on Linux/BSD Fixes #153. --- CHANGELOG.md | 1 + alacritty/src/cursor.rs | 24 ++++- alacritty/src/renderer/mod.rs | 33 ++++++- font/src/darwin/byte_order.rs | 21 +++++ font/src/darwin/mod.rs | 25 ++++- font/src/directwrite/mod.rs | 6 +- font/src/ft/fc/pattern.rs | 1 + font/src/ft/mod.rs | 172 +++++++++++++++++++++++++++++----- font/src/lib.rs | 27 +++--- 9 files changed, 258 insertions(+), 52 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4dcc112..c769d5d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Terminal escape bindings with combined modifiers for Delete and Insert - /Applications symlink into OS X DMG for easier installation +- Colored emojis on Linux/BSD ### Fixed diff --git a/alacritty/src/cursor.rs b/alacritty/src/cursor.rs index e671bf4b..a3e6a2ca 100644 --- a/alacritty/src/cursor.rs +++ b/alacritty/src/cursor.rs @@ -18,7 +18,7 @@ use std::cmp; use alacritty_terminal::ansi::CursorStyle; -use font::{Metrics, RasterizedGlyph}; +use font::{BitmapBuffer, Metrics, RasterizedGlyph}; /// Width/Height of the cursor relative to the font width pub const CURSOR_WIDTH_PERCENTAGE: i32 = 15; @@ -55,7 +55,14 @@ pub fn get_underline_cursor_glyph(width: i32, line_width: i32) -> RasterizedGlyp let buf = vec![255u8; (width * line_width * 3) as usize]; // Create a custom glyph with the rectangle data attached to it - RasterizedGlyph { c: ' ', top: line_width, left: 0, height: line_width, width, buf } + RasterizedGlyph { + c: ' ', + top: line_width, + left: 0, + height: line_width, + width, + buf: BitmapBuffer::RGB(buf), + } } // Returns a custom beam cursor character @@ -64,7 +71,14 @@ pub fn get_beam_cursor_glyph(height: i32, line_width: i32) -> RasterizedGlyph { let buf = vec![255u8; (line_width * height * 3) as usize]; // Create a custom glyph with the rectangle data attached to it - RasterizedGlyph { c: ' ', top: height, left: 0, height, width: line_width, buf } + RasterizedGlyph { + c: ' ', + top: height, + left: 0, + height, + width: line_width, + buf: BitmapBuffer::RGB(buf), + } } // Returns a custom box cursor character @@ -86,7 +100,7 @@ pub fn get_box_cursor_glyph(height: i32, width: i32, line_width: i32) -> Rasteri } // Create a custom glyph with the rectangle data attached to it - RasterizedGlyph { c: ' ', top: height, left: 0, height, width, buf } + RasterizedGlyph { c: ' ', top: height, left: 0, height, width, buf: BitmapBuffer::RGB(buf) } } // Returns a custom block cursor character @@ -95,5 +109,5 @@ pub fn get_block_cursor_glyph(height: i32, width: i32) -> RasterizedGlyph { let buf = vec![255u8; (width * height * 3) as usize]; // Create a custom glyph with the rectangle data attached to it - RasterizedGlyph { c: ' ', top: height, left: 0, height, width, buf } + RasterizedGlyph { c: ' ', top: height, left: 0, height, width, buf: BitmapBuffer::RGB(buf) } } diff --git a/alacritty/src/renderer/mod.rs b/alacritty/src/renderer/mod.rs index a0617988..2d124ccc 100644 --- a/alacritty/src/renderer/mod.rs +++ b/alacritty/src/renderer/mod.rs @@ -22,7 +22,9 @@ use std::sync::mpsc; use std::time::Duration; use fnv::FnvHasher; -use font::{self, FontDesc, FontKey, GlyphKey, Rasterize, RasterizedGlyph, Rasterizer}; +use font::{ + self, BitmapBuffer, FontDesc, FontKey, GlyphKey, Rasterize, RasterizedGlyph, Rasterizer, +}; use log::{error, info}; use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; @@ -139,6 +141,7 @@ pub struct RectShaderProgram { #[derive(Copy, Debug, Clone)] pub struct Glyph { tex_id: GLuint, + colored: bool, top: f32, left: f32, width: f32, @@ -462,11 +465,19 @@ impl Batch { Batch { tex: 0, instances: Vec::with_capacity(BATCH_MAX) } } - pub fn add_item(&mut self, cell: RenderableCell, glyph: &Glyph) { + pub fn add_item(&mut self, mut cell: RenderableCell, glyph: &Glyph) { if self.is_empty() { self.tex = glyph.tex_id; } + if glyph.colored { + // XXX Temporary workaround to prevent emojis being rendered with a wrong colors on, at + // least, dark backgrounds. For more info see #1864. + cell.fg.r = 255; + cell.fg.g = 255; + cell.fg.b = 255; + } + self.instances.push(InstanceData { col: cell.column.0 as f32, row: cell.line.0 as f32, @@ -1091,6 +1102,7 @@ fn load_glyph( }, Err(AtlasInsertError::GlyphTooLarge) => Glyph { tex_id: atlas[*current_atlas].id, + colored: false, top: 0.0, left: 0.0, width: 0.0, @@ -1573,11 +1585,23 @@ impl Atlas { let offset_x = self.row_extent; let height = glyph.height as i32; let width = glyph.width as i32; + let colored; unsafe { gl::BindTexture(gl::TEXTURE_2D, self.id); // Load data into OpenGL + let (format, buf) = match &glyph.buf { + BitmapBuffer::RGB(buf) => { + colored = false; + (gl::RGB, buf) + }, + BitmapBuffer::RGBA(buf) => { + colored = true; + (gl::RGBA, buf) + }, + }; + gl::TexSubImage2D( gl::TEXTURE_2D, 0, @@ -1585,9 +1609,9 @@ impl Atlas { offset_y, width, height, - gl::RGB, + format, gl::UNSIGNED_BYTE, - glyph.buf.as_ptr() as *const _, + buf.as_ptr() as *const _, ); gl::BindTexture(gl::TEXTURE_2D, 0); @@ -1608,6 +1632,7 @@ impl Atlas { Glyph { tex_id: self.id, + colored, top: glyph.top as f32, width: width as f32, height: height as f32, diff --git a/font/src/darwin/byte_order.rs b/font/src/darwin/byte_order.rs index 382caa31..1574cf19 100644 --- a/font/src/darwin/byte_order.rs +++ b/font/src/darwin/byte_order.rs @@ -23,6 +23,27 @@ pub const kCGBitmapByteOrder32Host: u32 = kCGBitmapByteOrder32Little; #[cfg(target_endian = "big")] pub const kCGBitmapByteOrder32Host: u32 = kCGBitmapByteOrder32Big; +#[cfg(target_endian = "little")] +pub fn extract_rgba(bytes: &[u8]) -> Vec { + let pixels = bytes.len() / 4; + let mut rgb = Vec::with_capacity(pixels * 4); + + for i in 0..pixels { + let offset = i * 4; + rgb.push(bytes[offset + 2]); + rgb.push(bytes[offset + 1]); + rgb.push(bytes[offset]); + rgb.push(bytes[offset + 3]); + } + + rgb +} + +#[cfg(target_endian = "big")] +pub fn extract_rgba(bytes: Vec) -> Vec { + bytes +} + #[cfg(target_endian = "little")] pub fn extract_rgb(bytes: &[u8]) -> Vec { let pixels = bytes.len() / 4; diff --git a/font/src/darwin/mod.rs b/font/src/darwin/mod.rs index dae7ee04..f95802fc 100644 --- a/font/src/darwin/mod.rs +++ b/font/src/darwin/mod.rs @@ -33,6 +33,7 @@ use core_text::font::{ }; use core_text::font_collection::create_for_family; use core_text::font_collection::get_family_names as ct_get_family_names; +use core_text::font_descriptor::kCTFontColorGlyphsTrait; use core_text::font_descriptor::kCTFontDefaultOrientation; use core_text::font_descriptor::kCTFontHorizontalOrientation; use core_text::font_descriptor::kCTFontVerticalOrientation; @@ -41,10 +42,9 @@ use core_text::font_descriptor::{CTFontDescriptor, CTFontOrientation}; use euclid::{Point2D, Rect, Size2D}; -use super::{FontDesc, FontKey, GlyphKey, Metrics, RasterizedGlyph}; +use super::{BitmapBuffer, FontDesc, FontKey, GlyphKey, Metrics, RasterizedGlyph}; pub mod byte_order; -use self::byte_order::extract_rgb; use self::byte_order::kCGBitmapByteOrder32Host; use super::Size; @@ -431,6 +431,10 @@ impl Font { self.ct_font.symbolic_traits().is_italic() } + pub fn is_colored(&self) -> bool { + (self.ct_font.symbolic_traits() & kCTFontColorGlyphsTrait) != 0 + } + fn glyph_advance(&self, character: char) -> f64 { let index = self.glyph_index(character).unwrap(); @@ -471,7 +475,7 @@ impl Font { height: 0, top: 0, left: 0, - buf: Vec::new(), + buf: BitmapBuffer::RGB(Vec::new()), }); } @@ -520,7 +524,11 @@ impl Font { let rasterized_pixels = cg_context.data().to_vec(); - let buf = extract_rgb(&rasterized_pixels); + let buf = if self.is_colored() { + BitmapBuffer::RGBA(byte_order::extract_rgba(&rasterized_pixels)) + } else { + BitmapBuffer::RGB(byte_order::extract_rgb(&rasterized_pixels)) + }; Ok(RasterizedGlyph { c: character, @@ -564,6 +572,8 @@ impl Font { #[cfg(test)] mod tests { + use super::BitmapBuffer; + #[test] fn get_family_names() { let names = super::get_family_names(); @@ -585,11 +595,16 @@ mod tests { for c in &['a', 'b', 'c', 'd'] { let glyph = font.get_glyph(*c, 72., false).unwrap(); + let buf = match &glyph.buf { + BitmapBuffer::RGB(buf) => buf, + BitmapBuffer::RGBA(buf) => buf, + }; + // Debug the glyph.. sigh for row in 0..glyph.height { for col in 0..glyph.width { let index = ((glyph.width * 3 * row) + (col * 3)) as usize; - let value = glyph.buf[index]; + let value = buf[index]; let c = match value { 0..=50 => ' ', 51..=100 => '.', diff --git a/font/src/directwrite/mod.rs b/font/src/directwrite/mod.rs index 579f7faa..74f3d6e2 100644 --- a/font/src/directwrite/mod.rs +++ b/font/src/directwrite/mod.rs @@ -18,7 +18,9 @@ use self::dwrote::{ FontCollection, FontStretch, FontStyle, FontWeight, GlyphOffset, GlyphRunAnalysis, }; -use super::{FontDesc, FontKey, GlyphKey, Metrics, RasterizedGlyph, Size, Slant, Style, Weight}; +use super::{ + BitmapBuffer, FontDesc, FontKey, GlyphKey, Metrics, RasterizedGlyph, Size, Slant, Style, Weight, +}; pub struct DirectWriteRasterizer { fonts: Vec, @@ -173,7 +175,7 @@ impl crate::Rasterize for DirectWriteRasterizer { height: (bounds.bottom - bounds.top) as i32, top: -bounds.top, left: bounds.left, - buf, + buf: BitmapBuffer::RGB(buf), }) } diff --git a/font/src/ft/fc/pattern.rs b/font/src/ft/fc/pattern.rs index 149a45b1..25007392 100644 --- a/font/src/ft/fc/pattern.rs +++ b/font/src/ft/fc/pattern.rs @@ -420,6 +420,7 @@ impl PatternRef { size() => b"size\0", aspect() => b"aspect\0", pixelsize() => b"pixelsize\0", + pixelsizefixupfactor() => b"pixelsizefixupfactor\0", scale() => b"scale\0", dpi() => b"dpi\0" } diff --git a/font/src/ft/mod.rs b/font/src/ft/mod.rs index 0886f177..431a5004 100644 --- a/font/src/ft/mod.rs +++ b/font/src/ft/mod.rs @@ -13,18 +13,22 @@ // limitations under the License. // //! Rasterization powered by FreeType and FontConfig -use std::cmp::min; +use std::cmp::{min, Ordering}; use std::collections::HashMap; use std::fmt; use std::path::PathBuf; +use freetype::freetype_sys; use freetype::tt_os2::TrueTypeOS2Table; use freetype::{self, Library}; use libc::c_uint; pub mod fc; -use super::{FontDesc, FontKey, GlyphKey, Metrics, RasterizedGlyph, Size, Slant, Style, Weight}; +use super::{ + BitmapBuffer, FontDesc, FontKey, GlyphKey, Metrics, Rasterize, RasterizedGlyph, Size, Slant, + Style, Weight, +}; struct FixedSize { pixelsize: f64, @@ -37,6 +41,8 @@ struct Face { render_mode: freetype::RenderMode, lcd_filter: c_uint, non_scalable: Option, + has_color: bool, + pixelsize_fixup_factor: Option, } impl fmt::Debug for Face { @@ -64,6 +70,7 @@ pub struct FreeTypeRasterizer { library: Library, keys: HashMap, device_pixel_ratio: f32, + pixel_size: f64, } #[inline] @@ -71,7 +78,7 @@ fn to_freetype_26_6(f: f32) -> isize { ((1i32 << 6) as f32 * f) as isize } -impl ::Rasterize for FreeTypeRasterizer { +impl Rasterize for FreeTypeRasterizer { type Err = Error; fn new(device_pixel_ratio: f32, _: bool) -> Result { @@ -82,6 +89,7 @@ impl ::Rasterize for FreeTypeRasterizer { keys: HashMap::new(), library, device_pixel_ratio, + pixel_size: 0.0, }) } @@ -181,15 +189,16 @@ impl FreeTypeRasterizer { fn get_face(&mut self, desc: &FontDesc, size: Size) -> Result { // Adjust for DPI let size = Size::new(size.as_f32_pts() * self.device_pixel_ratio * 96. / 72.); + self.pixel_size = f64::from(size.as_f32_pts()); match desc.style { Style::Description { slant, weight } => { // Match nearest font - self.get_matching_face(&desc, slant, weight, size) + self.get_matching_face(&desc, slant, weight) }, Style::Specific(ref style) => { // If a name was specified, try and load specifically that font. - self.get_specific_face(&desc, &style, size) + self.get_specific_face(&desc, &style) }, } } @@ -212,13 +221,12 @@ impl FreeTypeRasterizer { desc: &FontDesc, slant: Slant, weight: Weight, - size: Size, ) -> Result { let mut pattern = fc::Pattern::new(); pattern.add_family(&desc.name); pattern.set_weight(weight.into_fontconfig_type()); pattern.set_slant(slant.into_fontconfig_type()); - pattern.add_pixelsize(f64::from(size.as_f32_pts())); + pattern.add_pixelsize(self.pixel_size); let font = fc::font_match(fc::Config::get_current(), &mut pattern) .ok_or_else(|| Error::MissingFont(desc.to_owned()))?; @@ -228,16 +236,11 @@ impl FreeTypeRasterizer { }) } - fn get_specific_face( - &mut self, - desc: &FontDesc, - style: &str, - size: Size, - ) -> Result { + fn get_specific_face(&mut self, desc: &FontDesc, style: &str) -> Result { let mut pattern = fc::Pattern::new(); pattern.add_family(&desc.name); pattern.add_style(style); - pattern.add_pixelsize(f64::from(size.as_f32_pts())); + pattern.add_pixelsize(self.pixel_size); let font = fc::font_match(fc::Config::get_current(), &mut pattern) .ok_or_else(|| Error::MissingFont(desc.to_owned()))?; @@ -253,7 +256,7 @@ impl FreeTypeRasterizer { } trace!("Got font path={:?}", path); - let ft_face = self.library.new_face(&path, index)?; + let mut ft_face = self.library.new_face(&path, index)?; // Get available pixel sizes if font isn't scalable. let non_scalable = if pattern.scalable().next().unwrap_or(true) { @@ -265,6 +268,16 @@ impl FreeTypeRasterizer { Some(FixedSize { pixelsize: pixelsize.next().expect("has 1+ pixelsize") }) }; + let pixelsize_fixup_factor = pattern.pixelsizefixupfactor().next(); + + let has_color = ft_face.has_color(); + if has_color { + unsafe { + // Select the colored bitmap size to use from the array of available sizes + freetype_sys::FT_Select_Size(ft_face.raw_mut(), 0); + } + } + let face = Face { ft_face, key: FontKey::next(), @@ -272,6 +285,8 @@ impl FreeTypeRasterizer { render_mode: Self::ft_render_mode(pattern), lcd_filter: Self::ft_lcd_filter(pattern), non_scalable, + has_color, + pixelsize_fixup_factor, }; debug!("Loaded Face {:?}", face); @@ -320,7 +335,9 @@ impl FreeTypeRasterizer { glyph_key.size.as_f32_pts() * self.device_pixel_ratio * 96. / 72. }); - face.ft_face.set_char_size(to_freetype_26_6(size), 0, 0, 0)?; + if !face.has_color { + face.ft_face.set_char_size(to_freetype_26_6(size), 0, 0, 0)?; + } unsafe { let ft_lib = self.library.raw(); @@ -328,19 +345,33 @@ impl FreeTypeRasterizer { } face.ft_face.load_glyph(index as u32, face.load_flags)?; + let glyph = face.ft_face.glyph(); glyph.render_glyph(face.render_mode)?; let (pixel_height, pixel_width, buf) = Self::normalize_buffer(&glyph.bitmap())?; - Ok(RasterizedGlyph { + let rasterized_glyph = RasterizedGlyph { c: glyph_key.c, top: glyph.bitmap_top(), left: glyph.bitmap_left(), width: pixel_width, height: pixel_height, buf, - }) + }; + + if face.has_color { + let fixup_factor = if let Some(pixelsize_fixup_factor) = face.pixelsize_fixup_factor { + pixelsize_fixup_factor + } else { + // Fallback if user has bitmap scaling disabled + let metrics = face.ft_face.size_metrics().ok_or(Error::MissingSizeMetrics)?; + self.pixel_size as f64 / metrics.y_ppem as f64 + }; + Ok(downsample_bitmap(rasterized_glyph, fixup_factor)) + } else { + Ok(rasterized_glyph) + } } fn ft_load_flags(pat: &fc::Pattern) -> freetype::face::LoadFlag { @@ -348,6 +379,7 @@ impl FreeTypeRasterizer { let hinting = pat.hintstyle().next().unwrap_or(fc::HintStyle::Slight); let rgba = pat.rgba().next().unwrap_or(fc::Rgba::Unknown); let embedded_bitmaps = pat.embeddedbitmap().next().unwrap_or(true); + let color = pat.color().next().unwrap_or(false); use freetype::face::LoadFlag; let mut flags = match (antialias, hinting, rgba) { @@ -385,6 +417,10 @@ impl FreeTypeRasterizer { flags |= LoadFlag::NO_BITMAP; } + if color { + flags |= LoadFlag::COLOR; + } + flags } @@ -414,7 +450,7 @@ impl FreeTypeRasterizer { /// The i32 value in the return type is the number of pixels per row. fn normalize_buffer( bitmap: &freetype::bitmap::Bitmap, - ) -> freetype::FtResult<(i32, i32, Vec)> { + ) -> freetype::FtResult<(i32, i32, BitmapBuffer)> { use freetype::bitmap::PixelMode; let buf = bitmap.buffer(); @@ -427,7 +463,7 @@ impl FreeTypeRasterizer { let stop = start + bitmap.width() as usize; packed.extend_from_slice(&buf[start..stop]); } - Ok((bitmap.rows(), bitmap.width() / 3, packed)) + Ok((bitmap.rows(), bitmap.width() / 3, BitmapBuffer::RGB(packed))) }, PixelMode::LcdV => { for i in 0..bitmap.rows() / 3 { @@ -438,7 +474,7 @@ impl FreeTypeRasterizer { } } } - Ok((bitmap.rows() / 3, bitmap.width(), packed)) + Ok((bitmap.rows() / 3, bitmap.width(), BitmapBuffer::RGB(packed))) }, // Mono data is stored in a packed format using 1 bit per pixel. PixelMode::Mono => { @@ -469,7 +505,7 @@ impl FreeTypeRasterizer { byte += 1; } } - Ok((bitmap.rows(), bitmap.width(), packed)) + Ok((bitmap.rows(), bitmap.width(), BitmapBuffer::RGB(packed))) }, // Gray data is stored as a value between 0 and 255 using 1 byte per pixel. PixelMode::Gray => { @@ -482,7 +518,19 @@ impl FreeTypeRasterizer { packed.push(*byte); } } - Ok((bitmap.rows(), bitmap.width(), packed)) + Ok((bitmap.rows(), bitmap.width(), BitmapBuffer::RGB(packed))) + }, + PixelMode::Bgra => { + let buf_size = (bitmap.rows() * bitmap.width() * 4) as usize; + let mut i = 0; + while i < buf_size { + packed.push(buf[i + 2]); + packed.push(buf[i + 1]); + packed.push(buf[i]); + packed.push(buf[i + 3]); + i += 4; + } + Ok((bitmap.rows(), bitmap.width(), BitmapBuffer::RGBA(packed))) }, mode => panic!("unhandled pixel mode: {:?}", mode), } @@ -493,6 +541,7 @@ impl FreeTypeRasterizer { charset.add(glyph); let mut pattern = fc::Pattern::new(); pattern.add_charset(&charset); + pattern.add_pixelsize(self.pixel_size as f64); let config = fc::Config::get_current(); match fc::font_match(config, &mut pattern) { @@ -503,6 +552,9 @@ impl FreeTypeRasterizer { // load it again. Some(&key) => { debug!("Hit for font {:?}; no need to load", path); + // Update fixup factor + self.faces.get_mut(&key).unwrap().pixelsize_fixup_factor = + pattern.pixelsizefixupfactor().next(); Ok(key) }, @@ -529,6 +581,76 @@ impl FreeTypeRasterizer { } } +/// Downscale a bitmap by a fixed factor. +/// +/// This will take the `bitmap_glyph` as input and return the glyph's content downscaled by +/// `fixup_factor`. +fn downsample_bitmap(mut bitmap_glyph: RasterizedGlyph, fixup_factor: f64) -> RasterizedGlyph { + // Only scale colored buffers which are bigger than required + let bitmap_buffer = match (&bitmap_glyph.buf, fixup_factor.partial_cmp(&1.0)) { + (BitmapBuffer::RGBA(buffer), Some(Ordering::Less)) => buffer, + _ => return bitmap_glyph, + }; + + let bitmap_width = bitmap_glyph.width as usize; + let bitmap_height = bitmap_glyph.height as usize; + + let target_width = (bitmap_width as f64 * fixup_factor) as usize; + let target_height = (bitmap_height as f64 * fixup_factor) as usize; + + // Number of pixels in the input buffer, per pixel in the output buffer + let downsampling_step = 1.0 / fixup_factor; + + let mut downsampled_buffer = Vec::::with_capacity(target_width * target_height * 4); + + for line_index in 0..target_height { + // Get the first and last line which will be consolidated in the current output pixel + let line_index = line_index as f64; + let source_line_start = (line_index * downsampling_step).round() as usize; + let source_line_end = ((line_index + 1.) * downsampling_step).round() as usize; + + for column_index in 0..target_width { + // Get the first and last column which will be consolidated in the current output pixel + let column_index = column_index as f64; + let source_column_start = (column_index * downsampling_step).round() as usize; + let source_column_end = ((column_index + 1.) * downsampling_step).round() as usize; + + let (mut r, mut g, mut b, mut a) = (0u32, 0u32, 0u32, 0u32); + let mut pixels_picked: u32 = 0; + + // Consolidate all pixels within the source rectangle into a single averaged pixel + for source_line in source_line_start..source_line_end { + let source_pixel_index = source_line * bitmap_width; + + for source_column in source_column_start..source_column_end { + let offset = (source_pixel_index + source_column) * 4; + r += bitmap_buffer[offset] as u32; + g += bitmap_buffer[offset + 1] as u32; + b += bitmap_buffer[offset + 2] as u32; + a += bitmap_buffer[offset + 3] as u32; + pixels_picked += 1; + } + } + + // Add a single pixel to the output buffer for the downscaled source rectangle + downsampled_buffer.push((r / pixels_picked) as u8); + downsampled_buffer.push((g / pixels_picked) as u8); + downsampled_buffer.push((b / pixels_picked) as u8); + downsampled_buffer.push((a / pixels_picked) as u8); + } + } + + bitmap_glyph.buf = BitmapBuffer::RGBA(downsampled_buffer); + + // Downscale the metrics + bitmap_glyph.top = (bitmap_glyph.top as f64 * fixup_factor) as i32; + bitmap_glyph.left = (bitmap_glyph.left as f64 * fixup_factor) as i32; + bitmap_glyph.width = target_width as i32; + bitmap_glyph.height = target_height as i32; + + bitmap_glyph +} + /// Errors occurring when using the freetype rasterizer #[derive(Debug)] pub enum Error { @@ -545,7 +667,7 @@ pub enum Error { FontNotLoaded, } -impl ::std::error::Error for Error { +impl std::error::Error for Error { fn cause(&self) -> Option<&dyn std::error::Error> { match *self { Error::FreeType(ref err) => Some(err), @@ -563,7 +685,7 @@ impl ::std::error::Error for Error { } } -impl ::std::fmt::Display for Error { +impl std::fmt::Display for Error { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { match *self { Error::FreeType(ref err) => err.fmt(f), diff --git a/font/src/lib.rs b/font/src/lib.rs index 262cf911..8fd0f628 100644 --- a/font/src/lib.rs +++ b/font/src/lib.rs @@ -220,20 +220,25 @@ pub struct RasterizedGlyph { pub height: i32, pub top: i32, pub left: i32, - pub buf: Vec, + pub buf: BitmapBuffer, +} + +#[derive(Clone, Debug)] +pub enum BitmapBuffer { + RGB(Vec), + RGBA(Vec), } impl Default for RasterizedGlyph { fn default() -> RasterizedGlyph { - RasterizedGlyph { c: ' ', width: 0, height: 0, top: 0, left: 0, buf: Vec::new() } - } -} - -struct BufDebugger<'a>(&'a [u8]); - -impl<'a> fmt::Debug for BufDebugger<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("GlyphBuffer").field("len", &self.0.len()).field("bytes", &self.0).finish() + RasterizedGlyph { + c: ' ', + width: 0, + height: 0, + top: 0, + left: 0, + buf: BitmapBuffer::RGB(Vec::new()), + } } } @@ -245,7 +250,7 @@ impl fmt::Debug for RasterizedGlyph { .field("height", &self.height) .field("top", &self.top) .field("left", &self.left) - .field("buf", &BufDebugger(&self.buf[..])) + .field("buf", &self.buf) .finish() } }