diff options
Diffstat (limited to 'font/src/darwin/mod.rs')
-rw-r--r-- | font/src/darwin/mod.rs | 634 |
1 files changed, 0 insertions, 634 deletions
diff --git a/font/src/darwin/mod.rs b/font/src/darwin/mod.rs deleted file mode 100644 index 0194925f..00000000 --- a/font/src/darwin/mod.rs +++ /dev/null @@ -1,634 +0,0 @@ -//! Font rendering based on CoreText. - -#![allow(improper_ctypes)] -use std::collections::HashMap; -use std::path::PathBuf; -use std::ptr; - -use core_foundation::array::{CFArray, CFIndex}; -use core_foundation::string::CFString; -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::{ - cascade_list_for_languages as ct_cascade_list_for_languages, - new_from_descriptor as ct_new_from_descriptor, CTFont, -}; -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; -use core_text::font_descriptor::SymbolicTraitAccessors; -use core_text::font_descriptor::{CTFontDescriptor, CTFontOrientation}; - -use cocoa::base::{id, nil, NO}; -use cocoa::foundation::{NSOperatingSystemVersion, NSProcessInfo, NSString, NSUserDefaults}; - -use euclid::{Point2D, Rect, Size2D}; - -use log::{trace, warn}; - -pub mod byte_order; -use byte_order::kCGBitmapByteOrder32Host; - -use super::{ - BitmapBuffer, FontDesc, FontKey, GlyphKey, Metrics, RasterizedGlyph, Size, Slant, Style, Weight, -}; - -/// 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: PathBuf, - - 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(PathBuf::new), - 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) => "Unable to find the requested glyph", - Error::MissingFont(ref _desc) => "Unable to 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, "Unable to find the font {}", desc), - Error::FontNotLoaded => f.write_str("Tried to use a font that hasn't been loaded"), - } - } -} - -impl crate::Rasterize for Rasterizer { - type Err = Error; - - fn new(device_pixel_ratio: f32, use_thin_strokes: bool) -> Result<Rasterizer, Error> { - Ok(Rasterizer { - fonts: HashMap::new(), - keys: HashMap::new(), - device_pixel_ratio, - use_thin_strokes, - }) - } - - /// Get metrics for font specified by FontKey. - fn metrics(&self, key: FontKey, _size: Size) -> 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> { - let scaled_size = Size::new(size.as_f32_pts() * self.device_pixel_ratio); - self.keys.get(&(desc.to_owned(), scaled_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(), scaled_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)) - }) - } - - fn update_dpr(&mut self, device_pixel_ratio: f32) { - self.device_pixel_ratio = device_pixel_ratio; - } -} - -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 = f64::from(size.as_f32_pts()) * f64::from(self.device_pixel_ratio); - 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 = f64::from(size.as_f32_pts()) * f64::from(self.device_pixel_ratio); - - 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, f64::from(scaled_size), 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 {} - -/// Set subpixel anti-aliasing on macOS. -/// -/// Sub-pixel anti-aliasing has been disabled since macOS Mojave by default. This function allows -/// overriding the global `CGFontRenderingFontSmoothingDisabled` setting on a per-application basis -/// to re-enable it. -/// -/// This is a no-op on systems running High Sierra or earlier (< 10.14.0). -pub fn set_font_smoothing(enable: bool) { - let min_macos_version = NSOperatingSystemVersion::new(10, 14, 0); - unsafe { - // Check that we're running at least Mojave (10.14.0+). - if !NSProcessInfo::processInfo(nil).isOperatingSystemAtLeastVersion(min_macos_version) { - return; - } - - let key = NSString::alloc(nil).init_str("CGFontRenderingFontSmoothingDisabled"); - if enable { - id::standardUserDefaults().setBool_forKey_(NO, key); - } else { - id::standardUserDefaults().removeObject_forKey_(key); - } - } -} - -/// 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() { - owned_names.push(name.to_string()); - } - - owned_names -} - -/// Return fallback descriptors for font/language list. -fn cascade_list_for_languages(ct_font: &CTFont, languages: &[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); - - // Convert CFArray to Vec<Descriptor>. - list.into_iter().map(|fontdesc| Descriptor::new(fontdesc.clone())).collect() -} - -/// Get descriptors for family name. -pub fn descriptors_for_family(family: &str) -> Vec<Descriptor> { - let mut out = Vec::new(); - - trace!("Family: {}", family); - let ct_collection = create_for_family(family).unwrap_or_else(|| { - // Fallback to Menlo if we can't find the config specified font family. - warn!("Unable to load specified font {}, falling back to Menlo", &family); - create_for_family("Menlo").expect("Menlo exists") - }); - - // CFArray of CTFontDescriptorRef (i think). - let descriptors = ct_collection.get_descriptors(); - if let Some(descriptors) = descriptors { - for descriptor in descriptors.iter() { - out.push(Descriptor::new(descriptor.clone())); - } - } - - 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() - .find(|d| d.font_name == "Menlo-Regular") - .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, &["en".to_owned()]) - .into_iter() - .filter(|desc| !desc.font_path.as_os_str().is_empty()) - .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. - if let Some(descriptor) = - descriptors_for_family("Apple Symbols").into_iter().next() - { - 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, cg_font, 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(); - - // Strikeout and underline metrics. - // CoreText doesn't provide strikeout so we provide our own. - let underline_position = (self.ct_font.underline_position() - descent) as f32; - let underline_thickness = self.ct_font.underline_thickness() as f32; - let strikeout_position = (line_height / 2. - descent) as f32; - let strikeout_thickness = underline_thickness; - - Metrics { - average_advance, - line_height, - descent: -(descent as f32), - underline_position, - underline_thickness, - strikeout_position, - strikeout_thickness, - } - } - - 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() - } - - 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(); - - let indices = [index as CGGlyph]; - - unsafe { - 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> { - let glyph_index = - self.glyph_index(character).ok_or_else(|| 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 - f64::from(rasterized_left) + 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: BitmapBuffer::RGB(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, - ); - - let is_colored = self.is_colored(); - - // Set background color for graphics context. - let bg_a = if is_colored { 0.0 } else { 1.0 }; - cg_context.set_rgb_fill_color(0.0, 0.0, 0.0, bg_a); - - let context_rect = CGRect::new( - &CGPoint::new(0.0, 0.0), - &CGSize::new(f64::from(rasterized_width), f64::from(rasterized_height)), - ); - - 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: f64::from(-rasterized_left), y: f64::from(rasterized_descent) }; - - self.ct_font.draw_glyphs( - &[glyph_index as CGGlyph], - &[rasterization_origin], - cg_context.clone(), - ); - - let rasterized_pixels = cg_context.data().to_vec(); - - let buf = if is_colored { - BitmapBuffer::RGBA(byte_order::extract_rgba(&rasterized_pixels)) - } else { - BitmapBuffer::RGB(byte_order::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, - }) - } - - 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 = unsafe { - self.ct_font.get_glyphs_for_characters( - encoded.as_ptr(), - glyphs.as_mut_ptr(), - encoded.len() as CFIndex, - ) - }; - - if res { - Some(u32::from(glyphs[0])) - } else { - None - } - } -} - -#[cfg(test)] -mod tests { - use super::BitmapBuffer; - - #[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()); - println!("{:?}", 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(); - - 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 = buf[index]; - let c = match value { - 0..=50 => ' ', - 51..=100 => '.', - 101..=150 => '~', - 151..=200 => '*', - 201..=255 => '#', - }; - print!("{}", c); - } - println!(); - } - } - } - } -} |