diff --git a/Cargo.lock b/Cargo.lock index 90ec43863b0..3b0e72bdafc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2052,6 +2052,7 @@ dependencies = [ "log", "malloc_size_of", "malloc_size_of_derive", + "memmap2", "net_traits", "num-traits", "parking_lot", diff --git a/Cargo.toml b/Cargo.toml index b13847e3a1f..2e44c92fc36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,6 +80,7 @@ log = "0.4" mach2 = "0.4" malloc_size_of = { git = "https://github.com/servo/stylo", branch = "2024-09-02", features = ["servo"] } malloc_size_of_derive = "0.1" +memmap2 = "0.9.5" mime = "0.3.13" mime_guess = "2.0.5" mozangle = "0.5.1" diff --git a/components/canvas/canvas_data.rs b/components/canvas/canvas_data.rs index 08b269e3950..7ff8038f398 100644 --- a/components/canvas/canvas_data.rs +++ b/components/canvas/canvas_data.rs @@ -231,7 +231,7 @@ impl<'a> PathBuilderRef<'a> { } } -#[derive(Debug, Default)] +#[derive(Default)] struct UnshapedTextRun<'a> { font: Option, script: Script, diff --git a/components/canvas/raqote_backend.rs b/components/canvas/raqote_backend.rs index cccf2bf70d1..e7e3d116b5f 100644 --- a/components/canvas/raqote_backend.rs +++ b/components/canvas/raqote_backend.rs @@ -555,9 +555,8 @@ impl GenericDrawTarget for raqote::DrawTarget { SHARED_FONT_CACHE.with(|font_cache| { let identifier = template.identifier(); if !font_cache.borrow().contains_key(&identifier) { - let Ok(font) = - Font::from_bytes(run.font.data.as_arc().clone(), identifier.index()) - else { + let data = std::sync::Arc::new(run.font.data().as_ref().to_vec()); + let Ok(font) = Font::from_bytes(data, identifier.index()) else { return; }; font_cache.borrow_mut().insert(identifier.clone(), font); diff --git a/components/fonts/Cargo.toml b/components/fonts/Cargo.toml index 1041aa1660f..67059aef093 100644 --- a/components/fonts/Cargo.toml +++ b/components/fonts/Cargo.toml @@ -31,6 +31,7 @@ libc = { workspace = true } log = { workspace = true } malloc_size_of = { workspace = true } malloc_size_of_derive = { workspace = true } +memmap2 = { workspace = true } net_traits = { workspace = true } num-traits = { workspace = true } parking_lot = { workspace = true } @@ -78,5 +79,5 @@ harfbuzz-sys = { version = "0.6.1", features = ["bundled"] } [target.'cfg(target_os = "windows")'.dependencies] harfbuzz-sys = { version = "0.6", features = ["bundled"] } -dwrote = "0.11.1" +dwrote = "0.11.2" truetype = { version = "0.47.3", features = ["ignore-invalid-language-ids"] } diff --git a/components/fonts/font.rs b/components/fonts/font.rs index c5babf9393b..44ca3a8c4b9 100644 --- a/components/fonts/font.rs +++ b/components/fonts/font.rs @@ -27,14 +27,12 @@ use style::values::computed::{FontStretch, FontStyle, FontWeight}; use unicode_script::Script; use webrender_api::{FontInstanceFlags, FontInstanceKey}; -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; use crate::{ - ByteIndex, EmojiPresentationPreference, FallbackFontSelectionOptions, FontData, GlyphData, - GlyphId, GlyphStore, Shaper, + ByteIndex, EmojiPresentationPreference, FallbackFontSelectionOptions, FontContext, FontData, + FontIdentifier, FontTemplateDescriptor, FontTemplateRef, FontTemplateRefMethods, GlyphData, + GlyphId, GlyphStore, LocalFontIdentifier, Shaper, }; #[macro_export] @@ -66,19 +64,32 @@ pub trait PlatformFontMethods: Sized { fn new_from_template( template: FontTemplateRef, pt_size: Option, - data: &Arc>, + data: &Option, ) -> Result { let template = template.borrow(); - - let face_index = template.identifier().index(); let font_identifier = template.identifier.clone(); - Self::new_from_data(font_identifier, data.clone(), face_index, pt_size) + + match font_identifier { + FontIdentifier::Local(font_identifier) => { + Self::new_from_local_font_identifier(font_identifier, pt_size) + }, + FontIdentifier::Web(_) => Self::new_from_data( + font_identifier, + data.as_ref() + .expect("Should never create a web font without data."), + pt_size, + ), + } } + fn new_from_local_font_identifier( + font_identifier: LocalFontIdentifier, + pt_size: Option, + ) -> Result; + fn new_from_data( font_identifier: FontIdentifier, - data: Arc>, - face_index: u32, + data: &FontData, pt_size: Option, ) -> Result; @@ -215,13 +226,15 @@ impl malloc_size_of::MallocSizeOf for CachedShapeData { } } -#[derive(Debug)] pub struct Font { pub handle: PlatformFont, - pub data: Arc, pub template: FontTemplateRef, pub metrics: FontMetrics, pub descriptor: FontDescriptor, + + /// The data for this font. This might be uninitialized for system fonts. + data: OnceLock, + shaper: OnceLock, cached_shape_data: RwLock, pub font_instance_key: OnceLock, @@ -262,23 +275,20 @@ impl Font { pub fn new( template: FontTemplateRef, descriptor: FontDescriptor, - data: Arc, + data: Option, synthesized_small_caps: Option, ) -> Result { - let handle = PlatformFont::new_from_template( - template.clone(), - Some(descriptor.pt_size), - data.as_arc(), - )?; + let handle = + PlatformFont::new_from_template(template.clone(), Some(descriptor.pt_size), &data)?; let metrics = handle.metrics(); Ok(Font { handle, - data, template, - shaper: OnceLock::new(), - descriptor, metrics, + descriptor, + data: data.map(OnceLock::from).unwrap_or_default(), + shaper: OnceLock::new(), cached_shape_data: Default::default(), font_instance_key: Default::default(), synthesized_small_caps, @@ -309,6 +319,21 @@ impl Font { .font_instance_key .get_or_init(|| font_context.create_font_instance_key(self)) } + + /// Return the data for this `Font`. Note that this is currently highly inefficient for system + /// fonts and should not be used except in legacy canvas code. + pub fn data(&self) -> &FontData { + self.data.get_or_init(|| { + let FontIdentifier::Local(local_font_identifier) = self.identifier() else { + unreachable!("All web fonts should already have initialized data"); + }; + FontData::from_bytes( + &local_font_identifier + .read_data_from_file() + .unwrap_or_default(), + ) + }) + } } bitflags! { @@ -530,7 +555,7 @@ pub type FontRef = Arc; /// A `FontGroup` is a prioritised list of fonts for a given set of font styles. It is used by /// `TextRun` to decide which font to render a character with. If none of the fonts listed in the /// styles are suitable, a fallback font may be used. -#[derive(Debug, MallocSizeOf)] +#[derive(MallocSizeOf)] pub struct FontGroup { descriptor: FontDescriptor, families: SmallVec<[FontGroupFamily; 8]>, @@ -726,7 +751,7 @@ impl FontGroup { /// `unicode-range` descriptors. In this case, font selection will select a single member /// that contains the necessary unicode character. Unicode ranges are specified by the /// [`FontGroupFamilyMember::template`] member. -#[derive(Debug, MallocSizeOf)] +#[derive(MallocSizeOf)] struct FontGroupFamilyMember { #[ignore_malloc_size_of = "This measured in the FontContext template cache."] template: FontTemplateRef, @@ -739,7 +764,7 @@ struct FontGroupFamilyMember { /// families listed in the `font-family` CSS property. The corresponding font data is lazy-loaded, /// only if actually needed. A single `FontGroupFamily` can have multiple fonts, in the case that /// individual fonts only cover part of the Unicode range. -#[derive(Debug, MallocSizeOf)] +#[derive(MallocSizeOf)] struct FontGroupFamily { family_descriptor: FontFamilyDescriptor, members: Option>, diff --git a/components/fonts/font_context.rs b/components/fonts/font_context.rs index d3c4a66ce66..b5732d258d2 100644 --- a/components/fonts/font_context.rs +++ b/components/fonts/font_context.rs @@ -28,6 +28,7 @@ use style::shared_lock::SharedRwLockReadGuard; use style::stylesheets::{CssRule, DocumentStyleSheet, FontFaceRule, StylesheetInDocument}; use style::values::computed::font::{FamilyName, FontFamilyNameSyntax, SingleFontFamily}; use style::Atom; +use tracing::instrument; use url::Url; use webrender_api::{FontInstanceFlags, FontInstanceKey, FontKey}; use webrender_traits::CrossProcessCompositorApi; @@ -74,6 +75,10 @@ pub struct FontContext { /// this [`FontContext`] controls. webrender_font_instance_keys: RwLock>, + /// The data for each web font [`FontIdentifier`]. This data might be used by more than one + /// [`FontTemplate`] as each identifier refers to a URL. + font_data: RwLock>, + have_removed_web_fonts: AtomicBool, } @@ -114,6 +119,7 @@ impl FontContext { webrender_font_keys: RwLock::default(), webrender_font_instance_keys: RwLock::default(), have_removed_web_fonts: AtomicBool::new(false), + font_data: RwLock::default(), } } @@ -121,14 +127,11 @@ impl FontContext { self.web_fonts.read().number_of_fonts_still_loading() } - fn get_font_data(&self, identifier: &FontIdentifier) -> Arc { + fn get_font_data(&self, identifier: &FontIdentifier) -> Option { match identifier { - FontIdentifier::Web(_) => self.web_fonts.read().get_font_data(identifier), - FontIdentifier::Local(_) | FontIdentifier::Mock(_) => { - self.system_font_service_proxy.get_font_data(identifier) - }, + FontIdentifier::Web(_) => self.font_data.read().get(identifier).cloned(), + FontIdentifier::Local(_) => None, } - .expect("Could not find font data") } /// Handle the situation where a web font finishes loading, specifying if the load suceeded or failed. @@ -276,6 +279,7 @@ impl FontContext { /// Create a `Font` for use in layout calculations, from a `FontTemplateData` returned by the /// cache thread and a `FontDescriptor` which contains the styling parameters. + #[instrument(skip_all, fields(servo_profiling = true))] fn create_font( &self, font_template: FontTemplateRef, @@ -292,13 +296,11 @@ impl FontContext { pub(crate) fn create_font_instance_key(&self, font: &Font) -> FontInstanceKey { match font.template.identifier() { - FontIdentifier::Local(_) | FontIdentifier::Mock(_) => { - self.system_font_service_proxy.get_system_font_instance( - font.template.identifier(), - font.descriptor.pt_size, - font.webrender_font_instance_flags(), - ) - }, + 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.create_web_font_instance( font.template.clone(), font.descriptor.pt_size, @@ -314,7 +316,9 @@ impl FontContext { flags: FontInstanceFlags, ) -> FontInstanceKey { let identifier = font_template.identifier().clone(); - let font_data = self.get_font_data(&identifier); + let font_data = self + .get_font_data(&identifier) + .expect("Web font should have associated font data"); let font_key = *self .webrender_font_keys .write() @@ -519,7 +523,8 @@ impl FontContextWebFontMethods for Arc { } // Lock everything to prevent adding new fonts while we are cleaning up the old ones. - let mut web_fonts = self.web_fonts.write(); + let web_fonts = self.web_fonts.write(); + let mut font_data = self.font_data.write(); let _fonts = self.fonts.write(); let _font_groups = self.resolved_font_groups.write(); let mut webrender_font_keys = self.webrender_font_keys.write(); @@ -533,7 +538,7 @@ impl FontContextWebFontMethods for Arc { }); } - web_fonts.remove_all_font_data_for_identifiers(&unused_identifiers); + font_data.retain(|font_identifier, _| unused_identifiers.contains(font_identifier)); self.have_removed_web_fonts.store(false, Ordering::Relaxed); @@ -581,7 +586,7 @@ impl FontContext { RemoteWebFontDownloader::download(url_source, this, web_font_family_name, state) }, Source::Local(ref local_family_name) => { - if let Some((new_template, font_data)) = state + if let Some(new_template) = state .local_fonts .get(&local_family_name.name) .cloned() @@ -593,15 +598,13 @@ impl FontContext { state.stylesheet.clone(), ) .ok()?; - let font_data = self.get_font_data(&local_template.identifier()); - Some((template, font_data)) + Some(template) }) { - let not_cancelled = self.web_fonts.write().handle_web_font_loaded( - &state, - new_template, - font_data, - ); + let not_cancelled = self + .web_fonts + .write() + .handle_web_font_loaded(&state, new_template); self.handle_web_font_load_finished(&state.finished_callback, not_cancelled); } else { this.process_next_web_font_source(state); @@ -695,7 +698,7 @@ impl RemoteWebFontDownloader { ); let font_data = match fontsan::process(&font_data) { - Ok(bytes) => Arc::new(FontData::from_bytes(bytes)), + Ok(bytes) => FontData::from_bytes(&bytes), Err(error) => { debug!( "Sanitiser rejected web font: family={} url={:?} with {error:?}", @@ -707,9 +710,7 @@ impl RemoteWebFontDownloader { 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 { + let Ok(handle) = PlatformFont::new_from_data(identifier, &font_data, None) else { return false; }; let mut descriptor = handle.descriptor(); @@ -721,11 +722,16 @@ impl RemoteWebFontDownloader { descriptor, Some(state.stylesheet.clone()), ); - let not_cancelled = self.font_context.web_fonts.write().handle_web_font_loaded( - state, - new_template, - font_data, - ); + self.font_context + .font_data + .write() + .insert(new_template.identifier.clone(), font_data); + + let not_cancelled = self + .font_context + .web_fonts + .write() + .handle_web_font_loaded(state, new_template); self.font_context .handle_web_font_load_finished(&state.finished_callback, not_cancelled); diff --git a/components/fonts/font_store.rs b/components/fonts/font_store.rs index d0a79456ef6..5791319bb14 100644 --- a/components/fonts/font_store.rs +++ b/components/fonts/font_store.rs @@ -2,14 +2,12 @@ * 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, HashSet}; -use std::sync::{Arc, OnceLock}; +use std::collections::HashMap; +use std::sync::Arc; 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}; @@ -18,52 +16,10 @@ use crate::font_context::WebFontDownloadState; use crate::font_template::{FontTemplate, FontTemplateRef, FontTemplateRefMethods, IsOblique}; use crate::system_font_service::{FontIdentifier, LowercaseFontFamilyName}; -/// 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>`), 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, - /// A lazily-initialized copy of the data behind an [`Arc`] which can be used when - /// passing it to various APIs. - #[serde(skip)] - arc: OnceLock>>, -} - -impl FontData { - pub fn from_bytes(data: Vec) -> 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> { - 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 { - self.shared_memory.clone() - } -} - #[derive(Default)] pub struct FontStore { pub(crate) families: HashMap, 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>, } pub(crate) type CrossThreadFontStore = Arc>; @@ -120,67 +76,27 @@ 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, ) -> 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, - ) { - 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 { - 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())) - }, - _ => unreachable!("Web and mock fonts should always have data."), - }) - } - - pub(crate) fn get_font_data(&self, identifier: &FontIdentifier) -> Option> { - self.font_data.get(identifier).cloned() - } - - pub(crate) fn remove_all_font_data_for_identifiers( - &mut self, - identifiers: &HashSet, - ) { - self.font_data - .retain(|font_identifier, _| identifiers.contains(font_identifier)); - } } /// A struct that represents the available templates in a "simple family." A simple family diff --git a/components/fonts/lib.rs b/components/fonts/lib.rs index 429672f3616..b6954e35cd2 100644 --- a/components/fonts/lib.rs +++ b/components/fonts/lib.rs @@ -14,15 +14,41 @@ pub mod platform; mod shaper; mod system_font_service; +use std::sync::Arc; + pub use font::*; pub use font_context::*; pub use font_store::*; pub use font_template::*; pub use glyph::*; +use ipc_channel::ipc::IpcSharedMemory; +pub use platform::LocalFontIdentifier; pub use shaper::*; pub use system_font_service::*; use unicode_properties::{emoji, EmojiStatus, UnicodeEmoji}; +/// A data structure to store data for fonts. Data is stored internally in an +/// [`IpcSharedMemory`] handle, so that it can be send without serialization +/// across IPC channels. +#[derive(Clone)] +pub struct FontData(pub(crate) Arc); + +impl FontData { + pub fn from_bytes(bytes: &[u8]) -> Self { + Self(Arc::new(IpcSharedMemory::from_bytes(bytes))) + } + + pub(crate) fn as_ipc_shared_memory(&self) -> Arc { + self.0.clone() + } +} + +impl AsRef<[u8]> for FontData { + fn as_ref(&self) -> &[u8] { + &**self.0 + } +} + /// Whether or not font fallback selection prefers the emoji or text representation /// of a character. If `None` then either presentation is acceptable. #[derive(Clone, Copy, Debug, PartialEq)] diff --git a/components/fonts/platform/freetype/android/font_list.rs b/components/fonts/platform/freetype/android/font_list.rs index 34d4bdeeeae..35ad065a36e 100644 --- a/components/fonts/platform/freetype/android/font_list.rs +++ b/components/fonts/platform/freetype/android/font_list.rs @@ -20,33 +20,11 @@ use style::Atom; use super::xml::{Attribute, Node}; use crate::{ FallbackFontSelectionOptions, FontIdentifier, FontTemplate, FontTemplateDescriptor, - LowercaseFontFamilyName, + LocalFontIdentifier, LowercaseFontFamilyName, }; static FONT_LIST: LazyLock = LazyLock::new(|| FontList::new()); -/// An identifier for a local font on Android systems. -#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] -pub struct LocalFontIdentifier { - /// The path to the font. - pub path: Atom, -} - -impl LocalFontIdentifier { - pub(crate) fn index(&self) -> u32 { - 0 - } - - pub(crate) fn read_data_from_file(&self) -> Vec { - let mut bytes = Vec::new(); - File::open(Path::new(&*self.path)) - .expect("Couldn't open font file!") - .read_to_end(&mut bytes) - .unwrap(); - bytes - } -} - // Android doesn't provide an API to query system fonts until Android O: // https://developer.android.com/reference/android/text/FontConfig.html // System font configuration files must be parsed until Android O version is set as the minimum target. @@ -473,6 +451,7 @@ where let mut produce_font = |font: &Font| { let local_font_identifier = LocalFontIdentifier { path: Atom::from(FontList::font_absolute_path(&font.filename)), + variation_index: 0, }; let stretch = StyleFontStretch::NORMAL; let weight = font diff --git a/components/fonts/platform/freetype/font.rs b/components/fonts/platform/freetype/font.rs index 89980ca62d6..3a60459739e 100644 --- a/components/fonts/platform/freetype/font.rs +++ b/components/fonts/platform/freetype/font.rs @@ -2,8 +2,8 @@ * 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::os::raw::c_long; -use std::sync::Arc; use std::{mem, ptr}; use app_units::Au; @@ -11,10 +11,10 @@ use euclid::default::{Point2D, Rect, Size2D}; use freetype_sys::{ ft_sfnt_head, ft_sfnt_os2, FT_Byte, FT_Done_Face, FT_Error, FT_F26Dot6, FT_Face, FT_Fixed, FT_Get_Char_Index, FT_Get_Kerning, FT_Get_Sfnt_Table, FT_GlyphSlot, FT_Int32, FT_Load_Glyph, - FT_Long, FT_MulFix, FT_New_Memory_Face, FT_Pos, FT_Select_Size, FT_Set_Char_Size, FT_Short, - FT_SizeRec, FT_Size_Metrics, FT_UInt, FT_ULong, FT_UShort, FT_Vector, FT_FACE_FLAG_COLOR, - FT_FACE_FLAG_FIXED_SIZES, FT_FACE_FLAG_SCALABLE, FT_KERNING_DEFAULT, FT_LOAD_COLOR, - FT_LOAD_DEFAULT, FT_LOAD_NO_HINTING, FT_STYLE_FLAG_ITALIC, TT_OS2, + FT_Long, FT_MulFix, FT_New_Face, FT_New_Memory_Face, FT_Pos, FT_Select_Size, FT_Set_Char_Size, + FT_Short, FT_SizeRec, FT_Size_Metrics, FT_UInt, FT_ULong, FT_UShort, FT_Vector, + FT_FACE_FLAG_COLOR, FT_FACE_FLAG_FIXED_SIZES, FT_FACE_FLAG_SCALABLE, FT_KERNING_DEFAULT, + FT_LOAD_COLOR, FT_LOAD_DEFAULT, FT_LOAD_NO_HINTING, FT_STYLE_FLAG_ITALIC, TT_OS2, }; use log::debug; use parking_lot::ReentrantMutex; @@ -25,12 +25,14 @@ use style::Zero; use webrender_api::FontInstanceFlags; use super::library_handle::FreeTypeLibraryHandle; +use super::LocalFontIdentifier; use crate::font::{ FontMetrics, FontTableMethods, FontTableTag, FractionalPixel, PlatformFontMethods, }; use crate::font_template::FontTemplateDescriptor; use crate::glyph::GlyphId; use crate::system_font_service::FontIdentifier; +use crate::FontData; // This constant is not present in the freetype // bindings due to bindgen not handling the way @@ -68,9 +70,6 @@ struct OS2Table { #[derive(Debug)] #[allow(unused)] pub struct PlatformFont { - /// The font data itself, which must stay valid for the lifetime of the - /// platform [`FT_Face`]. - font_data: Arc>, face: ReentrantMutex, requested_face_size: Au, actual_face_size: Au, @@ -98,37 +97,62 @@ impl Drop for PlatformFont { } } -fn create_face(data: Arc>, face_index: u32) -> Result { - unsafe { - let mut face: FT_Face = ptr::null_mut(); +impl PlatformFontMethods for PlatformFont { + fn new_from_data( + _font_identifier: FontIdentifier, + font_data: &FontData, + requested_size: Option, + ) -> Result { let library = FreeTypeLibraryHandle::get().lock(); - - // This is to support 32bit Android where FT_Long is defined as i32. - let result = FT_New_Memory_Face( - library.freetype_library, - data.as_ptr(), - data.len() as FT_Long, - face_index as FT_Long, - &mut face, - ); + let data: &[u8] = font_data.as_ref(); + let mut face: FT_Face = ptr::null_mut(); + let result = unsafe { + FT_New_Memory_Face( + library.freetype_library, + data.as_ptr(), + data.len() as FT_Long, + 0, /* face_index */ + &mut face, + ) + }; + + if 0 != result || face.is_null() { + return Err("Could not create FreeType face"); + } + + let (requested_face_size, actual_face_size) = match requested_size { + Some(requested_size) => (requested_size, face.set_size(requested_size)?), + None => (Au::zero(), Au::zero()), + }; + + Ok(PlatformFont { + face: ReentrantMutex::new(face), + requested_face_size, + actual_face_size, + }) + } + + fn new_from_local_font_identifier( + font_identifier: LocalFontIdentifier, + requested_size: Option, + ) -> Result { + let mut face: FT_Face = ptr::null_mut(); + let library = FreeTypeLibraryHandle::get().lock(); + let filename = CString::new(&*font_identifier.path).expect("filename contains NUL byte!"); + + let result = unsafe { + FT_New_Face( + library.freetype_library, + filename.as_ptr(), + font_identifier.index() as FT_Long, + &mut face, + ) + }; if 0 != result || face.is_null() { return Err("Could not create FreeType face"); } - Ok(face) - } -} - -impl PlatformFontMethods for PlatformFont { - fn new_from_data( - _font_identifier: FontIdentifier, - data: Arc>, - face_index: u32, - requested_size: Option, - ) -> Result { - let face = create_face(data.clone(), face_index)?; - let (requested_face_size, actual_face_size) = match requested_size { Some(requested_size) => (requested_size, face.set_size(requested_size)?), None => (Au::zero(), Au::zero()), @@ -136,7 +160,6 @@ impl PlatformFontMethods for PlatformFont { Ok(PlatformFont { face: ReentrantMutex::new(face), - font_data: data, requested_face_size, actual_face_size, }) diff --git a/components/fonts/platform/freetype/font_list.rs b/components/fonts/platform/freetype/font_list.rs index b395173de27..9552b47ca6f 100644 --- a/components/fonts/platform/freetype/font_list.rs +++ b/components/fonts/platform/freetype/font_list.rs @@ -2,11 +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::convert::TryInto; use std::ffi::CString; -use std::fs::File; -use std::io::Read; -use std::path::Path; use std::ptr; use base::text::{UnicodeBlock, UnicodeBlockMethod}; @@ -25,14 +21,12 @@ use fontconfig_sys::{ }; use libc::{c_char, c_int}; use log::debug; -use malloc_size_of_derive::MallocSizeOf; -use serde::{Deserialize, Serialize}; use style::values::computed::font::GenericFontFamily; use style::values::computed::{FontStretch, FontStyle, FontWeight}; use style::Atom; use unicode_script::Script; -use super::c_str_to_string; +use super::{c_str_to_string, LocalFontIdentifier}; use crate::font::map_platform_values_to_style_values; use crate::font_template::{FontTemplate, FontTemplateDescriptor}; use crate::platform::add_noto_fallback_families; @@ -41,30 +35,6 @@ use crate::{ LowercaseFontFamilyName, }; -/// An identifier for a local font on systems using Freetype. -#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] -pub struct LocalFontIdentifier { - /// The path to the font. - pub path: Atom, - /// The variation index within the font. - pub variation_index: i32, -} - -impl LocalFontIdentifier { - pub(crate) fn index(&self) -> u32 { - self.variation_index.try_into().unwrap() - } - - pub(crate) fn read_data_from_file(&self) -> Vec { - let mut bytes = Vec::new(); - File::open(Path::new(&*self.path)) - .expect("Couldn't open font file!") - .read_to_end(&mut bytes) - .unwrap(); - bytes - } -} - pub fn for_each_available_family(mut callback: F) where F: FnMut(String), diff --git a/components/fonts/platform/freetype/mod.rs b/components/fonts/platform/freetype/mod.rs new file mode 100644 index 00000000000..e0a0a5967d6 --- /dev/null +++ b/components/fonts/platform/freetype/mod.rs @@ -0,0 +1,74 @@ +/* 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::convert::TryInto; +use std::ffi::CStr; +use std::fs::File; +use std::path::{Path, PathBuf}; +use std::str; + +use libc::c_char; +use malloc_size_of_derive::MallocSizeOf; +use memmap2::Mmap; +use serde::{Deserialize, Serialize}; +use style::Atom; +use webrender_api::NativeFontHandle; + +/// Creates a String from the given null-terminated buffer. +/// Panics if the buffer does not contain UTF-8. +unsafe fn c_str_to_string(s: *const c_char) -> String { + str::from_utf8(CStr::from_ptr(s).to_bytes()) + .unwrap() + .to_owned() +} + +pub mod font; + +#[cfg(all(target_os = "linux", not(target_env = "ohos"), not(ohos_mock)))] +pub mod font_list; + +#[cfg(target_os = "android")] +mod android { + pub mod font_list; + mod xml; +} +#[cfg(target_os = "android")] +pub use self::android::font_list; + +#[cfg(any(target_env = "ohos", ohos_mock))] +mod ohos { + pub mod font_list; +} +#[cfg(any(target_env = "ohos", ohos_mock))] +pub use self::ohos::font_list; + +mod library_handle; + +/// An identifier for a local font on systems using Freetype. +#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] +pub struct LocalFontIdentifier { + /// The path to the font. + pub path: Atom, + /// The variation index within the font. + pub variation_index: i32, +} + +impl LocalFontIdentifier { + pub(crate) fn index(&self) -> u32 { + self.variation_index.try_into().unwrap() + } + + pub(crate) fn native_font_handle(&self) -> NativeFontHandle { + NativeFontHandle { + path: PathBuf::from(&*self.path), + index: self.variation_index as u32, + } + } + + pub(crate) fn read_data_from_file(&self) -> Option> { + let file = File::open(Path::new(&*self.path)).ok()?; + let mmap = unsafe { Mmap::map(&file).ok()? }; + Some((&mmap[..]).to_vec()) + } +} diff --git a/components/fonts/platform/freetype/ohos/font_list.rs b/components/fonts/platform/freetype/ohos/font_list.rs index f3d3218dee1..62d3b5145e1 100644 --- a/components/fonts/platform/freetype/ohos/font_list.rs +++ b/components/fonts/platform/freetype/ohos/font_list.rs @@ -23,7 +23,7 @@ use unicode_script::Script; use crate::{ EmojiPresentationPreference, FallbackFontSelectionOptions, FontIdentifier, FontTemplate, - FontTemplateDescriptor, LowercaseFontFamilyName, + FontTemplateDescriptor, LocalFontIdentifier, LowercaseFontFamilyName, }; static FONT_LIST: LazyLock = LazyLock::new(|| FontList::new()); @@ -37,28 +37,6 @@ static OHOS_FONTS_DIR: &'static str = env!("OHOS_SDK_FONTS_DIR"); #[cfg(not(ohos_mock))] static OHOS_FONTS_DIR: &'static str = "/system/fonts"; -/// An identifier for a local font on OpenHarmony systems. -#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] -pub struct LocalFontIdentifier { - /// The path to the font. - pub path: Atom, -} - -impl LocalFontIdentifier { - pub(crate) fn index(&self) -> u32 { - 0 - } - - pub(crate) fn read_data_from_file(&self) -> Vec { - let mut bytes = Vec::new(); - File::open(Path::new(&*self.path)) - .expect("Couldn't open font file!") - .read_to_end(&mut bytes) - .unwrap(); - bytes - } -} - #[allow(unused)] #[derive(Clone, Copy, Debug, Default)] // HarmonyOS only comes in Condensed and Normal variants @@ -473,6 +451,7 @@ where let mut produce_font = |font: &Font| { let local_font_identifier = LocalFontIdentifier { path: Atom::from(font.filepath.clone()), + variation_index: 0, }; let stretch = font.width.into(); let weight = font diff --git a/components/fonts/platform/macos/core_text_font_cache.rs b/components/fonts/platform/macos/core_text_font_cache.rs index 50bf495c908..7f8f7f62899 100644 --- a/components/fonts/platform/macos/core_text_font_cache.rs +++ b/components/fonts/platform/macos/core_text_font_cache.rs @@ -17,6 +17,7 @@ use core_text::font_descriptor::kCTFontURLAttribute; use parking_lot::RwLock; use crate::system_font_service::FontIdentifier; +use crate::FontData; /// 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 @@ -33,7 +34,7 @@ type CachedCTFont = HashMap; impl CoreTextFontCache { pub(crate) fn core_text_font( font_identifier: FontIdentifier, - data: Arc>, + data: Option<&FontData>, pt_size: f64, ) -> Option { //// If you pass a zero font size to one of the Core Text APIs, it'll replace it with @@ -86,8 +87,11 @@ impl CoreTextFontCache { core_text::font::new_from_descriptor(&descriptor, clamped_pt_size) }, - FontIdentifier::Web(_) | FontIdentifier::Mock(_) => { - let provider = CGDataProvider::from_buffer(data); + FontIdentifier::Web(_) => { + let data = data + .expect("Should always have FontData for web fonts") + .clone(); + let provider = CGDataProvider::from_buffer(Arc::new(data)); let cgfont = CGFont::from_data_provider(provider).ok()?; core_text::font::new_from_CGFont(&cgfont, clamped_pt_size) }, diff --git a/components/fonts/platform/macos/font.rs b/components/fonts/platform/macos/font.rs index e2a68d340ce..e304fc80c22 100644 --- a/components/fonts/platform/macos/font.rs +++ b/components/fonts/platform/macos/font.rs @@ -4,7 +4,6 @@ use std::cmp::Ordering; use std::ops::Range; -use std::sync::Arc; use std::{fmt, ptr}; /// Implementation of Quartz (CoreGraphics) fonts. @@ -23,8 +22,9 @@ use style::values::computed::font::{FontStretch, FontStyle, FontWeight}; use webrender_api::FontInstanceFlags; use super::core_text_font_cache::CoreTextFontCache; +use super::font_list::LocalFontIdentifier; use crate::{ - map_platform_values_to_style_values, FontIdentifier, FontMetrics, FontTableMethods, + map_platform_values_to_style_values, FontData, FontIdentifier, FontMetrics, FontTableMethods, FontTableTag, FontTemplateDescriptor, FractionalPixel, GlyphId, PlatformFontMethods, CBDT, COLR, KERN, SBIX, }; @@ -55,9 +55,6 @@ impl FontTableMethods for FontTable { #[derive(Debug)] 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: Arc>, h_kern_subtable: Option, } @@ -167,31 +164,45 @@ impl fmt::Debug for CachedKernTable { } } -impl PlatformFontMethods for PlatformFont { - fn new_from_data( +impl PlatformFont { + fn new( font_identifier: FontIdentifier, - data: Arc>, - _face_index: u32, - pt_size: Option, + data: Option<&FontData>, + requested_size: Option, ) -> Result { - let size = match pt_size { + let size = match requested_size { Some(s) => s.to_f64_px(), None => 0.0, }; - let Some(core_text_font) = - CoreTextFontCache::core_text_font(font_identifier, data.clone(), size) + let Some(core_text_font) = CoreTextFontCache::core_text_font(font_identifier, data, size) else { return Err("Could not generate CTFont for FontTemplateData"); }; let mut handle = PlatformFont { - _data: data, ctfont: core_text_font.clone_with_font_size(size), h_kern_subtable: None, }; handle.h_kern_subtable = handle.find_h_kern_subtable(); Ok(handle) } +} + +impl PlatformFontMethods for PlatformFont { + fn new_from_data( + font_identifier: FontIdentifier, + data: &FontData, + requested_size: Option, + ) -> Result { + Self::new(font_identifier, Some(data), requested_size) + } + + fn new_from_local_font_identifier( + font_identifier: LocalFontIdentifier, + requested_size: Option, + ) -> Result { + Self::new(FontIdentifier::Local(font_identifier), None, requested_size) + } fn descriptor(&self) -> FontTemplateDescriptor { let traits = self.ctfont.all_traits(); diff --git a/components/fonts/platform/macos/font_list.rs b/components/fonts/platform/macos/font_list.rs index d93224eeab7..85ae3f1a735 100644 --- a/components/fonts/platform/macos/font_list.rs +++ b/components/fonts/platform/macos/font_list.rs @@ -3,12 +3,12 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::fs::File; -use std::io::Read; use std::path::Path; use base::text::{unicode_plane, UnicodeBlock, UnicodeBlockMethod}; use log::debug; use malloc_size_of_derive::MallocSizeOf; +use memmap2::Mmap; use serde::{Deserialize, Serialize}; use style::values::computed::font::GenericFontFamily; use style::Atom; @@ -43,13 +43,16 @@ impl LocalFontIdentifier { 0 } - pub(crate) fn read_data_from_file(&self) -> Vec { - let mut bytes = Vec::new(); - File::open(Path::new(&*self.path)) - .expect("Couldn't open font file!") - .read_to_end(&mut bytes) - .unwrap(); - bytes + pub(crate) fn read_data_from_file(&self) -> Option> { + // TODO: This is incorrect, if the font file is a TTC (collection) with more than + // one font. In that case we either need to reconstruct the pertinent tables into + // a bundle of font data (expensive) or make sure that the value returned by + // `index()` above is correct. The latter is potentially tricky as macOS might not + // do an accurate mapping between the PostScript name that it gives us and what is + // listed in the font. + let file = File::open(Path::new(&*self.path)).ok()?; + let mmap = unsafe { Mmap::map(&file).ok()? }; + Some((&mmap[..]).to_vec()) } } diff --git a/components/fonts/platform/mod.rs b/components/fonts/platform/mod.rs index 642b6c46a80..38ae016a4d1 100644 --- a/components/fonts/platform/mod.rs +++ b/components/fonts/platform/mod.rs @@ -8,49 +8,18 @@ use base::text::{UnicodeBlock, UnicodeBlockMethod}; use unicode_script::Script; #[cfg(any(target_os = "linux", target_os = "android"))] -pub use crate::platform::freetype::{font, font_list, library_handle}; +pub use crate::platform::freetype::{font, font_list, LocalFontIdentifier}; #[cfg(target_os = "macos")] -pub use crate::platform::macos::{core_text_font_cache, font, font_list}; +pub use crate::platform::macos::{ + core_text_font_cache, font, font_list, font_list::LocalFontIdentifier, +}; #[cfg(target_os = "windows")] -pub use crate::platform::windows::{font, font_list}; +pub use crate::platform::windows::{font, font_list, font_list::LocalFontIdentifier}; #[cfg(any(target_os = "linux", target_os = "macos"))] use crate::FallbackFontSelectionOptions; #[cfg(any(target_os = "linux", target_os = "android"))] -mod freetype { - use std::ffi::CStr; - use std::str; - - use libc::c_char; - - /// Creates a String from the given null-terminated buffer. - /// Panics if the buffer does not contain UTF-8. - unsafe fn c_str_to_string(s: *const c_char) -> String { - str::from_utf8(CStr::from_ptr(s).to_bytes()) - .unwrap() - .to_owned() - } - - pub mod font; - - #[cfg(all(target_os = "linux", not(target_env = "ohos"), not(ohos_mock)))] - pub mod font_list; - #[cfg(target_os = "android")] - mod android { - pub mod font_list; - mod xml; - } - #[cfg(target_os = "android")] - pub use self::android::font_list; - #[cfg(any(target_env = "ohos", ohos_mock))] - mod ohos { - pub mod font_list; - } - #[cfg(any(target_env = "ohos", ohos_mock))] - pub use self::ohos::font_list; - - pub mod library_handle; -} +pub mod freetype; #[cfg(target_os = "macos")] mod macos { diff --git a/components/fonts/platform/windows/font.rs b/components/fonts/platform/windows/font.rs index 6621d6cc9a3..f8d12db370f 100644 --- a/components/fonts/platform/windows/font.rs +++ b/components/fonts/platform/windows/font.rs @@ -13,7 +13,7 @@ use std::ops::Deref; use std::sync::Arc; use app_units::Au; -use dwrote::{FontFace, FontFile}; +use dwrote::{FontCollection, FontFace, FontFile}; use euclid::default::{Point2D, Rect, Size2D}; use log::{debug, warn}; use style::computed_values::font_stretch::T as StyleFontStretch; @@ -24,9 +24,10 @@ use truetype::tables::WindowsMetrics; use truetype::value::Read; use webrender_api::FontInstanceFlags; +use super::font_list::LocalFontIdentifier; use crate::{ - ot_tag, FontIdentifier, FontMetrics, FontTableMethods, FontTableTag, FontTemplateDescriptor, - FractionalPixel, GlyphId, PlatformFontMethods, + ot_tag, FontData, FontIdentifier, FontMetrics, FontTableMethods, FontTableTag, + FontTemplateDescriptor, FractionalPixel, GlyphId, PlatformFontMethods, }; // 1em = 12pt = 16px, assuming 72 points per inch and 96 px per inch @@ -64,9 +65,6 @@ impl FontTableMethods for FontTable { #[derive(Debug)] 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: Arc>, em_size: f32, du_to_px: f32, scaled_du_to_px: f32, @@ -94,20 +92,10 @@ impl Deref for Nondebug { } } -impl PlatformFontMethods for PlatformFont { - fn new_from_data( - _font_identifier: FontIdentifier, - data: Arc>, - face_index: u32, - pt_size: Option, - ) -> Result { - 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")?; - +impl PlatformFont { + fn new(font_face: FontFace, pt_size: Option) -> Result { let pt_size = pt_size.unwrap_or(au_from_pt(12.)); - let du_per_em = face.metrics().metrics0().designUnitsPerEm as f32; + let du_per_em = font_face.metrics().metrics0().designUnitsPerEm as f32; let em_size = pt_size.to_f32_px() / 16.; let design_units_per_pixel = du_per_em / 16.; @@ -116,13 +104,40 @@ impl PlatformFontMethods for PlatformFont { let scaled_design_units_to_pixels = em_size / design_units_per_pixel; Ok(PlatformFont { - face: Nondebug(face), - _data: data, + face: Nondebug(font_face), em_size, du_to_px: design_units_to_pixels, scaled_du_to_px: scaled_design_units_to_pixels, }) } +} + +impl PlatformFontMethods for PlatformFont { + fn new_from_data( + _font_identifier: FontIdentifier, + data: &FontData, + pt_size: Option, + ) -> Result { + let font_face = FontFile::new_from_buffer(Arc::new(data.clone())) + .ok_or("Could not create FontFile")? + .create_face( + 0, /* face_index */ + dwrote::DWRITE_FONT_SIMULATIONS_NONE, + ) + .map_err(|_| "Could not create FontFace")?; + Self::new(font_face, pt_size) + } + + fn new_from_local_font_identifier( + font_identifier: LocalFontIdentifier, + pt_size: Option, + ) -> Result { + let font_face = FontCollection::system() + .get_font_from_descriptor(&font_identifier.font_descriptor) + .ok_or("Could not create Font from descriptor")? + .create_font_face(); + Self::new(font_face, pt_size) + } fn descriptor(&self) -> FontTemplateDescriptor { // We need the font (DWriteFont) in order to be able to query things like diff --git a/components/fonts/platform/windows/font_list.rs b/components/fonts/platform/windows/font_list.rs index 4360db0021a..266693a2f3f 100644 --- a/components/fonts/platform/windows/font_list.rs +++ b/components/fonts/platform/windows/font_list.rs @@ -12,6 +12,7 @@ use serde::{Deserialize, Serialize}; use style::values::computed::font::GenericFontFamily; use style::values::computed::{FontStyle as StyleFontStyle, FontWeight as StyleFontWeight}; use style::values::specified::font::FontStretchKeyword; +use webrender_api::NativeFontHandle; use crate::{ EmojiPresentationPreference, FallbackFontSelectionOptions, FontIdentifier, FontTemplate, @@ -43,14 +44,29 @@ impl LocalFontIdentifier { .map_or(0, |font| font.create_font_face().get_index()) } - pub(crate) fn read_data_from_file(&self) -> Vec { - let font = FontCollection::system() + pub(crate) fn native_font_handle(&self) -> NativeFontHandle { + let face = FontCollection::system() .get_font_from_descriptor(&self.font_descriptor) - .unwrap(); + .expect("Could not create Font from FontDescriptor") + .create_font_face(); + let path = face + .get_files() + .first() + .expect("Could not get FontFace files") + .get_font_file_path() + .expect("Could not get FontFace files path"); + NativeFontHandle { + path, + index: face.get_index(), + } + } + + pub(crate) fn read_data_from_file(&self) -> Option> { + let font = FontCollection::system().get_font_from_descriptor(&self.font_descriptor)?; let face = font.create_font_face(); let files = face.get_files(); assert!(!files.is_empty()); - files[0].get_font_file_bytes() + Some(files[0].get_font_file_bytes()) } } diff --git a/components/fonts/system_font_service.rs b/components/fonts/system_font_service.rs index ee5183104b7..c82aa2f2cb4 100644 --- a/components/fonts/system_font_service.rs +++ b/components/fonts/system_font_service.rs @@ -24,7 +24,6 @@ use style::values::computed::font::{ }; use style::values::computed::{FontStretch, FontWeight}; use style::values::specified::FontStretch as SpecifiedFontStretch; -use style::Atom; use tracing::{instrument, span, Level}; use webrender_api::{FontInstanceFlags, FontInstanceKey, FontKey}; use webrender_traits::CrossProcessCompositorApi; @@ -34,39 +33,31 @@ use crate::font_store::FontStore; 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; +use crate::platform::LocalFontIdentifier; #[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] pub enum FontIdentifier { Local(LocalFontIdentifier), Web(ServoUrl), - Mock(Atom), } impl FontIdentifier { pub fn index(&self) -> u32 { match *self { Self::Local(ref local_font_identifier) => local_font_identifier.index(), - Self::Web(_) | Self::Mock(_) => 0, + Self::Web(_) => 0, } } } -#[derive(Debug, Default, Deserialize, Serialize)] -pub struct FontTemplateRequestResult { - pub templates: Vec, - pub template_data: Vec<(FontIdentifier, Arc)>, -} - /// Commands that the `FontContext` sends to the `SystemFontService`. #[derive(Debug, Deserialize, Serialize)] pub enum SystemFontServiceMessage { GetFontTemplates( Option, SingleFontFamily, - IpcSender, + IpcSender>, ), GetFontInstance( FontIdentifier, @@ -123,7 +114,6 @@ impl SystemFontServiceProxySender { SystemFontServiceProxy { sender: Mutex::new(self.0.clone()), templates: Default::default(), - data_cache: Default::default(), } } } @@ -218,32 +208,12 @@ impl SystemFontService { &mut self, font_descriptor: Option, font_family: SingleFontFamily, - ) -> FontTemplateRequestResult { + ) -> Vec { let templates = self.find_font_templates(font_descriptor.as_ref(), &font_family); - let templates: Vec<_> = templates + 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, - } + .collect() } #[instrument(skip_all, fields(servo_profiling = true))] @@ -292,29 +262,16 @@ impl SystemFontService { let compositor_api = &self.compositor_api; let webrender_fonts = &mut self.webrender_fonts; - let font_data = self.local_families.get_or_initialize_font_data(&identifier); let font_key = *webrender_fonts .entry(identifier.clone()) .or_insert_with(|| { let font_key = self.free_font_keys.pop().unwrap(); - // 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 { - compositor_api - .add_system_font(font_key, local_font_identifier.native_font_handle()); - return font_key; - } - - compositor_api.add_font( - font_key, - font_data.as_ipc_shared_memory(), - identifier.index(), - ); + let FontIdentifier::Local(local_font_identifier) = identifier else { + unreachable!("Should never have a web font in the system font service"); + }; + compositor_api + .add_system_font(font_key, local_font_identifier.native_font_handle()); font_key }); @@ -386,7 +343,6 @@ struct FontTemplateCacheKey { pub struct SystemFontServiceProxy { sender: Mutex>, templates: RwLock>>, - data_cache: RwLock>>, } /// A version of `FontStyle` from Stylo that is serializable. Normally this is not @@ -551,8 +507,7 @@ impl SystemFontServiceProxy { )) .expect("failed to send message to system font service"); - let reply = response_port.recv(); - let Ok(reply) = reply else { + let Ok(templates) = response_port.recv() else { let font_thread_has_closed = self .sender .lock() @@ -564,20 +519,11 @@ impl SystemFontServiceProxy { ); panic!("SystemFontService has already exited."); }; - - let templates: Vec<_> = reply - .templates + templates .into_iter() .map(AtomicRefCell::new) .map(Arc::new) - .collect(); - self.data_cache.write().extend(reply.template_data); - - templates - } - - pub(crate) fn get_font_data(&self, identifier: &FontIdentifier) -> Option> { - self.data_cache.read().get(identifier).cloned() + .collect() } pub(crate) fn generate_font_key(&self) -> FontKey { diff --git a/components/fonts/tests/font.rs b/components/fonts/tests/font.rs index 2f735fb229e..78c507e7b93 100644 --- a/components/fonts/tests/font.rs +++ b/components/fonts/tests/font.rs @@ -20,13 +20,15 @@ use style::values::computed::{FontStretch, FontStyle, FontWeight}; use unicode_script::Script; fn make_font(path: PathBuf) -> Font { - let identifier = FontIdentifier::Web(ServoUrl::from_file_path(path.clone()).unwrap()); - let file = File::open(path).unwrap(); - let data = Arc::new(FontData::from_bytes( - file.bytes().map(Result::unwrap).collect(), - )); - let platform_font = - PlatformFont::new_from_data(identifier.clone(), data.as_arc().clone(), 0, None).unwrap(); + let mut bytes = Vec::new(); + File::open(path.clone()) + .expect("Couldn't open font file!") + .read_to_end(&mut bytes) + .unwrap(); + let data = FontData::from_bytes(&bytes); + + let identifier = FontIdentifier::Web(ServoUrl::from_file_path(path).unwrap()); + let platform_font = PlatformFont::new_from_data(identifier.clone(), &data, None).unwrap(); let template = FontTemplate { identifier, @@ -43,7 +45,7 @@ fn make_font(path: PathBuf) -> Font { Font::new( Arc::new(atomic_refcell::AtomicRefCell::new(template)), descriptor, - data, + Some(data), None, ) .unwrap() diff --git a/components/fonts/tests/font_context.rs b/components/fonts/tests/font_context.rs index 42e7bbf8c74..7b141cf2d3c 100644 --- a/components/fonts/tests/font_context.rs +++ b/components/fonts/tests/font_context.rs @@ -2,407 +2,397 @@ * 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::fs::File; -use std::io::prelude::*; -use std::path::PathBuf; -use std::sync::atomic::{AtomicI32, Ordering}; -use std::sync::Arc; -use std::thread; +// This currently only works on FreeType platforms as it requires being able to create +// local font identifiers from paths. +#[cfg(target_os = "linux")] +mod font_context { + use std::collections::HashMap; + use std::ffi::OsStr; + use std::path::PathBuf; + use std::sync::atomic::{AtomicI32, Ordering}; + use std::sync::Arc; + use std::thread; -use app_units::Au; -use fonts::platform::font::PlatformFont; -use fonts::{ - fallback_font_families, FallbackFontSelectionOptions, FontContext, FontData, FontDescriptor, - FontFamilyDescriptor, FontIdentifier, FontSearchScope, FontTemplate, FontTemplateRequestResult, - FontTemplates, PlatformFontMethods, SystemFontServiceMessage, SystemFontServiceProxy, - SystemFontServiceProxySender, -}; -use ipc_channel::ipc::{self, IpcReceiver}; -use net_traits::ResourceThreads; -use parking_lot::Mutex; -use servo_arc::Arc as ServoArc; -use servo_atoms::Atom; -use style::properties::longhands::font_variant_caps::computed_value::T as FontVariantCaps; -use style::properties::style_structs::Font as FontStyleStruct; -use style::values::computed::font::{ - FamilyName, FontFamily, FontFamilyList, FontFamilyNameSyntax, FontSize, FontStretch, FontStyle, - FontWeight, SingleFontFamily, -}; -use style::values::computed::{FontLanguageOverride, XLang}; -use style::values::generics::font::LineHeight; -use style::ArcSlice; -use webrender_api::{FontInstanceKey, FontKey, IdNamespace}; -use webrender_traits::CrossProcessCompositorApi; + use app_units::Au; + use fonts::platform::font::PlatformFont; + use fonts::{ + fallback_font_families, FallbackFontSelectionOptions, FontContext, FontDescriptor, + FontFamilyDescriptor, FontIdentifier, FontSearchScope, FontTemplate, FontTemplates, + LocalFontIdentifier, PlatformFontMethods, SystemFontServiceMessage, SystemFontServiceProxy, + SystemFontServiceProxySender, + }; + use ipc_channel::ipc::{self, IpcReceiver}; + use net_traits::ResourceThreads; + use parking_lot::Mutex; + use servo_arc::Arc as ServoArc; + use servo_atoms::Atom; + use style::properties::longhands::font_variant_caps::computed_value::T as FontVariantCaps; + use style::properties::style_structs::Font as FontStyleStruct; + use style::values::computed::font::{ + FamilyName, FontFamily, FontFamilyList, FontFamilyNameSyntax, FontSize, FontStretch, + FontStyle, FontWeight, SingleFontFamily, + }; + use style::values::computed::{FontLanguageOverride, XLang}; + use style::values::generics::font::LineHeight; + use style::ArcSlice; + use webrender_api::{FontInstanceKey, FontKey, IdNamespace}; + use webrender_traits::CrossProcessCompositorApi; -struct TestContext { - context: FontContext, - system_font_service: Arc, - system_font_service_proxy: SystemFontServiceProxy, -} - -impl TestContext { - fn new() -> TestContext { - let (system_font_service, system_font_service_proxy) = MockSystemFontService::spawn(); - let (core_sender, _) = ipc::channel().unwrap(); - let (storage_sender, _) = ipc::channel().unwrap(); - let mock_resource_threads = ResourceThreads::new(core_sender, storage_sender); - let mock_compositor_api = CrossProcessCompositorApi::dummy(); - - let proxy_clone = Arc::new(system_font_service_proxy.to_sender().to_proxy()); - Self { - context: FontContext::new(proxy_clone, mock_compositor_api, mock_resource_threads), - system_font_service, - system_font_service_proxy, - } - } -} - -impl Drop for TestContext { - fn drop(&mut self) { - self.system_font_service_proxy.exit(); - } -} - -struct MockSystemFontService { - families: Mutex>, - data: Mutex>>, - find_font_count: AtomicI32, -} - -impl MockSystemFontService { - pub fn spawn() -> (Arc, SystemFontServiceProxy) { - let (sender, receiver) = ipc::channel().unwrap(); - let system_font_service = Arc::new(Self::new()); - - let system_font_service_clone = system_font_service.clone(); - thread::Builder::new() - .name("MockSystemFontService".to_owned()) - .spawn(move || system_font_service_clone.run(receiver)) - .expect("Thread spawning failed"); - ( - system_font_service, - SystemFontServiceProxySender(sender).to_proxy(), - ) + struct TestContext { + context: FontContext, + system_font_service: Arc, + system_font_service_proxy: SystemFontServiceProxy, } - fn run(&self, receiver: IpcReceiver) { - loop { - match receiver.recv().unwrap() { - SystemFontServiceMessage::GetFontTemplates( - descriptor_to_match, - font_family, - result_sender, - ) => { - self.find_font_count.fetch_add(1, Ordering::Relaxed); + impl TestContext { + fn new() -> TestContext { + let (system_font_service, system_font_service_proxy) = MockSystemFontService::spawn(); + let (core_sender, _) = ipc::channel().unwrap(); + let (storage_sender, _) = ipc::channel().unwrap(); + let mock_resource_threads = ResourceThreads::new(core_sender, storage_sender); + let mock_compositor_api = CrossProcessCompositorApi::dummy(); - let SingleFontFamily::FamilyName(family_name) = font_family else { - let _ = result_sender.send(FontTemplateRequestResult::default()); - continue; - }; - - let templates: Vec<_> = self - .families - .lock() - .get_mut(&*family_name.name) - .map(|family| family.find_for_descriptor(descriptor_to_match.as_ref())) - .unwrap() - .into_iter() - .map(|template| template.borrow().clone()) - .collect(); - - let template_data = templates - .iter() - .map(|template| { - let identifier = template.identifier().clone(); - let data = self.data.lock().get(&identifier).unwrap().clone(); - (identifier, data) - }) - .collect(); - - let _ = result_sender.send(FontTemplateRequestResult { - templates, - template_data, - }); - }, - SystemFontServiceMessage::GetFontInstanceKey(result_sender) | - SystemFontServiceMessage::GetFontInstance(_, _, _, result_sender) => { - let _ = result_sender.send(FontInstanceKey(IdNamespace(0), 0)); - }, - SystemFontServiceMessage::GetFontKey(result_sender) => { - let _ = result_sender.send(FontKey(IdNamespace(0), 0)); - }, - SystemFontServiceMessage::Exit(result_sender) => { - let _ = result_sender.send(()); - break; - }, - SystemFontServiceMessage::Ping => {}, + let proxy_clone = Arc::new(system_font_service_proxy.to_sender().to_proxy()); + Self { + context: FontContext::new(proxy_clone, mock_compositor_api, mock_resource_threads), + system_font_service, + system_font_service_proxy, } } } - fn new() -> Self { - let proxy = Self { - families: Default::default(), - data: Default::default(), - find_font_count: AtomicI32::new(0), + impl Drop for TestContext { + fn drop(&mut self) { + self.system_font_service_proxy.exit(); + } + } + + fn font_face_name(identifier: &FontIdentifier) -> String { + let FontIdentifier::Local(local_identifier) = identifier else { + unreachable!("Should never create a web font for this test."); }; + PathBuf::from(&*local_identifier.path) + .file_name() + .and_then(OsStr::to_str) + .map(|string| string.replace(".ttf", "")) + .expect("Could not extract font face name.") + } - let mut csstest_ascii = FontTemplates::default(); - proxy.add_face(&mut csstest_ascii, "csstest-ascii"); + struct MockSystemFontService { + families: Mutex>, + find_font_count: AtomicI32, + } - let mut csstest_basic = FontTemplates::default(); - proxy.add_face(&mut csstest_basic, "csstest-basic-regular"); + impl MockSystemFontService { + fn spawn() -> (Arc, SystemFontServiceProxy) { + let (sender, receiver) = ipc::channel().unwrap(); + let system_font_service = Arc::new(Self::new()); - let mut fallback = FontTemplates::default(); - 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, - ); + let system_font_service_clone = system_font_service.clone(); + thread::Builder::new() + .name("MockSystemFontService".to_owned()) + .spawn(move || system_font_service_clone.run(receiver)) + .expect("Thread spawning failed"); + ( + system_font_service, + SystemFontServiceProxySender(sender).to_proxy(), + ) } - proxy + fn run(&self, receiver: IpcReceiver) { + loop { + match receiver.recv().unwrap() { + SystemFontServiceMessage::GetFontTemplates( + descriptor_to_match, + font_family, + result_sender, + ) => { + self.find_font_count.fetch_add(1, Ordering::Relaxed); + + let SingleFontFamily::FamilyName(family_name) = font_family else { + let _ = result_sender.send(vec![]); + continue; + }; + + let _ = result_sender.send( + self.families + .lock() + .get_mut(&*family_name.name) + .map(|family| { + family.find_for_descriptor(descriptor_to_match.as_ref()) + }) + .unwrap() + .into_iter() + .map(|template| template.borrow().clone()) + .collect(), + ); + }, + SystemFontServiceMessage::GetFontInstanceKey(result_sender) | + SystemFontServiceMessage::GetFontInstance(_, _, _, result_sender) => { + let _ = result_sender.send(FontInstanceKey(IdNamespace(0), 0)); + }, + SystemFontServiceMessage::GetFontKey(result_sender) => { + let _ = result_sender.send(FontKey(IdNamespace(0), 0)); + }, + SystemFontServiceMessage::Exit(result_sender) => { + let _ = result_sender.send(()); + break; + }, + SystemFontServiceMessage::Ping => {}, + } + } + } + + fn new() -> Self { + let proxy = Self { + families: Default::default(), + find_font_count: AtomicI32::new(0), + }; + + let mut csstest_ascii = FontTemplates::default(); + proxy.add_face(&mut csstest_ascii, "csstest-ascii"); + + let mut csstest_basic = FontTemplates::default(); + proxy.add_face(&mut csstest_basic, "csstest-basic-regular"); + + let mut fallback = FontTemplates::default(); + 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 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 local_font_identifier = LocalFontIdentifier { + path: path.to_str().expect("Could not load test font").into(), + variation_index: 0, + }; + let handle = + PlatformFont::new_from_local_font_identifier(local_font_identifier.clone(), None) + .expect("Could not load test font"); + + family.add_template(FontTemplate::new( + FontIdentifier::Local(local_font_identifier), + handle.descriptor(), + None, + )); + } } - 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 = Arc::new(FontData::from_bytes( - file.bytes().map(|b| b.unwrap()).collect(), - )); - - let identifier = FontIdentifier::Mock(name.into()); - let handle = - PlatformFont::new_from_data(identifier.clone(), data.as_arc().clone(), 0, None) - .expect("Could not load test font"); - family.add_template(FontTemplate::new( - identifier.clone(), - handle.descriptor(), - None, - )); - - self.data.lock().insert(identifier, data); + fn style() -> FontStyleStruct { + let mut style = FontStyleStruct { + font_family: FontFamily::serif(), + font_style: FontStyle::NORMAL, + font_variant_caps: FontVariantCaps::Normal, + font_weight: FontWeight::normal(), + font_size: FontSize::medium(), + font_stretch: FontStretch::hundred(), + hash: 0, + font_language_override: FontLanguageOverride::normal(), + line_height: LineHeight::Normal, + _x_lang: XLang::get_initial_value(), + }; + style.compute_font_hash(); + style } -} -fn style() -> FontStyleStruct { - let mut style = FontStyleStruct { - font_family: FontFamily::serif(), - font_style: FontStyle::NORMAL, - font_variant_caps: FontVariantCaps::Normal, - font_weight: FontWeight::normal(), - font_size: FontSize::medium(), - font_stretch: FontStretch::hundred(), - hash: 0, - font_language_override: FontLanguageOverride::normal(), - line_height: LineHeight::Normal, - _x_lang: XLang::get_initial_value(), - }; - style.compute_font_hash(); - style -} + fn font_family(names: Vec<&str>) -> FontFamily { + let names = names.into_iter().map(|name| { + SingleFontFamily::FamilyName(FamilyName { + name: Atom::from(name), + syntax: FontFamilyNameSyntax::Quoted, + }) + }); -fn font_family(names: Vec<&str>) -> FontFamily { - let names = names.into_iter().map(|name| { - SingleFontFamily::FamilyName(FamilyName { - name: Atom::from(name), + FontFamily { + families: FontFamilyList { + list: ArcSlice::from_iter(names), + }, + is_system_font: false, + is_initial: false, + } + } + + #[test] + fn test_font_group_is_cached_by_style() { + let context = TestContext::new(); + + let style1 = style(); + + let mut style2 = style(); + style2.set_font_style(FontStyle::ITALIC); + + assert!( + std::ptr::eq( + &*context + .context + .font_group(ServoArc::new(style1.clone())) + .read(), + &*context + .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 + .context + .font_group(ServoArc::new(style1.clone())) + .read(), + &*context + .context + .font_group(ServoArc::new(style2.clone())) + .read() + ), + "different font groups should be returned for two styles with different hashes" + ) + } + + #[test] + fn test_font_group_find_by_codepoint() { + let mut context = TestContext::new(); + + let mut style = style(); + style.set_font_family(font_family(vec!["CSSTest ASCII", "CSSTest Basic"])); + + let group = context.context.font_group(ServoArc::new(style)); + + let font = group + .write() + .find_by_codepoint(&mut context.context, 'a', None) + .unwrap(); + assert_eq!(&font_face_name(&font.identifier()), "csstest-ascii"); + assert_eq!( + context + .system_font_service + .find_font_count + .fetch_add(0, Ordering::Relaxed), + 1, + "only the first font in the list should have been loaded" + ); + + let font = group + .write() + .find_by_codepoint(&mut context.context, 'a', None) + .unwrap(); + assert_eq!(&font_face_name(&font.identifier()), "csstest-ascii"); + assert_eq!( + context + .system_font_service + .find_font_count + .fetch_add(0, Ordering::Relaxed), + 1, + "we shouldn't load the same font a second time" + ); + + let font = group + .write() + .find_by_codepoint(&mut context.context, 'á', None) + .unwrap(); + assert_eq!(&font_face_name(&font.identifier()), "csstest-basic-regular"); + assert_eq!( + context + .system_font_service + .find_font_count + .fetch_add(0, Ordering::Relaxed), + 2, + "both fonts should now have been loaded" + ); + } + + #[test] + fn test_font_fallback() { + let mut context = TestContext::new(); + + let mut style = style(); + style.set_font_family(font_family(vec!["CSSTest ASCII"])); + + let group = context.context.font_group(ServoArc::new(style)); + + let font = group + .write() + .find_by_codepoint(&mut context.context, 'a', None) + .unwrap(); + assert_eq!( + &font_face_name(&font.identifier()), + "csstest-ascii", + "a family in the group should be used if there is a matching glyph" + ); + + let font = group + .write() + .find_by_codepoint(&mut context.context, 'á', None) + .unwrap(); + assert_eq!( + &font_face_name(&font.identifier()), + "csstest-basic-regular", + "a fallback font should be used if there is no matching glyph in the group" + ); + } + + #[test] + fn test_font_template_is_cached() { + let context = TestContext::new(); + + let mut font_descriptor = FontDescriptor { + weight: FontWeight::normal(), + stretch: FontStretch::hundred(), + style: FontStyle::normal(), + variant: FontVariantCaps::Normal, + pt_size: Au(10), + }; + + let family = SingleFontFamily::FamilyName(FamilyName { + name: "CSSTest Basic".into(), syntax: FontFamilyNameSyntax::Quoted, - }) - }); + }); + let family_descriptor = FontFamilyDescriptor::new(family, FontSearchScope::Any); - FontFamily { - families: FontFamilyList { - list: ArcSlice::from_iter(names), - }, - is_system_font: false, - is_initial: false, + let font_template = context + .context + .matching_templates(&font_descriptor, &family_descriptor)[0] + .clone(); + + let font1 = context + .context + .font(font_template.clone(), &font_descriptor) + .unwrap(); + + font_descriptor.pt_size = Au(20); + let font2 = context + .context + .font(font_template.clone(), &font_descriptor) + .unwrap(); + + assert_ne!( + font1.descriptor.pt_size, font2.descriptor.pt_size, + "the same font should not have been returned" + ); + + assert_eq!( + context + .system_font_service + .find_font_count + .fetch_add(0, Ordering::Relaxed), + 1, + "we should only have fetched the template data from the cache thread once" + ); } } - -#[test] -fn test_font_group_is_cached_by_style() { - let context = TestContext::new(); - - let style1 = style(); - - let mut style2 = style(); - style2.set_font_style(FontStyle::ITALIC); - - assert!( - std::ptr::eq( - &*context - .context - .font_group(ServoArc::new(style1.clone())) - .read(), - &*context - .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 - .context - .font_group(ServoArc::new(style1.clone())) - .read(), - &*context - .context - .font_group(ServoArc::new(style2.clone())) - .read() - ), - "different font groups should be returned for two styles with different hashes" - ) -} - -#[test] -fn test_font_group_find_by_codepoint() { - let mut context = TestContext::new(); - - let mut style = style(); - style.set_font_family(font_family(vec!["CSSTest ASCII", "CSSTest Basic"])); - - let group = context.context.font_group(ServoArc::new(style)); - - let font = group - .write() - .find_by_codepoint(&mut context.context, 'a', None) - .unwrap(); - assert_eq!( - font.identifier(), - FontIdentifier::Mock("csstest-ascii".into()) - ); - assert_eq!( - context - .system_font_service - .find_font_count - .fetch_add(0, Ordering::Relaxed), - 1, - "only the first font in the list should have been loaded" - ); - - let font = group - .write() - .find_by_codepoint(&mut context.context, 'a', None) - .unwrap(); - assert_eq!( - font.identifier(), - FontIdentifier::Mock("csstest-ascii".into()) - ); - assert_eq!( - context - .system_font_service - .find_font_count - .fetch_add(0, Ordering::Relaxed), - 1, - "we shouldn't load the same font a second time" - ); - - let font = group - .write() - .find_by_codepoint(&mut context.context, 'á', None) - .unwrap(); - assert_eq!( - font.identifier(), - FontIdentifier::Mock("csstest-basic-regular".into()) - ); - assert_eq!( - context - .system_font_service - .find_font_count - .fetch_add(0, Ordering::Relaxed), - 2, - "both fonts should now have been loaded" - ); -} - -#[test] -fn test_font_fallback() { - let mut context = TestContext::new(); - - let mut style = style(); - style.set_font_family(font_family(vec!["CSSTest ASCII"])); - - let group = context.context.font_group(ServoArc::new(style)); - - let font = group - .write() - .find_by_codepoint(&mut context.context, 'a', None) - .unwrap(); - assert_eq!( - font.identifier(), - FontIdentifier::Mock("csstest-ascii".into()), - "a family in the group should be used if there is a matching glyph" - ); - - let font = group - .write() - .find_by_codepoint(&mut context.context, 'á', None) - .unwrap(); - assert_eq!( - font.identifier(), - FontIdentifier::Mock("csstest-basic-regular".into()), - "a fallback font should be used if there is no matching glyph in the group" - ); -} - -#[test] -fn test_font_template_is_cached() { - let context = TestContext::new(); - - let mut font_descriptor = FontDescriptor { - weight: FontWeight::normal(), - stretch: FontStretch::hundred(), - style: FontStyle::normal(), - variant: FontVariantCaps::Normal, - pt_size: Au(10), - }; - - let family = SingleFontFamily::FamilyName(FamilyName { - name: "CSSTest Basic".into(), - syntax: FontFamilyNameSyntax::Quoted, - }); - let family_descriptor = FontFamilyDescriptor::new(family, FontSearchScope::Any); - - let font_template = context - .context - .matching_templates(&font_descriptor, &family_descriptor)[0] - .clone(); - - let font1 = context - .context - .font(font_template.clone(), &font_descriptor) - .unwrap(); - - font_descriptor.pt_size = Au(20); - let font2 = context - .context - .font(font_template.clone(), &font_descriptor) - .unwrap(); - - assert_ne!( - font1.descriptor.pt_size, font2.descriptor.pt_size, - "the same font should not have been returned" - ); - - assert_eq!( - context - .system_font_service - .find_font_count - .fetch_add(0, Ordering::Relaxed), - 1, - "we should only have fetched the template data from the cache thread once" - ); -} diff --git a/components/fonts/tests/font_template.rs b/components/fonts/tests/font_template.rs index 75290708cc9..031e57342db 100644 --- a/components/fonts/tests/font_template.rs +++ b/components/fonts/tests/font_template.rs @@ -9,10 +9,9 @@ fn test_font_template_descriptor() { use std::fs::File; use std::io::prelude::*; use std::path::PathBuf; - use std::sync::Arc; use fonts::platform::font::PlatformFont; - use fonts::{FontIdentifier, FontTemplateDescriptor, PlatformFontMethods}; + use fonts::{FontData, FontIdentifier, FontTemplateDescriptor, PlatformFontMethods}; use servo_url::ServoUrl; use style::values::computed::font::{FontStretch, FontStyle, FontWeight}; @@ -29,9 +28,15 @@ fn test_font_template_descriptor() { path.push(format!("{}.ttf", filename)); let identifier = FontIdentifier::Web(ServoUrl::from_file_path(path.clone()).unwrap()); - let file = File::open(path.clone()).unwrap(); - let data = file.bytes().map(|b| b.unwrap()).collect(); - let handle = PlatformFont::new_from_data(identifier, Arc::new(data), 0, None).unwrap(); + + let mut bytes = Vec::new(); + File::open(path.clone()) + .expect("Couldn't open font file!") + .read_to_end(&mut bytes) + .unwrap(); + let data = FontData::from_bytes(&bytes); + + let handle = PlatformFont::new_from_data(identifier, &data, None).unwrap(); handle.descriptor() }