diff options
Diffstat (limited to 'components/fonts')
-rw-r--r-- | components/fonts/font.rs | 49 | ||||
-rw-r--r-- | components/fonts/font_context.rs | 288 | ||||
-rw-r--r-- | components/fonts/font_store.rs | 112 | ||||
-rw-r--r-- | components/fonts/font_template.rs | 67 | ||||
-rw-r--r-- | components/fonts/lib.rs | 4 | ||||
-rw-r--r-- | components/fonts/platform/freetype/android/font_list.rs | 2 | ||||
-rw-r--r-- | components/fonts/platform/freetype/font.rs | 2 | ||||
-rw-r--r-- | components/fonts/platform/freetype/ohos/font_list.rs | 2 | ||||
-rw-r--r-- | components/fonts/platform/macos/core_text_font_cache.rs | 2 | ||||
-rw-r--r-- | components/fonts/system_font_service.rs (renamed from components/fonts/font_cache_thread.rs) | 302 | ||||
-rw-r--r-- | components/fonts/tests/font_context.rs | 146 |
11 files changed, 519 insertions, 457 deletions
diff --git a/components/fonts/font.rs b/components/fonts/font.rs index e423c546dae..3af077b4e06 100644 --- a/components/fonts/font.rs +++ b/components/fonts/font.rs @@ -27,14 +27,14 @@ use style::values::computed::{FontStretch, FontStyle, FontWeight}; use unicode_script::Script; use webrender_api::{FontInstanceFlags, FontInstanceKey}; -use crate::font_cache_thread::{FontIdentifier, FontSource}; use crate::font_context::FontContext; use crate::font_template::{FontTemplateDescriptor, FontTemplateRef, FontTemplateRefMethods}; use crate::platform::font::{FontTable, PlatformFont}; pub use crate::platform::font_list::fallback_font_families; +use crate::system_font_service::{FontIdentifier, SystemFontServiceProxyTrait}; use crate::{ - ByteIndex, EmojiPresentationPreference, FallbackFontSelectionOptions, GlyphData, GlyphId, - GlyphStore, Shaper, + ByteIndex, EmojiPresentationPreference, FallbackFontSelectionOptions, FontData, GlyphData, + GlyphId, GlyphStore, Shaper, }; #[macro_export] @@ -66,11 +66,13 @@ pub trait PlatformFontMethods: Sized { fn new_from_template( template: FontTemplateRef, pt_size: Option<Au>, + data: &Arc<Vec<u8>>, ) -> Result<PlatformFont, &'static str> { - let data = template.data(); + let template = template.borrow(); + let face_index = template.identifier().index(); - let font_identifier = template.borrow().identifier.clone(); - Self::new_from_data(font_identifier, data, face_index, pt_size) + let font_identifier = template.identifier.clone(); + Self::new_from_data(font_identifier, data.clone(), face_index, pt_size) } fn new_from_data( @@ -218,6 +220,7 @@ impl malloc_size_of::MallocSizeOf for CachedShapeData { #[derive(Debug)] pub struct Font { pub handle: PlatformFont, + pub data: Arc<FontData>, pub template: FontTemplateRef, pub metrics: FontMetrics, pub descriptor: FontDescriptor, @@ -251,13 +254,19 @@ impl Font { pub fn new( template: FontTemplateRef, descriptor: FontDescriptor, + data: Arc<FontData>, synthesized_small_caps: Option<FontRef>, ) -> Result<Font, &'static str> { - let handle = PlatformFont::new_from_template(template.clone(), Some(descriptor.pt_size))?; + let handle = PlatformFont::new_from_template( + template.clone(), + Some(descriptor.pt_size), + data.as_arc(), + )?; let metrics = handle.metrics(); Ok(Font { handle, + data, template, shaper: OnceLock::new(), descriptor, @@ -526,7 +535,7 @@ impl FontGroup { /// `codepoint`. If no such font is found, returns the first available font or fallback font /// (which will cause a "glyph not found" character to be rendered). If no font at all can be /// found, returns None. - pub fn find_by_codepoint<S: FontSource>( + pub fn find_by_codepoint<S: SystemFontServiceProxyTrait>( &mut self, font_context: &FontContext<S>, codepoint: char, @@ -598,7 +607,10 @@ impl FontGroup { } /// Find the first available font in the group, or the first available fallback font. - pub fn first<S: FontSource>(&mut self, font_context: &FontContext<S>) -> Option<FontRef> { + pub fn first<S: SystemFontServiceProxyTrait>( + &mut self, + font_context: &FontContext<S>, + ) -> Option<FontRef> { // From https://drafts.csswg.org/css-fonts/#first-available-font: // > The first available font, used for example in the definition of font-relative lengths // > such as ex or in the definition of the line-height property, is defined to be the first @@ -629,7 +641,7 @@ impl FontGroup { font_predicate: FontPredicate, ) -> Option<FontRef> where - S: FontSource, + S: SystemFontServiceProxyTrait, TemplatePredicate: Fn(FontTemplateRef) -> bool, FontPredicate: Fn(&FontRef) -> bool, { @@ -659,7 +671,7 @@ impl FontGroup { font_predicate: FontPredicate, ) -> Option<FontRef> where - S: FontSource, + S: SystemFontServiceProxyTrait, TemplatePredicate: Fn(FontTemplateRef) -> bool, FontPredicate: Fn(&FontRef) -> bool, { @@ -731,7 +743,7 @@ impl FontGroupFamily { font_predicate: &FontPredicate, ) -> Option<FontRef> where - S: FontSource, + S: SystemFontServiceProxyTrait, TemplatePredicate: Fn(FontTemplateRef) -> bool, FontPredicate: Fn(&FontRef) -> bool, { @@ -754,7 +766,7 @@ impl FontGroupFamily { .next() } - fn members<S: FontSource>( + fn members<S: SystemFontServiceProxyTrait>( &mut self, font_descriptor: &FontDescriptor, font_context: &FontContext<S>, @@ -883,6 +895,8 @@ pub(crate) fn map_platform_values_to_style_values(mapping: &[(f64, f64)], value: #[cfg(test)] mod test { + use crate::FontData; + #[cfg(target_os = "windows")] #[test] fn test_shape_text_fast() { @@ -917,14 +931,16 @@ mod test { let identifier = FontIdentifier::Web(ServoUrl::from_file_path(path.clone()).unwrap()); let file = File::open(path).unwrap(); - let data: Arc<Vec<u8>> = Arc::new(file.bytes().map(|b| b.unwrap()).collect()); + let data = Arc::new(FontData::from_bytes( + file.bytes().map(|b| b.unwrap()).collect(), + )); let platform_font = - PlatformFont::new_from_data(identifier.clone(), data.clone(), 0, None).unwrap(); + PlatformFont::new_from_data(identifier.clone(), data.as_arc().clone(), 0, None) + .unwrap(); let template = FontTemplate { identifier, descriptor: platform_font.descriptor(), - data: Some(data), stylesheet: None, }; let descriptor = FontDescriptor { @@ -937,6 +953,7 @@ mod test { let font = Font::new( Arc::new(atomic_refcell::AtomicRefCell::new(template)), descriptor, + data, None, ) .unwrap(); diff --git a/components/fonts/font_context.rs b/components/fonts/font_context.rs index 140fa56f14d..cdd97466341 100644 --- a/components/fonts/font_context.rs +++ b/components/fonts/font_context.rs @@ -19,6 +19,7 @@ use net_traits::request::{Destination, Referrer, RequestBuilder}; use net_traits::{fetch_async, CoreResourceThread, FetchResponseMsg, ResourceThreads}; use parking_lot::{Mutex, ReentrantMutex, RwLock}; use servo_arc::Arc as ServoArc; +use servo_url::ServoUrl; use style::computed_values::font_variant_caps::T as FontVariantCaps; use style::font_face::{FontFaceSourceFormat, FontFaceSourceFormatKeyword, Source, UrlSource}; use style::media_queries::Device; @@ -33,39 +34,67 @@ use webrender_api::{FontInstanceKey, FontKey}; use crate::font::{ Font, FontDescriptor, FontFamilyDescriptor, FontGroup, FontRef, FontSearchScope, }; -use crate::font_cache_thread::{CSSFontFaceDescriptors, FontIdentifier, FontSource}; use crate::font_store::{CrossThreadFontStore, CrossThreadWebRenderFontStore}; use crate::font_template::{FontTemplate, FontTemplateRef, FontTemplateRefMethods}; -use crate::LowercaseFontFamilyName; +use crate::platform::font::PlatformFont; +use crate::system_font_service::{ + CSSFontFaceDescriptors, FontIdentifier, SystemFontServiceProxyTrait, +}; +use crate::{FontData, LowercaseFontFamilyName, PlatformFontMethods}; static SMALL_CAPS_SCALE_FACTOR: f32 = 0.8; // Matches FireFox (see gfxFont.h) /// The FontContext represents the per-thread/thread state necessary for /// working with fonts. It is the public API used by the layout and -/// paint code. It talks directly to the font cache thread where +/// paint code. It talks directly to the system font service where /// required. -pub struct FontContext<S: FontSource> { - font_source: ReentrantMutex<S>, +pub struct FontContext<Proxy: SystemFontServiceProxyTrait> { + pub(crate) system_font_service_proxy: Arc<Proxy>, resource_threads: ReentrantMutex<CoreResourceThread>, - cache: CachingFontSource<S>, + + /// The actual instances of fonts ie a [`FontTemplate`] combined with a size and + /// other font properties, along with the font data and a platform font instance. + fonts: RwLock<HashMap<FontCacheKey, Option<FontRef>>>, + + /// A caching map between the specification of a font in CSS style and + /// resolved [`FontGroup`] which contains information about all fonts that + /// can be selected with that style. + resolved_font_groups: + RwLock<HashMap<FontGroupCacheKey, Arc<RwLock<FontGroup>>, BuildHasherDefault<FnvHasher>>>, + web_fonts: CrossThreadFontStore, webrender_font_store: CrossThreadWebRenderFontStore, have_removed_web_fonts: AtomicBool, } -impl<S: FontSource> MallocSizeOf for FontContext<S> { +impl<S: SystemFontServiceProxyTrait> MallocSizeOf for FontContext<S> { fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - self.cache.size_of(ops) + let font_cache_size = self + .fonts + .read() + .iter() + .map(|(key, font)| { + key.size_of(ops) + font.as_ref().map_or(0, |font| (*font).size_of(ops)) + }) + .sum::<usize>(); + let font_group_cache_size = self + .resolved_font_groups + .read() + .iter() + .map(|(key, font_group)| key.size_of(ops) + (*font_group.read()).size_of(ops)) + .sum::<usize>(); + font_cache_size + font_group_cache_size } } -impl<S: FontSource> FontContext<S> { - pub fn new(font_source: S, resource_threads: ResourceThreads) -> FontContext<S> { +impl<Proxy: SystemFontServiceProxyTrait> FontContext<Proxy> { + pub fn new(system_font_service_proxy: Arc<Proxy>, resource_threads: ResourceThreads) -> Self { #[allow(clippy::default_constructed_unit_structs)] - FontContext { - font_source: ReentrantMutex::new(font_source.clone()), + Self { + system_font_service_proxy, resource_threads: ReentrantMutex::new(resource_threads.core_thread), - cache: CachingFontSource::new(font_source), + fonts: Default::default(), + resolved_font_groups: Default::default(), web_fonts: Arc::new(RwLock::default()), webrender_font_store: Arc::new(RwLock::default()), have_removed_web_fonts: AtomicBool::new(false), @@ -76,6 +105,14 @@ impl<S: FontSource> FontContext<S> { self.web_fonts.read().number_of_fonts_still_loading() } + pub(crate) fn get_font_data(&self, identifier: &FontIdentifier) -> Arc<FontData> { + self.web_fonts + .read() + .get_font_data(identifier) + .or_else(|| self.system_font_service_proxy.get_font_data(identifier)) + .expect("Could not find font data") + } + /// Handle the situation where a web font finishes loading, specifying if the load suceeded or failed. fn handle_web_font_load_finished( &self, @@ -83,7 +120,7 @@ impl<S: FontSource> FontContext<S> { succeeded: bool, ) { if succeeded { - self.cache.invalidate_after_web_font_load(); + self.invalidate_font_groups_after_web_font_load(); } finished_callback(succeeded); } @@ -103,7 +140,19 @@ impl<S: FontSource> FontContext<S> { style: ServoArc<FontStyleStruct>, size: Au, ) -> Arc<RwLock<FontGroup>> { - self.cache.font_group_with_size(style, size) + let cache_key = FontGroupCacheKey { size, style }; + if let Some(font_group) = self.resolved_font_groups.read().get(&cache_key) { + return font_group.clone(); + } + + let mut descriptor = FontDescriptor::from(&*cache_key.style); + descriptor.pt_size = size; + + let font_group = Arc::new(RwLock::new(FontGroup::new(&cache_key.style, descriptor))); + self.resolved_font_groups + .write() + .insert(cache_key, font_group.clone()); + font_group } /// Returns a font matching the parameters. Fonts are cached, so repeated calls will return a @@ -148,7 +197,7 @@ impl<S: FontSource> FontContext<S> { font_descriptor: font_descriptor.clone(), }; - if let Some(font) = self.cache.fonts.read().get(&cache_key).cloned() { + if let Some(font) = self.fonts.read().get(&cache_key).cloned() { return font; } @@ -166,7 +215,7 @@ impl<S: FontSource> FontContext<S> { synthesized_small_caps_font, ) .ok(); - self.cache.fonts.write().insert(cache_key, font.clone()); + self.fonts.write().insert(cache_key, font.clone()); font } @@ -192,7 +241,7 @@ impl<S: FontSource> FontContext<S> { } /// Try to find matching templates in this [`FontContext`], first looking in the list of web fonts and - /// falling back to asking the [`super::FontCacheThread`] for a matching system font. + /// falling back to asking the [`super::SystemFontService`] for a matching system font. pub fn matching_templates( &self, descriptor_to_match: &FontDescriptor, @@ -200,8 +249,10 @@ impl<S: FontSource> FontContext<S> { ) -> Vec<FontTemplateRef> { self.matching_web_font_templates(descriptor_to_match, family_descriptor) .unwrap_or_else(|| { - self.cache - .matching_templates(descriptor_to_match, family_descriptor) + self.system_font_service_proxy.find_matching_font_templates( + Some(descriptor_to_match), + &family_descriptor.family, + ) }) } @@ -216,18 +267,18 @@ impl<S: FontSource> FontContext<S> { let mut font = Font::new( font_template.clone(), font_descriptor.clone(), + self.get_font_data(&font_template.identifier()), synthesized_small_caps, )?; - let font_source = self.font_source.lock(); font.font_key = match font_template.identifier() { - FontIdentifier::Local(_) => font_source.get_system_font_instance( + FontIdentifier::Local(_) => self.system_font_service_proxy.get_system_font_instance( font_template.identifier(), font_descriptor.pt_size, font.webrender_font_instance_flags(), ), FontIdentifier::Web(_) => self.webrender_font_store.write().get_font_instance( - &*font_source, + self, font_template.clone(), font_descriptor.pt_size, font.webrender_font_instance_flags(), @@ -236,6 +287,10 @@ impl<S: FontSource> FontContext<S> { Ok(Arc::new(font)) } + + fn invalidate_font_groups_after_web_font_load(&self) { + self.resolved_font_groups.write().clear(); + } } #[derive(Clone)] @@ -263,7 +318,7 @@ pub trait FontContextWebFontMethods { -> (Vec<FontKey>, Vec<FontInstanceKey>); } -impl<S: FontSource + Send + 'static> FontContextWebFontMethods for Arc<FontContext<S>> { +impl<S: SystemFontServiceProxyTrait + 'static> FontContextWebFontMethods for Arc<FontContext<S>> { fn add_all_web_fonts_from_stylesheet( &self, stylesheet: &DocumentStyleSheet, @@ -325,8 +380,7 @@ impl<S: FontSource + Send + 'static> FontContextWebFontMethods for Arc<FontConte name: family_name.name.clone(), syntax: FontFamilyNameSyntax::Quoted, }); - self.font_source - .lock() + self.system_font_service_proxy .find_matching_font_templates(None, &family) .first() .cloned() @@ -373,24 +427,27 @@ impl<S: FontSource + Send + 'static> FontContextWebFontMethods for Arc<FontConte RemoteWebFontDownloader::download(url_source, this, web_font_family_name, state) }, Source::Local(ref local_family_name) => { - if let Some(new_template) = state + if let Some((new_template, font_data)) = state .local_fonts .get(&local_family_name.name) .cloned() .flatten() .and_then(|local_template| { - FontTemplate::new_for_local_web_font( - local_template, + let template = FontTemplate::new_for_local_web_font( + local_template.clone(), &state.css_font_face_descriptors, state.stylesheet.clone(), ) - .ok() + .ok()?; + let font_data = self.get_font_data(&local_template.identifier()); + Some((template, font_data)) }) { - let not_cancelled = self - .web_fonts - .write() - .handle_web_font_loaded(&state, new_template); + let not_cancelled = self.web_fonts.write().handle_web_font_loaded( + &state, + new_template, + font_data, + ); self.handle_web_font_load_finished(&state.finished_callback, not_cancelled); } else { this.process_next_web_font_source(state); @@ -401,8 +458,8 @@ impl<S: FontSource + Send + 'static> FontContextWebFontMethods for Arc<FontConte fn remove_all_web_fonts_from_stylesheet(&self, stylesheet: &DocumentStyleSheet) { let mut web_fonts = self.web_fonts.write(); - let mut fonts = self.cache.fonts.write(); - let mut font_groups = self.cache.resolved_font_groups.write(); + let mut fonts = self.fonts.write(); + let mut font_groups = self.resolved_font_groups.write(); // Cancel any currently in-progress web font loads. web_fonts.handle_stylesheet_removed(stylesheet); @@ -443,9 +500,9 @@ impl<S: FontSource + Send + 'static> FontContextWebFontMethods for Arc<FontConte } // Lock everything to prevent adding new fonts while we are cleaning up the old ones. - let web_fonts = self.web_fonts.write(); - let _fonts = self.cache.fonts.write(); - let _font_groups = self.cache.resolved_font_groups.write(); + let mut web_fonts = self.web_fonts.write(); + let _fonts = self.fonts.write(); + let _font_groups = self.resolved_font_groups.write(); let mut webrender_font_store = self.webrender_font_store.write(); let mut unused_identifiers: HashSet<FontIdentifier> = webrender_font_store @@ -459,13 +516,15 @@ impl<S: FontSource + Send + 'static> FontContextWebFontMethods for Arc<FontConte }); } + web_fonts.remove_all_font_data_for_identifiers(&unused_identifiers); + self.have_removed_web_fonts.store(false, Ordering::Relaxed); - webrender_font_store.remove_all_fonts_for_identifiers(unused_identifiers) + webrender_font_store.remove_all_fonts_for_identifiers(&unused_identifiers) } } -struct RemoteWebFontDownloader<FCT: FontSource> { - font_context: Arc<FontContext<FCT>>, +struct RemoteWebFontDownloader<Proxy: SystemFontServiceProxyTrait> { + font_context: Arc<FontContext<Proxy>>, url: ServoArc<Url>, web_font_family_name: LowercaseFontFamilyName, response_valid: Mutex<bool>, @@ -478,10 +537,10 @@ enum DownloaderResponseResult { Failure, } -impl<FCT: FontSource + Send + 'static> RemoteWebFontDownloader<FCT> { +impl<Proxy: SystemFontServiceProxyTrait + 'static> RemoteWebFontDownloader<Proxy> { fn download( url_source: UrlSource, - font_context: Arc<FontContext<FCT>>, + font_context: Arc<FontContext<Proxy>>, web_font_family_name: LowercaseFontFamilyName, state: WebFontDownloadState, ) { @@ -548,7 +607,7 @@ impl<FCT: FontSource + Send + 'static> RemoteWebFontDownloader<FCT> { ); let font_data = match fontsan::process(&font_data) { - Ok(bytes) => bytes, + Ok(bytes) => Arc::new(FontData::from_bytes(bytes)), Err(error) => { debug!( "Sanitiser rejected web font: family={} url={:?} with {error:?}", @@ -558,20 +617,28 @@ impl<FCT: FontSource + Send + 'static> RemoteWebFontDownloader<FCT> { }, }; - let Ok(new_template) = FontTemplate::new_for_remote_web_font( - self.url.clone().into(), - Arc::new(font_data), - &state.css_font_face_descriptors, - Some(state.stylesheet.clone()), - ) else { + let url: ServoUrl = self.url.clone().into(); + let identifier = FontIdentifier::Web(url.clone()); + let Ok(handle) = + PlatformFont::new_from_data(identifier, font_data.as_arc().clone(), 0, None) + else { return false; }; + let mut descriptor = handle.descriptor(); + descriptor + .override_values_with_css_font_template_descriptors(&state.css_font_face_descriptors); - let not_cancelled = self - .font_context - .web_fonts - .write() - .handle_web_font_loaded(state, new_template); + let Ok(new_template) = + FontTemplate::new_for_remote_web_font(url, descriptor, Some(state.stylesheet.clone())) + else { + return false; + }; + + let not_cancelled = self.font_context.web_fonts.write().handle_web_font_loaded( + state, + new_template, + font_data, + ); self.font_context .handle_web_font_load_finished(&state.finished_callback, not_cancelled); @@ -623,121 +690,12 @@ impl<FCT: FontSource + Send + 'static> RemoteWebFontDownloader<FCT> { } } -#[derive(Default)] -pub struct CachingFontSource<FCT: FontSource> { - font_cache_thread: ReentrantMutex<FCT>, - fonts: RwLock<HashMap<FontCacheKey, Option<FontRef>>>, - templates: RwLock<HashMap<FontTemplateCacheKey, Vec<FontTemplateRef>>>, - resolved_font_groups: - RwLock<HashMap<FontGroupCacheKey, Arc<RwLock<FontGroup>>, BuildHasherDefault<FnvHasher>>>, -} - -impl<FCT: FontSource> CachingFontSource<FCT> { - fn new(font_cache_thread: FCT) -> Self { - Self { - font_cache_thread: ReentrantMutex::new(font_cache_thread), - fonts: Default::default(), - templates: Default::default(), - resolved_font_groups: Default::default(), - } - } - - fn invalidate_after_web_font_load(&self) { - self.resolved_font_groups.write().clear(); - } - - pub fn matching_templates( - &self, - descriptor_to_match: &FontDescriptor, - family_descriptor: &FontFamilyDescriptor, - ) -> Vec<FontTemplateRef> { - let cache_key = FontTemplateCacheKey { - font_descriptor: descriptor_to_match.clone(), - family_descriptor: family_descriptor.clone(), - }; - if let Some(templates) = self.templates.read().get(&cache_key).cloned() { - return templates; - } - - debug!( - "CachingFontSource: cache miss for template_descriptor={:?} family_descriptor={:?}", - descriptor_to_match, family_descriptor - ); - let templates = self - .font_cache_thread - .lock() - .find_matching_font_templates(Some(descriptor_to_match), &family_descriptor.family); - self.templates.write().insert(cache_key, templates.clone()); - - templates - } - - pub fn font_group_with_size( - &self, - style: ServoArc<FontStyleStruct>, - size: Au, - ) -> Arc<RwLock<FontGroup>> { - let cache_key = FontGroupCacheKey { size, style }; - if let Some(font_group) = self.resolved_font_groups.read().get(&cache_key) { - return font_group.clone(); - } - - let mut descriptor = FontDescriptor::from(&*cache_key.style); - descriptor.pt_size = size; - - let font_group = Arc::new(RwLock::new(FontGroup::new(&cache_key.style, descriptor))); - self.resolved_font_groups - .write() - .insert(cache_key, font_group.clone()); - font_group - } -} - -impl<FCT: FontSource> MallocSizeOf for CachingFontSource<FCT> { - fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { - let font_cache_size = self - .fonts - .read() - .iter() - .map(|(key, font)| { - key.size_of(ops) + font.as_ref().map_or(0, |font| (*font).size_of(ops)) - }) - .sum::<usize>(); - let font_template_cache_size = self - .templates - .read() - .iter() - .map(|(key, templates)| { - let templates_size = templates - .iter() - .map(|template| template.borrow().size_of(ops)) - .sum::<usize>(); - key.size_of(ops) + templates_size - }) - .sum::<usize>(); - let font_group_cache_size = self - .resolved_font_groups - .read() - .iter() - .map(|(key, font_group)| key.size_of(ops) + (*font_group.read()).size_of(ops)) - .sum::<usize>(); - - font_cache_size + font_template_cache_size + font_group_cache_size - } -} - #[derive(Debug, Eq, Hash, MallocSizeOf, PartialEq)] struct FontCacheKey { font_identifier: FontIdentifier, font_descriptor: FontDescriptor, } -#[derive(Debug, Eq, Hash, MallocSizeOf, PartialEq)] -struct FontTemplateCacheKey { - font_descriptor: FontDescriptor, - family_descriptor: FontFamilyDescriptor, -} - #[derive(Debug, MallocSizeOf)] struct FontGroupCacheKey { #[ignore_malloc_size_of = "This is also stored as part of styling."] diff --git a/components/fonts/font_store.rs b/components/fonts/font_store.rs index 19e710120d3..e6e5b08ef84 100644 --- a/components/fonts/font_store.rs +++ b/components/fonts/font_store.rs @@ -3,25 +3,72 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::collections::{HashMap, HashSet}; -use std::sync::Arc; +use std::sync::{Arc, OnceLock}; use app_units::Au; use atomic_refcell::AtomicRefCell; +use ipc_channel::ipc::IpcSharedMemory; use log::warn; use parking_lot::RwLock; +use serde::{Deserialize, Serialize}; use style::stylesheets::DocumentStyleSheet; use style::values::computed::{FontStyle, FontWeight}; use webrender_api::{FontInstanceFlags, FontInstanceKey, FontKey}; use crate::font::FontDescriptor; -use crate::font_cache_thread::{FontIdentifier, FontSource, LowercaseFontFamilyName}; use crate::font_context::WebFontDownloadState; use crate::font_template::{FontTemplate, FontTemplateRef, FontTemplateRefMethods, IsOblique}; +use crate::system_font_service::{ + FontIdentifier, LowercaseFontFamilyName, SystemFontServiceProxyTrait, +}; +use crate::FontContext; + +/// A data structure to store data for fonts. If sent across IPC channels and only a +/// [`IpcSharedMemory`] handle is sent, avoiding the overhead of serialization and +/// deserialization. In addition, if a shared handle to data is requested +/// (`Arc<Vec<u8>>`), the data is lazily copied out of shared memory once per +/// [`FontData`]. +#[derive(Debug, Deserialize, Serialize)] +pub struct FontData { + /// The data of this font in shared memory. Suitable for sending across IPC channels. + shared_memory: Arc<IpcSharedMemory>, + /// A lazily-initialized copy of the data behind an [`Arc`] which can be used when + /// passing it to various APIs. + #[serde(skip)] + arc: OnceLock<Arc<Vec<u8>>>, +} + +impl FontData { + pub fn from_bytes(data: Vec<u8>) -> FontData { + FontData { + shared_memory: Arc::new(IpcSharedMemory::from_bytes(&data)), + arc: Arc::new(data).into(), + } + } + + /// Return a non-shared memory `Arc` view of this data. This may copy the data once + /// per [`FontData`], but subsequent calls will return the same shared view. + pub fn as_arc(&self) -> &Arc<Vec<u8>> { + self.arc + .get_or_init(|| Arc::new((**self.shared_memory).into())) + } + + /// Return a the [`IpcSharedMemory`] view of this data suitable for sending directly across + /// an IPC channel if necessary. An `Arc` is returned to avoid the overhead of copying the + /// platform-specific shared memory handle. + pub(crate) fn as_ipc_shared_memory(&self) -> Arc<IpcSharedMemory> { + self.shared_memory.clone() + } +} #[derive(Default)] pub struct FontStore { pub(crate) families: HashMap<LowercaseFontFamilyName, FontTemplates>, web_fonts_loading: Vec<(DocumentStyleSheet, usize)>, + /// The data for each [`FontIdentifier`]. This data might be used by + /// more than one [`FontTemplate`] as each identifier refers to a URL + /// or font that can contain more than a single font. + font_data: HashMap<FontIdentifier, Arc<FontData>>, } pub(crate) type CrossThreadFontStore = Arc<RwLock<FontStore>>; @@ -78,28 +125,67 @@ impl FontStore { /// Handle a web font load finishing, adding the new font to the [`FontStore`]. If the web font /// load was canceled (for instance, if the stylesheet was removed), then do nothing and return /// false. + /// + /// In addition pass newly loaded data for this font. Add this data the cached [`FontData`] store + /// inside this [`FontStore`]. pub(crate) fn handle_web_font_loaded( &mut self, state: &WebFontDownloadState, new_template: FontTemplate, + data: Arc<FontData>, ) -> bool { // Abort processing this web font if the originating stylesheet was removed. if self.font_load_cancelled_for_stylesheet(&state.stylesheet) { return false; } - let family_name = state.css_font_face_descriptors.family_name.clone(); + self.add_template_and_data(family_name, new_template, data); + self.remove_one_web_font_loading_for_stylesheet(&state.stylesheet); + true + } + + pub(crate) fn add_template_and_data( + &mut self, + family_name: LowercaseFontFamilyName, + new_template: FontTemplate, + data: Arc<FontData>, + ) { + self.font_data.insert(new_template.identifier.clone(), data); self.families .entry(family_name) .or_default() .add_template(new_template); - self.remove_one_web_font_loading_for_stylesheet(&state.stylesheet); - true } pub(crate) fn number_of_fonts_still_loading(&self) -> usize { self.web_fonts_loading.iter().map(|(_, count)| count).sum() } + + pub(crate) fn get_or_initialize_font_data( + &mut self, + identifier: &FontIdentifier, + ) -> &Arc<FontData> { + self.font_data + .entry(identifier.clone()) + .or_insert_with(|| match identifier { + FontIdentifier::Local(local_identifier) => { + Arc::new(FontData::from_bytes(local_identifier.read_data_from_file())) + }, + FontIdentifier::Web(_) => unreachable!("Web fonts should always have data."), + }) + } + + pub(crate) fn get_font_data(&self, identifier: &FontIdentifier) -> Option<Arc<FontData>> { + self.font_data.get(identifier).cloned() + } + + pub(crate) fn remove_all_font_data_for_identifiers( + &mut self, + identifiers: &HashSet<FontIdentifier>, + ) { + self.font_data + .retain(|font_identifier, _| identifiers.contains(font_identifier)); + } } #[derive(Default)] @@ -110,26 +196,32 @@ pub struct WebRenderFontStore { pub(crate) type CrossThreadWebRenderFontStore = Arc<RwLock<WebRenderFontStore>>; impl WebRenderFontStore { - pub(crate) fn get_font_instance<FCT: FontSource>( + pub(crate) fn get_font_instance<Proxy: SystemFontServiceProxyTrait>( &mut self, - font_cache_thread: &FCT, + font_context: &FontContext<Proxy>, font_template: FontTemplateRef, pt_size: Au, flags: FontInstanceFlags, ) -> FontInstanceKey { let webrender_font_key_map = &mut self.webrender_font_key_map; let identifier = font_template.identifier().clone(); + let font_key = *webrender_font_key_map .entry(identifier.clone()) .or_insert_with(|| { - font_cache_thread.get_web_font(font_template.data(), identifier.index()) + let data = font_context.get_font_data(&identifier); + font_context + .system_font_service_proxy + .get_web_font(data, identifier.index()) }); *self .webrender_font_instance_map .entry((font_key, pt_size)) .or_insert_with(|| { - font_cache_thread.get_web_font_instance(font_key, pt_size.to_f32_px(), flags) + font_context + .system_font_service_proxy + .get_web_font_instance(font_key, pt_size.to_f32_px(), flags) }) } @@ -148,7 +240,7 @@ impl WebRenderFontStore { pub(crate) fn remove_all_fonts_for_identifiers( &mut self, - identifiers: HashSet<FontIdentifier>, + identifiers: &HashSet<FontIdentifier>, ) -> (Vec<FontKey>, Vec<FontInstanceKey>) { let mut removed_keys: HashSet<FontKey> = HashSet::new(); self.webrender_font_key_map.retain(|identifier, font_key| { diff --git a/components/fonts/font_template.rs b/components/fonts/font_template.rs index 19c71ed83fa..d053fe9f2d4 100644 --- a/components/fonts/font_template.rs +++ b/components/fonts/font_template.rs @@ -15,12 +15,11 @@ use style::computed_values::font_style::T as FontStyle; use style::stylesheets::DocumentStyleSheet; use style::values::computed::font::FontWeight; -use crate::font::{FontDescriptor, PlatformFontMethods}; -use crate::font_cache_thread::{ +use crate::font::FontDescriptor; +use crate::platform::font_list::LocalFontIdentifier; +use crate::system_font_service::{ CSSFontFaceDescriptors, ComputedFontStyleDescriptor, FontIdentifier, }; -use crate::platform::font::PlatformFont; -use crate::platform::font_list::LocalFontIdentifier; /// A reference to a [`FontTemplate`] with shared ownership and mutability. pub type FontTemplateRef = Arc<AtomicRefCell<FontTemplate>>; @@ -109,7 +108,7 @@ impl FontTemplateDescriptor { self.stretch.1 >= descriptor_to_match.stretch } - fn override_values_with_css_font_template_descriptors( + pub(crate) fn override_values_with_css_font_template_descriptors( &mut self, css_font_template_descriptors: &CSSFontFaceDescriptors, ) { @@ -137,27 +136,20 @@ impl FontTemplateDescriptor { /// This describes all the information needed to create /// font instance handles. It contains a unique /// FontTemplateData structure that is platform specific. -#[derive(Clone)] +#[derive(Clone, Deserialize, MallocSizeOf, Serialize)] pub struct FontTemplate { pub identifier: FontIdentifier, 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. - pub data: Option<Arc<Vec<u8>>>, /// If this font is a web font, this is a reference to the stylesheet that /// created it. This will be used to remove this font from caches, when the /// stylesheet is removed. + /// + /// This is not serialized, as it's only useful in the [`super::FontContext`] + /// that it is created in. + #[serde(skip)] pub stylesheet: Option<DocumentStyleSheet>, } -impl malloc_size_of::MallocSizeOf for FontTemplate { - fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize { - self.identifier.size_of(ops) + - self.descriptor.size_of(ops) + - self.data.as_ref().map_or(0, |data| (*data).size_of(ops)) - } -} - impl Debug for FontTemplate { fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { self.identifier.fmt(f) @@ -176,7 +168,6 @@ impl FontTemplate { FontTemplate { identifier: FontIdentifier::Local(identifier), descriptor, - data: None, stylesheet: None, } } @@ -184,22 +175,12 @@ impl FontTemplate { /// Create a new [`FontTemplate`] for a `@font-family` with a `url(...)` `src` font. pub fn new_for_remote_web_font( url: ServoUrl, - data: Arc<Vec<u8>>, - css_font_template_descriptors: &CSSFontFaceDescriptors, + descriptor: FontTemplateDescriptor, stylesheet: Option<DocumentStyleSheet>, ) -> Result<FontTemplate, &'static str> { - let identifier = FontIdentifier::Web(url.clone()); - let Ok(handle) = PlatformFont::new_from_data(identifier, data.clone(), 0, None) else { - return Err("Could not initialize platform font data for: {url:?}"); - }; - - let mut descriptor = handle.descriptor(); - descriptor - .override_values_with_css_font_template_descriptors(css_font_template_descriptors); Ok(FontTemplate { identifier: FontIdentifier::Web(url), descriptor, - data: Some(data), stylesheet, }) } @@ -223,19 +204,9 @@ impl FontTemplate { pub fn identifier(&self) -> &FontIdentifier { &self.identifier } - - /// Returns a reference to the bytes in this font if they are in memory. - /// This function never performs disk I/O. - pub fn data_if_in_memory(&self) -> Option<Arc<Vec<u8>>> { - self.data.clone() - } } pub trait FontTemplateRefMethods { - /// Returns a reference to the data in this font. This may be a hugely expensive - /// operation (depending on the platform) which performs synchronous disk I/O - /// and should never be done lightly. - fn data(&self) -> Arc<Vec<u8>>; /// Get the descriptor. fn descriptor(&self) -> FontTemplateDescriptor; /// Get the [`FontIdentifier`] for this template. @@ -267,24 +238,6 @@ impl FontTemplateRefMethods for FontTemplateRef { self.descriptor().distance_from(descriptor_to_match) } - fn data(&self) -> Arc<Vec<u8>> { - if let Some(data) = self.borrow().data.clone() { - return data; - } - - let mut template = self.borrow_mut(); - let identifier = template.identifier.clone(); - template - .data - .get_or_insert_with(|| match identifier { - FontIdentifier::Local(local_identifier) => { - Arc::new(local_identifier.read_data_from_file()) - }, - FontIdentifier::Web(_) => unreachable!("Web fonts should always have data."), - }) - .clone() - } - fn char_in_unicode_range(&self, character: char) -> bool { let character = character as u32; self.borrow() diff --git a/components/fonts/lib.rs b/components/fonts/lib.rs index 305db5e53ef..429672f3616 100644 --- a/components/fonts/lib.rs +++ b/components/fonts/lib.rs @@ -5,7 +5,6 @@ #![deny(unsafe_code)] mod font; -mod font_cache_thread; mod font_context; mod font_store; mod font_template; @@ -13,14 +12,15 @@ mod glyph; #[allow(unsafe_code)] pub mod platform; mod shaper; +mod system_font_service; pub use font::*; -pub use font_cache_thread::*; pub use font_context::*; pub use font_store::*; pub use font_template::*; pub use glyph::*; pub use shaper::*; +pub use system_font_service::*; use unicode_properties::{emoji, EmojiStatus, UnicodeEmoji}; /// Whether or not font fallback selection prefers the emoji or text representation diff --git a/components/fonts/platform/freetype/android/font_list.rs b/components/fonts/platform/freetype/android/font_list.rs index 7f599041ce3..c52b06d8b03 100644 --- a/components/fonts/platform/freetype/android/font_list.rs +++ b/components/fonts/platform/freetype/android/font_list.rs @@ -452,7 +452,7 @@ impl FontList { } } -// Functions used by FontCacheThread +// Functions used by SystemFontSerivce pub fn for_each_available_family<F>(mut callback: F) where F: FnMut(String), diff --git a/components/fonts/platform/freetype/font.rs b/components/fonts/platform/freetype/font.rs index 3a77ab2d8d9..43885556942 100644 --- a/components/fonts/platform/freetype/font.rs +++ b/components/fonts/platform/freetype/font.rs @@ -29,9 +29,9 @@ use crate::font::{ FontMetrics, FontTableMethods, FontTableTag, FractionalPixel, PlatformFontMethods, GPOS, GSUB, KERN, }; -use crate::font_cache_thread::FontIdentifier; use crate::font_template::FontTemplateDescriptor; use crate::glyph::GlyphId; +use crate::system_font_service::FontIdentifier; // This constant is not present in the freetype // bindings due to bindgen not handling the way diff --git a/components/fonts/platform/freetype/ohos/font_list.rs b/components/fonts/platform/freetype/ohos/font_list.rs index bc8303b78aa..82a5740f124 100644 --- a/components/fonts/platform/freetype/ohos/font_list.rs +++ b/components/fonts/platform/freetype/ohos/font_list.rs @@ -453,7 +453,7 @@ impl FontList { } } -// Functions used by FontCacheThread +// Functions used by SystemFontService pub fn for_each_available_family<F>(mut callback: F) where F: FnMut(String), diff --git a/components/fonts/platform/macos/core_text_font_cache.rs b/components/fonts/platform/macos/core_text_font_cache.rs index 6f41a25ce70..0574ff78325 100644 --- a/components/fonts/platform/macos/core_text_font_cache.rs +++ b/components/fonts/platform/macos/core_text_font_cache.rs @@ -16,7 +16,7 @@ use core_text::font::CTFont; use core_text::font_descriptor::kCTFontURLAttribute; use parking_lot::RwLock; -use crate::font_cache_thread::FontIdentifier; +use crate::system_font_service::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 diff --git a/components/fonts/font_cache_thread.rs b/components/fonts/system_font_service.rs index 5d5f3cff4b7..8bf15eaa2eb 100644 --- a/components/fonts/font_cache_thread.rs +++ b/components/fonts/system_font_service.rs @@ -11,9 +11,10 @@ use std::{fmt, thread}; use app_units::Au; use atomic_refcell::AtomicRefCell; -use ipc_channel::ipc::{self, IpcBytesReceiver, IpcBytesSender, IpcReceiver, IpcSender}; +use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; use log::debug; use malloc_size_of_derive::MallocSizeOf; +use parking_lot::{ReentrantMutex, RwLock}; use serde::{Deserialize, Serialize}; use servo_config::pref; use servo_url::ServoUrl; @@ -29,13 +30,12 @@ use webrender_traits::WebRenderFontApi; use crate::font::FontDescriptor; use crate::font_store::FontStore; -use crate::font_template::{ - FontTemplate, FontTemplateDescriptor, FontTemplateRef, FontTemplateRefMethods, -}; +use crate::font_template::{FontTemplate, FontTemplateRef}; use crate::platform::font_list::{ default_system_generic_font_family, for_each_available_family, for_each_variation, LocalFontIdentifier, }; +use crate::FontData; #[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] pub enum FontIdentifier { @@ -53,19 +53,18 @@ impl FontIdentifier { } #[derive(Debug, Deserialize, Serialize)] -pub struct SerializedFontTemplate { - identifier: FontIdentifier, - descriptor: FontTemplateDescriptor, - bytes_receiver: ipc_channel::ipc::IpcBytesReceiver, +pub struct FontTemplateRequestResult { + templates: Vec<FontTemplate>, + template_data: Vec<(FontIdentifier, Arc<FontData>)>, } -/// Commands that the FontContext sends to the font cache thread. +/// Commands that the `FontContext` sends to the `SystemFontService`. #[derive(Debug, Deserialize, Serialize)] pub enum Command { GetFontTemplates( Option<FontDescriptor>, SingleFontFamily, - IpcSender<Vec<SerializedFontTemplate>>, + IpcSender<FontTemplateRequestResult>, ), GetFontInstance( FontIdentifier, @@ -73,7 +72,7 @@ pub enum Command { FontInstanceFlags, IpcSender<FontInstanceKey>, ), - GetWebFont(IpcBytesReceiver, u32, IpcSender<FontKey>), + GetWebFont(Arc<FontData>, u32, IpcSender<FontKey>), GetWebFontInstance(FontKey, f32, FontInstanceFlags, IpcSender<FontInstanceKey>), Exit(IpcSender<()>), Ping, @@ -90,11 +89,11 @@ struct ResolvedGenericFontFamilies { system_ui: OnceCell<LowercaseFontFamilyName>, } -/// The font cache thread itself. It maintains a list of reference counted -/// font templates that are currently in use. -struct FontCache { +/// The system font service. There is one of these for every Servo instance. This is a thread, +/// responsible for reading the list of system fonts, handling requests to match against +/// them, and ensuring that only one copy of system font data is loaded at a time. +pub struct SystemFontService { port: IpcReceiver<Command>, - font_data: HashMap<FontIdentifier, Arc<Vec<u8>>>, local_families: FontStore, webrender_api: Box<dyn WebRenderFontApi>, webrender_fonts: HashMap<FontIdentifier, FontKey>, @@ -102,68 +101,62 @@ struct FontCache { generic_fonts: ResolvedGenericFontFamilies, } -impl FontCache { +#[derive(Clone, Deserialize, Serialize)] +pub struct SystemFontServiceProxySender(IpcSender<Command>); + +impl SystemFontServiceProxySender { + pub fn to_proxy(&self) -> SystemFontServiceProxy { + SystemFontServiceProxy { + sender: ReentrantMutex::new(self.0.clone()), + templates: Default::default(), + data_cache: Default::default(), + } + } +} + +impl SystemFontService { + pub fn spawn(webrender_api: Box<dyn WebRenderFontApi + Send>) -> SystemFontServiceProxySender { + let (sender, receiver) = ipc::channel().unwrap(); + + thread::Builder::new() + .name("SystemFontService".to_owned()) + .spawn(move || { + #[allow(clippy::default_constructed_unit_structs)] + let mut cache = SystemFontService { + port: receiver, + local_families: Default::default(), + webrender_api, + webrender_fonts: HashMap::new(), + font_instances: HashMap::new(), + generic_fonts: Default::default(), + }; + + cache.refresh_local_families(); + cache.run(); + }) + .expect("Thread spawning failed"); + + SystemFontServiceProxySender(sender) + } + #[tracing::instrument(skip(self), fields(servo_profiling = true))] fn run(&mut self) { loop { let msg = self.port.recv().unwrap(); match msg { - Command::GetFontTemplates(descriptor_to_match, font_family, result) => { - let span = span!( - Level::TRACE, - "Command::GetFontTemplates", - servo_profiling = true - ); + Command::GetFontTemplates(font_descriptor, font_family, result_sender) => { + let span = span!(Level::TRACE, "GetFontTemplates", servo_profiling = true); let _span = span.enter(); - let templates = - self.find_font_templates(descriptor_to_match.as_ref(), &font_family); - debug!("Found templates for descriptor {descriptor_to_match:?}: "); - debug!(" {templates:?}"); - - let (serialized_templates, senders): ( - Vec<SerializedFontTemplate>, - Vec<(FontTemplateRef, IpcBytesSender)>, - ) = templates - .into_iter() - .map(|template| { - let (bytes_sender, bytes_receiver) = - ipc::bytes_channel().expect("failed to create IPC channel"); - ( - SerializedFontTemplate { - identifier: template.identifier().clone(), - descriptor: template.descriptor().clone(), - bytes_receiver, - }, - (template.clone(), bytes_sender), - ) - }) - .unzip(); - - let _ = result.send(serialized_templates); - - // NB: This will load the font into memory if it hasn't been loaded already. - for (font_template, bytes_sender) in senders.iter() { - let identifier = font_template.identifier(); - let data = self - .font_data - .entry(identifier) - .or_insert_with(|| font_template.data()); - let span = span!( - Level::TRACE, - "GetFontTemplates send", - servo_profiling = true - ); - let _span = span.enter(); - let _ = bytes_sender.send(data); - } + let _ = + result_sender.send(self.get_font_templates(font_descriptor, font_family)); }, Command::GetFontInstance(identifier, pt_size, flags, result) => { let _ = result.send(self.get_font_instance(identifier, pt_size, flags)); }, - Command::GetWebFont(bytes_receiver, font_index, result_sender) => { + Command::GetWebFont(data, font_index, result_sender) => { self.webrender_api.forward_add_font_message( - bytes_receiver, + data.as_ipc_shared_memory(), font_index, result_sender, ); @@ -190,6 +183,38 @@ impl FontCache { } } + fn get_font_templates( + &mut self, + font_descriptor: Option<FontDescriptor>, + font_family: SingleFontFamily, + ) -> FontTemplateRequestResult { + let templates = self.find_font_templates(font_descriptor.as_ref(), &font_family); + let templates: Vec<_> = templates + .into_iter() + .map(|template| template.borrow().clone()) + .collect(); + + // The `FontData` for all templates is also sent along with the `FontTemplate`s. This is to ensure that + // the data is not read from disk in each content process. The data is loaded once here in the system + // font service and each process gets another handle to the `IpcSharedMemory` view of that data. + let template_data = templates + .iter() + .map(|template| { + let identifier = template.identifier.clone(); + let data = self + .local_families + .get_or_initialize_font_data(&identifier) + .clone(); + (identifier, data) + }) + .collect(); + + FontTemplateRequestResult { + templates, + template_data, + } + } + fn refresh_local_families(&mut self) { self.local_families.clear(); for_each_available_family(|family_name| { @@ -232,11 +257,7 @@ impl FontCache { ) -> FontInstanceKey { let webrender_font_api = &self.webrender_api; let webrender_fonts = &mut self.webrender_fonts; - let font_data = self - .font_data - .get(&identifier) - .expect("Got unexpected FontIdentifier") - .clone(); + let font_data = self.local_families.get_or_initialize_font_data(&identifier); let font_key = *webrender_fonts .entry(identifier.clone()) @@ -252,7 +273,7 @@ impl FontCache { .add_system_font(local_font_identifier.native_font_handle()); } - webrender_font_api.add_font(font_data, identifier.index()) + webrender_font_api.add_font(font_data.as_ipc_shared_memory(), identifier.index()) }); *self @@ -304,7 +325,8 @@ impl FontCache { } } -pub trait FontSource: Clone { +/// A trait for accessing the [`SystemFontServiceProxy`] necessary for unit testing. +pub trait SystemFontServiceProxyTrait: Send + Sync { fn find_matching_font_templates( &self, descriptor_to_match: Option<&FontDescriptor>, @@ -316,20 +338,29 @@ pub trait FontSource: Clone { size: Au, flags: FontInstanceFlags, ) -> FontInstanceKey; - fn get_web_font(&self, data: Arc<Vec<u8>>, index: u32) -> FontKey; + fn get_web_font(&self, data: Arc<FontData>, index: u32) -> FontKey; fn get_web_font_instance( &self, font_key: FontKey, size: f32, flags: FontInstanceFlags, ) -> FontInstanceKey; + fn get_font_data(&self, identifier: &FontIdentifier) -> Option<Arc<FontData>>; } -/// The public interface to the font cache thread, used by per-thread `FontContext` instances (via -/// the `FontSource` trait), and also by layout. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct FontCacheThread { - chan: IpcSender<Command>, +#[derive(Debug, Eq, Hash, MallocSizeOf, PartialEq)] +struct FontTemplateCacheKey { + font_descriptor: Option<FontDescriptor>, + family_descriptor: SingleFontFamily, +} + +/// The public interface to the [`SystemFontService`], used by per-Document `FontContext` +/// instances (via [`SystemFontServiceProxyTrait`]). +#[derive(Debug)] +pub struct SystemFontServiceProxy { + sender: ReentrantMutex<IpcSender<Command>>, + templates: RwLock<HashMap<FontTemplateCacheKey, Vec<FontTemplateRef>>>, + data_cache: RwLock<HashMap<FontIdentifier, Arc<FontData>>>, } /// A version of `FontStyle` from Stylo that is serializable. Normally this is not @@ -417,44 +448,24 @@ impl From<&FontFaceRuleData> for CSSFontFaceDescriptors { } } -impl FontCacheThread { - pub fn new(webrender_api: Box<dyn WebRenderFontApi + Send>) -> FontCacheThread { - let (chan, port) = ipc::channel().unwrap(); - - thread::Builder::new() - .name("FontCache".to_owned()) - .spawn(move || { - #[allow(clippy::default_constructed_unit_structs)] - let mut cache = FontCache { - port, - font_data: HashMap::new(), - local_families: Default::default(), - webrender_api, - webrender_fonts: HashMap::new(), - font_instances: HashMap::new(), - generic_fonts: Default::default(), - }; - - cache.refresh_local_families(); - cache.run(); - }) - .expect("Thread spawning failed"); - - FontCacheThread { chan } - } - +impl SystemFontServiceProxy { pub fn exit(&self) { let (response_chan, response_port) = ipc::channel().unwrap(); - self.chan + self.sender + .lock() .send(Command::Exit(response_chan)) - .expect("Couldn't send FontCacheThread exit message"); + .expect("Couldn't send SystemFontService exit message"); response_port .recv() - .expect("Couldn't receive FontCacheThread reply"); + .expect("Couldn't receive SystemFontService reply"); + } + + pub fn to_sender(&self) -> SystemFontServiceProxySender { + SystemFontServiceProxySender(self.sender.lock().clone()) } } -impl FontSource for FontCacheThread { +impl SystemFontServiceProxyTrait for SystemFontServiceProxy { fn get_system_font_instance( &self, identifier: FontIdentifier, @@ -462,23 +473,24 @@ impl FontSource for FontCacheThread { flags: FontInstanceFlags, ) -> FontInstanceKey { let (response_chan, response_port) = ipc::channel().expect("failed to create IPC channel"); - self.chan + self.sender + .lock() .send(Command::GetFontInstance( identifier, size, flags, response_chan, )) - .expect("failed to send message to font cache thread"); + .expect("failed to send message to system font service"); let instance_key = response_port.recv(); if instance_key.is_err() { - let font_thread_has_closed = self.chan.send(Command::Ping).is_err(); + let font_thread_has_closed = self.sender.lock().send(Command::Ping).is_err(); assert!( font_thread_has_closed, "Failed to receive a response from live font cache" ); - panic!("Font cache thread has already exited."); + panic!("SystemFontService has already exited."); } instance_key.unwrap() } @@ -486,51 +498,59 @@ impl FontSource for FontCacheThread { fn find_matching_font_templates( &self, descriptor_to_match: Option<&FontDescriptor>, - font_family: &SingleFontFamily, + family_descriptor: &SingleFontFamily, ) -> Vec<FontTemplateRef> { + let cache_key = FontTemplateCacheKey { + font_descriptor: descriptor_to_match.cloned(), + family_descriptor: family_descriptor.clone(), + }; + if let Some(templates) = self.templates.read().get(&cache_key).cloned() { + return templates; + } + + debug!( + "SystemFontServiceProxy: cache miss for template_descriptor={:?} family_descriptor={:?}", + descriptor_to_match, family_descriptor + ); + let (response_chan, response_port) = ipc::channel().expect("failed to create IPC channel"); - self.chan + self.sender + .lock() .send(Command::GetFontTemplates( descriptor_to_match.cloned(), - font_family.clone(), + family_descriptor.clone(), response_chan, )) - .expect("failed to send message to font cache thread"); + .expect("failed to send message to system font service"); let reply = response_port.recv(); - if reply.is_err() { - let font_thread_has_closed = self.chan.send(Command::Ping).is_err(); + let Ok(reply) = reply else { + let font_thread_has_closed = self.sender.lock().send(Command::Ping).is_err(); assert!( font_thread_has_closed, "Failed to receive a response from live font cache" ); - panic!("Font cache thread has already exited."); - } + panic!("SystemFontService has already exited."); + }; - reply - .unwrap() + let templates: Vec<_> = reply + .templates .into_iter() - .map(|serialized_font_template| { - let font_data = serialized_font_template.bytes_receiver.recv().ok(); - Arc::new(AtomicRefCell::new(FontTemplate { - identifier: serialized_font_template.identifier, - descriptor: serialized_font_template.descriptor.clone(), - data: font_data.map(Arc::new), - stylesheet: None, - })) - }) - .collect() + .map(AtomicRefCell::new) + .map(Arc::new) + .collect(); + self.data_cache.write().extend(reply.template_data); + + templates } - fn get_web_font(&self, data: Arc<Vec<u8>>, index: u32) -> FontKey { + fn get_web_font(&self, data: Arc<FontData>, index: u32) -> FontKey { let (result_sender, result_receiver) = ipc::channel().expect("failed to create IPC channel"); - let (bytes_sender, bytes_receiver) = - ipc::bytes_channel().expect("failed to create IPC channel"); let _ = self - .chan - .send(Command::GetWebFont(bytes_receiver, index, result_sender)); - let _ = bytes_sender.send(&data); + .sender + .lock() + .send(Command::GetWebFont(data, index, result_sender)); result_receiver.recv().unwrap() } @@ -542,7 +562,7 @@ impl FontSource for FontCacheThread { ) -> FontInstanceKey { let (result_sender, result_receiver) = ipc::channel().expect("failed to create IPC channel"); - let _ = self.chan.send(Command::GetWebFontInstance( + let _ = self.sender.lock().send(Command::GetWebFontInstance( font_key, font_size, font_flags, @@ -550,6 +570,10 @@ impl FontSource for FontCacheThread { )); result_receiver.recv().unwrap() } + + fn get_font_data(&self, identifier: &FontIdentifier) -> Option<Arc<FontData>> { + self.data_cache.read().get(identifier).cloned() + } } #[derive(Clone, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)] diff --git a/components/fonts/tests/font_context.rs b/components/fonts/tests/font_context.rs index 91eddaeccc3..470155a2871 100644 --- a/components/fonts/tests/font_context.rs +++ b/components/fonts/tests/font_context.rs @@ -2,22 +2,24 @@ * 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::cell::{Cell, RefCell}; use std::collections::HashMap; use std::fs::File; use std::io::prelude::*; use std::path::PathBuf; -use std::rc::Rc; +use std::sync::atomic::{AtomicI32, Ordering}; +use std::sync::Arc; use app_units::Au; +use fonts::platform::font::PlatformFont; use fonts::{ - fallback_font_families, CSSFontFaceDescriptors, FallbackFontSelectionOptions, FontContext, - FontDescriptor, FontFamilyDescriptor, FontIdentifier, FontSearchScope, FontSource, - FontTemplate, FontTemplateRef, FontTemplates, + fallback_font_families, FallbackFontSelectionOptions, FontContext, FontData, FontDescriptor, + FontFamilyDescriptor, FontIdentifier, FontSearchScope, FontTemplate, FontTemplateRef, + FontTemplates, PlatformFontMethods, SystemFontServiceProxyTrait, }; use ipc_channel::ipc; use net_traits::ResourceThreads; -use servo_arc::Arc; +use parking_lot::Mutex; +use servo_arc::Arc as ServoArc; use servo_atoms::Atom; use servo_url::ServoUrl; use style::properties::longhands::font_variant_caps::computed_value::T as FontVariantCaps; @@ -31,35 +33,40 @@ use style::values::generics::font::LineHeight; use style::ArcSlice; use webrender_api::{FontInstanceFlags, FontInstanceKey, FontKey, IdNamespace}; -#[derive(Clone)] struct MockFontCacheThread { - families: RefCell<HashMap<String, FontTemplates>>, - find_font_count: Rc<Cell<isize>>, + families: Mutex<HashMap<String, FontTemplates>>, + data: Mutex<HashMap<FontIdentifier, Arc<FontData>>>, + find_font_count: AtomicI32, } impl MockFontCacheThread { - fn new() -> MockFontCacheThread { + fn new() -> Self { + let proxy = Self { + families: Default::default(), + data: Default::default(), + find_font_count: AtomicI32::new(0), + }; + let mut csstest_ascii = FontTemplates::default(); - Self::add_face(&mut csstest_ascii, "csstest-ascii"); + proxy.add_face(&mut csstest_ascii, "csstest-ascii"); let mut csstest_basic = FontTemplates::default(); - Self::add_face(&mut csstest_basic, "csstest-basic-regular"); + proxy.add_face(&mut csstest_basic, "csstest-basic-regular"); let mut fallback = FontTemplates::default(); - Self::add_face(&mut fallback, "csstest-basic-regular"); - - let mut families = HashMap::new(); - families.insert("CSSTest ASCII".to_owned(), csstest_ascii); - families.insert("CSSTest Basic".to_owned(), csstest_basic); - families.insert( - fallback_font_families(FallbackFontSelectionOptions::default())[0].to_owned(), - fallback, - ); - - MockFontCacheThread { - families: RefCell::new(families), - find_font_count: Rc::new(Cell::new(0)), + proxy.add_face(&mut fallback, "csstest-basic-regular"); + + { + let mut families = proxy.families.lock(); + families.insert("CSSTest ASCII".to_owned(), csstest_ascii); + families.insert("CSSTest Basic".to_owned(), csstest_basic); + families.insert( + fallback_font_families(FallbackFontSelectionOptions::default())[0].to_owned(), + fallback, + ); } + + proxy } fn identifier_for_font_name(name: &str) -> FontIdentifier { @@ -74,39 +81,44 @@ impl MockFontCacheThread { ServoUrl::from_file_path(path).unwrap() } - fn add_face(family: &mut FontTemplates, name: &str) { + fn add_face(&self, family: &mut FontTemplates, name: &str) { let mut path: PathBuf = [env!("CARGO_MANIFEST_DIR"), "tests", "support", "CSSTest"] .iter() .collect(); path.push(format!("{}.ttf", name)); let file = File::open(path).unwrap(); - let data: Vec<u8> = file.bytes().map(|b| b.unwrap()).collect(); - family.add_template( - FontTemplate::new_for_remote_web_font( - Self::url_for_font_name(name), - std::sync::Arc::new(data), - &CSSFontFaceDescriptors::new(name), - None, - ) - .unwrap(), - ); + let data = Arc::new(FontData::from_bytes( + file.bytes().map(|b| b.unwrap()).collect(), + )); + + let url = Self::url_for_font_name(name); + let identifier = FontIdentifier::Web(url.clone()); + let handle = + PlatformFont::new_from_data(identifier.clone(), data.as_arc().clone(), 0, None) + .expect("Could not load test font"); + let template = + FontTemplate::new_for_remote_web_font(url, handle.descriptor(), None).unwrap(); + family.add_template(template); + + self.data.lock().insert(identifier, data); } } -impl FontSource for MockFontCacheThread { +impl SystemFontServiceProxyTrait for MockFontCacheThread { fn find_matching_font_templates( &self, descriptor_to_match: Option<&FontDescriptor>, font_family: &SingleFontFamily, ) -> Vec<FontTemplateRef> { - self.find_font_count.set(self.find_font_count.get() + 1); + self.find_font_count.fetch_add(1, Ordering::Relaxed); + let SingleFontFamily::FamilyName(family_name) = font_family else { return Vec::new(); }; self.families - .borrow_mut() + .lock() .get_mut(&*family_name.name) .map(|family| family.find_for_descriptor(descriptor_to_match)) .unwrap_or_default() @@ -114,25 +126,29 @@ impl FontSource for MockFontCacheThread { fn get_system_font_instance( &self, - _: FontIdentifier, - _: Au, - _: FontInstanceFlags, + _font_identifier: FontIdentifier, + _size: Au, + _flags: FontInstanceFlags, ) -> FontInstanceKey { FontInstanceKey(IdNamespace(0), 0) } - fn get_web_font(&self, _: std::sync::Arc<Vec<u8>>, _: u32) -> webrender_api::FontKey { + fn get_web_font(&self, _data: Arc<fonts::FontData>, _index: u32) -> FontKey { FontKey(IdNamespace(0), 0) } fn get_web_font_instance( &self, - _: webrender_api::FontKey, - _: f32, - _: FontInstanceFlags, + _font_key: FontKey, + _size: f32, + _flags: FontInstanceFlags, ) -> FontInstanceKey { FontInstanceKey(IdNamespace(0), 0) } + + fn get_font_data(&self, identifier: &FontIdentifier) -> Option<Arc<fonts::FontData>> { + self.data.lock().get(identifier).cloned() + } } fn style() -> FontStyleStruct { @@ -177,7 +193,7 @@ fn mock_resource_threads() -> ResourceThreads { #[test] fn test_font_group_is_cached_by_style() { - let source = MockFontCacheThread::new(); + let source = Arc::new(MockFontCacheThread::new()); let context = FontContext::new(source, mock_resource_threads()); let style1 = style(); @@ -187,16 +203,16 @@ fn test_font_group_is_cached_by_style() { assert!( std::ptr::eq( - &*context.font_group(Arc::new(style1.clone())).read(), - &*context.font_group(Arc::new(style1.clone())).read() + &*context.font_group(ServoArc::new(style1.clone())).read(), + &*context.font_group(ServoArc::new(style1.clone())).read() ), "the same font group should be returned for two styles with the same hash" ); assert!( !std::ptr::eq( - &*context.font_group(Arc::new(style1.clone())).read(), - &*context.font_group(Arc::new(style2.clone())).read() + &*context.font_group(ServoArc::new(style1.clone())).read(), + &*context.font_group(ServoArc::new(style2.clone())).read() ), "different font groups should be returned for two styles with different hashes" ) @@ -204,14 +220,13 @@ fn test_font_group_is_cached_by_style() { #[test] fn test_font_group_find_by_codepoint() { - let source = MockFontCacheThread::new(); - let count = source.find_font_count.clone(); - let mut context = FontContext::new(source, mock_resource_threads()); + let source = Arc::new(MockFontCacheThread::new()); + let mut context = FontContext::new(source.clone(), mock_resource_threads()); let mut style = style(); style.set_font_family(font_family(vec!["CSSTest ASCII", "CSSTest Basic"])); - let group = context.font_group(Arc::new(style)); + let group = context.font_group(ServoArc::new(style)); let font = group .write() @@ -222,7 +237,7 @@ fn test_font_group_find_by_codepoint() { MockFontCacheThread::identifier_for_font_name("csstest-ascii") ); assert_eq!( - count.get(), + source.find_font_count.fetch_add(0, Ordering::Relaxed), 1, "only the first font in the list should have been loaded" ); @@ -236,7 +251,7 @@ fn test_font_group_find_by_codepoint() { MockFontCacheThread::identifier_for_font_name("csstest-ascii") ); assert_eq!( - count.get(), + source.find_font_count.fetch_add(0, Ordering::Relaxed), 1, "we shouldn't load the same font a second time" ); @@ -249,18 +264,22 @@ fn test_font_group_find_by_codepoint() { font.identifier(), MockFontCacheThread::identifier_for_font_name("csstest-basic-regular") ); - assert_eq!(count.get(), 2, "both fonts should now have been loaded"); + assert_eq!( + source.find_font_count.fetch_add(0, Ordering::Relaxed), + 2, + "both fonts should now have been loaded" + ); } #[test] fn test_font_fallback() { - let source = MockFontCacheThread::new(); + let source = Arc::new(MockFontCacheThread::new()); let mut context = FontContext::new(source, mock_resource_threads()); let mut style = style(); style.set_font_family(font_family(vec!["CSSTest ASCII"])); - let group = context.font_group(Arc::new(style)); + let group = context.font_group(ServoArc::new(style)); let font = group .write() @@ -285,9 +304,8 @@ fn test_font_fallback() { #[test] fn test_font_template_is_cached() { - let source = MockFontCacheThread::new(); - let count = source.find_font_count.clone(); - let context = FontContext::new(source, mock_resource_threads()); + let source = Arc::new(MockFontCacheThread::new()); + let context = FontContext::new(source.clone(), mock_resource_threads()); let mut font_descriptor = FontDescriptor { weight: FontWeight::normal(), @@ -320,7 +338,7 @@ fn test_font_template_is_cached() { ); assert_eq!( - count.get(), + source.find_font_count.fetch_add(0, Ordering::Relaxed), 1, "we should only have fetched the template data from the cache thread once" ); |