mirror of
https://github.com/servo/servo.git
synced 2025-07-28 17:50:37 +01:00
This is the first part of ensuring that unused fonts do not leak. This change makes it so that when a stylesheet is removed, the corresponding web fonts are removed from the `FontContext`. Note: WebRender assets are still leaked, which was the situation before for all fonts. A followup change will fix this issue. Fixes #15139. Signed-off-by: Martin Robinson <mrobinson@igalia.com> Co-authored-by: Mukilan Thiyagarajan <mukilan@igalia.com>
555 lines
20 KiB
Rust
555 lines
20 KiB
Rust
/* 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::borrow::ToOwned;
|
|
use std::collections::HashMap;
|
|
use std::ops::{Deref, RangeInclusive};
|
|
use std::sync::Arc;
|
|
use std::{fmt, thread};
|
|
|
|
use app_units::Au;
|
|
use atomic_refcell::AtomicRefCell;
|
|
use ipc_channel::ipc::{self, IpcBytesReceiver, IpcBytesSender, IpcReceiver, IpcSender};
|
|
use log::debug;
|
|
use malloc_size_of_derive::MallocSizeOf;
|
|
use serde::{Deserialize, Serialize};
|
|
use servo_atoms::Atom;
|
|
use servo_url::ServoUrl;
|
|
use style::font_face::{FontFaceRuleData, FontStyle as FontFaceStyle};
|
|
use style::values::computed::font::{FixedPoint, FontStyleFixedPoint};
|
|
use style::values::computed::{FontStretch, FontWeight};
|
|
use style::values::specified::FontStretch as SpecifiedFontStretch;
|
|
use webrender_api::{FontInstanceFlags, FontInstanceKey, FontKey};
|
|
use webrender_traits::WebRenderFontApi;
|
|
|
|
use crate::font::{FontDescriptor, FontFamilyName};
|
|
use crate::font_store::FontStore;
|
|
use crate::font_template::{
|
|
FontTemplate, FontTemplateDescriptor, FontTemplateRef, FontTemplateRefMethods,
|
|
};
|
|
use crate::platform::font_list::{
|
|
for_each_available_family, for_each_variation, system_default_family, LocalFontIdentifier,
|
|
SANS_SERIF_FONT_FAMILY,
|
|
};
|
|
|
|
#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
|
|
pub enum FontIdentifier {
|
|
Local(LocalFontIdentifier),
|
|
Web(ServoUrl),
|
|
}
|
|
|
|
impl FontIdentifier {
|
|
pub fn index(&self) -> u32 {
|
|
match *self {
|
|
Self::Local(ref local_font_identifier) => local_font_identifier.index(),
|
|
Self::Web(_) => 0,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
pub struct SerializedFontTemplate {
|
|
identifier: FontIdentifier,
|
|
descriptor: FontTemplateDescriptor,
|
|
bytes_receiver: ipc_channel::ipc::IpcBytesReceiver,
|
|
}
|
|
|
|
/// Commands that the FontContext sends to the font cache thread.
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
pub enum Command {
|
|
GetFontTemplates(
|
|
Option<FontDescriptor>,
|
|
FontFamilyName,
|
|
IpcSender<Vec<SerializedFontTemplate>>,
|
|
),
|
|
GetFontInstance(
|
|
FontIdentifier,
|
|
Au,
|
|
FontInstanceFlags,
|
|
IpcSender<FontInstanceKey>,
|
|
),
|
|
GetWebFont(IpcBytesReceiver, u32, IpcSender<FontKey>),
|
|
GetWebFontInstance(FontKey, f32, FontInstanceFlags, IpcSender<FontInstanceKey>),
|
|
Exit(IpcSender<()>),
|
|
Ping,
|
|
}
|
|
|
|
/// The font cache thread itself. It maintains a list of reference counted
|
|
/// font templates that are currently in use.
|
|
struct FontCache {
|
|
port: IpcReceiver<Command>,
|
|
generic_fonts: HashMap<FontFamilyName, LowercaseString>,
|
|
font_data: HashMap<FontIdentifier, Arc<Vec<u8>>>,
|
|
local_families: FontStore,
|
|
webrender_api: Box<dyn WebRenderFontApi>,
|
|
webrender_fonts: HashMap<FontIdentifier, FontKey>,
|
|
font_instances: HashMap<(FontKey, Au), FontInstanceKey>,
|
|
}
|
|
|
|
fn populate_generic_fonts() -> HashMap<FontFamilyName, LowercaseString> {
|
|
let mut generic_fonts = HashMap::with_capacity(5);
|
|
|
|
append_map(&mut generic_fonts, "serif", "Times New Roman");
|
|
append_map(&mut generic_fonts, "sans-serif", SANS_SERIF_FONT_FAMILY);
|
|
append_map(&mut generic_fonts, "cursive", "Apple Chancery");
|
|
append_map(&mut generic_fonts, "fantasy", "Papyrus");
|
|
append_map(&mut generic_fonts, "monospace", "Menlo");
|
|
|
|
fn append_map(
|
|
generic_fonts: &mut HashMap<FontFamilyName, LowercaseString>,
|
|
generic_name: &str,
|
|
mapped_name: &str,
|
|
) {
|
|
let family_name = match system_default_family(generic_name) {
|
|
Some(system_default) => LowercaseString::new(&system_default),
|
|
None => LowercaseString::new(mapped_name),
|
|
};
|
|
|
|
let generic_name = FontFamilyName::Generic(Atom::from(generic_name));
|
|
|
|
generic_fonts.insert(generic_name, family_name);
|
|
}
|
|
|
|
generic_fonts
|
|
}
|
|
|
|
impl FontCache {
|
|
fn run(&mut self) {
|
|
loop {
|
|
let msg = self.port.recv().unwrap();
|
|
|
|
match msg {
|
|
Command::GetFontTemplates(descriptor_to_match, font_family_name, result) => {
|
|
let templates =
|
|
self.find_font_templates(descriptor_to_match.as_ref(), &font_family_name);
|
|
debug!("Found templates for descriptor {descriptor_to_match:?}: ");
|
|
debug!(" {templates:?}");
|
|
|
|
let (serialized_templates, senders): (
|
|
Vec<SerializedFontTemplate>,
|
|
Vec<(FontTemplateRef, IpcBytesSender)>,
|
|
) = templates
|
|
.into_iter()
|
|
.map(|template| {
|
|
let (bytes_sender, bytes_receiver) =
|
|
ipc::bytes_channel().expect("failed to create IPC channel");
|
|
(
|
|
SerializedFontTemplate {
|
|
identifier: template.identifier().clone(),
|
|
descriptor: template.descriptor().clone(),
|
|
bytes_receiver,
|
|
},
|
|
(template.clone(), bytes_sender),
|
|
)
|
|
})
|
|
.unzip();
|
|
|
|
let _ = result.send(serialized_templates);
|
|
|
|
// NB: This will load the font into memory if it hasn't been loaded already.
|
|
for (font_template, bytes_sender) in senders.iter() {
|
|
let identifier = font_template.identifier();
|
|
let data = self
|
|
.font_data
|
|
.entry(identifier)
|
|
.or_insert_with(|| font_template.data());
|
|
let _ = bytes_sender.send(data);
|
|
}
|
|
},
|
|
Command::GetFontInstance(identifier, pt_size, flags, result) => {
|
|
let _ = result.send(self.get_font_instance(identifier, pt_size, flags));
|
|
},
|
|
Command::GetWebFont(bytes_receiver, font_index, result_sender) => {
|
|
self.webrender_api.forward_add_font_message(
|
|
bytes_receiver,
|
|
font_index,
|
|
result_sender,
|
|
);
|
|
},
|
|
Command::GetWebFontInstance(
|
|
font_key,
|
|
font_size,
|
|
font_instance_flags,
|
|
result_sender,
|
|
) => {
|
|
self.webrender_api.forward_add_font_instance_message(
|
|
font_key,
|
|
font_size,
|
|
font_instance_flags,
|
|
result_sender,
|
|
);
|
|
},
|
|
Command::Ping => (),
|
|
Command::Exit(result) => {
|
|
let _ = result.send(());
|
|
break;
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
fn refresh_local_families(&mut self) {
|
|
self.local_families.clear();
|
|
for_each_available_family(|family_name| {
|
|
let family_name = LowercaseString::new(&family_name);
|
|
self.local_families.families.entry(family_name).or_default();
|
|
});
|
|
}
|
|
|
|
fn transform_family(&self, family_name: &FontFamilyName) -> LowercaseString {
|
|
match self.generic_fonts.get(family_name) {
|
|
None => LowercaseString::from(family_name),
|
|
Some(mapped_family) => (*mapped_family).clone(),
|
|
}
|
|
}
|
|
|
|
fn find_font_templates(
|
|
&mut self,
|
|
descriptor_to_match: Option<&FontDescriptor>,
|
|
family_name: &FontFamilyName,
|
|
) -> Vec<FontTemplateRef> {
|
|
// TODO(Issue #188): look up localized font family names if canonical name not found
|
|
// look up canonical name
|
|
// TODO(Issue #192: handle generic font families, like 'serif' and 'sans-serif'.
|
|
// if such family exists, try to match style to a font
|
|
let family_name = self.transform_family(family_name);
|
|
self.local_families
|
|
.families
|
|
.get_mut(&family_name)
|
|
.map(|font_templates| {
|
|
if font_templates.templates.is_empty() {
|
|
for_each_variation(&family_name, |font_template| {
|
|
font_templates.add_template(font_template);
|
|
});
|
|
}
|
|
|
|
font_templates.find_for_descriptor(descriptor_to_match)
|
|
})
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
fn get_font_instance(
|
|
&mut self,
|
|
identifier: FontIdentifier,
|
|
pt_size: Au,
|
|
flags: FontInstanceFlags,
|
|
) -> FontInstanceKey {
|
|
let webrender_font_api = &self.webrender_api;
|
|
let webrender_fonts = &mut self.webrender_fonts;
|
|
let font_data = self
|
|
.font_data
|
|
.get(&identifier)
|
|
.expect("Got unexpected FontIdentifier")
|
|
.clone();
|
|
|
|
let font_key = *webrender_fonts
|
|
.entry(identifier.clone())
|
|
.or_insert_with(|| {
|
|
// CoreText cannot reliably create CoreTextFonts for system fonts stored
|
|
// as part of TTC files, so on CoreText platforms, create a system font in
|
|
// WebRender using the LocalFontIdentifier. This has the downside of
|
|
// causing the font to be loaded into memory again (bummer!), so only do
|
|
// this for those platforms.
|
|
#[cfg(target_os = "macos")]
|
|
if let FontIdentifier::Local(local_font_identifier) = identifier {
|
|
return webrender_font_api
|
|
.add_system_font(local_font_identifier.native_font_handle());
|
|
}
|
|
|
|
webrender_font_api.add_font(font_data, identifier.index())
|
|
});
|
|
|
|
*self
|
|
.font_instances
|
|
.entry((font_key, pt_size))
|
|
.or_insert_with(|| {
|
|
webrender_font_api.add_font_instance(font_key, pt_size.to_f32_px(), flags)
|
|
})
|
|
}
|
|
}
|
|
|
|
pub trait FontSource: Clone {
|
|
fn find_matching_font_templates(
|
|
&self,
|
|
descriptor_to_match: Option<&FontDescriptor>,
|
|
font_family_name: &FontFamilyName,
|
|
) -> Vec<FontTemplateRef>;
|
|
fn get_system_font_instance(
|
|
&self,
|
|
font_identifier: FontIdentifier,
|
|
size: Au,
|
|
flags: FontInstanceFlags,
|
|
) -> FontInstanceKey;
|
|
fn get_web_font(&self, data: Arc<Vec<u8>>, index: u32) -> FontKey;
|
|
fn get_web_font_instance(
|
|
&self,
|
|
font_key: FontKey,
|
|
size: f32,
|
|
flags: FontInstanceFlags,
|
|
) -> FontInstanceKey;
|
|
}
|
|
|
|
/// The public interface to the font cache thread, used by per-thread `FontContext` instances (via
|
|
/// the `FontSource` trait), and also by layout.
|
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
pub struct FontCacheThread {
|
|
chan: IpcSender<Command>,
|
|
}
|
|
|
|
/// A version of `FontStyle` from Stylo that is serializable. Normally this is not
|
|
/// because the specified version of `FontStyle` contains floats.
|
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
pub enum ComputedFontStyleDescriptor {
|
|
Normal,
|
|
Italic,
|
|
Oblique(FontStyleFixedPoint, FontStyleFixedPoint),
|
|
}
|
|
|
|
/// This data structure represents the various optional descriptors that can be
|
|
/// applied to a `@font-face` rule in CSS. These are used to create a [`FontTemplate`]
|
|
/// from the given font data used as the source of the `@font-face` rule. If values
|
|
/// like weight, stretch, and style are not specified they are initialized based
|
|
/// on the contents of the font itself.
|
|
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
|
pub struct CSSFontFaceDescriptors {
|
|
pub family_name: LowercaseString,
|
|
pub weight: Option<(FontWeight, FontWeight)>,
|
|
pub stretch: Option<(FontStretch, FontStretch)>,
|
|
pub style: Option<ComputedFontStyleDescriptor>,
|
|
pub unicode_range: Option<Vec<RangeInclusive<u32>>>,
|
|
}
|
|
|
|
impl CSSFontFaceDescriptors {
|
|
pub fn new(family_name: &str) -> Self {
|
|
CSSFontFaceDescriptors {
|
|
family_name: LowercaseString::new(family_name),
|
|
..Default::default()
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<&FontFaceRuleData> for CSSFontFaceDescriptors {
|
|
fn from(rule_data: &FontFaceRuleData) -> Self {
|
|
let family_name = rule_data
|
|
.family
|
|
.as_ref()
|
|
.expect("Expected rule to contain a font family.")
|
|
.name
|
|
.clone();
|
|
let weight = rule_data
|
|
.weight
|
|
.as_ref()
|
|
.map(|weight_range| (weight_range.0.compute(), weight_range.1.compute()));
|
|
|
|
let stretch_to_computed = |specified: SpecifiedFontStretch| match specified {
|
|
SpecifiedFontStretch::Stretch(percentage) => {
|
|
FontStretch::from_percentage(percentage.compute().0)
|
|
},
|
|
SpecifiedFontStretch::Keyword(keyword) => keyword.compute(),
|
|
SpecifiedFontStretch::System(_) => FontStretch::NORMAL,
|
|
};
|
|
let stretch = rule_data.stretch.as_ref().map(|stretch_range| {
|
|
(
|
|
stretch_to_computed(stretch_range.0),
|
|
stretch_to_computed(stretch_range.1),
|
|
)
|
|
});
|
|
|
|
fn style_to_computed(specified: &FontFaceStyle) -> ComputedFontStyleDescriptor {
|
|
match specified {
|
|
FontFaceStyle::Normal => ComputedFontStyleDescriptor::Normal,
|
|
FontFaceStyle::Italic => ComputedFontStyleDescriptor::Italic,
|
|
FontFaceStyle::Oblique(angle_a, angle_b) => ComputedFontStyleDescriptor::Oblique(
|
|
FixedPoint::from_float(angle_a.degrees()),
|
|
FixedPoint::from_float(angle_b.degrees()),
|
|
),
|
|
}
|
|
}
|
|
let style = rule_data.style.as_ref().map(style_to_computed);
|
|
let unicode_range = rule_data
|
|
.unicode_range
|
|
.as_ref()
|
|
.map(|ranges| ranges.iter().map(|range| range.start..=range.end).collect());
|
|
|
|
CSSFontFaceDescriptors {
|
|
family_name: LowercaseString::new(&family_name),
|
|
weight,
|
|
stretch,
|
|
style,
|
|
unicode_range,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl FontCacheThread {
|
|
pub fn new(webrender_api: Box<dyn WebRenderFontApi + Send>) -> FontCacheThread {
|
|
let (chan, port) = ipc::channel().unwrap();
|
|
|
|
thread::Builder::new()
|
|
.name("FontCache".to_owned())
|
|
.spawn(move || {
|
|
// TODO: Allow users to specify these.
|
|
let generic_fonts = populate_generic_fonts();
|
|
|
|
#[allow(clippy::default_constructed_unit_structs)]
|
|
let mut cache = FontCache {
|
|
port,
|
|
generic_fonts,
|
|
font_data: HashMap::new(),
|
|
local_families: Default::default(),
|
|
webrender_api,
|
|
webrender_fonts: HashMap::new(),
|
|
font_instances: HashMap::new(),
|
|
};
|
|
|
|
cache.refresh_local_families();
|
|
cache.run();
|
|
})
|
|
.expect("Thread spawning failed");
|
|
|
|
FontCacheThread { chan }
|
|
}
|
|
|
|
pub fn exit(&self) {
|
|
let (response_chan, response_port) = ipc::channel().unwrap();
|
|
self.chan
|
|
.send(Command::Exit(response_chan))
|
|
.expect("Couldn't send FontCacheThread exit message");
|
|
response_port
|
|
.recv()
|
|
.expect("Couldn't receive FontCacheThread reply");
|
|
}
|
|
}
|
|
|
|
impl FontSource for FontCacheThread {
|
|
fn get_system_font_instance(
|
|
&self,
|
|
identifier: FontIdentifier,
|
|
size: Au,
|
|
flags: FontInstanceFlags,
|
|
) -> FontInstanceKey {
|
|
let (response_chan, response_port) = ipc::channel().expect("failed to create IPC channel");
|
|
self.chan
|
|
.send(Command::GetFontInstance(
|
|
identifier,
|
|
size,
|
|
flags,
|
|
response_chan,
|
|
))
|
|
.expect("failed to send message to font cache thread");
|
|
|
|
let instance_key = response_port.recv();
|
|
if instance_key.is_err() {
|
|
let font_thread_has_closed = self.chan.send(Command::Ping).is_err();
|
|
assert!(
|
|
font_thread_has_closed,
|
|
"Failed to receive a response from live font cache"
|
|
);
|
|
panic!("Font cache thread has already exited.");
|
|
}
|
|
instance_key.unwrap()
|
|
}
|
|
|
|
fn find_matching_font_templates(
|
|
&self,
|
|
descriptor_to_match: Option<&FontDescriptor>,
|
|
font_family_name: &FontFamilyName,
|
|
) -> Vec<FontTemplateRef> {
|
|
let (response_chan, response_port) = ipc::channel().expect("failed to create IPC channel");
|
|
self.chan
|
|
.send(Command::GetFontTemplates(
|
|
descriptor_to_match.cloned(),
|
|
font_family_name.clone(),
|
|
response_chan,
|
|
))
|
|
.expect("failed to send message to font cache thread");
|
|
|
|
let reply = response_port.recv();
|
|
if reply.is_err() {
|
|
let font_thread_has_closed = self.chan.send(Command::Ping).is_err();
|
|
assert!(
|
|
font_thread_has_closed,
|
|
"Failed to receive a response from live font cache"
|
|
);
|
|
panic!("Font cache thread has already exited.");
|
|
}
|
|
|
|
reply
|
|
.unwrap()
|
|
.into_iter()
|
|
.map(|serialized_font_template| {
|
|
let font_data = serialized_font_template.bytes_receiver.recv().ok();
|
|
Arc::new(AtomicRefCell::new(FontTemplate {
|
|
identifier: serialized_font_template.identifier,
|
|
descriptor: serialized_font_template.descriptor.clone(),
|
|
data: font_data.map(Arc::new),
|
|
stylesheet: None,
|
|
}))
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn get_web_font(&self, data: Arc<Vec<u8>>, index: u32) -> FontKey {
|
|
let (result_sender, result_receiver) =
|
|
ipc::channel().expect("failed to create IPC channel");
|
|
let (bytes_sender, bytes_receiver) =
|
|
ipc::bytes_channel().expect("failed to create IPC channel");
|
|
let _ = self
|
|
.chan
|
|
.send(Command::GetWebFont(bytes_receiver, index, result_sender));
|
|
let _ = bytes_sender.send(&data);
|
|
result_receiver.recv().unwrap()
|
|
}
|
|
|
|
fn get_web_font_instance(
|
|
&self,
|
|
font_key: FontKey,
|
|
font_size: f32,
|
|
font_flags: FontInstanceFlags,
|
|
) -> FontInstanceKey {
|
|
let (result_sender, result_receiver) =
|
|
ipc::channel().expect("failed to create IPC channel");
|
|
let _ = self.chan.send(Command::GetWebFontInstance(
|
|
font_key,
|
|
font_size,
|
|
font_flags,
|
|
result_sender,
|
|
));
|
|
result_receiver.recv().unwrap()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
|
pub struct LowercaseString {
|
|
inner: String,
|
|
}
|
|
|
|
impl LowercaseString {
|
|
pub fn new(s: &str) -> LowercaseString {
|
|
LowercaseString {
|
|
inner: s.to_lowercase(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> From<&'a FontFamilyName> for LowercaseString {
|
|
fn from(family_name: &'a FontFamilyName) -> LowercaseString {
|
|
LowercaseString::new(family_name.name())
|
|
}
|
|
}
|
|
|
|
impl Deref for LowercaseString {
|
|
type Target = str;
|
|
|
|
#[inline]
|
|
fn deref(&self) -> &str {
|
|
&self.inner
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for LowercaseString {
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
self.inner.fmt(f)
|
|
}
|
|
}
|