mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
fonts: Instantiate system fonts using system font loaders (#33747)
System fonts used to be instantiated using the system font loader and this change restores that behavior. In addition, on macOS and FreeType platforms font data for system fonts is loaded using memory mapping. The benefit is that system font loaders typically are able to cache fonts in system memory (using memory mapping, for instance) and we'd like to load them in a the way most compatible with other applications. On my Linux system, this manages to get the overhead of loading a very large font down from 10ms to approximately 1ms. Subsequent runs show even less overhead. We've measured similar gains on macOS systems. Currently, system font data must be loaded into memory manually for canvas and this is unlikely to change even with a switch to `vello`. The use of explicit memmory mapping should help in this case -- though it probably won't be possible to use this properly on macOS and Windows if we ever want to load fonts from TTCs properly. Signed-off-by: Martin Robinson <mrobinson@igalia.com> Co-authored-by: Mukilan Thiyagarajan <mukilan@igalia.com>
This commit is contained in:
parent
4564ce2fcc
commit
0553789d48
24 changed files with 771 additions and 810 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2052,6 +2052,7 @@ dependencies = [
|
|||
"log",
|
||||
"malloc_size_of",
|
||||
"malloc_size_of_derive",
|
||||
"memmap2",
|
||||
"net_traits",
|
||||
"num-traits",
|
||||
"parking_lot",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -231,7 +231,7 @@ impl<'a> PathBuilderRef<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Default)]
|
||||
struct UnshapedTextRun<'a> {
|
||||
font: Option<FontRef>,
|
||||
script: Script,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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<Au>,
|
||||
data: &Arc<Vec<u8>>,
|
||||
data: &Option<FontData>,
|
||||
) -> Result<PlatformFont, &'static str> {
|
||||
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<Au>,
|
||||
) -> Result<PlatformFont, &'static str>;
|
||||
|
||||
fn new_from_data(
|
||||
font_identifier: FontIdentifier,
|
||||
data: Arc<Vec<u8>>,
|
||||
face_index: u32,
|
||||
data: &FontData,
|
||||
pt_size: Option<Au>,
|
||||
) -> Result<PlatformFont, &'static str>;
|
||||
|
||||
|
@ -215,13 +226,15 @@ 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,
|
||||
|
||||
/// The data for this font. This might be uninitialized for system fonts.
|
||||
data: OnceLock<FontData>,
|
||||
|
||||
shaper: OnceLock<Shaper>,
|
||||
cached_shape_data: RwLock<CachedShapeData>,
|
||||
pub font_instance_key: OnceLock<FontInstanceKey>,
|
||||
|
@ -262,23 +275,20 @@ impl Font {
|
|||
pub fn new(
|
||||
template: FontTemplateRef,
|
||||
descriptor: FontDescriptor,
|
||||
data: Arc<FontData>,
|
||||
data: Option<FontData>,
|
||||
synthesized_small_caps: Option<FontRef>,
|
||||
) -> Result<Font, &'static str> {
|
||||
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<Font>;
|
|||
/// 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<Vec<FontGroupFamilyMember>>,
|
||||
|
|
|
@ -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<HashMap<(FontKey, Au), FontInstanceKey>>,
|
||||
|
||||
/// 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<HashMap<FontIdentifier, FontData>>,
|
||||
|
||||
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<FontData> {
|
||||
fn get_font_data(&self, identifier: &FontIdentifier) -> Option<FontData> {
|
||||
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<FontContext> {
|
|||
}
|
||||
|
||||
// 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<FontContext> {
|
|||
});
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
|
|
@ -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<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>>;
|
||||
|
||||
|
@ -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<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()))
|
||||
},
|
||||
_ => unreachable!("Web and mock 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));
|
||||
}
|
||||
}
|
||||
|
||||
/// A struct that represents the available templates in a "simple family." A simple family
|
||||
|
|
|
@ -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<IpcSharedMemory>);
|
||||
|
||||
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<IpcSharedMemory> {
|
||||
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)]
|
||||
|
|
|
@ -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<FontList> = 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<u8> {
|
||||
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
|
||||
|
|
|
@ -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<Vec<u8>>,
|
||||
face: ReentrantMutex<FT_Face>,
|
||||
requested_face_size: Au,
|
||||
actual_face_size: Au,
|
||||
|
@ -98,37 +97,62 @@ impl Drop for PlatformFont {
|
|||
}
|
||||
}
|
||||
|
||||
fn create_face(data: Arc<Vec<u8>>, face_index: u32) -> Result<FT_Face, &'static str> {
|
||||
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<Au>,
|
||||
) -> Result<PlatformFont, &'static str> {
|
||||
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<Au>,
|
||||
) -> Result<PlatformFont, &'static str> {
|
||||
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<Vec<u8>>,
|
||||
face_index: u32,
|
||||
requested_size: Option<Au>,
|
||||
) -> Result<PlatformFont, &'static str> {
|
||||
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,
|
||||
})
|
||||
|
|
|
@ -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<u8> {
|
||||
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<F>(mut callback: F)
|
||||
where
|
||||
F: FnMut(String),
|
||||
|
|
74
components/fonts/platform/freetype/mod.rs
Normal file
74
components/fonts/platform/freetype/mod.rs
Normal file
|
@ -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<Vec<u8>> {
|
||||
let file = File::open(Path::new(&*self.path)).ok()?;
|
||||
let mmap = unsafe { Mmap::map(&file).ok()? };
|
||||
Some((&mmap[..]).to_vec())
|
||||
}
|
||||
}
|
|
@ -23,7 +23,7 @@ use unicode_script::Script;
|
|||
|
||||
use crate::{
|
||||
EmojiPresentationPreference, FallbackFontSelectionOptions, FontIdentifier, FontTemplate,
|
||||
FontTemplateDescriptor, LowercaseFontFamilyName,
|
||||
FontTemplateDescriptor, LocalFontIdentifier, LowercaseFontFamilyName,
|
||||
};
|
||||
|
||||
static FONT_LIST: LazyLock<FontList> = 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<u8> {
|
||||
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
|
||||
|
|
|
@ -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<Au, CTFont>;
|
|||
impl CoreTextFontCache {
|
||||
pub(crate) fn core_text_font(
|
||||
font_identifier: FontIdentifier,
|
||||
data: Arc<Vec<u8>>,
|
||||
data: Option<&FontData>,
|
||||
pt_size: f64,
|
||||
) -> Option<CTFont> {
|
||||
//// 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)
|
||||
},
|
||||
|
|
|
@ -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<Vec<u8>>,
|
||||
h_kern_subtable: Option<CachedKernTable>,
|
||||
}
|
||||
|
||||
|
@ -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<Vec<u8>>,
|
||||
_face_index: u32,
|
||||
pt_size: Option<Au>,
|
||||
data: Option<&FontData>,
|
||||
requested_size: Option<Au>,
|
||||
) -> Result<PlatformFont, &'static str> {
|
||||
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<Au>,
|
||||
) -> Result<PlatformFont, &'static str> {
|
||||
Self::new(font_identifier, Some(data), requested_size)
|
||||
}
|
||||
|
||||
fn new_from_local_font_identifier(
|
||||
font_identifier: LocalFontIdentifier,
|
||||
requested_size: Option<Au>,
|
||||
) -> Result<PlatformFont, &'static str> {
|
||||
Self::new(FontIdentifier::Local(font_identifier), None, requested_size)
|
||||
}
|
||||
|
||||
fn descriptor(&self) -> FontTemplateDescriptor {
|
||||
let traits = self.ctfont.all_traits();
|
||||
|
|
|
@ -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<u8> {
|
||||
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<Vec<u8>> {
|
||||
// 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())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<FontFace>,
|
||||
/// A reference to this data used to create this [`PlatformFont`], ensuring the
|
||||
/// data stays alive of the lifetime of this struct.
|
||||
_data: Arc<Vec<u8>>,
|
||||
em_size: f32,
|
||||
du_to_px: f32,
|
||||
scaled_du_to_px: f32,
|
||||
|
@ -94,20 +92,10 @@ impl<T> Deref for Nondebug<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl PlatformFontMethods for PlatformFont {
|
||||
fn new_from_data(
|
||||
_font_identifier: FontIdentifier,
|
||||
data: Arc<Vec<u8>>,
|
||||
face_index: u32,
|
||||
pt_size: Option<Au>,
|
||||
) -> Result<Self, &'static str> {
|
||||
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<Au>) -> Result<Self, &'static str> {
|
||||
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<Au>,
|
||||
) -> Result<Self, &'static str> {
|
||||
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<Au>,
|
||||
) -> Result<PlatformFont, &'static str> {
|
||||
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
|
||||
|
|
|
@ -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<u8> {
|
||||
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<Vec<u8>> {
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<FontTemplate>,
|
||||
pub template_data: Vec<(FontIdentifier, Arc<FontData>)>,
|
||||
}
|
||||
|
||||
/// Commands that the `FontContext` sends to the `SystemFontService`.
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub enum SystemFontServiceMessage {
|
||||
GetFontTemplates(
|
||||
Option<FontDescriptor>,
|
||||
SingleFontFamily,
|
||||
IpcSender<FontTemplateRequestResult>,
|
||||
IpcSender<Vec<FontTemplate>>,
|
||||
),
|
||||
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<FontDescriptor>,
|
||||
font_family: SingleFontFamily,
|
||||
) -> FontTemplateRequestResult {
|
||||
) -> Vec<FontTemplate> {
|
||||
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<IpcSender<SystemFontServiceMessage>>,
|
||||
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
|
||||
|
@ -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<Arc<FontData>> {
|
||||
self.data_cache.read().get(identifier).cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn generate_font_key(&self) -> FontKey {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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<MockSystemFontService>,
|
||||
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<HashMap<String, FontTemplates>>,
|
||||
data: Mutex<HashMap<FontIdentifier, Arc<FontData>>>,
|
||||
find_font_count: AtomicI32,
|
||||
}
|
||||
|
||||
impl MockSystemFontService {
|
||||
pub fn spawn() -> (Arc<MockSystemFontService>, 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<MockSystemFontService>,
|
||||
system_font_service_proxy: SystemFontServiceProxy,
|
||||
}
|
||||
|
||||
fn run(&self, receiver: IpcReceiver<SystemFontServiceMessage>) {
|
||||
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<HashMap<String, FontTemplates>>,
|
||||
find_font_count: AtomicI32,
|
||||
}
|
||||
|
||||
let mut csstest_basic = FontTemplates::default();
|
||||
proxy.add_face(&mut csstest_basic, "csstest-basic-regular");
|
||||
impl MockSystemFontService {
|
||||
fn spawn() -> (Arc<MockSystemFontService>, 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<SystemFontServiceMessage>) {
|
||||
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"
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue