blob: 7dde899aaff442e929cb151d44e93192d531c4a0 [file] [log] [blame]
// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//! Font rendering based on CoreText
//!
//! TODO error handling... just search for unwrap.
#![allow(improper_ctypes)]
use std::collections::HashMap;
use std::ptr;
use ::{Slant, Weight, Style};
use core_foundation::base::TCFType;
use core_foundation::string::{CFString, CFStringRef};
use core_foundation::array::{CFIndex, CFArray};
use core_graphics::base::kCGImageAlphaPremultipliedFirst;
use core_graphics::color_space::CGColorSpace;
use core_graphics::context::{CGContext};
use core_graphics::font::{CGFont, CGGlyph};
use core_graphics::geometry::{CGPoint, CGRect, CGSize};
use core_text::font::{CTFont, new_from_descriptor as ct_new_from_descriptor, cascade_list_for_languages as ct_cascade_list_for_languages};
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::kCTFontDefaultOrientation;
use core_text::font_descriptor::kCTFontHorizontalOrientation;
use core_text::font_descriptor::kCTFontVerticalOrientation;
use core_text::font_descriptor::{CTFontDescriptor, CTFontDescriptorRef, CTFontOrientation};
use core_text::font_descriptor::SymbolicTraitAccessors;
use euclid::{Point2D, Rect, Size2D};
use super::{FontDesc, RasterizedGlyph, Metrics, FontKey, GlyphKey};
pub mod byte_order;
use self::byte_order::kCGBitmapByteOrder32Host;
use self::byte_order::extract_rgb;
use super::Size;
/// Font descriptor
///
/// The descriptor provides data about a font and supports creating a font.
#[derive(Debug)]
pub struct Descriptor {
family_name: String,
font_name: String,
style_name: String,
display_name: String,
font_path: String,
ct_descriptor: CTFontDescriptor
}
impl Descriptor {
fn new(desc:CTFontDescriptor) -> Descriptor {
Descriptor {
family_name: desc.family_name(),
font_name: desc.font_name(),
style_name: desc.style_name(),
display_name: desc.display_name(),
font_path: desc.font_path().unwrap_or_else(||{"".to_owned()}),
ct_descriptor: desc,
}
}
}
/// Rasterizer, the main type exported by this package
///
/// Given a fontdesc, can rasterize fonts.
pub struct Rasterizer {
fonts: HashMap<FontKey, Font>,
keys: HashMap<(FontDesc, Size), FontKey>,
device_pixel_ratio: f32,
use_thin_strokes: bool,
}
/// Errors occurring when using the core text rasterizer
#[derive(Debug)]
pub enum Error {
/// Tried to rasterize a glyph but it was not available
MissingGlyph(char),
/// Couldn't find font matching description
MissingFont(FontDesc),
/// Requested an operation with a FontKey that isn't known to the rasterizer
FontNotLoaded,
}
impl ::std::error::Error for Error {
fn description(&self) -> &str {
match *self {
Error::MissingGlyph(ref _c) => "couldn't find the requested glyph",
Error::MissingFont(ref _desc) => "couldn't find the requested font",
Error::FontNotLoaded => "tried to operate on font that hasn't been loaded",
}
}
}
impl ::std::fmt::Display for Error {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
match *self {
Error::MissingGlyph(ref c) => {
write!(f, "Glyph not found for char {:?}", c)
},
Error::MissingFont(ref desc) => {
write!(f, "Couldn't find a font with {}\
\n\tPlease check the font config in your alacritty.yml.", desc)
},
Error::FontNotLoaded => {
f.write_str("Tried to use a font that hasn't been loaded")
}
}
}
}
impl ::Rasterize for Rasterizer {
type Err = Error;
fn new(device_pixel_ratio: f32, use_thin_strokes: bool) -> Result<Rasterizer, Error> {
info!("device_pixel_ratio: {}", device_pixel_ratio);
Ok(Rasterizer {
fonts: HashMap::new(),
keys: HashMap::new(),
device_pixel_ratio: device_pixel_ratio,
use_thin_strokes: use_thin_strokes,
})
}
/// Get metrics for font specified by FontKey
fn metrics(&self, key: FontKey) -> Result<Metrics, Error> {
let font = self.fonts
.get(&key)
.ok_or(Error::FontNotLoaded)?;
Ok(font.metrics())
}
fn load_font(&mut self, desc: &FontDesc, size: Size) -> Result<FontKey, Error> {
self.keys
.get(&(desc.to_owned(), size))
.map(|k| Ok(*k))
.unwrap_or_else(|| {
let font = self.get_font(desc, size)?;
let key = FontKey::next();
self.fonts.insert(key, font);
self.keys.insert((desc.clone(), size), key);
Ok(key)
})
}
/// Get rasterized glyph for given glyph key
fn get_glyph(&mut self, glyph: &GlyphKey) -> Result<RasterizedGlyph, Error> {
// get loaded font
let font = self.fonts
.get(&glyph.font_key)
.ok_or(Error::FontNotLoaded)?;
// first try the font itself as a direct hit
self.maybe_get_glyph(glyph, font)
.unwrap_or_else(|| {
// then try fallbacks
for fallback in &font.fallbacks {
if let Some(result) = self.maybe_get_glyph(glyph, &fallback) {
// found a fallback
return result;
}
}
// no fallback, give up.
Err(Error::MissingGlyph(glyph.c))
})
}
}
impl Rasterizer {
fn get_specific_face(
&mut self,
desc: &FontDesc,
style: &str,
size: Size
) -> Result<Font, Error> {
let descriptors = descriptors_for_family(&desc.name[..]);
for descriptor in descriptors {
if descriptor.style_name == style {
// Found the font we want
let scaled_size = size.as_f32_pts() as f64 * self.device_pixel_ratio as f64;
let font = descriptor.to_font(scaled_size, true);
return Ok(font);
}
}
Err(Error::MissingFont(desc.to_owned()))
}
fn get_matching_face(
&mut self,
desc: &FontDesc,
slant: Slant,
weight: Weight,
size: Size
) -> Result<Font, Error> {
let bold = match weight {
Weight::Bold => true,
_ => false
};
let italic = match slant {
Slant::Normal => false,
_ => true,
};
let scaled_size = size.as_f32_pts() as f64 * self.device_pixel_ratio as f64;
let descriptors = descriptors_for_family(&desc.name[..]);
for descriptor in descriptors {
let font = descriptor.to_font(scaled_size, true);
if font.is_bold() == bold && font.is_italic() == italic {
// Found the font we want
return Ok(font);
}
}
Err(Error::MissingFont(desc.to_owned()))
}
fn get_font(&mut self, desc: &FontDesc, size: Size) -> Result<Font, Error> {
match desc.style {
Style::Specific(ref style) => self.get_specific_face(desc, style, size),
Style::Description { slant, weight } => {
self.get_matching_face(desc, slant, weight, size)
},
}
}
// Helper to try and get a glyph for a given font. Used for font fallback.
fn maybe_get_glyph(
&self,
glyph: &GlyphKey,
font: &Font,
) -> Option<Result<RasterizedGlyph, Error>> {
let scaled_size = self.device_pixel_ratio * glyph.size.as_f32_pts();
font.get_glyph(glyph.c, scaled_size as _, self.use_thin_strokes)
.map(|r| Some(Ok(r)))
.unwrap_or_else(|e| match e {
Error::MissingGlyph(_) => None,
_ => Some(Err(e)),
})
}
}
/// Specifies the intended rendering orientation of the font for obtaining glyph metrics
#[derive(Debug)]
pub enum FontOrientation {
Default = kCTFontDefaultOrientation as isize,
Horizontal = kCTFontHorizontalOrientation as isize,
Vertical = kCTFontVerticalOrientation as isize,
}
impl Default for FontOrientation {
fn default() -> FontOrientation {
FontOrientation::Default
}
}
/// A font
#[derive(Clone)]
pub struct Font {
ct_font: CTFont,
cg_font: CGFont,
fallbacks: Vec<Font>,
}
unsafe impl Send for Font {}
/// List all family names
pub fn get_family_names() -> Vec<String> {
// CFArray of CFStringRef
let names = ct_get_family_names();
let mut owned_names = Vec::new();
for name in names.iter() {
let family: CFString = unsafe { TCFType::wrap_under_get_rule(name as CFStringRef) };
owned_names.push(format!("{}", family));
}
owned_names
}
/// Return fallback descriptors for font/language list
fn cascade_list_for_languages(
ct_font: &CTFont,
languages: &Vec<String>
) -> Vec<Descriptor> {
// convert language type &Vec<String> -> CFArray
let langarr:CFArray<CFString> = {
let tmp:Vec<CFString> = languages.iter()
.map(|language| CFString::new(&language))
.collect();
CFArray::from_CFTypes(&tmp)
};
// CFArray of CTFontDescriptorRef (again)
let list = ct_cascade_list_for_languages(ct_font, &langarr.as_untyped());
// convert CFArray to Vec<Descriptor>
list.into_iter()
.map(|fontdesc| {
let desc: CTFontDescriptor = unsafe {
TCFType::wrap_under_get_rule(fontdesc as CTFontDescriptorRef)
};
Descriptor::new(desc)
})
.collect()
}
/// Get descriptors for family name
pub fn descriptors_for_family(family: &str) -> Vec<Descriptor> {
let mut out = Vec::new();
let ct_collection = match create_for_family(family) {
Some(c) => c,
None => return out,
};
// CFArray of CTFontDescriptorRef (i think)
let descriptors = ct_collection.get_descriptors();
for descriptor in descriptors.iter() {
let desc: CTFontDescriptor = unsafe {
TCFType::wrap_under_get_rule(descriptor as CTFontDescriptorRef)
};
out.push(Descriptor::new(desc));
}
out
}
impl Descriptor {
/// Create a Font from this descriptor
pub fn to_font(&self, size: f64, load_fallbacks:bool) -> Font {
let ct_font = ct_new_from_descriptor(&self.ct_descriptor, size);
let cg_font = ct_font.copy_to_CGFont();
let fallbacks = if load_fallbacks {
descriptors_for_family("Menlo")
.into_iter()
.filter(|d| d.family_name == "Menlo Regular")
.nth(0)
.map(|descriptor| {
let menlo = ct_new_from_descriptor(&descriptor.ct_descriptor, size);
// TODO fixme, hardcoded en for english
let mut fallbacks = cascade_list_for_languages(&menlo, &vec!["en".to_owned()])
.into_iter()
.filter(|desc| desc.font_path != "")
.map(|desc| desc.to_font(size, false))
.collect::<Vec<_>>();
// TODO, we can't use apple's proposed
// .Apple Symbol Fallback (filtered out below),
// but not having these makes us not able to render
// many chars. We add the symbols back in.
// Investigate if we can actually use the .-prefixed
// fallbacks somehow.
descriptors_for_family("Apple Symbols")
.into_iter()
.next() // should only have one element; use it
.map(|descriptor| {
fallbacks.push(descriptor.to_font(size, false))
});
// Include Menlo in the fallback list as well
fallbacks.insert(0, Font {
cg_font: menlo.copy_to_CGFont(),
ct_font: menlo,
fallbacks: Vec::new()
});
fallbacks
})
.unwrap_or_else(Vec::new)
} else {
Vec::new()
};
Font {
ct_font: ct_font,
cg_font: cg_font,
fallbacks: fallbacks,
}
}
}
impl Font {
/// The the bounding rect of a glyph
pub fn bounding_rect_for_glyph(
&self,
orientation: FontOrientation,
index: u32
) -> Rect<f64> {
let cg_rect = self.ct_font.get_bounding_rects_for_glyphs(
orientation as CTFontOrientation,
&[index as CGGlyph]
);
Rect::new(
Point2D::new(cg_rect.origin.x, cg_rect.origin.y),
Size2D::new(cg_rect.size.width, cg_rect.size.height),
)
}
pub fn metrics(&self) -> Metrics {
let average_advance = self.glyph_advance('0');
let ascent = self.ct_font.ascent() as f64;
let descent = self.ct_font.descent() as f64;
let leading = self.ct_font.leading() as f64;
let line_height = (ascent + descent + leading + 0.5).floor();
Metrics {
average_advance: average_advance,
line_height: line_height,
descent: -(self.ct_font.descent() as f32),
}
}
pub fn is_bold(&self) -> bool {
self.ct_font.symbolic_traits().is_bold()
}
pub fn is_italic(&self) -> bool {
self.ct_font.symbolic_traits().is_italic()
}
fn glyph_advance(&self, character: char) -> f64 {
let index = self.glyph_index(character).unwrap();
let indices = [index as CGGlyph];
self.ct_font.get_advances_for_glyphs(
FontOrientation::Default as _,
&indices[0],
ptr::null_mut(),
1
)
}
pub fn get_glyph(&self, character: char, _size: f64, use_thin_strokes: bool) -> Result<RasterizedGlyph, Error> {
// Render custom symbols for underline and beam cursor
match character {
super::UNDERLINE_CURSOR_CHAR => {
// Get the bottom of the bounding box
let descent = -(self.ct_font.descent() as i32);
// Get the width of the cell
let width = self.glyph_advance('0') as i32;
// Return the new custom glyph
return super::get_underline_cursor_glyph(descent, width);
}
super::BEAM_CURSOR_CHAR | super::BOX_CURSOR_CHAR => {
// Get the top of the bounding box
let metrics = self.metrics();
let height = metrics.line_height;
let ascent = (height - self.ct_font.descent()).ceil();
// Get the width of the cell
let width = self.glyph_advance('0') as i32;
// Return the new custom glyph
if character == super::BEAM_CURSOR_CHAR {
return super::get_beam_cursor_glyph(ascent as i32, height as i32, width);
} else {
return super::get_box_cursor_glyph(ascent as i32, height as i32, width);
}
}
_ => ()
}
let glyph_index = self.glyph_index(character)
.ok_or(Error::MissingGlyph(character))?;
let bounds = self.bounding_rect_for_glyph(Default::default(), glyph_index);
let rasterized_left = bounds.origin.x.floor() as i32;
let rasterized_width =
(bounds.origin.x - (rasterized_left as f64) + bounds.size.width).ceil() as u32;
let rasterized_descent = (-bounds.origin.y).ceil() as i32;
let rasterized_ascent = (bounds.size.height + bounds.origin.y).ceil() as i32;
let rasterized_height = (rasterized_descent + rasterized_ascent) as u32;
if rasterized_width == 0 || rasterized_height == 0 {
return Ok(RasterizedGlyph {
c: ' ',
width: 0,
height: 0,
top: 0,
left: 0,
buf: Vec::new()
});
}
let mut cg_context = CGContext::create_bitmap_context(
None,
rasterized_width as usize,
rasterized_height as usize,
8, // bits per component
rasterized_width as usize * 4,
&CGColorSpace::create_device_rgb(),
kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host
);
// Give the context an opaque, black background
cg_context.set_rgb_fill_color(0.0, 0.0, 0.0, 1.0);
let context_rect = CGRect::new(
&CGPoint::new(0.0, 0.0),
&CGSize::new(
rasterized_width as f64,
rasterized_height as f64
)
);
cg_context.fill_rect(context_rect);
if use_thin_strokes {
cg_context.set_font_smoothing_style(16);
}
cg_context.set_allows_font_smoothing(true);
cg_context.set_should_smooth_fonts(true);
cg_context.set_allows_font_subpixel_quantization(true);
cg_context.set_should_subpixel_quantize_fonts(true);
cg_context.set_allows_font_subpixel_positioning(true);
cg_context.set_should_subpixel_position_fonts(true);
cg_context.set_allows_antialiasing(true);
cg_context.set_should_antialias(true);
// Set fill color to white for drawing the glyph
cg_context.set_rgb_fill_color(1.0, 1.0, 1.0, 1.0);
let rasterization_origin = CGPoint {
x: -rasterized_left as f64,
y: rasterized_descent as f64,
};
self.ct_font.draw_glyphs(&[glyph_index as CGGlyph],
&[rasterization_origin],
cg_context.clone());
let rasterized_pixels = cg_context.data().to_vec();
let buf = extract_rgb(rasterized_pixels);
Ok(RasterizedGlyph {
c: character,
left: rasterized_left,
top: (bounds.size.height + bounds.origin.y).ceil() as i32,
width: rasterized_width as i32,
height: rasterized_height as i32,
buf: buf,
})
}
fn glyph_index(&self, character: char) -> Option<u32> {
// encode this char as utf-16
let mut buf = [0; 2];
let encoded:&[u16] = character.encode_utf16(&mut buf);
// and use the utf-16 buffer to get the index
self.glyph_index_utf16(encoded)
}
fn glyph_index_utf16(&self, encoded: &[u16]) -> Option<u32> {
// output buffer for the glyph. for non-BMP glyphs, like
// emojis, this will be filled with two chars the second
// always being a 0.
let mut glyphs:[CGGlyph; 2] = [0; 2];
let res = self.ct_font.get_glyphs_for_characters(
encoded.as_ptr(),
glyphs.as_mut_ptr(),
encoded.len() as CFIndex
);
if res {
Some(glyphs[0] as u32)
} else {
None
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn get_family_names() {
let names = super::get_family_names();
assert!(names.contains(&String::from("Menlo")));
assert!(names.contains(&String::from("Monaco")));
}
#[test]
fn get_descriptors_and_build_font() {
let list = super::descriptors_for_family("Menlo");
assert!(!list.is_empty());
info!("{:?}", list);
// Check to_font
let fonts = list.iter()
.map(|desc| desc.to_font(72., false))
.collect::<Vec<_>>();
for font in fonts {
// Get a glyph
for c in &['a', 'b', 'c', 'd'] {
let glyph = font.get_glyph(*c, 72., false).unwrap();
// 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 c = match value {
0...50 => ' ',
51...100 => '.',
101...150 => '~',
151...200 => '*',
201...255 => '#',
_ => unreachable!()
};
print!("{}", c);
}
print!("\n");
}
}
}
}
}