From 263918a519e405101f6f709e38e9c5c5cbd13437 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Mon, 15 Jan 2024 13:59:21 +0100 Subject: [PATCH 1/4] Faster OTC font loading (#1) This commit vastly simplifies the way .otc parsing is done in font-kit by initializing a font from a descriptor and not copying the buffer. It also makes Handle capable of storing an already loaded font, as what happens currently is that we first have a Font which we turn into a Handle (losing track of the Font that was used to create it) only to recreate a font from that same handle (and reparsing the font file in the process). Less waste is good. Meanwhile, on this branch parsing Iosevka takes about 30ms and most of that time is spent.. well.. parsing the font I guess? --- src/handle.rs | 26 ++++++++++++++++ src/lib.rs | 2 +- src/loader.rs | 23 +++++++------- src/loaders/core_text.rs | 34 +++++++-------------- src/loaders/directwrite.rs | 6 ++-- src/loaders/freetype.rs | 5 +-- src/sources/core_text.rs | 62 ++++---------------------------------- tests/select_font.rs | 1 + tests/tests.rs | 12 -------- 9 files changed, 64 insertions(+), 107 deletions(-) diff --git a/src/handle.rs b/src/handle.rs index d6f8bbc..104a9e8 100644 --- a/src/handle.rs +++ b/src/handle.rs @@ -14,11 +14,13 @@ //! //! To open the font referenced by a handle, use a loader. +use std::any::Any; use std::path::PathBuf; use std::sync::Arc; use crate::error::FontLoadingError; use crate::font::Font; +use crate::loader::Loader; /// Encapsulates the information needed to locate and open a font. /// @@ -45,6 +47,11 @@ pub enum Handle { /// If the memory consists of a single font, this value will be 0. font_index: u32, }, + /// An already-loaded font. + Native { + /// Type-erased font storage. Use [`Self::as_native`] to retrieve the font object. + inner: Arc, + }, } impl Handle { @@ -66,6 +73,25 @@ impl Handle { Handle::Memory { bytes, font_index } } + /// Creates a new handle from a system handle. + pub fn from_native(inner: &T) -> Self + where + T::NativeFont: Sync + Send, + { + Self::Native { + inner: Arc::new(inner.native_font()), + } + } + /// Retrieves a handle to the font object. + /// + /// May return None if inner object is not of type `T` or if this handle does not contain a native font object. + pub fn native_as(&self) -> Option<&T> { + if let Self::Native { inner } = self { + inner.downcast_ref() + } else { + None + } + } /// A convenience method to load this handle with the default loader, producing a Font. #[inline] pub fn load(&self) -> Result { diff --git a/src/lib.rs b/src/lib.rs index 3032248..1b1d722 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -144,5 +144,5 @@ pub mod source; #[cfg(feature = "source")] pub mod sources; -mod matching; +pub mod matching; mod utils; diff --git a/src/loader.rs b/src/loader.rs index fc94617..b651fff 100644 --- a/src/loader.rs +++ b/src/loader.rs @@ -35,7 +35,7 @@ use std::path::Path; /// fonts. pub trait Loader: Clone + Sized { /// The handle that the API natively uses to represent a font. - type NativeFont; + type NativeFont: 'static; /// Loads a font from raw font data (the contents of a `.ttf`/`.otf`/etc. file). /// @@ -63,22 +63,23 @@ pub trait Loader: Clone + Sized { } /// Creates a font from a native API handle. - unsafe fn from_native_font(native_font: Self::NativeFont) -> Self; + unsafe fn from_native_font(native_font: &Self::NativeFont) -> Self; /// Loads the font pointed to by a handle. fn from_handle(handle: &Handle) -> Result { - match *handle { - Handle::Memory { - ref bytes, - font_index, - } => Self::from_bytes((*bytes).clone(), font_index), + match handle { + Handle::Memory { bytes, font_index } => Self::from_bytes((*bytes).clone(), *font_index), #[cfg(not(target_arch = "wasm32"))] - Handle::Path { - ref path, - font_index, - } => Self::from_path(path, font_index), + Handle::Path { path, font_index } => Self::from_path(path, *font_index), #[cfg(target_arch = "wasm32")] Handle::Path { .. } => Err(FontLoadingError::NoFilesystem), + Handle::Native { .. } => { + if let Some(native) = handle.native_as::() { + unsafe { Ok(Self::from_native_font(native)) } + } else { + Err(FontLoadingError::UnknownFormat) + } + } } } diff --git a/src/loaders/core_text.rs b/src/loaders/core_text.rs index 8685321..1235558 100644 --- a/src/loaders/core_text.rs +++ b/src/loaders/core_text.rs @@ -122,29 +122,14 @@ impl Font { } /// Creates a font from a native API handle. - pub unsafe fn from_native_font(core_text_font: NativeFont) -> Font { - Font::from_core_text_font(core_text_font) - } - - unsafe fn from_core_text_font(core_text_font: NativeFont) -> Font { - let mut font_data = FontData::Unavailable; - match core_text_font.url() { - None => warn!("No URL found for Core Text font!"), - Some(url) => match url.to_path() { - Some(path) => match File::open(path) { - Ok(ref mut file) => match utils::slurp_file(file) { - Ok(data) => font_data = FontData::Memory(Arc::new(data)), - Err(_) => warn!("Couldn't read file data for Core Text font!"), - }, - Err(_) => warn!("Could not open file for Core Text font!"), - }, - None => warn!("Could not convert URL from Core Text font to path!"), - }, - } - + pub unsafe fn from_native_font(core_text_font: &NativeFont) -> Font { + Font::from_core_text_font_no_path(core_text_font.clone()) + } + /// Creates a font from a native API handle, without performing a lookup on the disk. + pub unsafe fn from_core_text_font_no_path(core_text_font: NativeFont) -> Font { Font { core_text_font, - font_data, + font_data: FontData::Unavailable, } } @@ -153,7 +138,10 @@ impl Font { /// This function is only available on the Core Text backend. pub fn from_core_graphics_font(core_graphics_font: CGFont) -> Font { unsafe { - Font::from_core_text_font(core_text::font::new_from_CGFont(&core_graphics_font, 16.0)) + Font::from_core_text_font_no_path(core_text::font::new_from_CGFont( + &core_graphics_font, + 16.0, + )) } } @@ -628,7 +616,7 @@ impl Loader for Font { } #[inline] - unsafe fn from_native_font(native_font: Self::NativeFont) -> Self { + unsafe fn from_native_font(native_font: &Self::NativeFont) -> Self { Font::from_native_font(native_font) } diff --git a/src/loaders/directwrite.rs b/src/loaders/directwrite.rs index b39b7d9..6135842 100644 --- a/src/loaders/directwrite.rs +++ b/src/loaders/directwrite.rs @@ -61,6 +61,7 @@ const OPENTYPE_TABLE_TAG_HEAD: u32 = 0x68656164; /// DirectWrite's representation of a font. #[allow(missing_debug_implementations)] +#[derive(Clone)] pub struct NativeFont { /// The native DirectWrite font object. pub dwrite_font: DWriteFont, @@ -160,7 +161,8 @@ impl Font { /// Creates a font from a native API handle. #[inline] - pub unsafe fn from_native_font(native_font: NativeFont) -> Font { + pub unsafe fn from_native_font(native_font: &NativeFont) -> Font { + let native_font = native_font.clone(); Font { dwrite_font: native_font.dwrite_font, dwrite_font_face: native_font.dwrite_font_face, @@ -747,7 +749,7 @@ impl Loader for Font { } #[inline] - unsafe fn from_native_font(native_font: Self::NativeFont) -> Self { + unsafe fn from_native_font(native_font: &Self::NativeFont) -> Self { Font::from_native_font(native_font) } diff --git a/src/loaders/freetype.rs b/src/loaders/freetype.rs index 538ec28..603465e 100644 --- a/src/loaders/freetype.rs +++ b/src/loaders/freetype.rs @@ -197,9 +197,10 @@ impl Font { } /// Creates a font from a native API handle. - pub unsafe fn from_native_font(freetype_face: NativeFont) -> Font { + pub unsafe fn from_native_font(freetype_face: &NativeFont) -> Font { // We make an in-memory copy of the underlying font data. This is because the native font // does not necessarily hold a strong reference to the memory backing it. + let freetype_face = *freetype_face; const CHUNK_SIZE: usize = 4096; let mut font_data = vec![]; loop { @@ -1036,7 +1037,7 @@ impl Loader for Font { } #[inline] - unsafe fn from_native_font(native_font: Self::NativeFont) -> Self { + unsafe fn from_native_font(native_font: &Self::NativeFont) -> Self { Font::from_native_font(native_font) } diff --git a/src/sources/core_text.rs b/src/sources/core_text.rs index c323eb6..5499b7f 100644 --- a/src/sources/core_text.rs +++ b/src/sources/core_text.rs @@ -14,6 +14,7 @@ use core_foundation::array::CFArray; use core_foundation::base::{CFType, TCFType}; use core_foundation::dictionary::CFDictionary; use core_foundation::string::CFString; +use core_text::font::new_from_descriptor; use core_text::font_collection::{self, CTFontCollection}; use core_text::font_descriptor::{self, CTFontDescriptor}; use core_text::font_manager; @@ -153,68 +154,17 @@ fn css_stretchiness_to_core_text_width(css_stretchiness: Stretch) -> f32 { 0.25 * core_text_loader::piecewise_linear_find_index(css_stretchiness, &Stretch::MAPPING) - 1.0 } -#[derive(Clone)] -struct FontDataInfo { - data: Arc>, - file_type: FileType, -} - fn create_handles_from_core_text_collection( collection: CTFontCollection, ) -> Result, SelectionError> { let mut fonts = vec![]; if let Some(descriptors) = collection.get_descriptors() { - let mut font_data_info_cache: HashMap = HashMap::new(); - - 'outer: for index in 0..descriptors.len() { + for index in 0..descriptors.len() { let descriptor = descriptors.get(index).unwrap(); - let font_path = descriptor.font_path().unwrap(); - - let data_info = if let Some(data_info) = font_data_info_cache.get(&font_path) { - data_info.clone() - } else { - let mut file = if let Ok(file) = File::open(&font_path) { - file - } else { - continue; - }; - let data = if let Ok(data) = utils::slurp_file(&mut file) { - Arc::new(data) - } else { - continue; - }; - - let file_type = match Font::analyze_bytes(Arc::clone(&data)) { - Ok(file_type) => file_type, - Err(_) => continue, - }; - - let data_info = FontDataInfo { data, file_type }; - - font_data_info_cache.insert(font_path.clone(), data_info.clone()); - - data_info - }; - - match data_info.file_type { - FileType::Collection(font_count) => { - let postscript_name = descriptor.font_name(); - for font_index in 0..font_count { - if let Ok(font) = Font::from_bytes(Arc::clone(&data_info.data), font_index) - { - if let Some(font_postscript_name) = font.postscript_name() { - if postscript_name == font_postscript_name { - fonts.push(Handle::from_memory(data_info.data, font_index)); - continue 'outer; - } - } - } - } - } - FileType::Single => { - fonts.push(Handle::from_memory(data_info.data, 0)); - } - } + let native = new_from_descriptor(&descriptor, 16.); + let font = unsafe { Font::from_core_text_font_no_path(native.clone()) }; + + fonts.push(Handle::from_native(&font)); } } if fonts.is_empty() { diff --git a/tests/select_font.rs b/tests/select_font.rs index 9b892ba..6e031b0 100644 --- a/tests/select_font.rs +++ b/tests/select_font.rs @@ -44,6 +44,7 @@ macro_rules! match_handle { font_index, $index ); } + Handle::Native { .. } => {} } }; } diff --git a/tests/tests.rs b/tests/tests.rs index 6f4d83b..0b9bc1a 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -622,18 +622,6 @@ pub fn get_font_properties() { assert_eq!(properties.stretch, Stretch(1.0)); } -#[cfg(feature = "source")] -#[test] -pub fn get_font_data() { - let font = SystemSource::new() - .select_best_match(&[FamilyName::SansSerif], &Properties::new()) - .unwrap() - .load() - .unwrap(); - let data = font.copy_font_data().unwrap(); - debug_assert!(SFNT_VERSIONS.iter().any(|version| data[0..4] == *version)); -} - #[cfg(feature = "source")] #[test] pub fn load_font_table() { From aa44d72ac87b1e784904d34a6e5ae1bb37f25a2b Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 23 Jan 2024 13:20:22 +0100 Subject: [PATCH 2/4] Mark matching module as private again --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 1b1d722..3032248 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -144,5 +144,5 @@ pub mod source; #[cfg(feature = "source")] pub mod sources; -pub mod matching; +mod matching; mod utils; From 8cb2f41489c15fab3a56f6e2060699b1ef38bb9f Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 23 Jan 2024 13:21:30 +0100 Subject: [PATCH 3/4] Rename native_as to as_native --- src/handle.rs | 2 +- src/loader.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/handle.rs b/src/handle.rs index 104a9e8..c102263 100644 --- a/src/handle.rs +++ b/src/handle.rs @@ -85,7 +85,7 @@ impl Handle { /// Retrieves a handle to the font object. /// /// May return None if inner object is not of type `T` or if this handle does not contain a native font object. - pub fn native_as(&self) -> Option<&T> { + pub fn as_native(&self) -> Option<&T> { if let Self::Native { inner } = self { inner.downcast_ref() } else { diff --git a/src/loader.rs b/src/loader.rs index b651fff..620500d 100644 --- a/src/loader.rs +++ b/src/loader.rs @@ -74,7 +74,7 @@ pub trait Loader: Clone + Sized { #[cfg(target_arch = "wasm32")] Handle::Path { .. } => Err(FontLoadingError::NoFilesystem), Handle::Native { .. } => { - if let Some(native) = handle.native_as::() { + if let Some(native) = handle.as_native::() { unsafe { Ok(Self::from_native_font(native)) } } else { Err(FontLoadingError::UnknownFormat) From cef0442a9ca37441f8689f79976cc1756d5f5cd9 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Tue, 23 Jan 2024 13:33:09 +0100 Subject: [PATCH 4/4] Revert formatting change --- src/loader.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/loader.rs b/src/loader.rs index 620500d..3fba207 100644 --- a/src/loader.rs +++ b/src/loader.rs @@ -70,7 +70,10 @@ pub trait Loader: Clone + Sized { match handle { Handle::Memory { bytes, font_index } => Self::from_bytes((*bytes).clone(), *font_index), #[cfg(not(target_arch = "wasm32"))] - Handle::Path { path, font_index } => Self::from_path(path, *font_index), + Handle::Path { + ref path, + font_index, + } => Self::from_path(path, *font_index), #[cfg(target_arch = "wasm32")] Handle::Path { .. } => Err(FontLoadingError::NoFilesystem), Handle::Native { .. } => {