diff options
author | Mukilan Thiyagarajan <mukilan@igalia.com> | 2024-04-22 15:08:21 +0530 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-22 09:38:21 +0000 |
commit | 821893b2eecfc72918ab8154c3cb61cd45d53857 (patch) | |
tree | 035596a6a3af9a3138b0a58974d9745ffa702a26 | |
parent | 25b182c372427e798954b814b0f1a0875ab43f98 (diff) | |
download | servo-821893b2eecfc72918ab8154c3cb61cd45d53857.tar.gz servo-821893b2eecfc72918ab8154c3cb61cd45d53857.zip |
fonts: Rework platform font initialization (#32127)
This change reworks the way that platform fonts are created and
descriptor data is on `FontTemplate` is initialized.
The main change here is that platform fonts for local font faces are
always initialized using the font data loaded into memory from disk.
This means that there is now only a single path for creating platform
fonts.
In addition, the font list is now responsible for getting the
`FontTemplateDescriptor` for local `FontTemplate`s. Before the font had
to be loaded into memory to get the weight, style, and width used for
the descriptor. This is what fonts lists are for though, so for every
platform we have that information before needing to load the font. In
the future, hopefully this will allow discarding fonts before needing to
load them into memory. Web fonts still get the descriptor from the
platform handle, but hopefully that can be done with skrifa in the
future.
Thsese two fixes together allow properly loading indexed font variations
on Linux machines. Before only the first variation could be
instantiated.
Fixes https://github.com/servo/servo/issues/13317.
Fixes https://github.com/servo/servo/issues/24554.
Co-authored-by: Martin Robinson <mrobinson@igalia.com>
----
- [x] `./mach build -d` does not report any errors
- [x] `./mach test-tidy` does not report any errors
- [x] These changes fix #13317 and #24554
- [x] There are tests for these changes
---------
Co-authored-by: Martin Robinson <mrobinson@igalia.com>
21 files changed, 650 insertions, 429 deletions
diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index f8be4415e0a..48752bd44bd 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -21,7 +21,7 @@ use embedder_traits::Cursor; use euclid::{Point2D, Rect, Scale, Transform3D, Vector2D}; use fnv::{FnvHashMap, FnvHashSet}; use gfx::rendering_context::RenderingContext; -use gfx_traits::{Epoch, FontData, WebRenderEpochToU16}; +use gfx_traits::{Epoch, WebRenderEpochToU16}; use image::{DynamicImage, ImageFormat}; use ipc_channel::ipc; use libc::c_void; @@ -877,16 +877,30 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> { let _ = sender.send(key); }, - ForwardedToCompositorMsg::Font(FontToCompositorMsg::AddFont(data, sender)) => { + ForwardedToCompositorMsg::Font(FontToCompositorMsg::AddFont( + key_sender, + index, + bytes_receiver, + )) => { let font_key = self.webrender_api.generate_font_key(); - let mut txn = Transaction::new(); - match data { - FontData::Raw(bytes) => txn.add_raw_font(font_key, bytes, 0), - FontData::Native(native_font) => txn.add_native_font(font_key, native_font), - } + let mut transaction = Transaction::new(); + let bytes = bytes_receiver.recv().unwrap_or_default(); + transaction.add_raw_font(font_key, bytes, index); self.webrender_api - .send_transaction(self.webrender_document, txn); - let _ = sender.send(font_key); + .send_transaction(self.webrender_document, transaction); + let _ = key_sender.send(font_key); + }, + + ForwardedToCompositorMsg::Font(FontToCompositorMsg::AddSystemFont( + key_sender, + native_handle, + )) => { + let font_key = self.webrender_api.generate_font_key(); + let mut transaction = Transaction::new(); + transaction.add_native_font(font_key, native_handle); + self.webrender_api + .send_transaction(self.webrender_document, transaction); + let _ = key_sender.send(font_key); }, ForwardedToCompositorMsg::Canvas(CanvasToCompositorMsg::GenerateKey(sender)) => { @@ -938,7 +952,7 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> { let _ = sender.send(self.webrender_api.generate_font_instance_key()); }, CompositorMsg::Forwarded(ForwardedToCompositorMsg::Font( - FontToCompositorMsg::AddFont(_, sender), + FontToCompositorMsg::AddFont(sender, _, _), )) => { let _ = sender.send(self.webrender_api.generate_font_key()); }, diff --git a/components/gfx/font.rs b/components/gfx/font.rs index 50486aa9690..315d6845597 100644 --- a/components/gfx/font.rs +++ b/components/gfx/font.rs @@ -26,7 +26,7 @@ use webrender_api::FontInstanceKey; use crate::font_cache_thread::FontIdentifier; use crate::font_context::{FontContext, FontSource}; -use crate::font_template::{FontTemplateDescriptor, FontTemplateRef}; +use crate::font_template::{FontTemplateDescriptor, FontTemplateRef, FontTemplateRefMethods}; use crate::platform::font::{FontTable, PlatformFont}; pub use crate::platform::font_list::fallback_font_families; use crate::text::glyph::{ByteIndex, GlyphData, GlyphId, GlyphStore}; @@ -56,7 +56,19 @@ pub trait PlatformFontMethods: Sized { fn new_from_template( template: FontTemplateRef, pt_size: Option<Au>, - ) -> Result<Self, &'static str>; + ) -> Result<PlatformFont, &'static str> { + let data = template.data(); + let face_index = template.borrow().identifier().index(); + let font_identifier = template.borrow().identifier.clone(); + Self::new_from_data(font_identifier, data, face_index, pt_size) + } + + fn new_from_data( + font_identifier: FontIdentifier, + data: Arc<Vec<u8>>, + face_index: u32, + pt_size: Option<Au>, + ) -> Result<PlatformFont, &'static str>; fn family_name(&self) -> Option<String>; fn face_name(&self) -> Option<String>; @@ -396,7 +408,7 @@ impl FontGroup { pub fn new(style: &FontStyleStruct) -> FontGroup { let descriptor = FontDescriptor::from(style); - let families = style + let families: SmallVec<[FontGroupFamily; 8]> = style .font_family .families .iter() @@ -642,3 +654,36 @@ impl FontFamilyDescriptor { self.name.name() } } + +/// Given a mapping array `mapping` and a value, map that value onto +/// the value specified by the array. For instance, for FontConfig +/// values of weights, we would map these onto the CSS [0..1000] range +/// by creating an array as below. Values that fall between two mapped +/// values, will be adjusted by the weighted mean. +/// +/// ```rust +/// let mapping = [ +/// (0., 0.), +/// (FC_WEIGHT_REGULAR as f64, 400 as f64), +/// (FC_WEIGHT_BOLD as f64, 700 as f64), +/// (FC_WEIGHT_EXTRABLACK as f64, 1000 as f64), +/// ]; +/// let mapped_weight = apply_font_config_to_style_mapping(&mapping, weight as f64); +/// ``` +pub(crate) fn map_platform_values_to_style_values(mapping: &[(f64, f64)], value: f64) -> f64 { + if value < mapping[0].0 { + return mapping[0].1; + } + + for window in mapping.windows(2) { + let (font_config_value_a, css_value_a) = window[0]; + let (font_config_value_b, css_value_b) = window[1]; + + if value >= font_config_value_a && value <= font_config_value_b { + let ratio = (value - font_config_value_a) / (font_config_value_b - font_config_value_a); + return css_value_a + ((css_value_b - css_value_a) * ratio); + } + } + + mapping[mapping.len() - 1].1 +} diff --git a/components/gfx/font_cache_thread.rs b/components/gfx/font_cache_thread.rs index 82aed651d95..303cabf3228 100644 --- a/components/gfx/font_cache_thread.rs +++ b/components/gfx/font_cache_thread.rs @@ -11,7 +11,7 @@ use std::sync::{Arc, Mutex}; use std::{f32, fmt, mem, thread}; use app_units::Au; -use gfx_traits::{FontData, WebrenderApi}; +use gfx_traits::WebrenderApi; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; use log::{debug, trace}; use net_traits::request::{Destination, Referrer, RequestBuilder}; @@ -25,11 +25,12 @@ use style::shared_lock::SharedRwLockReadGuard; use style::stylesheets::{Stylesheet, StylesheetInDocument}; use webrender_api::{FontInstanceKey, FontKey}; -use crate::font::{FontFamilyDescriptor, FontFamilyName, FontSearchScope}; +use crate::font::{FontFamilyDescriptor, FontFamilyName, FontSearchScope, PlatformFontMethods}; use crate::font_context::FontSource; use crate::font_template::{ FontTemplate, FontTemplateDescriptor, FontTemplateRef, FontTemplateRefMethods, }; +use crate::platform::font::PlatformFont; use crate::platform::font_list::{ for_each_available_family, for_each_variation, system_default_family, LocalFontIdentifier, SANS_SERIF_FONT_FAMILY, @@ -59,10 +60,19 @@ pub enum FontIdentifier { Web(ServoUrl), } +impl FontIdentifier { + pub fn index(&self) -> u32 { + match *self { + Self::Local(ref local_font_identifier) => local_font_identifier.index(), + Self::Web(_) => 0, + } + } +} + #[derive(Debug, Deserialize, Serialize)] pub struct SerializedFontTemplate { identifier: FontIdentifier, - descriptor: Option<FontTemplateDescriptor>, + descriptor: FontTemplateDescriptor, bytes_receiver: ipc_channel::ipc::IpcBytesReceiver, } @@ -73,7 +83,6 @@ impl SerializedFontTemplate { identifier: self.identifier.clone(), descriptor: self.descriptor, data: font_data.map(Arc::new), - is_valid: true, } } } @@ -97,11 +106,10 @@ impl FontTemplates { // TODO(#190): Do a better job. let (mut best_template, mut best_distance) = (None, f32::MAX); for template in self.templates.iter() { - if let Some(distance) = template.descriptor_distance(desc) { - if distance < best_distance { - best_template = Some(template); - best_distance = distance - } + let distance = template.descriptor_distance(desc); + if distance < best_distance { + best_template = Some(template); + best_distance = distance } } if best_template.is_some() { @@ -112,24 +120,22 @@ impl FontTemplates { // pick the first valid font in the family if we failed // to find an exact match for the descriptor. for template in &mut self.templates.iter() { - if template.is_valid() { - return Some(template.clone()); - } + return Some(template.clone()); } None } - pub fn add_template(&mut self, identifier: FontIdentifier, maybe_data: Option<Vec<u8>>) { - for template in &self.templates { - if *template.borrow().identifier() == identifier { + pub fn add_template(&mut self, new_template: FontTemplate) { + for existing_template in &self.templates { + let existing_template = existing_template.borrow(); + if *existing_template.identifier() == new_template.identifier && + existing_template.descriptor == new_template.descriptor + { return; } } - - if let Ok(template) = FontTemplate::new(identifier, maybe_data) { - self.templates.push(Rc::new(RefCell::new(template))); - } + self.templates.push(Rc::new(RefCell::new(new_template))); } } @@ -245,7 +251,22 @@ impl FontCache { }, Command::AddDownloadedWebFont(family_name, url, bytes, result) => { let templates = &mut self.web_families.get_mut(&family_name).unwrap(); - templates.add_template(FontIdentifier::Web(url), Some(bytes)); + + let data = Arc::new(bytes); + let identifier = FontIdentifier::Web(url.clone()); + let Ok(handle) = PlatformFont::new_from_data(identifier, data.clone(), 0, None) + else { + drop(result.send(())); + return; + }; + + let descriptor = FontTemplateDescriptor::new( + handle.boldness(), + handle.stretchiness(), + handle.style(), + ); + + templates.add_template(FontTemplate::new_web_font(url, descriptor, data)); drop(result.send(())); }, Command::Ping => (), @@ -356,9 +377,9 @@ impl FontCache { let font_face_name = LowercaseString::new(&font.name); let templates = &mut self.web_families.get_mut(&family_name).unwrap(); let mut found = false; - for_each_variation(&font_face_name, |local_font_identifier| { + for_each_variation(&font_face_name, |font_template| { found = true; - templates.add_template(FontIdentifier::Local(local_font_identifier), None); + templates.add_template(font_template); }); if found { sender.send(()).unwrap(); @@ -396,18 +417,18 @@ impl FontCache { // look up canonical name if self.local_families.contains_key(&family_name) { debug!("FontList: Found font family with name={}", &*family_name); - let s = self.local_families.get_mut(&family_name).unwrap(); + let font_templates = self.local_families.get_mut(&family_name).unwrap(); - if s.templates.is_empty() { - for_each_variation(&family_name, |local_font_identifier| { - s.add_template(FontIdentifier::Local(local_font_identifier), None); + if font_templates.templates.is_empty() { + for_each_variation(&family_name, |font_template| { + font_templates.add_template(font_template); }); } // TODO(Issue #192: handle generic font families, like 'serif' and 'sans-serif'. // if such family exists, try to match style to a font - s.find_font_for_style(template_descriptor) + font_templates.find_font_for_style(template_descriptor) } else { debug!( "FontList: Couldn't find font family with name={}", @@ -435,16 +456,23 @@ impl FontCache { fn get_font_key_for_template(&mut self, template: &FontTemplateRef) -> FontKey { let webrender_api = &self.webrender_api; let webrender_fonts = &mut self.webrender_fonts; + let identifier = template.borrow().identifier.clone(); *webrender_fonts - .entry(template.borrow().identifier.clone()) + .entry(identifier.clone()) .or_insert_with(|| { - let template = template.borrow(); - let font = match (template.data_if_in_memory(), template.native_font_handle()) { - (Some(bytes), _) => FontData::Raw((*bytes).clone()), - (None, Some(native_font)) => FontData::Native(native_font), - (None, None) => unreachable!("Font should either be local or a web font."), - }; - webrender_api.add_font(font) + // CoreText cannot reliably create CoreTextFonts for system fonts stored + // as part of TTC files, so on CoreText platforms, create a system font in + // WebRender using the LocalFontIdentifier. This has the downside of + // causing the font to be loaded into memory again (bummer!), so only do + // this for those platforms. + #[cfg(target_os = "macos")] + if let FontIdentifier::Local(local_font_identifier) = identifier { + return webrender_api + .add_system_font(local_font_identifier.native_font_handle()); + } + + let bytes = template.data(); + webrender_api.add_font(bytes, identifier.index()) }) } diff --git a/components/gfx/font_context.rs b/components/gfx/font_context.rs index 16ba06cf2b1..3d652cd41d7 100644 --- a/components/gfx/font_context.rs +++ b/components/gfx/font_context.rs @@ -19,9 +19,9 @@ use webrender_api::{FontInstanceKey, FontKey}; use crate::font::{Font, FontDescriptor, FontFamilyDescriptor, FontGroup, FontRef}; use crate::font_cache_thread::FontTemplateAndWebRenderFontKey; -#[cfg(target_os = "macos")] -use crate::font_template::FontTemplate; use crate::font_template::FontTemplateDescriptor; +#[cfg(target_os = "macos")] +use crate::platform::core_text_font_cache::CoreTextFontCache; static SMALL_CAPS_SCALE_FACTOR: f32 = 0.8; // Matches FireFox (see gfxFont.h) @@ -263,5 +263,5 @@ pub fn invalidate_font_caches() { FONT_CACHE_EPOCH.fetch_add(1, Ordering::SeqCst); #[cfg(target_os = "macos")] - FontTemplate::clear_core_text_font_cache(); + CoreTextFontCache::clear_core_text_font_cache(); } diff --git a/components/gfx/font_template.rs b/components/gfx/font_template.rs index ccc7ac781bd..edaa3a96111 100644 --- a/components/gfx/font_template.rs +++ b/components/gfx/font_template.rs @@ -4,21 +4,18 @@ use std::cell::RefCell; use std::fmt::{Debug, Error, Formatter}; -use std::io::Error as IoError; use std::rc::Rc; use std::sync::Arc; -use log::warn; use serde::{Deserialize, Serialize}; +use servo_url::ServoUrl; use style::computed_values::font_stretch::T as FontStretch; use style::computed_values::font_style::T as FontStyle; use style::properties::style_structs::Font as FontStyleStruct; use style::values::computed::font::FontWeight; -use webrender_api::NativeFontHandle; -use crate::font::PlatformFontMethods; use crate::font_cache_thread::FontIdentifier; -use crate::platform::font::PlatformFont; +use crate::platform::font_list::LocalFontIdentifier; /// A reference to a [`FontTemplate`] with shared ownership and mutability. pub(crate) type FontTemplateRef = Rc<RefCell<FontTemplate>>; @@ -91,13 +88,12 @@ impl<'a> From<&'a FontStyleStruct> for FontTemplateDescriptor { /// FontTemplateData structure that is platform specific. pub struct FontTemplate { pub identifier: FontIdentifier, - pub descriptor: Option<FontTemplateDescriptor>, + pub descriptor: FontTemplateDescriptor, /// The data to use for this [`FontTemplate`]. For web fonts, this is always filled, but /// for local fonts, this is loaded only lazily in layout. /// /// TODO: There is no mechanism for web fonts to unset their data! pub data: Option<Arc<Vec<u8>>>, - pub is_valid: bool, } impl Debug for FontTemplate { @@ -110,13 +106,27 @@ impl Debug for FontTemplate { /// is common, regardless of the number of instances of /// this font handle per thread. impl FontTemplate { - pub fn new(identifier: FontIdentifier, data: Option<Vec<u8>>) -> Result<FontTemplate, IoError> { - Ok(FontTemplate { - identifier, - descriptor: None, - data: data.map(Arc::new), - is_valid: true, - }) + pub fn new_local( + identifier: LocalFontIdentifier, + descriptor: FontTemplateDescriptor, + ) -> FontTemplate { + FontTemplate { + identifier: FontIdentifier::Local(identifier), + descriptor, + data: None, + } + } + + pub fn new_web_font( + url: ServoUrl, + descriptor: FontTemplateDescriptor, + data: Arc<Vec<u8>>, + ) -> FontTemplate { + FontTemplate { + identifier: FontIdentifier::Web(url), + descriptor, + data: Some(data), + } } pub fn identifier(&self) -> &FontIdentifier { @@ -128,14 +138,6 @@ impl FontTemplate { pub fn data_if_in_memory(&self) -> Option<Arc<Vec<u8>>> { self.data.clone() } - - /// Returns a [`NativeFontHandle`] for this font template, if it is local. - pub fn native_font_handle(&self) -> Option<NativeFontHandle> { - match &self.identifier { - FontIdentifier::Local(local_identifier) => local_identifier.native_font_handle(), - FontIdentifier::Web(_) => None, - } - } } pub trait FontTemplateRefMethods { @@ -143,68 +145,26 @@ pub trait FontTemplateRefMethods { /// operation (depending on the platform) which performs synchronous disk I/O /// and should never be done lightly. fn data(&self) -> Arc<Vec<u8>>; - /// Return true if this is a valid [`FontTemplate`] ie it is possible to construct a [`FontHandle`] - /// from its data. - fn is_valid(&self) -> bool; - /// If not done already for this [`FontTemplate`] load the font into a platform font face and - /// populate the `descriptor` field. Note that calling [`FontTemplateRefMethods::descriptor()`] - /// does this implicitly. If this fails, [`FontTemplateRefMethods::is_valid()`] will return - /// false in the future. - fn instantiate(&self) -> Result<(), &'static str>; /// Get the descriptor. Returns `None` when instantiating the data fails. - fn descriptor(&self) -> Option<FontTemplateDescriptor>; + fn descriptor(&self) -> FontTemplateDescriptor; /// Returns true if the given descriptor matches the one in this [`FontTemplate`]. fn descriptor_matches(&self, requested_desc: &FontTemplateDescriptor) -> bool; /// Calculate the distance from this [`FontTemplate`]s descriptor and return it /// or None if this is not a valid [`FontTemplate`]. - fn descriptor_distance(&self, requested_descriptor: &FontTemplateDescriptor) -> Option<f32>; + fn descriptor_distance(&self, requested_descriptor: &FontTemplateDescriptor) -> f32; } impl FontTemplateRefMethods for FontTemplateRef { - fn descriptor(&self) -> Option<FontTemplateDescriptor> { - // Store the style information about the template separately from the data, - // so that we can do font matching against it again in the future without - // having to reload the font (unless it is an actual match). - if let Some(descriptor) = self.borrow().descriptor { - return Some(descriptor); - } - - if let Err(error) = self.instantiate() { - warn!("Could not initiate FonteTemplate descriptor: {error:?}"); - } - + fn descriptor(&self) -> FontTemplateDescriptor { self.borrow().descriptor } - fn descriptor_matches(&self, requested_desc: &FontTemplateDescriptor) -> bool { - self.descriptor() - .map_or(false, |descriptor| descriptor == *requested_desc) - } - - fn descriptor_distance(&self, requested_descriptor: &FontTemplateDescriptor) -> Option<f32> { - self.descriptor() - .map(|descriptor| descriptor.distance_from(requested_descriptor)) - } - - fn instantiate(&self) -> Result<(), &'static str> { - if !self.borrow().is_valid { - return Err("Invalid font template"); - } - - let handle = PlatformFontMethods::new_from_template(self.clone(), None); - let mut template = self.borrow_mut(); - template.is_valid = handle.is_ok(); - let handle: PlatformFont = handle?; - template.descriptor = Some(FontTemplateDescriptor::new( - handle.boldness(), - handle.stretchiness(), - handle.style(), - )); - Ok(()) + fn descriptor_matches(&self, requested_descriptor: &FontTemplateDescriptor) -> bool { + self.descriptor() == *requested_descriptor } - fn is_valid(&self) -> bool { - self.instantiate().is_ok() + fn descriptor_distance(&self, requested_descriptor: &FontTemplateDescriptor) -> f32 { + self.descriptor().distance_from(requested_descriptor) } fn data(&self) -> Arc<Vec<u8>> { diff --git a/components/gfx/lib.rs b/components/gfx/lib.rs index aee2d56c7d1..4840918fe1f 100644 --- a/components/gfx/lib.rs +++ b/components/gfx/lib.rs @@ -9,6 +9,6 @@ pub mod font_cache_thread; pub mod font_context; pub mod font_template; #[allow(unsafe_code)] -mod platform; +pub mod platform; pub mod rendering_context; pub mod text; diff --git a/components/gfx/platform/freetype/android/font_list.rs b/components/gfx/platform/freetype/android/font_list.rs index f2130dff45f..2a069320eb8 100644 --- a/components/gfx/platform/freetype/android/font_list.rs +++ b/components/gfx/platform/freetype/android/font_list.rs @@ -8,11 +8,15 @@ use std::path::{Path, PathBuf}; use log::warn; use serde::{Deserialize, Serialize}; +use style::values::computed::{ + FontStretch as StyleFontStretch, FontStyle as StyleFontStyle, FontWeight as StyleFontWeight, +}; use style::Atom; use ucd::{Codepoint, UnicodeBlock}; use webrender_api::NativeFontHandle; use super::xml::{Attribute, Node}; +use crate::font_template::{FontTemplate, FontTemplateDescriptor}; use crate::text::util::is_cjk; lazy_static::lazy_static! { @@ -27,11 +31,8 @@ pub struct LocalFontIdentifier { } impl LocalFontIdentifier { - pub(crate) fn native_font_handle(&self) -> Option<NativeFontHandle> { - Some(NativeFontHandle { - path: PathBuf::from(&*self.path), - index: 0, - }) + pub(crate) fn index(&self) -> u32 { + 0 } pub(crate) fn read_data_from_file(&self) -> Vec<u8> { @@ -132,6 +133,7 @@ impl LocalFontIdentifier { struct Font { filename: String, weight: Option<i32>, + style: Option<String>, } struct FontFamily { @@ -242,6 +244,7 @@ impl FontList { fonts: vec![Font { filename: item.1.into(), weight: None, + style: None, }], }) .collect() @@ -351,6 +354,7 @@ impl FontList { .map(|f| Font { filename: f.clone(), weight: None, + style: None, }) .collect(); @@ -370,10 +374,12 @@ impl FontList { if let Some(filename) = Self::text_content(nodes) { // Parse font weight let weight = Self::find_attrib("weight", attrs).and_then(|w| w.parse().ok()); + let style = Self::find_attrib("style", attrs); out.push(Font { - filename: filename, - weight: weight, + filename, + weight, + style, }) } } @@ -456,12 +462,35 @@ where pub fn for_each_variation<F>(family_name: &str, mut callback: F) where - F: FnMut(LocalFontIdentifier), + F: FnMut(FontTemplate), { let mut produce_font = |font: &Font| { - callback(LocalFontIdentifier { + let local_font_identifier = LocalFontIdentifier { path: Atom::from(FontList::font_absolute_path(&font.filename)), - }) + }; + let stretch = StyleFontStretch::NORMAL; + let weight = font + .weight + .map(|w| StyleFontWeight::from_float(w as f32)) + .unwrap_or(StyleFontWeight::NORMAL); + let style = match font.style.as_deref() { + Some("italic") => StyleFontStyle::ITALIC, + Some("normal") => StyleFontStyle::NORMAL, + Some(value) => { + warn!( + "unknown value \"{value}\" for \"style\" attribute in the font {}", + font.filename + ); + StyleFontStyle::NORMAL + }, + None => StyleFontStyle::NORMAL, + }; + let descriptor = FontTemplateDescriptor { + weight, + stretch, + style, + }; + callback(FontTemplate::new_local(local_font_identifier, descriptor)); }; if let Some(family) = FONT_LIST.find_family(family_name) { diff --git a/components/gfx/platform/freetype/font.rs b/components/gfx/platform/freetype/font.rs index 13f29e1939c..8287b0f8db6 100644 --- a/components/gfx/platform/freetype/font.rs +++ b/components/gfx/platform/freetype/font.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use std::ffi::CString; +use std::convert::TryInto; use std::os::raw::{c_char, c_long}; use std::sync::Arc; use std::{mem, ptr}; @@ -11,8 +11,8 @@ use app_units::Au; use freetype::freetype::{ FT_Done_Face, FT_F26Dot6, FT_Face, FT_FaceRec, FT_Get_Char_Index, FT_Get_Kerning, FT_Get_Postscript_Name, FT_Get_Sfnt_Table, FT_GlyphSlot, FT_Int32, FT_Kerning_Mode, - FT_Load_Glyph, FT_Load_Sfnt_Table, FT_Long, FT_New_Face, FT_New_Memory_Face, FT_Set_Char_Size, - FT_Sfnt_Tag, FT_SizeRec, FT_Size_Metrics, FT_UInt, FT_ULong, FT_Vector, FT_STYLE_FLAG_ITALIC, + FT_Load_Glyph, FT_Load_Sfnt_Table, FT_Long, FT_New_Memory_Face, FT_Set_Char_Size, FT_Sfnt_Tag, + FT_SizeRec, FT_Size_Metrics, FT_UInt, FT_ULong, FT_Vector, FT_STYLE_FLAG_ITALIC, }; use freetype::succeeded; use freetype::tt_os2::TT_OS2; @@ -28,7 +28,6 @@ use crate::font::{ KERN, }; use crate::font_cache_thread::FontIdentifier; -use crate::font_template::FontTemplateRef; use crate::text::glyph::GlyphId; use crate::text::util::fixed_to_float; @@ -73,11 +72,8 @@ struct OS2Table { #[allow(unused)] pub struct PlatformFont { /// The font data itself, which must stay valid for the lifetime of the - /// platform [`FT_Face`], if it's created via [`FT_New_Memory_Face`]. A reference - /// to this data is also stored in the [`crate::font::Font`] that holds - /// this [`PlatformFont`], but if it's ever separated from it's font, - /// this ensures the data stays alive. - font_data: Option<Arc<Vec<u8>>>, + /// platform [`FT_Face`]. + font_data: Arc<Vec<u8>>, face: FT_Face, can_do_fast_shaping: bool, } @@ -97,41 +93,24 @@ impl Drop for PlatformFont { } } -fn create_face(template: &FontTemplateRef, pt_size: Option<Au>) -> Result<FT_Face, &'static str> { +fn create_face( + data: Arc<Vec<u8>>, + face_index: u32, + pt_size: Option<Au>, +) -> Result<FT_Face, &'static str> { unsafe { let mut face: FT_Face = ptr::null_mut(); - let face_index = 0 as FT_Long; let library = FreeTypeLibraryHandle::get().lock(); - let result = match template.borrow().identifier { - FontIdentifier::Web(_) => { - let bytes = template - .borrow() - .data_if_in_memory() - .expect("Web font should always have data."); - FT_New_Memory_Face( - library.freetype_library, - bytes.as_ptr(), - bytes.len() as FT_Long, - face_index, - &mut face, - ) - }, - FontIdentifier::Local(ref local_identifier) => { - // This will trigger a synchronous file read during layout, which we may want to - // revisit at some point. See discussion here: - // - // https://github.com/servo/servo/pull/20506#issuecomment-378838800 - let filename = - CString::new(&*local_identifier.path).expect("filename contains NUL byte!"); - FT_New_Face( - library.freetype_library, - filename.as_ptr(), - face_index, - &mut face, - ) - }, - }; + // This is to support 32bit Android where FT_Long is defined as i32. + let face_index = face_index.try_into().unwrap(); + let result = FT_New_Memory_Face( + library.freetype_library, + data.as_ptr(), + data.len() as FT_Long, + face_index, + &mut face, + ); if !succeeded(result) || face.is_null() { return Err("Could not create FreeType face"); @@ -146,19 +125,23 @@ fn create_face(template: &FontTemplateRef, pt_size: Option<Au>) -> Result<FT_Fac } impl PlatformFontMethods for PlatformFont { - fn new_from_template( - template: FontTemplateRef, + fn new_from_data( + _font_identifier: FontIdentifier, + data: Arc<Vec<u8>>, + face_index: u32, pt_size: Option<Au>, ) -> Result<PlatformFont, &'static str> { - let face = create_face(&template, pt_size)?; + let face = create_face(data.clone(), face_index, pt_size)?; let mut handle = PlatformFont { face, - font_data: template.borrow().data_if_in_memory(), + font_data: data, can_do_fast_shaping: false, }; + // TODO (#11310): Implement basic support for GPOS and GSUB. handle.can_do_fast_shaping = handle.has_table(KERN) && !handle.has_table(GPOS) && !handle.has_table(GSUB); + Ok(handle) } diff --git a/components/gfx/platform/freetype/font_list.rs b/components/gfx/platform/freetype/font_list.rs index 9cd7e7663d9..0741fb61ce2 100644 --- a/components/gfx/platform/freetype/font_list.rs +++ b/components/gfx/platform/freetype/font_list.rs @@ -2,32 +2,37 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use std::convert::TryInto; use std::ffi::CString; use std::fs::File; use std::io::Read; -use std::path::{Path, PathBuf}; +use std::path::Path; use std::ptr; +use fontconfig_sys::constants::{ + FC_FAMILY, FC_FILE, FC_FONTFORMAT, FC_INDEX, FC_SLANT, FC_SLANT_ITALIC, FC_SLANT_OBLIQUE, + FC_WEIGHT, FC_WEIGHT_BOLD, FC_WEIGHT_EXTRABLACK, FC_WEIGHT_REGULAR, FC_WIDTH, + FC_WIDTH_CONDENSED, FC_WIDTH_EXPANDED, FC_WIDTH_EXTRACONDENSED, FC_WIDTH_EXTRAEXPANDED, + FC_WIDTH_NORMAL, FC_WIDTH_SEMICONDENSED, FC_WIDTH_SEMIEXPANDED, FC_WIDTH_ULTRACONDENSED, + FC_WIDTH_ULTRAEXPANDED, +}; use fontconfig_sys::{ FcChar8, FcConfigGetCurrent, FcConfigGetFonts, FcConfigSubstitute, FcDefaultSubstitute, FcFontMatch, FcFontSetDestroy, FcFontSetList, FcMatchPattern, FcNameParse, FcObjectSetAdd, - FcObjectSetCreate, FcObjectSetDestroy, FcPatternAddString, FcPatternCreate, FcPatternDestroy, - FcPatternGetInteger, FcPatternGetString, FcResultMatch, FcSetSystem, + FcObjectSetCreate, FcObjectSetDestroy, FcPattern, FcPatternAddString, FcPatternCreate, + FcPatternDestroy, FcPatternGetInteger, FcPatternGetString, FcResultMatch, FcSetSystem, }; use libc::{c_char, c_int}; use log::debug; use serde::{Deserialize, Serialize}; +use style::values::computed::{FontStretch, FontStyle, FontWeight}; use style::Atom; -use webrender_api::NativeFontHandle; use super::c_str_to_string; +use crate::font::map_platform_values_to_style_values; +use crate::font_template::{FontTemplate, FontTemplateDescriptor}; use crate::text::util::is_cjk; -static FC_FAMILY: &[u8] = b"family\0"; -static FC_FILE: &[u8] = b"file\0"; -static FC_INDEX: &[u8] = b"index\0"; -static FC_FONTFORMAT: &[u8] = b"fontformat\0"; - /// An identifier for a local font on systems using Freetype. #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] pub struct LocalFontIdentifier { @@ -38,12 +43,8 @@ pub struct LocalFontIdentifier { } impl LocalFontIdentifier { - pub(crate) fn native_font_handle(&self) -> Option<NativeFontHandle> { - Some(NativeFontHandle { - path: PathBuf::from(&*self.path), - // TODO: Shouldn't this be using the variation index from the LocalFontIdentifier? - index: 0, - }) + pub(crate) fn index(&self) -> u32 { + self.variation_index.try_into().unwrap() } pub(crate) fn read_data_from_file(&self) -> Vec<u8> { @@ -93,21 +94,19 @@ where pub fn for_each_variation<F>(family_name: &str, mut callback: F) where - F: FnMut(LocalFontIdentifier), + F: FnMut(FontTemplate), { - debug!("getting variations for {}", family_name); unsafe { let config = FcConfigGetCurrent(); let mut font_set = FcConfigGetFonts(config, FcSetSystem); let font_set_array_ptr = &mut font_set; let pattern = FcPatternCreate(); assert!(!pattern.is_null()); - let family_name_c = CString::new(family_name).unwrap(); - let family_name = family_name_c.as_ptr(); + let family_name_cstr: CString = CString::new(family_name).unwrap(); let ok = FcPatternAddString( pattern, FC_FAMILY.as_ptr() as *mut c_char, - family_name as *mut FcChar8, + family_name_cstr.as_ptr() as *const FcChar8, ); assert_ne!(ok, 0); @@ -116,12 +115,15 @@ where FcObjectSetAdd(object_set, FC_FILE.as_ptr() as *mut c_char); FcObjectSetAdd(object_set, FC_INDEX.as_ptr() as *mut c_char); + FcObjectSetAdd(object_set, FC_WEIGHT.as_ptr() as *mut c_char); + FcObjectSetAdd(object_set, FC_SLANT.as_ptr() as *mut c_char); + FcObjectSetAdd(object_set, FC_WIDTH.as_ptr() as *mut c_char); let matches = FcFontSetList(config, font_set_array_ptr, 1, pattern, object_set); - debug!("found {} variations", (*matches).nfont); + debug!("Found {} variations for {}", (*matches).nfont, family_name); - for i in 0..((*matches).nfont as isize) { - let font = (*matches).fonts.offset(i); + for variation_index in 0..((*matches).nfont as isize) { + let font = (*matches).fonts.offset(variation_index); let mut path: *mut FcChar8 = ptr::null_mut(); let result = FcPatternGetString(*font, FC_FILE.as_ptr() as *mut c_char, 0, &mut path); @@ -132,10 +134,27 @@ where FcPatternGetInteger(*font, FC_INDEX.as_ptr() as *mut c_char, 0, &mut index); assert_eq!(result, FcResultMatch); - callback(LocalFontIdentifier { + let Some(weight) = font_weight_from_fontconfig_pattern(*font) else { + continue; + }; + let Some(stretch) = font_stretch_from_fontconfig_pattern(*font) else { + continue; + }; + let Some(style) = font_style_from_fontconfig_pattern(*font) else { + continue; + }; + + let local_font_identifier = LocalFontIdentifier { path: Atom::from(c_str_to_string(path as *const c_char)), variation_index: index as i32, - }); + }; + let descriptor = FontTemplateDescriptor { + weight, + stretch, + style, + }; + + callback(FontTemplate::new_local(local_font_identifier, descriptor)) } FcFontSetDestroy(matches); @@ -204,3 +223,60 @@ pub fn fallback_font_families(codepoint: Option<char>) -> Vec<&'static str> { families } + +fn font_style_from_fontconfig_pattern(pattern: *mut FcPattern) -> Option<FontStyle> { + let mut slant: c_int = 0; + unsafe { + if FcResultMatch != FcPatternGetInteger(pattern, FC_SLANT.as_ptr(), 0, &mut slant) { + return None; + } + } + Some(match slant { + FC_SLANT_ITALIC => FontStyle::ITALIC, + FC_SLANT_OBLIQUE => FontStyle::OBLIQUE, + _ => FontStyle::NORMAL, + }) +} + +fn font_stretch_from_fontconfig_pattern(pattern: *mut FcPattern) -> Option<FontStretch> { + let mut width: c_int = 0; + unsafe { + if FcResultMatch != FcPatternGetInteger(pattern, FC_WIDTH.as_ptr(), 0, &mut width) { + return None; + } + } + let mapping = [ + (FC_WIDTH_ULTRACONDENSED as f64, 0.5), + (FC_WIDTH_EXTRACONDENSED as f64, 0.625), + (FC_WIDTH_CONDENSED as f64, 0.75), + (FC_WIDTH_SEMICONDENSED as f64, 0.875), + (FC_WIDTH_NORMAL as f64, 1.0), + (FC_WIDTH_SEMIEXPANDED as f64, 1.125), + (FC_WIDTH_EXPANDED as f64, 1.25), + (FC_WIDTH_EXTRAEXPANDED as f64, 1.50), + (FC_WIDTH_ULTRAEXPANDED as f64, 2.00), + ]; + + let mapped_width = map_platform_values_to_style_values(&mapping, width as f64); + Some(FontStretch::from_percentage(mapped_width as f32)) +} + +fn font_weight_from_fontconfig_pattern(pattern: *mut FcPattern) -> Option<FontWeight> { + let mut weight: c_int = 0; + unsafe { + let result = FcPatternGetInteger(pattern, FC_WEIGHT.as_ptr(), 0, &mut weight); + if result != FcResultMatch { + return None; + } + } + + let mapping = [ + (0., 0.), + (FC_WEIGHT_REGULAR as f64, 400 as f64), + (FC_WEIGHT_BOLD as f64, 700 as f64), + (FC_WEIGHT_EXTRABLACK as f64, 1000 as f64), + ]; + + let mapped_weight = map_platform_values_to_style_values(&mapping, weight as f64); + Some(FontWeight::from_float(mapped_weight as f32)) +} diff --git a/components/gfx/platform/macos/core_text_font_cache.rs b/components/gfx/platform/macos/core_text_font_cache.rs new file mode 100644 index 00000000000..4232b3f375d --- /dev/null +++ b/components/gfx/platform/macos/core_text_font_cache.rs @@ -0,0 +1,106 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::collections::HashMap; +use std::sync::{Arc, OnceLock}; + +use app_units::Au; +use core_foundation::base::TCFType; +use core_foundation::string::CFString; +use core_foundation::url::{kCFURLPOSIXPathStyle, CFURL}; +use core_graphics::data_provider::CGDataProvider; +use core_graphics::display::CFDictionary; +use core_graphics::font::CGFont; +use core_text::font::CTFont; +use core_text::font_descriptor::kCTFontURLAttribute; +use parking_lot::RwLock; + +use crate::font_cache_thread::FontIdentifier; + +/// A cache of `CTFont` to avoid having to create `CTFont` instances over and over. It is +/// always possible to create a `CTFont` using a `FontTemplate` even if it isn't in this +/// cache. +pub(crate) struct CoreTextFontCache(OnceLock<RwLock<HashMap<FontIdentifier, CachedCTFont>>>); + +/// The global [`CoreTextFontCache`]. +static CACHE: CoreTextFontCache = CoreTextFontCache(OnceLock::new()); + +/// A [`HashMap`] of cached [`CTFont`] for a single [`FontIdentifier`]. There is one [`CTFont`] +/// for each cached font size. +type CachedCTFont = HashMap<Au, CTFont>; + +impl CoreTextFontCache { + pub(crate) fn core_text_font( + font_identifier: FontIdentifier, + data: Arc<Vec<u8>>, + pt_size: f64, + ) -> Option<CTFont> { + //// If you pass a zero font size to one of the Core Text APIs, it'll replace it with + //// 12.0. We don't want that! (Issue #10492.) + let clamped_pt_size = pt_size.max(0.01); + let au_size = Au::from_f64_px(clamped_pt_size); + + let cache = CACHE.0.get_or_init(Default::default); + { + let cache = cache.read(); + if let Some(core_text_font) = cache + .get(&font_identifier) + .and_then(|identifier_cache| identifier_cache.get(&au_size)) + { + return Some(core_text_font.clone()); + } + } + + let mut cache = cache.write(); + let identifier_cache = cache + .entry(font_identifier.clone()) + .or_insert_with(Default::default); + + // It could be that between the time of the cache miss above and now, after the write lock + // on the cache has been acquired, the cache was populated with the data that we need. Thus + // check again and return the CTFont if it is is already cached. + if let Some(core_text_font) = identifier_cache.get(&au_size) { + return Some(core_text_font.clone()); + } + + let core_text_font = match font_identifier { + FontIdentifier::Local(local_font_identifier) => { + // Other platforms can instantiate a platform font by loading the data + // from a file and passing an index in the case the file is a TTC bundle. + // The only way to reliably load the correct font from a TTC bundle on + // macOS is to create the font using a descriptor with both the PostScript + // name and path. + let cf_name = CFString::new(&local_font_identifier.postscript_name); + let mut descriptor = core_text::font_descriptor::new_from_postscript_name(&cf_name); + + let cf_path = CFString::new(&local_font_identifier.path); + let url_attribute = unsafe { CFString::wrap_under_get_rule(kCTFontURLAttribute) }; + let attributes = CFDictionary::from_CFType_pairs(&[( + url_attribute, + CFURL::from_file_system_path(cf_path, kCFURLPOSIXPathStyle, false), + )]); + if let Ok(descriptor_with_path) = + descriptor.create_copy_with_attributes(attributes.to_untyped()) + { + descriptor = descriptor_with_path; + } + + core_text::font::new_from_descriptor(&descriptor, clamped_pt_size) + }, + FontIdentifier::Web(_) => { + let provider = CGDataProvider::from_buffer(data); + let cgfont = CGFont::from_data_provider(provider).ok()?; + core_text::font::new_from_CGFont(&cgfont, clamped_pt_size) + }, + }; + + identifier_cache.insert(au_size, core_text_font.clone()); + Some(core_text_font) + } + + pub(crate) fn clear_core_text_font_cache() { + let cache = CACHE.0.get_or_init(Default::default); + cache.write().clear(); + } +} diff --git a/components/gfx/platform/macos/font.rs b/components/gfx/platform/macos/font.rs index 477ec36e3cd..9fd3934ed07 100644 --- a/components/gfx/platform/macos/font.rs +++ b/components/gfx/platform/macos/font.rs @@ -16,17 +16,17 @@ use core_foundation::string::UniChar; use core_graphics::font::CGGlyph; use core_text::font::CTFont; use core_text::font_descriptor::{ - kCTFontDefaultOrientation, SymbolicTraitAccessors, TraitAccessors, + kCTFontDefaultOrientation, CTFontTraits, SymbolicTraitAccessors, TraitAccessors, }; use log::debug; use style::values::computed::font::{FontStretch, FontStyle, FontWeight}; -use super::font_template::CoreTextFontTemplateMethods; +use super::core_text_font_cache::CoreTextFontCache; use crate::font::{ - FontMetrics, FontTableMethods, FontTableTag, FractionalPixel, PlatformFontMethods, GPOS, GSUB, - KERN, + map_platform_values_to_style_values, FontMetrics, FontTableMethods, FontTableTag, + FractionalPixel, PlatformFontMethods, GPOS, GSUB, KERN, }; -use crate::font_template::FontTemplateRef; +use crate::font_cache_thread::FontIdentifier; use crate::text::glyph::GlyphId; const KERN_PAIR_LEN: usize = 6; @@ -57,7 +57,7 @@ pub struct PlatformFont { ctfont: CTFont, /// A reference to this data used to create this [`PlatformFont`], ensuring the /// data stays alive of the lifetime of this struct. - _data: Option<Arc<Vec<u8>>>, + _data: Arc<Vec<u8>>, h_kern_subtable: Option<CachedKernTable>, can_do_fast_shaping: bool, } @@ -158,20 +158,24 @@ impl fmt::Debug for CachedKernTable { } impl PlatformFontMethods for PlatformFont { - fn new_from_template( - font_template: FontTemplateRef, + fn new_from_data( + font_identifier: FontIdentifier, + data: Arc<Vec<u8>>, + _face_index: u32, pt_size: Option<Au>, ) -> Result<PlatformFont, &'static str> { let size = match pt_size { Some(s) => s.to_f64_px(), None => 0.0, }; - let Some(core_text_font) = font_template.core_text_font(size) else { + let Some(core_text_font) = + CoreTextFontCache::core_text_font(font_identifier, data.clone(), size) + else { return Err("Could not generate CTFont for FontTemplateData"); }; let mut handle = PlatformFont { - _data: font_template.borrow().data_if_in_memory(), + _data: data, ctfont: core_text_font.clone_with_font_size(size), h_kern_subtable: None, can_do_fast_shaping: false, @@ -193,29 +197,15 @@ impl PlatformFontMethods for PlatformFont { } fn style(&self) -> FontStyle { - if self.ctfont.symbolic_traits().is_italic() { - FontStyle::ITALIC - } else { - FontStyle::NORMAL - } + self.ctfont.all_traits().style() } fn boldness(&self) -> FontWeight { - let normalized = self.ctfont.all_traits().normalized_weight(); // [-1.0, 1.0] - - // TODO(emilio): It may make sense to make this range [.01, 10.0], to - // align with css-fonts-4's range of [1, 1000]. - let normalized = if normalized <= 0.0 { - 4.0 + normalized * 3.0 // [1.0, 4.0] - } else { - 4.0 + normalized * 5.0 // [4.0, 9.0] - }; // [1.0, 9.0], centered on 4.0 - FontWeight::from_float(normalized as f32 * 100.) + self.ctfont.all_traits().weight() } fn stretchiness(&self) -> FontStretch { - let normalized = self.ctfont.all_traits().normalized_width(); // [-1.0, 1.0] - FontStretch::from_percentage(normalized as f32 + 1.0) + self.ctfont.all_traits().stretch() } fn glyph_index(&self, codepoint: char) -> Option<GlyphId> { @@ -315,3 +305,50 @@ impl PlatformFontMethods for PlatformFont { result.map(FontTable::wrap) } } + +pub(super) trait CoreTextFontTraitsMapping { + fn weight(&self) -> FontWeight; + fn style(&self) -> FontStyle; + fn stretch(&self) -> FontStretch; +} + +impl CoreTextFontTraitsMapping for CTFontTraits { + fn weight(&self) -> FontWeight { + // From https://developer.apple.com/documentation/coretext/kctfontweighttrait?language=objc + // > The value returned is a CFNumberRef representing a float value between -1.0 and + // > 1.0 for normalized weight. The value of 0.0 corresponds to the regular or + // > medium font weight. + let mapping = [(-1., 0.), (0., 400.), (1., 1000.)]; + + let mapped_weight = map_platform_values_to_style_values(&mapping, self.normalized_weight()); + FontWeight::from_float(mapped_weight as f32) + } + + fn style(&self) -> FontStyle { + let slant = self.normalized_slant(); + if slant == 0. && self.symbolic_traits().is_italic() { + return FontStyle::ITALIC; + } + if slant == 0. { + return FontStyle::NORMAL; + } + + // From https://developer.apple.com/documentation/coretext/kctfontslanttrait?language=objc + // > The value returned is a CFNumberRef object representing a float value + // > between -1.0 and 1.0 for normalized slant angle. The value of 0.0 + // > corresponds to 0 degrees clockwise rotation from the vertical and 1.0 + // > corresponds to 30 degrees clockwise rotation. + let mapping = [(-1., -30.), (0., 0.), (1., 30.)]; + let mapped_slant = map_platform_values_to_style_values(&mapping, slant); + FontStyle::oblique(mapped_slant as f32) + } + + fn stretch(&self) -> FontStretch { + // From https://developer.apple.com/documentation/coretext/kctfontwidthtrait?language=objc + // > This value corresponds to the relative interglyph spacing for a given font. + // > The value returned is a CFNumberRef object representing a float between -1.0 + // > and 1.0. The value of 0.0 corresponds to regular glyph spacing, and negative + // > values represent condensed glyph spacing. + FontStretch::from_percentage(self.normalized_width() as f32 + 1.0) + } +} diff --git a/components/gfx/platform/macos/font_list.rs b/components/gfx/platform/macos/font_list.rs index 8d3dc2a09e8..dddd0b93275 100644 --- a/components/gfx/platform/macos/font_list.rs +++ b/components/gfx/platform/macos/font_list.rs @@ -12,6 +12,8 @@ use style::Atom; use ucd::{Codepoint, UnicodeBlock}; use webrender_api::NativeFontHandle; +use crate::font_template::{FontTemplate, FontTemplateDescriptor}; +use crate::platform::font::CoreTextFontTraitsMapping; use crate::text::util::unicode_plane; /// An identifier for a local font on a MacOS system. These values comes from the CoreText @@ -24,11 +26,15 @@ pub struct LocalFontIdentifier { } impl LocalFontIdentifier { - pub(crate) fn native_font_handle(&self) -> Option<NativeFontHandle> { - Some(NativeFontHandle { + pub(crate) fn native_font_handle(&self) -> NativeFontHandle { + NativeFontHandle { name: self.postscript_name.to_string(), path: self.path.to_string(), - }) + } + } + + pub(crate) fn index(&self) -> u32 { + 0 } pub(crate) fn read_data_from_file(&self) -> Vec<u8> { @@ -53,10 +59,9 @@ where pub fn for_each_variation<F>(family_name: &str, mut callback: F) where - F: FnMut(LocalFontIdentifier), + F: FnMut(FontTemplate), { debug!("Looking for faces of family: {}", family_name); - let family_collection = core_text::font_collection::create_for_family(family_name); if let Some(family_collection) = family_collection { if let Some(family_descriptors) = family_collection.get_descriptors() { @@ -66,10 +71,18 @@ where Some(path) => path, None => continue, }; - callback(LocalFontIdentifier { + + let traits = family_descriptor.traits(); + let descriptor = FontTemplateDescriptor { + weight: traits.weight(), + stretch: traits.stretch(), + style: traits.style(), + }; + let identifier = LocalFontIdentifier { postscript_name: Atom::from(family_descriptor.font_name()), path: Atom::from(path), - }) + }; + callback(FontTemplate::new_local(identifier, descriptor)); } } } diff --git a/components/gfx/platform/macos/font_template.rs b/components/gfx/platform/macos/font_template.rs deleted file mode 100644 index f7a4cde5efa..00000000000 --- a/components/gfx/platform/macos/font_template.rs +++ /dev/null @@ -1,74 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - -use std::collections::HashMap; -use std::sync::OnceLock; - -use app_units::Au; -use core_graphics::data_provider::CGDataProvider; -use core_graphics::font::CGFont; -use core_text::font::CTFont; -use parking_lot::RwLock; - -use crate::font_cache_thread::FontIdentifier; -use crate::font_template::{FontTemplate, FontTemplateRef, FontTemplateRefMethods}; - -// A cache of `CTFont` to avoid having to create `CTFont` instances over and over. It is -// always possible to create a `CTFont` using a `FontTemplate` even if it isn't in this -// cache. -static CTFONT_CACHE: OnceLock<RwLock<HashMap<FontIdentifier, CachedCTFont>>> = OnceLock::new(); - -/// A [`HashMap`] of cached [`CTFont`] for a single [`FontIdentifier`]. There is one [`CTFont`] -/// for each cached font size. -type CachedCTFont = HashMap<Au, CTFont>; - -pub(crate) trait CoreTextFontTemplateMethods { - /// Retrieves a [`CTFont`] instance, instantiating it if necessary if it is not - /// stored in the shared Core Text font cache. - fn core_text_font(&self, pt_size: f64) -> Option<CTFont>; -} - -impl CoreTextFontTemplateMethods for FontTemplateRef { - fn core_text_font(&self, pt_size: f64) -> Option<CTFont> { - //// If you pass a zero font size to one of the Core Text APIs, it'll replace it with - //// 12.0. We don't want that! (Issue #10492.) - let clamped_pt_size = pt_size.max(0.01); - let au_size = Au::from_f64_px(clamped_pt_size); - - let cache = CTFONT_CACHE.get_or_init(Default::default); - let identifier = self.borrow().identifier.clone(); - { - let cache = cache.read(); - if let Some(core_text_font) = cache - .get(&identifier) - .and_then(|identifier_cache| identifier_cache.get(&au_size)) - { - return Some(core_text_font.clone()); - } - } - - let mut cache = cache.write(); - let identifier_cache = cache.entry(identifier).or_insert_with(Default::default); - - // It could be that between the time of the cache miss above and now, after the write lock - // on the cache has been acquired, the cache was populated with the data that we need. Thus - // check again and return the CTFont if it is is already cached. - if let Some(core_text_font) = identifier_cache.get(&au_size) { - return Some(core_text_font.clone()); - } - - let provider = CGDataProvider::from_buffer(self.data()); - let cgfont = CGFont::from_data_provider(provider).ok()?; - let core_text_font = core_text::font::new_from_CGFont(&cgfont, clamped_pt_size); - identifier_cache.insert(au_size, core_text_font.clone()); - Some(core_text_font) - } -} - -impl FontTemplate { - pub(crate) fn clear_core_text_font_cache() { - let cache = CTFONT_CACHE.get_or_init(Default::default); - cache.write().clear(); - } -} diff --git a/components/gfx/platform/mod.rs b/components/gfx/platform/mod.rs index 72482e7df61..5509c214531 100644 --- a/components/gfx/platform/mod.rs +++ b/components/gfx/platform/mod.rs @@ -5,7 +5,7 @@ #[cfg(any(target_os = "linux", target_os = "android"))] pub use crate::platform::freetype::{font, font_list, library_handle}; #[cfg(target_os = "macos")] -pub use crate::platform::macos::{font, font_list, font_template}; +pub use crate::platform::macos::{core_text_font_cache, font, font_list}; #[cfg(target_os = "windows")] pub use crate::platform::windows::{font, font_list}; @@ -41,9 +41,9 @@ mod freetype { #[cfg(target_os = "macos")] mod macos { + pub mod core_text_font_cache; pub mod font; pub mod font_list; - pub mod font_template; } #[cfg(target_os = "windows")] diff --git a/components/gfx/platform/windows/font.rs b/components/gfx/platform/windows/font.rs index 3baef67ca28..89ea63f3a8c 100644 --- a/components/gfx/platform/windows/font.rs +++ b/components/gfx/platform/windows/font.rs @@ -11,7 +11,7 @@ use std::ops::Deref; use std::sync::Arc; use app_units::Au; -use dwrote::{Font, FontFace, FontFile, FontStretch, FontStyle}; +use dwrote::{FontFace, FontFile}; use log::debug; use style::computed_values::font_stretch::T as StyleFontStretch; use style::computed_values::font_weight::T as StyleFontWeight; @@ -22,7 +22,6 @@ use crate::font::{ FontMetrics, FontTableMethods, FontTableTag, FractionalPixel, PlatformFontMethods, }; use crate::font_cache_thread::FontIdentifier; -use crate::font_template::{FontTemplateRef, FontTemplateRefMethods}; use crate::text::glyph::GlyphId; // 1em = 12pt = 16px, assuming 72 points per inch and 96 px per inch @@ -57,11 +56,6 @@ impl FontTableMethods for FontTable { } } -fn make_tag(tag_bytes: &[u8]) -> FontTableTag { - assert_eq!(tag_bytes.len(), 4); - unsafe { *(tag_bytes.as_ptr() as *const FontTableTag) } -} - // We need the font (DWriteFont) in order to be able to query things like // the family name, face name, weight, etc. On Windows 10, the // DWriteFontFace3 interface provides this on the FontFace, but that's only @@ -79,6 +73,16 @@ struct FontInfo { style: StyleFontStyle, } +#[macro_export] +/// Packs the components of a font tag name into 32 bytes, while respecting the +/// necessary Rust 4-byte alignment for pointers. This is similar to +/// [`crate::ot_tag`], but the bytes are reversed. +macro_rules! font_tag { + ($t1:expr, $t2:expr, $t3:expr, $t4:expr) => { + (($t4 as u32) << 24) | (($t3 as u32) << 16) | (($t2 as u32) << 8) | ($t1 as u32) + }; +} + impl FontInfo { fn new_from_face(face: &FontFace) -> Result<FontInfo, &'static str> { use std::cmp::{max, min}; @@ -89,8 +93,10 @@ impl FontInfo { use truetype::tables::WindowsMetrics; use truetype::value::Read; - let names_bytes = face.get_font_table(make_tag(b"name")); - let windows_metrics_bytes = face.get_font_table(make_tag(b"OS/2")); + let name_tag = font_tag!('n', 'a', 'm', 'e'); + let os2_tag = font_tag!('O', 'S', '/', '2'); + let names_bytes = face.get_font_table(name_tag); + let windows_metrics_bytes = face.get_font_table(os2_tag); if names_bytes.is_none() || windows_metrics_bytes.is_none() { return Err("No 'name' or 'OS/2' tables"); } @@ -166,36 +172,6 @@ impl FontInfo { style, }) } - - fn new_from_font(font: &Font) -> Result<FontInfo, &'static str> { - let style = match font.style() { - FontStyle::Normal => StyleFontStyle::NORMAL, - FontStyle::Oblique => StyleFontStyle::OBLIQUE, - FontStyle::Italic => StyleFontStyle::ITALIC, - }; - let weight = StyleFontWeight::from_float(font.weight().to_u32() as f32); - let stretch = match font.stretch() { - FontStretch::Undefined => FontStretchKeyword::Normal, - FontStretch::UltraCondensed => FontStretchKeyword::UltraCondensed, - FontStretch::ExtraCondensed => FontStretchKeyword::ExtraCondensed, - FontStretch::Condensed => FontStretchKeyword::Condensed, - FontStretch::SemiCondensed => FontStretchKeyword::SemiCondensed, - FontStretch::Normal => FontStretchKeyword::Normal, - FontStretch::SemiExpanded => FontStretchKeyword::SemiExpanded, - FontStretch::Expanded => FontStretchKeyword::Expanded, - FontStretch::ExtraExpanded => FontStretchKeyword::ExtraExpanded, - FontStretch::UltraExpanded => FontStretchKeyword::UltraExpanded, - } - .compute(); - - Ok(FontInfo { - family_name: font.family_name(), - face_name: font.face_name(), - style, - weight, - stretch, - }) - } } #[derive(Debug)] @@ -203,7 +179,7 @@ pub struct PlatformFont { face: Nondebug<FontFace>, /// A reference to this data used to create this [`PlatformFont`], ensuring the /// data stays alive of the lifetime of this struct. - data: Option<Arc<Vec<u8>>>, + _data: Arc<Vec<u8>>, info: FontInfo, em_size: f32, du_to_px: f32, @@ -226,32 +202,17 @@ impl<T> Deref for Nondebug<T> { } impl PlatformFontMethods for PlatformFont { - fn new_from_template( - font_template: FontTemplateRef, + fn new_from_data( + _font_identifier: FontIdentifier, + data: Arc<Vec<u8>>, + face_index: u32, pt_size: Option<Au>, ) -> Result<Self, &'static str> { - let direct_write_font = match font_template.borrow().identifier { - FontIdentifier::Local(ref local_identifier) => local_identifier.direct_write_font(), - FontIdentifier::Web(_) => None, - }; - - let (face, info, data) = match direct_write_font { - Some(font) => ( - font.create_font_face(), - FontInfo::new_from_font(&font)?, - None, - ), - None => { - let bytes = font_template.data(); - let font_file = - FontFile::new_from_data(bytes).ok_or("Could not create FontFile")?; - let face = font_file - .create_face(0, dwrote::DWRITE_FONT_SIMULATIONS_NONE) - .map_err(|_| "Could not create FontFace")?; - let info = FontInfo::new_from_face(&face)?; - (face, info, font_template.borrow().data_if_in_memory()) - }, - }; + let font_file = FontFile::new_from_data(data.clone()).ok_or("Could not create FontFile")?; + let face = font_file + .create_face(face_index, dwrote::DWRITE_FONT_SIMULATIONS_NONE) + .map_err(|_| "Could not create FontFace")?; + let info = FontInfo::new_from_face(&face)?; let pt_size = pt_size.unwrap_or(au_from_pt(12.)); let du_per_em = face.metrics().metrics0().designUnitsPerEm as f32; @@ -264,7 +225,7 @@ impl PlatformFontMethods for PlatformFont { Ok(PlatformFont { face: Nondebug(face), - data, + _data: data, info, em_size, du_to_px: design_units_to_pixels, diff --git a/components/gfx/platform/windows/font_list.rs b/components/gfx/platform/windows/font_list.rs index d90c7d1aab8..fa96c7de226 100644 --- a/components/gfx/platform/windows/font_list.rs +++ b/components/gfx/platform/windows/font_list.rs @@ -5,11 +5,13 @@ use std::hash::Hash; use std::sync::Arc; -use dwrote::{Font, FontCollection, FontDescriptor}; +use dwrote::{Font, FontCollection, FontDescriptor, FontStretch, FontStyle}; use serde::{Deserialize, Serialize}; +use style::values::computed::{FontStyle as StyleFontStyle, FontWeight as StyleFontWeight}; +use style::values::specified::font::FontStretchKeyword; use ucd::{Codepoint, UnicodeBlock}; -use webrender_api::NativeFontHandle; +use crate::font_template::{FontTemplate, FontTemplateDescriptor}; use crate::text::util::unicode_plane; pub static SANS_SERIF_FONT_FAMILY: &str = "Arial"; @@ -36,18 +38,10 @@ pub struct LocalFontIdentifier { } impl LocalFontIdentifier { - /// Create a [`Font`] for this font. - pub fn direct_write_font(&self) -> Option<Font> { - FontCollection::system().get_font_from_descriptor(&self.font_descriptor) - } - - pub fn native_font_handle(&self) -> Option<NativeFontHandle> { - let face = self.direct_write_font()?.create_font_face(); - let path = face.get_files().first()?.get_font_file_path()?; - Some(NativeFontHandle { - path, - index: face.get_index(), - }) + pub fn index(&self) -> u32 { + FontCollection::system() + .get_font_from_descriptor(&self.font_descriptor) + .map_or(0, |font| font.create_font_face().get_index()) } pub(crate) fn read_data_from_file(&self) -> Vec<u8> { @@ -72,27 +66,23 @@ impl Hash for LocalFontIdentifier { } } -// for_each_variation is supposed to return a string that can be -// atomized and then uniquely used to return back to this font. -// Some platforms use the full postscript name (MacOS X), or -// a font filename. -// -// For windows we're going to use just a basic integer value that -// we'll stringify, and then put them all in a HashMap with -// the actual FontDescriptor there. - pub fn for_each_variation<F>(family_name: &str, mut callback: F) where - F: FnMut(LocalFontIdentifier), + F: FnMut(FontTemplate), { let system_fc = FontCollection::system(); if let Some(family) = system_fc.get_font_family_by_name(family_name) { let count = family.get_font_count(); for i in 0..count { let font = family.get_font(i); - callback(LocalFontIdentifier { + let template_descriptor = (&font).into(); + let local_font_identifier = LocalFontIdentifier { font_descriptor: Arc::new(font.to_descriptor()), - }); + }; + callback(FontTemplate::new_local( + local_font_identifier, + template_descriptor, + )) } } } @@ -357,3 +347,28 @@ pub fn fallback_font_families(codepoint: Option<char>) -> Vec<&'static str> { families.push("Arial Unicode MS"); families } + +impl From<&Font> for FontTemplateDescriptor { + fn from(font: &Font) -> Self { + let style = match font.style() { + FontStyle::Normal => StyleFontStyle::NORMAL, + FontStyle::Oblique => StyleFontStyle::OBLIQUE, + FontStyle::Italic => StyleFontStyle::ITALIC, + }; + let weight = StyleFontWeight::from_float(font.weight().to_u32() as f32); + let stretch = match font.stretch() { + FontStretch::Undefined => FontStretchKeyword::Normal, + FontStretch::UltraCondensed => FontStretchKeyword::UltraCondensed, + FontStretch::ExtraCondensed => FontStretchKeyword::ExtraCondensed, + FontStretch::Condensed => FontStretchKeyword::Condensed, + FontStretch::SemiCondensed => FontStretchKeyword::SemiCondensed, + FontStretch::Normal => FontStretchKeyword::Normal, + FontStretch::SemiExpanded => FontStretchKeyword::SemiExpanded, + FontStretch::Expanded => FontStretchKeyword::Expanded, + FontStretch::ExtraExpanded => FontStretchKeyword::ExtraExpanded, + FontStretch::UltraExpanded => FontStretchKeyword::UltraExpanded, + } + .compute(); + FontTemplateDescriptor::new(weight, stretch, style) + } +} diff --git a/components/gfx/tests/font_context.rs b/components/gfx/tests/font_context.rs index 6a787ae9996..5f1fc332e67 100644 --- a/components/gfx/tests/font_context.rs +++ b/components/gfx/tests/font_context.rs @@ -12,10 +12,12 @@ use std::rc::Rc; use app_units::Au; use gfx::font::{ fallback_font_families, FontDescriptor, FontFamilyDescriptor, FontFamilyName, FontSearchScope, + PlatformFontMethods, }; use gfx::font_cache_thread::{FontIdentifier, FontTemplateAndWebRenderFontKey, FontTemplates}; use gfx::font_context::{FontContext, FontSource}; -use gfx::font_template::FontTemplateDescriptor; +use gfx::font_template::{FontTemplate, FontTemplateDescriptor}; +use gfx::platform::font::PlatformFont; use servo_arc::Arc; use servo_atoms::Atom; use servo_url::ServoUrl; @@ -57,11 +59,15 @@ impl TestFontSource { } fn identifier_for_font_name(name: &str) -> FontIdentifier { + FontIdentifier::Web(Self::url_for_font_name(name)) + } + + fn url_for_font_name(name: &str) -> ServoUrl { let mut path: PathBuf = [env!("CARGO_MANIFEST_DIR"), "tests", "support", "CSSTest"] .iter() .collect(); path.push(format!("{}.ttf", name)); - FontIdentifier::Web(ServoUrl::from_file_path(path).unwrap()) + ServoUrl::from_file_path(path).unwrap() } fn add_face(family: &mut FontTemplates, name: &str) { @@ -71,10 +77,22 @@ impl TestFontSource { path.push(format!("{}.ttf", name)); let file = File::open(path).unwrap(); - family.add_template( + let data: Vec<u8> = file.bytes().map(|b| b.unwrap()).collect(); + let data = std::sync::Arc::new(data); + let handle = PlatformFont::new_from_data( Self::identifier_for_font_name(name), - Some(file.bytes().map(|b| b.unwrap()).collect()), + data.clone(), + 0, + None, ) + .unwrap(); + let descriptor = + FontTemplateDescriptor::new(handle.boldness(), handle.stretchiness(), handle.style()); + family.add_template(FontTemplate::new_web_font( + Self::url_for_font_name(name), + descriptor, + data, + )); } } diff --git a/components/gfx/tests/font_template.rs b/components/gfx/tests/font_template.rs index 5c8e9f8b37a..d6baf042e5d 100644 --- a/components/gfx/tests/font_template.rs +++ b/components/gfx/tests/font_template.rs @@ -6,14 +6,15 @@ #[cfg(not(target_os = "macos"))] #[test] fn test_font_template_descriptor() { - use std::cell::RefCell; use std::fs::File; use std::io::prelude::*; use std::path::PathBuf; - use std::rc::Rc; + use std::sync::Arc; + use gfx::font::PlatformFontMethods; use gfx::font_cache_thread::FontIdentifier; - use gfx::font_template::{FontTemplate, FontTemplateDescriptor, FontTemplateRefMethods}; + use gfx::font_template::FontTemplateDescriptor; + use gfx::platform::font::PlatformFont; use servo_url::ServoUrl; use style::values::computed::font::{FontStretch, FontStyle, FontWeight}; @@ -29,15 +30,11 @@ fn test_font_template_descriptor() { .collect(); path.push(format!("{}.ttf", filename)); + let identifier = FontIdentifier::Web(ServoUrl::from_file_path(path.clone()).unwrap()); let file = File::open(path.clone()).unwrap(); - let template = FontTemplate::new( - FontIdentifier::Web(ServoUrl::from_file_path(path).unwrap()), - Some(file.bytes().map(|b| b.unwrap()).collect()), - ) - .unwrap(); - let template = Rc::new(RefCell::new(template)); - - template.descriptor().unwrap() + let data = file.bytes().map(|b| b.unwrap()).collect(); + let handle = PlatformFont::new_from_data(identifier, Arc::new(data), 0, None).unwrap(); + FontTemplateDescriptor::new(handle.boldness(), handle.stretchiness(), handle.style()) } assert_eq!( diff --git a/components/servo/lib.rs b/components/servo/lib.rs index b98b3aeb386..9a2fa92e70a 100644 --- a/components/servo/lib.rs +++ b/components/servo/lib.rs @@ -89,7 +89,9 @@ use surfman::{GLApi, GLVersion}; #[cfg(target_os = "linux")] use surfman::{NativeConnection, NativeContext}; use webrender::{RenderApiSender, ShaderPrecacheFlags}; -use webrender_api::{ColorF, DocumentId, FontInstanceKey, FontKey, FramePublishId, ImageKey}; +use webrender_api::{ + ColorF, DocumentId, FontInstanceKey, FontKey, FramePublishId, ImageKey, NativeFontHandle, +}; use webrender_traits::{ WebrenderExternalImageHandlers, WebrenderExternalImageRegistry, WebrenderImageHandlerType, }; @@ -1050,12 +1052,25 @@ impl gfx_traits::WebrenderApi for FontCacheWR { ))); receiver.recv().unwrap() } - fn add_font(&self, data: gfx_traits::FontData) -> FontKey { + fn add_font(&self, data: Arc<Vec<u8>>, index: u32) -> FontKey { + let (sender, receiver) = unbounded(); + let (bytes_sender, bytes_receiver) = + ipc::bytes_channel().expect("failed to create IPC channel"); + let _ = self + .0 + .send(CompositorMsg::Forwarded(ForwardedToCompositorMsg::Font( + FontToCompositorMsg::AddFont(sender, index, bytes_receiver), + ))); + let _ = bytes_sender.send(&data); + receiver.recv().unwrap() + } + + fn add_system_font(&self, handle: NativeFontHandle) -> FontKey { let (sender, receiver) = unbounded(); let _ = self .0 .send(CompositorMsg::Forwarded(ForwardedToCompositorMsg::Font( - FontToCompositorMsg::AddFont(data, sender), + FontToCompositorMsg::AddSystemFont(sender, handle), ))); receiver.recv().unwrap() } diff --git a/components/shared/compositing/lib.rs b/components/shared/compositing/lib.rs index 9c759bc4acf..6af096dbbbf 100644 --- a/components/shared/compositing/lib.rs +++ b/components/shared/compositing/lib.rs @@ -25,7 +25,7 @@ use script_traits::{ }; use style_traits::CSSPixel; use webrender_api::units::{DeviceIntPoint, DeviceIntSize, DeviceRect}; -use webrender_api::{self, FontInstanceKey, FontKey, ImageKey}; +use webrender_api::{self, FontInstanceKey, FontKey, ImageKey, NativeFontHandle}; /// Sends messages to the compositor. pub struct CompositorProxy { @@ -140,7 +140,8 @@ pub struct CompositionPipeline { pub enum FontToCompositorMsg { AddFontInstance(FontKey, f32, Sender<FontInstanceKey>), - AddFont(gfx_traits::FontData, Sender<FontKey>), + AddFont(Sender<FontKey>, u32, ipc_channel::ipc::IpcBytesReceiver), + AddSystemFont(Sender<FontKey>, NativeFontHandle), } pub enum CanvasToCompositorMsg { diff --git a/components/shared/gfx/lib.rs b/components/shared/gfx/lib.rs index 5045a677c86..aaf57b5dfb1 100644 --- a/components/shared/gfx/lib.rs +++ b/components/shared/gfx/lib.rs @@ -9,6 +9,7 @@ pub mod print_tree; use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::Arc; use malloc_size_of_derive::MallocSizeOf; use range::{int_range_index, RangeIndex}; @@ -119,12 +120,8 @@ pub fn node_id_from_scroll_id(id: usize) -> Option<usize> { None } -pub enum FontData { - Raw(Vec<u8>), - Native(NativeFontHandle), -} - pub trait WebrenderApi { fn add_font_instance(&self, font_key: FontKey, size: f32) -> FontInstanceKey; - fn add_font(&self, data: FontData) -> FontKey; + fn add_font(&self, data: Arc<Vec<u8>>, index: u32) -> FontKey; + fn add_system_font(&self, handle: NativeFontHandle) -> FontKey; } |