use std::borrow::ToOwned;
use std::collections::HashMap;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, OnceLock};
use std::time::Instant;
use std::{iter, str};
use app_units::Au;
use bitflags::bitflags;
use euclid::default::{Point2D, Rect, Size2D};
use euclid::num::Zero;
use log::debug;
use malloc_size_of_derive::MallocSizeOf;
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use style::computed_values::font_variant_caps;
use style::properties::style_structs::Font as FontStyleStruct;
use style::values::computed::font::{
FamilyName, FontFamilyNameSyntax, GenericFontFamily, SingleFontFamily,
};
use style::values::computed::{FontStretch, FontStyle, FontWeight};
use unicode_script::Script;
use webrender_api::{FontInstanceFlags, FontInstanceKey};
use crate::platform::font::{FontTable, PlatformFont};
pub use crate::platform::font_list::fallback_font_families;
use crate::{
ByteIndex, EmojiPresentationPreference, FallbackFontSelectionOptions, FontContext, FontData,
FontIdentifier, FontTemplateDescriptor, FontTemplateRef, FontTemplateRefMethods, GlyphData,
GlyphId, GlyphStore, LocalFontIdentifier, Shaper,
};
#[macro_export]
macro_rules! ot_tag {
($t1:expr, $t2:expr, $t3:expr, $t4:expr) => {
(($t1 as u32) << 24) | (($t2 as u32) << 16) | (($t3 as u32) << 8) | ($t4 as u32)
};
}
pub const GPOS: u32 = ot_tag!('G', 'P', 'O', 'S');
pub const GSUB: u32 = ot_tag!('G', 'S', 'U', 'B');
pub const KERN: u32 = ot_tag!('k', 'e', 'r', 'n');
pub const SBIX: u32 = ot_tag!('s', 'b', 'i', 'x');
pub const CBDT: u32 = ot_tag!('C', 'B', 'D', 'T');
pub const COLR: u32 = ot_tag!('C', 'O', 'L', 'R');
pub const BASE: u32 = ot_tag!('B', 'A', 'S', 'E');
pub const LAST_RESORT_GLYPH_ADVANCE: FractionalPixel = 10.0;
static TEXT_SHAPING_PERFORMANCE_COUNTER: AtomicUsize = AtomicUsize::new(0);
pub trait PlatformFontMethods: Sized {
#[cfg_attr(
feature = "tracing",
tracing::instrument(
name = "PlatformFontMethods::new_from_template",
skip_all,
fields(servo_profiling = true),
level = "trace",
)
)]
fn new_from_template(
template: FontTemplateRef,
pt_size: Option<Au>,
data: &Option<FontData>,
) -> Result<PlatformFont, &'static str> {
let template = template.borrow();
let font_identifier = template.identifier.clone();
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: &FontData,
pt_size: Option<Au>,
) -> Result<PlatformFont, &'static str>;
fn descriptor(&self) -> FontTemplateDescriptor;
fn glyph_index(&self, codepoint: char) -> Option<GlyphId>;
fn glyph_h_advance(&self, _: GlyphId) -> Option<FractionalPixel>;
fn glyph_h_kerning(&self, glyph0: GlyphId, glyph1: GlyphId) -> FractionalPixel;
fn metrics(&self) -> FontMetrics;
fn table_for_tag(&self, _: FontTableTag) -> Option<FontTable>;
fn typographic_bounds(&self, _: GlyphId) -> Rect<f32>;
fn webrender_font_instance_flags(&self) -> FontInstanceFlags;
}
pub type FractionalPixel = f64;
pub type FontTableTag = u32;
trait FontTableTagConversions {
fn tag_to_str(&self) -> String;
}
impl FontTableTagConversions for FontTableTag {
fn tag_to_str(&self) -> String {
let bytes = [
(self >> 24) as u8,
(self >> 16) as u8,
(self >> 8) as u8,
*self as u8,
];
str::from_utf8(&bytes).unwrap().to_owned()
}
}
pub trait FontTableMethods {
fn buffer(&self) -> &[u8];
}
#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
pub struct FontMetrics {
pub underline_size: Au,
pub underline_offset: Au,
pub strikeout_size: Au,
pub strikeout_offset: Au,
pub leading: Au,
pub x_height: Au,
pub em_size: Au,
pub ascent: Au,
pub descent: Au,
pub max_advance: Au,
pub average_advance: Au,
pub line_gap: Au,
pub zero_horizontal_advance: Option<Au>,
pub ic_horizontal_advance: Option<Au>,
pub space_advance: Au,
}
impl FontMetrics {
pub fn empty() -> Self {
Self {
underline_size: Au::zero(),
underline_offset: Au::zero(),
strikeout_size: Au::zero(),
strikeout_offset: Au::zero(),
leading: Au::zero(),
x_height: Au::zero(),
em_size: Au::zero(),
ascent: Au::zero(),
descent: Au::zero(),
max_advance: Au::zero(),
average_advance: Au::zero(),
line_gap: Au::zero(),
zero_horizontal_advance: None,
ic_horizontal_advance: None,
space_advance: Au::zero(),
}
}
}
#[derive(Clone, Debug, Deserialize, Hash, MallocSizeOf, PartialEq, Serialize)]
pub struct FontDescriptor {
pub weight: FontWeight,
pub stretch: FontStretch,
pub style: FontStyle,
pub variant: font_variant_caps::T,
pub pt_size: Au,
}
impl Eq for FontDescriptor {}
impl<'a> From<&'a FontStyleStruct> for FontDescriptor {
fn from(style: &'a FontStyleStruct) -> Self {
FontDescriptor {
weight: style.font_weight,
stretch: style.font_stretch,
style: style.font_style,
variant: style.font_variant_caps,
pt_size: Au::from_f32_px(style.font_size.computed_size().px()),
}
}
}
#[derive(Debug, Default)]
struct CachedShapeData {
glyph_advances: HashMap<GlyphId, FractionalPixel>,
glyph_indices: HashMap<char, Option<GlyphId>>,
shaped_text: HashMap<ShapeCacheEntry, Arc<GlyphStore>>,
}
impl malloc_size_of::MallocSizeOf for CachedShapeData {
fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
let shaped_text_size = self
.shaped_text
.iter()
.map(|(key, value)| key.size_of(ops) + (*value).size_of(ops))
.sum::<usize>();
self.glyph_advances.size_of(ops) + self.glyph_indices.size_of(ops) + shaped_text_size
}
}
pub struct Font {
pub handle: PlatformFont,
pub template: FontTemplateRef,
pub metrics: FontMetrics,
pub descriptor: FontDescriptor,
data: OnceLock<FontData>,
shaper: OnceLock<Shaper>,
cached_shape_data: RwLock<CachedShapeData>,
pub font_instance_key: OnceLock<FontInstanceKey>,
pub synthesized_small_caps: Option<FontRef>,
has_color_bitmap_or_colr_table: OnceLock<bool>,
can_do_fast_shaping: OnceLock<bool>,
}
impl malloc_size_of::MallocSizeOf for Font {
fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
self.metrics.size_of(ops) +
self.descriptor.size_of(ops) +
self.cached_shape_data.read().size_of(ops) +
self.font_instance_key
.get()
.map_or(0, |key| key.size_of(ops))
}
}
impl Font {
pub fn new(
template: FontTemplateRef,
descriptor: FontDescriptor,
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)?;
let metrics = handle.metrics();
Ok(Font {
handle,
template,
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,
has_color_bitmap_or_colr_table: OnceLock::new(),
can_do_fast_shaping: OnceLock::new(),
})
}
pub fn identifier(&self) -> FontIdentifier {
self.template.identifier()
}
pub fn webrender_font_instance_flags(&self) -> FontInstanceFlags {
self.handle.webrender_font_instance_flags()
}
pub fn has_color_bitmap_or_colr_table(&self) -> bool {
*self.has_color_bitmap_or_colr_table.get_or_init(|| {
self.table_for_tag(SBIX).is_some() ||
self.table_for_tag(CBDT).is_some() ||
self.table_for_tag(COLR).is_some()
})
}
pub fn key(&self, font_context: &FontContext) -> FontInstanceKey {
*self
.font_instance_key
.get_or_init(|| font_context.create_font_instance_key(self))
}
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! {
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct ShapingFlags: u8 {
const IS_WHITESPACE_SHAPING_FLAG = 1 << 0;
const ENDS_WITH_WHITESPACE_SHAPING_FLAG = 1 << 1;
const IGNORE_LIGATURES_SHAPING_FLAG = 1 << 2;
const DISABLE_KERNING_SHAPING_FLAG = 1 << 3;
const RTL_FLAG = 1 << 4;
const KEEP_ALL_FLAG = 1 << 5;
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct ShapingOptions {
pub letter_spacing: Option<Au>,
pub word_spacing: Au,
pub script: Script,
pub flags: ShapingFlags,
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
struct ShapeCacheEntry {
text: String,
options: ShapingOptions,
}
impl Font {
pub fn shape_text(&self, text: &str, options: &ShapingOptions) -> Arc<GlyphStore> {
let lookup_key = ShapeCacheEntry {
text: text.to_owned(),
options: *options,
};
{
let cache = self.cached_shape_data.read();
if let Some(shaped_text) = cache.shaped_text.get(&lookup_key) {
return shaped_text.clone();
}
}
let is_single_preserved_newline = text.len() == 1 && text.starts_with('\n');
let start_time = Instant::now();
let mut glyphs = GlyphStore::new(
text.len(),
options
.flags
.contains(ShapingFlags::IS_WHITESPACE_SHAPING_FLAG),
options
.flags
.contains(ShapingFlags::ENDS_WITH_WHITESPACE_SHAPING_FLAG),
is_single_preserved_newline,
options.flags.contains(ShapingFlags::RTL_FLAG),
);
if self.can_do_fast_shaping(text, options) {
debug!("shape_text: Using ASCII fast path.");
self.shape_text_fast(text, options, &mut glyphs);
} else {
debug!("shape_text: Using Harfbuzz.");
self.shape_text_harfbuzz(text, options, &mut glyphs);
}
let shaped_text = Arc::new(glyphs);
let mut cache = self.cached_shape_data.write();
cache.shaped_text.insert(lookup_key, shaped_text.clone());
let end_time = Instant::now();
TEXT_SHAPING_PERFORMANCE_COUNTER.fetch_add(
(end_time.duration_since(start_time).as_nanos()) as usize,
Ordering::Relaxed,
);
shaped_text
}
fn shape_text_harfbuzz(&self, text: &str, options: &ShapingOptions, glyphs: &mut GlyphStore) {
let this = self as *const Font;
self.shaper
.get_or_init(|| Shaper::new(this))
.shape_text(text, options, glyphs);
}
pub fn can_do_fast_shaping(&self, text: &str, options: &ShapingOptions) -> bool {
options.script == Script::Latin &&
!options.flags.contains(ShapingFlags::RTL_FLAG) &&
*self.can_do_fast_shaping.get_or_init(|| {
self.table_for_tag(KERN).is_some() &&
self.table_for_tag(GPOS).is_none() &&
self.table_for_tag(GSUB).is_none()
}) &&
text.is_ascii()
}
fn shape_text_fast(&self, text: &str, options: &ShapingOptions, glyphs: &mut GlyphStore) {
let mut prev_glyph_id = None;
for (i, byte) in text.bytes().enumerate() {
let character = byte as char;
let glyph_id = match self.glyph_index(character) {
Some(id) => id,
None => continue,
};
let mut advance = Au::from_f64_px(self.glyph_h_advance(glyph_id));
if character == ' ' {
advance += options.word_spacing;
}
if let Some(letter_spacing) = options.letter_spacing {
advance += letter_spacing;
}
let offset = prev_glyph_id.map(|prev| {
let h_kerning = Au::from_f64_px(self.glyph_h_kerning(prev, glyph_id));
advance += h_kerning;
Point2D::new(h_kerning, Au::zero())
});
let glyph = GlyphData::new(glyph_id, advance, offset, true, true);
glyphs.add_glyph_for_byte_index(ByteIndex(i as isize), character, &glyph);
prev_glyph_id = Some(glyph_id);
}
glyphs.finalize_changes();
}
pub fn table_for_tag(&self, tag: FontTableTag) -> Option<FontTable> {
let result = self.handle.table_for_tag(tag);
let status = if result.is_some() {
"Found"
} else {
"Didn't find"
};
debug!(
"{} font table[{}] in {:?},",
status,
tag.tag_to_str(),
self.identifier()
);
result
}
#[inline]
pub fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
{
let cache = self.cached_shape_data.read();
if let Some(glyph) = cache.glyph_indices.get(&codepoint) {
return *glyph;
}
}
let codepoint = match self.descriptor.variant {
font_variant_caps::T::SmallCaps => codepoint.to_ascii_uppercase(),
font_variant_caps::T::Normal => codepoint,
};
let glyph_index = self.handle.glyph_index(codepoint);
let mut cache = self.cached_shape_data.write();
cache.glyph_indices.insert(codepoint, glyph_index);
glyph_index
}
pub fn has_glyph_for(&self, codepoint: char) -> bool {
self.glyph_index(codepoint).is_some()
}
pub fn glyph_h_kerning(&self, first_glyph: GlyphId, second_glyph: GlyphId) -> FractionalPixel {
self.handle.glyph_h_kerning(first_glyph, second_glyph)
}
pub fn glyph_h_advance(&self, glyph_id: GlyphId) -> FractionalPixel {
{
let cache = self.cached_shape_data.read();
if let Some(width) = cache.glyph_advances.get(&glyph_id) {
return *width;
}
}
let new_width = match self.handle.glyph_h_advance(glyph_id) {
Some(adv) => adv,
None => LAST_RESORT_GLYPH_ADVANCE as FractionalPixel,
};
let mut cache = self.cached_shape_data.write();
cache.glyph_advances.insert(glyph_id, new_width);
new_width
}
pub fn typographic_bounds(&self, glyph_id: GlyphId) -> Rect<f32> {
self.handle.typographic_bounds(glyph_id)
}
pub fn baseline(&self) -> Option<FontBaseline> {
let this = self as *const Font;
self.shaper.get_or_init(|| Shaper::new(this)).baseline()
}
}
pub type FontRef = Arc<Font>;
#[derive(MallocSizeOf)]
pub struct FontGroup {
descriptor: FontDescriptor,
families: SmallVec<[FontGroupFamily; 8]>,
#[ignore_malloc_size_of = "This measured in the FontContext font cache."]
last_matching_fallback: Option<FontRef>,
}
impl FontGroup {
pub fn new(style: &FontStyleStruct, descriptor: FontDescriptor) -> FontGroup {
let families: SmallVec<[FontGroupFamily; 8]> = style
.font_family
.families
.iter()
.map(FontGroupFamily::new)
.collect();
FontGroup {
descriptor,
families,
last_matching_fallback: None,
}
}
pub fn find_by_codepoint(
&mut self,
font_context: &FontContext,
codepoint: char,
next_codepoint: Option<char>,
) -> Option<FontRef> {
let codepoint = match codepoint {
'\t' => ' ',
_ => codepoint,
};
let options = FallbackFontSelectionOptions::new(codepoint, next_codepoint);
let should_look_for_small_caps = self.descriptor.variant == font_variant_caps::T::SmallCaps &&
options.character.is_ascii_lowercase();
let font_or_synthesized_small_caps = |font: FontRef| {
if should_look_for_small_caps && font.synthesized_small_caps.is_some() {
return font.synthesized_small_caps.clone();
}
Some(font)
};
let font_has_glyph_and_presentation = |font: &FontRef| {
match options.presentation_preference {
EmojiPresentationPreference::Text if font.has_color_bitmap_or_colr_table() => {
return false
},
EmojiPresentationPreference::Emoji if !font.has_color_bitmap_or_colr_table() => {
return false
},
_ => {},
}
font.has_glyph_for(options.character)
};
let char_in_template =
|template: FontTemplateRef| template.char_in_unicode_range(options.character);
if let Some(font) = self.find(
font_context,
char_in_template,
font_has_glyph_and_presentation,
) {
return font_or_synthesized_small_caps(font);
}
if let Some(ref last_matching_fallback) = self.last_matching_fallback {
if char_in_template(last_matching_fallback.template.clone()) &&
font_has_glyph_and_presentation(last_matching_fallback)
{
return font_or_synthesized_small_caps(last_matching_fallback.clone());
}
}
if let Some(font) = self.find_fallback(
font_context,
options,
char_in_template,
font_has_glyph_and_presentation,
) {
self.last_matching_fallback = Some(font.clone());
return font_or_synthesized_small_caps(font);
}
self.first(font_context)
}
pub fn first(&mut self, font_context: &FontContext) -> Option<FontRef> {
let space_in_template = |template: FontTemplateRef| template.char_in_unicode_range(' ');
let font_predicate = |_: &FontRef| true;
self.find(font_context, space_in_template, font_predicate)
.or_else(|| {
self.find_fallback(
font_context,
FallbackFontSelectionOptions::default(),
space_in_template,
font_predicate,
)
})
}
fn find<TemplatePredicate, FontPredicate>(
&mut self,
font_context: &FontContext,
template_predicate: TemplatePredicate,
font_predicate: FontPredicate,
) -> Option<FontRef>
where
TemplatePredicate: Fn(FontTemplateRef) -> bool,
FontPredicate: Fn(&FontRef) -> bool,
{
let font_descriptor = self.descriptor.clone();
self.families
.iter_mut()
.filter_map(|font_group_family| {
font_group_family.find(
&font_descriptor,
font_context,
&template_predicate,
&font_predicate,
)
})
.next()
}
fn find_fallback<TemplatePredicate, FontPredicate>(
&mut self,
font_context: &FontContext,
options: FallbackFontSelectionOptions,
template_predicate: TemplatePredicate,
font_predicate: FontPredicate,
) -> Option<FontRef>
where
TemplatePredicate: Fn(FontTemplateRef) -> bool,
FontPredicate: Fn(&FontRef) -> bool,
{
iter::once(FontFamilyDescriptor::default())
.chain(
fallback_font_families(options)
.into_iter()
.map(|family_name| {
let family = SingleFontFamily::FamilyName(FamilyName {
name: family_name.into(),
syntax: FontFamilyNameSyntax::Quoted,
});
FontFamilyDescriptor::new(family, FontSearchScope::Local)
}),
)
.filter_map(|family_descriptor| {
FontGroupFamily {
family_descriptor,
members: None,
}
.find(
&self.descriptor,
font_context,
&template_predicate,
&font_predicate,
)
})
.next()
}
}
#[derive(MallocSizeOf)]
struct FontGroupFamilyMember {
#[ignore_malloc_size_of = "This measured in the FontContext template cache."]
template: FontTemplateRef,
#[ignore_malloc_size_of = "This measured in the FontContext font cache."]
font: Option<FontRef>,
loaded: bool,
}
#[derive(MallocSizeOf)]
struct FontGroupFamily {
family_descriptor: FontFamilyDescriptor,
members: Option<Vec<FontGroupFamilyMember>>,
}
impl FontGroupFamily {
fn new(family: &SingleFontFamily) -> FontGroupFamily {
FontGroupFamily {
family_descriptor: FontFamilyDescriptor::new(family.clone(), FontSearchScope::Any),
members: None,
}
}
fn find<TemplatePredicate, FontPredicate>(
&mut self,
font_descriptor: &FontDescriptor,
font_context: &FontContext,
template_predicate: &TemplatePredicate,
font_predicate: &FontPredicate,
) -> Option<FontRef>
where
TemplatePredicate: Fn(FontTemplateRef) -> bool,
FontPredicate: Fn(&FontRef) -> bool,
{
self.members(font_descriptor, font_context)
.filter_map(|member| {
if !template_predicate(member.template.clone()) {
return None;
}
if !member.loaded {
member.font = font_context.font(member.template.clone(), font_descriptor);
member.loaded = true;
}
if matches!(&member.font, Some(font) if font_predicate(font)) {
return member.font.clone();
}
None
})
.next()
}
fn members(
&mut self,
font_descriptor: &FontDescriptor,
font_context: &FontContext,
) -> impl Iterator<Item = &mut FontGroupFamilyMember> {
let family_descriptor = &self.family_descriptor;
let members = self.members.get_or_insert_with(|| {
font_context
.matching_templates(font_descriptor, family_descriptor)
.into_iter()
.map(|template| FontGroupFamilyMember {
template,
loaded: false,
font: None,
})
.collect()
});
members.iter_mut()
}
}
pub struct RunMetrics {
pub advance_width: Au,
pub ascent: Au, pub descent: Au, pub bounding_box: Rect<Au>,
}
impl RunMetrics {
pub fn new(advance: Au, ascent: Au, descent: Au) -> RunMetrics {
let bounds = Rect::new(
Point2D::new(Au::zero(), -ascent),
Size2D::new(advance, ascent + descent),
);
RunMetrics {
advance_width: advance,
bounding_box: bounds,
ascent,
descent,
}
}
}
pub fn get_and_reset_text_shaping_performance_counter() -> usize {
TEXT_SHAPING_PERFORMANCE_COUNTER.swap(0, Ordering::SeqCst)
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
pub enum FontSearchScope {
Any,
Local,
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
pub struct FontFamilyDescriptor {
pub family: SingleFontFamily,
pub scope: FontSearchScope,
}
impl FontFamilyDescriptor {
pub fn new(family: SingleFontFamily, scope: FontSearchScope) -> FontFamilyDescriptor {
FontFamilyDescriptor { family, scope }
}
fn default() -> FontFamilyDescriptor {
FontFamilyDescriptor {
family: SingleFontFamily::Generic(GenericFontFamily::None),
scope: FontSearchScope::Local,
}
}
}
pub struct FontBaseline {
pub ideographic_baseline: f32,
pub alphabetic_baseline: f32,
pub hanging_baseline: f32,
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
pub(crate) fn map_platform_values_to_style_values(mapping: &[(f64, f64)], value: f64) -> f64 {
if value < mapping[0].0 {
return mapping[0].1;
}
for window in mapping.windows(2) {
let (font_config_value_a, css_value_a) = window[0];
let (font_config_value_b, css_value_b) = window[1];
if value >= font_config_value_a && value <= font_config_value_b {
let ratio = (value - font_config_value_a) / (font_config_value_b - font_config_value_a);
return css_value_a + ((css_value_b - css_value_a) * ratio);
}
}
mapping[mapping.len() - 1].1
}