diff --git a/src/darwin/mod.rs b/src/darwin/mod.rs index c12426d..b738a2a 100644 --- a/src/darwin/mod.rs +++ b/src/darwin/mod.rs @@ -35,8 +35,8 @@ pub mod byte_order; use byte_order::kCGBitmapByteOrder32Host; use super::{ - BitmapBuffer, Error, FontDesc, FontKey, GlyphKey, Metrics, RasterizedGlyph, Size, Slant, Style, - Weight, + BitmapBuffer, Error, FontDesc, FontKey, GlyphId, GlyphKey, Metrics, RasterizedGlyph, Size, + Slant, Style, Weight, }; /// According to the documentation, the index of 0 must be a missing glyph character: @@ -64,7 +64,7 @@ impl Descriptor { 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), + font_path: desc.font_path().unwrap_or_default(), ct_descriptor: desc, } } @@ -89,11 +89,16 @@ impl Descriptor { // Investigate if we can actually use the .-prefixed // fallbacks somehow. if let Ok(apple_symbols) = new_from_name("Apple Symbols", size) { - fallbacks.push(Font { + let path = apple_symbols.copy_descriptor().font_path().unwrap_or_default(); + let mut font = Font { cg_font: apple_symbols.copy_to_CGFont(), ct_font: apple_symbols, fallbacks: Vec::new(), - }) + placeholder_glyph_index: 0, + path, + }; + font.placeholder_glyph_index = font.glyph_index(' '); + fallbacks.push(font); }; fallbacks @@ -101,7 +106,15 @@ impl Descriptor { Vec::new() }; - Font { ct_font, cg_font, fallbacks } + let mut font = Font { + ct_font, + cg_font, + fallbacks, + placeholder_glyph_index: 0, + path: self.font_path.clone(), + }; + font.placeholder_glyph_index = font.glyph_index(' '); + font } } @@ -153,13 +166,27 @@ impl crate::Rasterize for Rasterizer { // Find a font where the given character is present. let (font, glyph_index) = iter::once(font) .chain(font.fallbacks.iter()) - .find_map(|font| match font.glyph_index(glyph.character) { - MISSING_GLYPH_INDEX => None, - glyph_index => Some((font, glyph_index)), + .find_map(|font| { + if let Some(c) = glyph.id.as_char() { + match font.glyph_index(c) { + MISSING_GLYPH_INDEX => None, + glyph_index => Some((font, glyph_index)), + } + } else { + let index = glyph.id.value(); + if index == 0 { + match font.placeholder_glyph_index { + MISSING_GLYPH_INDEX => None, + glyph_index => Some((font, glyph_index)), + } + } else { + Some((font, index)) + } + } }) .unwrap_or((font, MISSING_GLYPH_INDEX)); - let glyph = font.get_glyph(glyph.character, glyph_index, self.use_thin_strokes); + let glyph = font.get_glyph(glyph.id, glyph_index, self.use_thin_strokes); if glyph_index == MISSING_GLYPH_INDEX { Err(Error::MissingGlyph(glyph)) @@ -171,6 +198,10 @@ impl crate::Rasterize for Rasterizer { fn update_dpr(&mut self, device_pixel_ratio: f32) { self.device_pixel_ratio = device_pixel_ratio; } + + fn font_path(&self, key: FontKey) -> Result<&std::path::Path, Error> { + self.fonts.get(&key).ok_or(Error::UnknownFontKey).map(|font| font.path.as_path()) + } } impl Rasterizer { @@ -315,6 +346,8 @@ pub struct Font { ct_font: CTFont, cg_font: CGFont, fallbacks: Vec, + placeholder_glyph_index: u32, + path: PathBuf, } unsafe impl Send for Font {} @@ -375,7 +408,7 @@ impl Font { pub fn get_glyph( &self, - character: char, + id: GlyphId, glyph_index: u32, use_thin_strokes: bool, ) -> RasterizedGlyph { @@ -391,14 +424,7 @@ impl Font { let rasterized_height = (rasterized_descent + rasterized_ascent) as u32; if rasterized_width == 0 || rasterized_height == 0 { - return RasterizedGlyph { - character: ' ', - width: 0, - height: 0, - top: 0, - left: 0, - buffer: BitmapBuffer::Rgb(Vec::new()), - }; + return RasterizedGlyph::default(); } let mut cg_context = CGContext::create_bitmap_context( @@ -457,7 +483,7 @@ impl Font { }; RasterizedGlyph { - character, + id, left: rasterized_left, top: (bounds.size.height + bounds.origin.y).ceil() as i32, width: rasterized_width as i32, @@ -498,6 +524,8 @@ impl Font { #[cfg(test)] mod tests { + use crate::GlyphId; + use super::BitmapBuffer; #[test] @@ -520,7 +548,7 @@ mod tests { // Get a glyph. for character in &['a', 'b', 'c', 'd'] { let glyph_index = font.glyph_index(*character); - let glyph = font.get_glyph(*character, glyph_index, false); + let glyph = font.get_glyph(GlyphId::char(*character), glyph_index, false); let buffer = match &glyph.buffer { BitmapBuffer::Rgb(buffer) | BitmapBuffer::Rgba(buffer) => buffer, diff --git a/src/directwrite/mod.rs b/src/directwrite/mod.rs index a42261a..a3dd19c 100644 --- a/src/directwrite/mod.rs +++ b/src/directwrite/mod.rs @@ -4,6 +4,7 @@ use std::borrow::Cow; use std::collections::HashMap; use std::ffi::OsString; use std::os::windows::ffi::OsStringExt; +use std::path::{Path, PathBuf}; use dwrote::{ FontCollection, FontFace, FontFallback, FontStretch, FontStyle, FontWeight, GlyphOffset, @@ -15,8 +16,8 @@ use winapi::um::dwrite; use winapi::um::winnls::GetUserDefaultLocaleName; use super::{ - BitmapBuffer, Error, FontDesc, FontKey, GlyphKey, Metrics, RasterizedGlyph, Size, Slant, Style, - Weight, + BitmapBuffer, Error, FontDesc, FontKey, GlyphId, GlyphKey, Metrics, RasterizedGlyph, Size, + Slant, Style, Weight, }; /// DirectWrite uses 0 for missing glyph symbols. @@ -30,6 +31,8 @@ struct Font { weight: FontWeight, style: FontStyle, stretch: FontStretch, + placeholder_glyph_index: u16, + path: PathBuf, } pub struct DirectWriteRasterizer { @@ -45,7 +48,7 @@ impl DirectWriteRasterizer { &self, face: &FontFace, size: Size, - character: char, + id: GlyphId, glyph_index: u16, ) -> Result { let em_size = em_size(size); @@ -85,7 +88,7 @@ impl DirectWriteRasterizer { ); Ok(RasterizedGlyph { - character, + id, width: (bounds.right - bounds.left) as i32, height: (bounds.bottom - bounds.top) as i32, top: -bounds.top, @@ -232,17 +235,28 @@ impl crate::Rasterize for DirectWriteRasterizer { let loaded_fallback_font; let mut font = loaded_font; - let mut glyph_index = self.get_glyph_index(&loaded_font.face, glyph.character); - if glyph_index == MISSING_GLYPH_INDEX { - if let Some(fallback_font) = self.get_fallback_font(&loaded_font, glyph.character) { - loaded_fallback_font = Font::from(fallback_font); - glyph_index = self.get_glyph_index(&loaded_fallback_font.face, glyph.character); - font = &loaded_fallback_font; + + let glyph_index = if let Some(character) = glyph.id.as_char() { + let mut glyph_index = self.get_glyph_index(&loaded_font.face, character); + if glyph_index == MISSING_GLYPH_INDEX { + if let Some(fallback_font) = self.get_fallback_font(&loaded_font, character) { + loaded_fallback_font = Font::from(fallback_font); + glyph_index = self.get_glyph_index(&loaded_fallback_font.face, character); + font = &loaded_fallback_font; + } } - } + glyph_index + } else { + let index = glyph.id.value(); + if index == 0 { + loaded_font.placeholder_glyph_index + } else { + index as u16 + } + }; let rasterized_glyph = - self.rasterize_glyph(&font.face, glyph.size, glyph.character, glyph_index)?; + self.rasterize_glyph(&font.face, glyph.size, glyph.id, glyph_index)?; if glyph_index == MISSING_GLYPH_INDEX { Err(Error::MissingGlyph(rasterized_glyph)) @@ -254,6 +268,10 @@ impl crate::Rasterize for DirectWriteRasterizer { fn update_dpr(&mut self, device_pixel_ratio: f32) { self.device_pixel_ratio = device_pixel_ratio; } + + fn font_path(&self, key: FontKey) -> Result<&Path, Error> { + Ok(&self.get_loaded_font(key)?.path) + } } fn em_size(size: Size) -> f32 { @@ -262,12 +280,20 @@ fn em_size(size: Size) -> f32 { impl From for Font { fn from(font: dwrote::Font) -> Font { + let face = font.create_font_face(); + let placeholder_glyph_index = + face.get_glyph_indices(&[' ' as u32]).first().copied().unwrap_or(MISSING_GLYPH_INDEX); + let files = face.get_files(); + let path = files.first().unwrap().get_font_file_path().unwrap(); + Font { - face: font.create_font_face(), + face, family_name: font.family_name(), weight: font.weight(), style: font.style(), stretch: font.stretch(), + placeholder_glyph_index, + path, } } } diff --git a/src/ft/fc/pattern.rs b/src/ft/fc/pattern.rs index 8a6f511..e25f4dd 100644 --- a/src/ft/fc/pattern.rs +++ b/src/ft/fc/pattern.rs @@ -345,7 +345,7 @@ macro_rules! string_accessor { #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct PatternHash(pub u32); -#[derive(Hash, Eq, PartialEq, Debug)] +#[derive(Clone, Hash, Eq, PartialEq, Debug)] pub struct FtFaceLocation { pub path: PathBuf, pub index: isize, diff --git a/src/ft/mod.rs b/src/ft/mod.rs index 95c0f8a..4b427c6 100644 --- a/src/ft/mod.rs +++ b/src/ft/mod.rs @@ -3,6 +3,7 @@ use std::cmp::{min, Ordering}; use std::collections::HashMap; use std::fmt::{self, Formatter}; +use std::path::PathBuf; use std::rc::Rc; use std::time::{Duration, Instant}; @@ -53,7 +54,7 @@ struct FallbackList { coverage: CharSet, } -struct FaceLoadingProperties { +pub struct FaceLoadingProperties { load_flags: LoadFlag, render_mode: freetype::RenderMode, lcd_filter: c_uint, @@ -64,6 +65,7 @@ struct FaceLoadingProperties { pixelsize_fixup_factor: Option, ft_face: Rc, rgba: Rgba, + placeholder_glyph_index: u32, } impl fmt::Debug for FaceLoadingProperties { @@ -116,7 +118,7 @@ impl Rasterize for FreeTypeRasterizer { } fn metrics(&self, key: FontKey, _size: Size) -> Result { - let face = &mut self.loader.faces.get(&key).ok_or(Error::UnknownFontKey)?; + let face = &self.loader.faces.get(&key).ok_or(Error::UnknownFontKey)?.0; let full = self.full_metrics(face)?; let ascent = (full.size_metrics.ascender / 64) as f32; @@ -179,6 +181,10 @@ impl Rasterize for FreeTypeRasterizer { fn update_dpr(&mut self, device_pixel_ratio: f32) { self.device_pixel_ratio = device_pixel_ratio; } + + fn font_path(&self, key: FontKey) -> Result<&std::path::Path, Error> { + self.loader.faces.get(&key).ok_or(Error::UnknownFontKey).map(|(_, path)| path.as_path()) + } } impl From for fc::Slant { @@ -297,11 +303,13 @@ impl FreeTypeRasterizer { } fn face_for_glyph(&mut self, glyph_key: GlyphKey) -> FontKey { - if let Some(face) = self.loader.faces.get(&glyph_key.font_key) { - let index = face.ft_face.get_char_index(glyph_key.character as usize); + if let Some(c) = glyph_key.id.as_char() { + if let Some(face) = self.loader.faces.get(&glyph_key.font_key) { + let index = face.0.ft_face.get_char_index(c as usize); - if index != 0 { - return glyph_key.font_key; + if index != 0 { + return glyph_key.font_key; + } } } @@ -309,10 +317,16 @@ impl FreeTypeRasterizer { } fn load_face_with_glyph(&mut self, glyph: GlyphKey) -> Result { + let c = if let Some(c) = glyph.id.as_char() { + c + } else { + return Ok(glyph.font_key); + }; + let fallback_list = self.fallback_lists.get(&glyph.font_key).unwrap(); // Check whether glyph is presented in any fallback font. - if !fallback_list.coverage.has_char(glyph.character) { + if !fallback_list.coverage.has_char(c) { return Ok(glyph.font_key); } @@ -321,7 +335,7 @@ impl FreeTypeRasterizer { let font_pattern = &fallback_font.pattern; match self.loader.faces.get(&font_key) { Some(face) => { - let index = face.ft_face.get_char_index(glyph.character as usize); + let index = face.0.ft_face.get_char_index(c as usize); // We found something in a current face, so let's use it. if index != 0 { @@ -329,8 +343,7 @@ impl FreeTypeRasterizer { } }, None => { - if !font_pattern.get_charset().map_or(false, |cs| cs.has_char(glyph.character)) - { + if !font_pattern.get_charset().map_or(false, |cs| cs.has_char(c)) { continue; } @@ -349,8 +362,19 @@ impl FreeTypeRasterizer { fn get_rendered_glyph(&mut self, glyph_key: GlyphKey) -> Result { // Render a normal character if it's not a cursor. let font_key = self.face_for_glyph(glyph_key); - let face = &self.loader.faces[&font_key]; - let index = face.ft_face.get_char_index(glyph_key.character as usize) as u32; + let face = &self.loader.faces[&font_key].0; + + let index = if let Some(c) = glyph_key.id.as_char() { + face.ft_face.get_char_index(c as usize) as u32 + } else { + let val = glyph_key.id.value(); + if val == 0 { + face.placeholder_glyph_index + } else { + val + } + }; + let pixelsize = face .non_scalable .unwrap_or_else(|| glyph_key.size.as_f32_pts() * self.device_pixel_ratio * 96. / 72.); @@ -399,7 +423,7 @@ impl FreeTypeRasterizer { Self::normalize_buffer(&glyph.bitmap(), &face.rgba)?; let mut rasterized_glyph = RasterizedGlyph { - character: glyph_key.character, + id: glyph_key.id, top: glyph.bitmap_top(), left: glyph.bitmap_left(), width: pixel_width, @@ -612,9 +636,9 @@ impl From for Error { unsafe impl Send for FreeTypeRasterizer {} -struct FreeTypeLoader { +pub struct FreeTypeLoader { library: Library, - faces: HashMap, + faces: HashMap, ft_faces: HashMap>, } @@ -660,9 +684,13 @@ impl FreeTypeLoader { let ft_face = match self.ft_faces.get(&ft_face_location) { Some(ft_face) => Rc::clone(ft_face), - None => self.load_ft_face(ft_face_location)?, + None => self.load_ft_face(ft_face_location.clone())?, }; + // This will be different for each font so we can't use a constant but we don't want to + // look it up every time so we cache it on font load. + let placeholder_glyph_index = ft_face.get_char_index(' ' as usize); + let non_scalable = if pattern.scalable().next().unwrap_or(true) { None } else { @@ -696,11 +724,12 @@ impl FreeTypeLoader { pixelsize_fixup_factor, ft_face, rgba, + placeholder_glyph_index, }; debug!("Loaded Face {:?}", face); - self.faces.insert(font_key, face); + self.faces.insert(font_key, (face, ft_face_location.path)); Ok(Some(font_key)) } else { diff --git a/src/lib.rs b/src/lib.rs index c825af3..72a850e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ use std::fmt::{self, Display, Formatter}; use std::ops::{Add, Mul}; +use std::path::Path; use std::sync::atomic::{AtomicUsize, Ordering}; // If target isn't macos or windows, reexport everything from ft. @@ -96,9 +97,70 @@ impl FontKey { } } +/// An identifier of a glyph. +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub struct GlyphId(u32); + +const C_BIT: u32 = 0b1000_0000_0000_0000_0000_0000_0000_0000; + +const C_MASK: u32 = !C_BIT; + +impl GlyphId { + /// Creates a `GlyphId` representing a unicode scalar value. + pub fn char(c: char) -> Self { + Self(c as u32 | C_BIT) + } + + /// Creates a `GlyphId` representing a glyph index. + /// + /// The index must not have the most significant bit set. + pub fn with_glyph_index(n: u32) -> Self { + assert!(n < C_BIT); + Self(n) + } + + /// Creates a `GlyphId` representing a placeholder value. + pub fn placeholder() -> Self { + Self(0) + } + + pub fn value(self) -> u32 { + self.0 + } + + pub fn as_char(self) -> Option { + use std::convert::TryFrom; + let value = self.value(); + + if value & C_BIT == 0 { + None + } else { + match char::try_from(value & C_MASK) { + Ok(c) => Some(c), + // we never construct a `GlyphId` with the C_BIT set with an invalid character. + Err(_) => unreachable!(), + } + } + } +} + +impl fmt::Debug for GlyphId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut f = f.debug_tuple("GlyphId"); + + if let Some(c) = self.as_char() { + f.field(&c); + } else { + f.field(&self.value()); + } + + f.finish() + } +} + #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct GlyphKey { - pub character: char, + pub id: GlyphId, pub font_key: FontKey, pub size: Size, } @@ -149,7 +211,7 @@ impl From for Size { #[derive(Clone)] pub struct RasterizedGlyph { - pub character: char, + pub id: GlyphId, pub width: i32, pub height: i32, pub top: i32, @@ -169,7 +231,7 @@ pub enum BitmapBuffer { impl Default for RasterizedGlyph { fn default() -> RasterizedGlyph { RasterizedGlyph { - character: ' ', + id: GlyphId::placeholder(), width: 0, height: 0, top: 0, @@ -182,7 +244,7 @@ impl Default for RasterizedGlyph { impl fmt::Debug for RasterizedGlyph { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("RasterizedGlyph") - .field("character", &self.character) + .field("id", &self.id) .field("width", &self.width) .field("height", &self.height) .field("top", &self.top) @@ -232,9 +294,7 @@ impl Display for Error { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { Error::FontNotFound(font) => write!(f, "font {:?} not found", font), - Error::MissingGlyph(glyph) => { - write!(f, "glyph for character {:?} not found", glyph.character) - }, + Error::MissingGlyph(glyph) => write!(f, "glyph for {:?} not found", glyph.id), Error::UnknownFontKey => f.write_str("invalid font key"), Error::MetricsNotFound => f.write_str("metrics not found"), Error::PlatformError(err) => write!(f, "{}", err), @@ -259,4 +319,9 @@ pub trait Rasterize { /// Update the Rasterizer's DPI factor. fn update_dpr(&mut self, device_pixel_ratio: f32); + + /// Get the path of a font by its key. + /// + /// This is useful when you want to load the font for another library. + fn font_path(&self, _: FontKey) -> Result<&Path, Error>; }