summaryrefslogtreecommitdiff
path: root/font
diff options
context:
space:
mode:
authorKirill Chibisov <wchibisovkirill@gmail.com>2019-12-10 01:12:44 +0300
committerChristian Duerr <contact@christianduerr.com>2019-12-09 23:12:44 +0100
commit79b19176eeb57fbd6b137160afd6bc9f5518ad33 (patch)
treea3f76e83973e1bba2090afe39fbaa688d48efbf6 /font
parent88b4dbfc5a890569fcfac3fe400fe0ad0ea234cc (diff)
downloadalacritty-79b19176eeb57fbd6b137160afd6bc9f5518ad33.tar.gz
alacritty-79b19176eeb57fbd6b137160afd6bc9f5518ad33.zip
Add support for colored emojis on Linux/BSD
Fixes #153.
Diffstat (limited to 'font')
-rw-r--r--font/src/darwin/byte_order.rs21
-rw-r--r--font/src/darwin/mod.rs25
-rw-r--r--font/src/directwrite/mod.rs6
-rw-r--r--font/src/ft/fc/pattern.rs1
-rw-r--r--font/src/ft/mod.rs172
-rw-r--r--font/src/lib.rs27
6 files changed, 209 insertions, 43 deletions
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
@@ -24,6 +24,27 @@ pub const kCGBitmapByteOrder32Host: u32 = kCGBitmapByteOrder32Little;
pub const kCGBitmapByteOrder32Host: u32 = kCGBitmapByteOrder32Big;
#[cfg(target_endian = "little")]
+pub fn extract_rgba(bytes: &[u8]) -> Vec<u8> {
+ 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<u8>) -> Vec<u8> {
+ bytes
+}
+
+#[cfg(target_endian = "little")]
pub fn extract_rgb(bytes: &[u8]) -> Vec<u8> {
let pixels = bytes.len() / 4;
let mut rgb = Vec::with_capacity(pixels * 3);
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<dwrote::FontFace>,
@@ -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<FixedSize>,
+ has_color: bool,
+ pixelsize_fixup_factor: Option<f64>,
}
impl fmt::Debug for Face {
@@ -64,6 +70,7 @@ pub struct FreeTypeRasterizer {
library: Library,
keys: HashMap<PathBuf, FontKey>,
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<FreeTypeRasterizer, Error> {
@@ -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<FontKey, Error> {
// 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<FontKey, Error> {
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<FontKey, Error> {
+ fn get_specific_face(&mut self, desc: &FontDesc, style: &str) -> Result<FontKey, Error> {
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<u8>)> {
+ ) -> 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::<u8>::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<u8>,
+ pub buf: BitmapBuffer,
}
-impl Default for RasterizedGlyph {
- fn default() -> RasterizedGlyph {
- RasterizedGlyph { c: ' ', width: 0, height: 0, top: 0, left: 0, buf: Vec::new() }
- }
+#[derive(Clone, Debug)]
+pub enum BitmapBuffer {
+ RGB(Vec<u8>),
+ RGBA(Vec<u8>),
}
-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()
+impl Default for RasterizedGlyph {
+ fn default() -> RasterizedGlyph {
+ 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()
}
}