aboutsummaryrefslogtreecommitdiffstats
path: root/components/fonts/font_cache_thread.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/fonts/font_cache_thread.rs')
-rw-r--r--components/fonts/font_cache_thread.rs555
1 files changed, 555 insertions, 0 deletions
diff --git a/components/fonts/font_cache_thread.rs b/components/fonts/font_cache_thread.rs
new file mode 100644
index 00000000000..bde22d4b515
--- /dev/null
+++ b/components/fonts/font_cache_thread.rs
@@ -0,0 +1,555 @@
+/* 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)
+ }
+}