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 IOCompositor { 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 IOCompositor { 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, - ) -> Result; + ) -> Result { + 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>, + face_index: u32, + pt_size: Option, + ) -> Result; fn family_name(&self) -> Option; fn face_name(&self) -> Option; @@ -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, + 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>) { - 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>; @@ -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, + 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>>, - 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>) -> Result { - 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>, + ) -> 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>> { self.data.clone() } - - /// Returns a [`NativeFontHandle`] for this font template, if it is local. - pub fn native_font_handle(&self) -> Option { - 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>; - /// 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; + 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; + fn descriptor_distance(&self, requested_descriptor: &FontTemplateDescriptor) -> f32; } impl FontTemplateRefMethods for FontTemplateRef { - fn descriptor(&self) -> Option { - // 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_matches(&self, requested_descriptor: &FontTemplateDescriptor) -> bool { + self.descriptor() == *requested_descriptor } - fn descriptor_distance(&self, requested_descriptor: &FontTemplateDescriptor) -> Option { - 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 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> { 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 { - 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 { @@ -132,6 +133,7 @@ impl LocalFontIdentifier { struct Font { filename: String, weight: Option, + style: Option, } 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(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>>, + /// platform [`FT_Face`]. + font_data: Arc>, face: FT_Face, can_do_fast_shaping: bool, } @@ -97,41 +93,24 @@ impl Drop for PlatformFont { } } -fn create_face(template: &FontTemplateRef, pt_size: Option) -> Result { +fn create_face( + data: Arc>, + face_index: u32, + pt_size: Option, +) -> Result { 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) -> Result>, + face_index: u32, pt_size: Option, ) -> Result { - 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 { - 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 { @@ -93,21 +94,19 @@ where pub fn for_each_variation(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) -> Vec<&'static str> { families } + +fn font_style_from_fontconfig_pattern(pattern: *mut FcPattern) -> Option { + 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 { + 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 { + 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>>); + +/// 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; + +impl CoreTextFontCache { + pub(crate) fn core_text_font( + font_identifier: FontIdentifier, + data: Arc>, + pt_size: f64, + ) -> Option { + //// 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>>, + _data: Arc>, h_kern_subtable: Option, 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>, + _face_index: u32, pt_size: Option, ) -> Result { 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 { @@ -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 { - 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 { @@ -53,10 +59,9 @@ where pub fn for_each_variation(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>> = OnceLock::new(); - -/// A [`HashMap`] of cached [`CTFont`] for a single [`FontIdentifier`]. There is one [`CTFont`] -/// for each cached font size. -type CachedCTFont = HashMap; - -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; -} - -impl CoreTextFontTemplateMethods for FontTemplateRef { - fn core_text_font(&self, pt_size: f64) -> Option { - //// 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 { 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 { - 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, /// A reference to this data used to create this [`PlatformFont`], ensuring the /// data stays alive of the lifetime of this struct. - data: Option>>, + _data: Arc>, info: FontInfo, em_size: f32, du_to_px: f32, @@ -226,32 +202,17 @@ impl Deref for Nondebug { } impl PlatformFontMethods for PlatformFont { - fn new_from_template( - font_template: FontTemplateRef, + fn new_from_data( + _font_identifier: FontIdentifier, + data: Arc>, + face_index: u32, pt_size: Option, ) -> Result { - 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 { - FontCollection::system().get_font_from_descriptor(&self.font_descriptor) - } - - pub fn native_font_handle(&self) -> Option { - 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 { @@ -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(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) -> 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 = 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>, 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), - AddFont(gfx_traits::FontData, Sender), + AddFont(Sender, u32, ipc_channel::ipc::IpcBytesReceiver), + AddSystemFont(Sender, 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 { None } -pub enum FontData { - Raw(Vec), - 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>, index: u32) -> FontKey; + fn add_system_font(&self, handle: NativeFontHandle) -> FontKey; }