/* 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::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 log::debug; use malloc_size_of_derive::MallocSizeOf; use parking_lot::RwLock; use serde::{Deserialize, Serialize}; use servo_atoms::{atom, Atom}; use smallvec::SmallVec; use style::computed_values::font_variant_caps; use style::properties::style_structs::Font as FontStyleStruct; use style::values::computed::font::{GenericFontFamily, SingleFontFamily}; use style::values::computed::{FontStretch, FontStyle, FontWeight}; use unicode_script::Script; use webrender_api::{FontInstanceFlags, FontInstanceKey}; use crate::font_cache_thread::{FontIdentifier, FontSource}; 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::text::glyph::{ByteIndex, GlyphData, GlyphId, GlyphStore}; use crate::text::shaping::ShaperMethods; use crate::text::{EmojiPresentationPreference, FallbackFontSelectionOptions, 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 LAST_RESORT_GLYPH_ADVANCE: FractionalPixel = 10.0; static TEXT_SHAPING_PERFORMANCE_COUNTER: AtomicUsize = AtomicUsize::new(0); // PlatformFont encapsulates access to the platform's font API, // e.g. quartz, FreeType. It provides access to metrics and tables // needed by the text shaper as well as access to the underlying font // resources needed by the graphics layer to draw glyphs. pub trait PlatformFontMethods: Sized { fn new_from_template( template: FontTemplateRef, pt_size: Option, ) -> Result { let data = template.data(); let face_index = template.identifier().index(); let font_identifier = template.borrow().identifier.clone(); Self::new_from_data(font_identifier, data, face_index, pt_size) } fn new_from_data( font_identifier: FontIdentifier, data: Arc>, face_index: u32, pt_size: Option, ) -> Result; /// Get a [`FontTemplateDescriptor`] from a [`PlatformFont`]. This is used to get /// descriptors for web fonts. fn descriptor(&self) -> FontTemplateDescriptor; fn glyph_index(&self, codepoint: char) -> Option; fn glyph_h_advance(&self, _: GlyphId) -> Option; fn glyph_h_kerning(&self, glyph0: GlyphId, glyph1: GlyphId) -> FractionalPixel; /// Can this font do basic horizontal LTR shaping without Harfbuzz? fn can_do_fast_shaping(&self) -> bool; fn metrics(&self) -> FontMetrics; fn table_for_tag(&self, _: FontTableTag) -> Option; /// Get the necessary [`FontInstanceFlags`]` for this font. fn webrender_font_instance_flags(&self) -> FontInstanceFlags; } // Used to abstract over the shaper's choice of fixed int representation. 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, pub ic_horizontal_advance: Option, } impl FontMetrics { /// Create an empty [`FontMetrics`] mainly to be used in situations where /// no font can be found. pub fn empty() -> Self { Self { underline_size: Au(0), underline_offset: Au(0), strikeout_size: Au(0), strikeout_offset: Au(0), leading: Au(0), x_height: Au(0), em_size: Au(0), ascent: Au(0), descent: Au(0), max_advance: Au(0), average_advance: Au(0), line_gap: Au(0), zero_horizontal_advance: None, ic_horizontal_advance: None, } } } /// `FontDescriptor` describes the parameters of a `Font`. It represents rendering a given font /// template at a particular size, with a particular font-variant-caps applied, etc. This contrasts /// with `FontTemplateDescriptor` in that the latter represents only the parameters inherent in the /// font data (weight, stretch, etc.). #[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, glyph_indices: HashMap>, shaped_text: HashMap>, } impl malloc_size_of::MallocSizeOf for CachedShapeData { fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize { // Estimate the size of the shaped text cache. This will be smaller, because // HashMap has some overhead, but we are mainly interested in the actual data. let shaped_text_size = self .shaped_text .iter() .map(|(key, value)| key.size_of(ops) + (*value).size_of(ops)) .sum::(); self.glyph_advances.size_of(ops) + self.glyph_indices.size_of(ops) + shaped_text_size } } #[derive(Debug)] pub struct Font { pub handle: PlatformFont, pub template: FontTemplateRef, pub metrics: FontMetrics, pub descriptor: FontDescriptor, shaper: OnceLock, cached_shape_data: RwLock, pub font_key: FontInstanceKey, /// If this is a synthesized small caps font, then this font reference is for /// the version of the font used to replace lowercase ASCII letters. It's up /// to the consumer of this font to properly use this reference. pub synthesized_small_caps: Option, /// Whether or not this font supports color bitmaps or a COLR table. This is /// essentially equivalent to whether or not we use it for emoji presentation. /// This is cached, because getting table data is expensive. has_color_bitmap_or_colr_table: OnceLock, } impl malloc_size_of::MallocSizeOf for Font { fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize { // TODO: Collect memory usage for platform fonts and for shapers. // This skips the template, because they are already stored in the template cache. self.metrics.size_of(ops) + self.descriptor.size_of(ops) + self.cached_shape_data.read().size_of(ops) + self.font_key.size_of(ops) } } impl Font { pub fn new( template: FontTemplateRef, descriptor: FontDescriptor, synthesized_small_caps: Option, ) -> Result { let handle = PlatformFont::new_from_template(template.clone(), Some(descriptor.pt_size))?; let metrics = handle.metrics(); Ok(Font { handle, template, shaper: OnceLock::new(), descriptor, metrics, cached_shape_data: Default::default(), font_key: FontInstanceKey::default(), synthesized_small_caps, has_color_bitmap_or_colr_table: OnceLock::new(), }) } /// A unique identifier for the font, allowing comparison. 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() }) } } bitflags! { #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct ShapingFlags: u8 { /// Set if the text is entirely whitespace. const IS_WHITESPACE_SHAPING_FLAG = 0x01; /// Set if we are to ignore ligatures. const IGNORE_LIGATURES_SHAPING_FLAG = 0x02; /// Set if we are to disable kerning. const DISABLE_KERNING_SHAPING_FLAG = 0x04; /// Text direction is right-to-left. const RTL_FLAG = 0x08; /// Set if word-break is set to keep-all. const KEEP_ALL_FLAG = 0x10; } } /// Various options that control text shaping. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct ShapingOptions { /// Spacing to add between each letter. Corresponds to the CSS 2.1 `letter-spacing` property. /// NB: You will probably want to set the `IGNORE_LIGATURES_SHAPING_FLAG` if this is non-null. pub letter_spacing: Option, /// Spacing to add between each word. Corresponds to the CSS 2.1 `word-spacing` property. pub word_spacing: Au, /// The Unicode script property of the characters in this run. pub script: Script, /// Various flags. pub flags: ShapingFlags, } /// An entry in the shape cache. #[derive(Clone, Debug, Eq, Hash, PartialEq)] struct ShapeCacheEntry { text: String, options: ShapingOptions, } impl Font { pub fn shape_text(&self, text: &str, options: &ShapingOptions) -> Arc { let this = self as *const Font; 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.chars().next() == Some('\n'); let start_time = Instant::now(); let mut glyphs = GlyphStore::new( text.len(), options .flags .contains(ShapingFlags::IS_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.shaper .get_or_init(|| Shaper::new(this)) .shape_text(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 can_do_fast_shaping(&self, text: &str, options: &ShapingOptions) -> bool { options.script == Script::Latin && !options.flags.contains(ShapingFlags::RTL_FLAG) && self.handle.can_do_fast_shaping() && text.is_ascii() } /// Fast path for ASCII text that only needs simple horizontal LTR kerning. 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 == ' ' { // https://drafts.csswg.org/css-text-3/#word-spacing-property 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(0)) }); 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 { 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 { { 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; } } // TODO: Need a fallback strategy. 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 type FontRef = Arc; /// A `FontGroup` is a prioritised list of fonts for a given set of font styles. It is used by /// `TextRun` to decide which font to render a character with. If none of the fonts listed in the /// styles are suitable, a fallback font may be used. #[derive(Debug, MallocSizeOf)] pub struct FontGroup { descriptor: FontDescriptor, families: SmallVec<[FontGroupFamily; 8]>, #[ignore_malloc_size_of = "This measured in the FontContext font cache."] last_matching_fallback: Option, } impl FontGroup { pub fn new(style: &FontStyleStruct) -> FontGroup { let descriptor = FontDescriptor::from(style); let families: SmallVec<[FontGroupFamily; 8]> = style .font_family .families .iter() .map(FontGroupFamily::new) .collect(); FontGroup { descriptor, families, last_matching_fallback: None, } } /// Finds the first font, or else the first fallback font, which contains a glyph for /// `codepoint`. If no such font is found, returns the first available font or fallback font /// (which will cause a "glyph not found" character to be rendered). If no font at all can be /// found, returns None. pub fn find_by_codepoint( &mut self, font_context: &FontContext, codepoint: char, next_codepoint: Option, ) -> Option { 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| { // Do not select this font if it goes against our emoji preference. 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) } /// Find the first available font in the group, or the first available fallback font. pub fn first(&mut self, font_context: &FontContext) -> Option { // From https://drafts.csswg.org/css-fonts/#first-available-font: // > The first available font, used for example in the definition of font-relative lengths // > such as ex or in the definition of the line-height property, is defined to be the first // > font for which the character U+0020 (space) is not excluded by a unicode-range, given the // > font families in the font-family list (or a user agent’s default font if none are // > available). // > Note: it does not matter whether that font actually has a glyph for the space character. 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, ) }) } /// Attempts to find a font which matches the given `template_predicate` and `font_predicate`. /// This method mutates because we may need to load new font data in the process of finding /// a suitable font. fn find( &mut self, font_context: &FontContext, template_predicate: TemplatePredicate, font_predicate: FontPredicate, ) -> Option where S: FontSource, 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() } /// Attempts to find a suitable fallback font which matches the given `template_predicate` and /// `font_predicate`. The default family (i.e. "serif") will be tried first, followed by /// platform-specific family names. If a `codepoint` is provided, then its Unicode block may be /// used to refine the list of family names which will be tried. fn find_fallback( &mut self, font_context: &FontContext, options: FallbackFontSelectionOptions, template_predicate: TemplatePredicate, font_predicate: FontPredicate, ) -> Option where S: FontSource, TemplatePredicate: Fn(FontTemplateRef) -> bool, FontPredicate: Fn(&FontRef) -> bool, { iter::once(FontFamilyDescriptor::serif()) .chain(fallback_font_families(options).into_iter().map(|family| { FontFamilyDescriptor::new(FontFamilyName::from(family), FontSearchScope::Local) })) .filter_map(|family_descriptor| { FontGroupFamily { family_descriptor, members: None, } .find( &self.descriptor, font_context, &template_predicate, &font_predicate, ) }) .next() } } /// A [`FontGroupFamily`] can have multiple members if it is a "composite face", meaning /// that it is defined by multiple `@font-face` declarations which vary only by their /// `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)] 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, loaded: bool, } /// A `FontGroupFamily` is a single font family in a `FontGroup`. It corresponds to one of the /// 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)] struct FontGroupFamily { family_descriptor: FontFamilyDescriptor, members: Option>, } impl FontGroupFamily { fn new(family: &SingleFontFamily) -> FontGroupFamily { let family_descriptor = FontFamilyDescriptor::new(FontFamilyName::from(family), FontSearchScope::Any); FontGroupFamily { family_descriptor, members: None, } } fn find( &mut self, font_descriptor: &FontDescriptor, font_context: &FontContext, template_predicate: &TemplatePredicate, font_predicate: &FontPredicate, ) -> Option where S: FontSource, 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 { 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 { // may be negative due to negative width (i.e., kerning of '.' in 'P.T.') pub advance_width: Au, pub ascent: Au, // nonzero pub descent: Au, // nonzero // this bounding box is relative to the left origin baseline. // so, bounding_box.position.y = -ascent pub bounding_box: Rect, } impl RunMetrics { pub fn new(advance: Au, ascent: Au, descent: Au) -> RunMetrics { let bounds = Rect::new( Point2D::new(Au(0), -ascent), Size2D::new(advance, ascent + descent), ); // TODO(Issue #125): support loose and tight bounding boxes; using the // ascent+descent and advance is sometimes too generous and // looking at actual glyph extents can yield a tighter box. 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) } /// The scope within which we will look for a font. #[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] pub enum FontSearchScope { /// All fonts will be searched, including those specified via `@font-face` rules. Any, /// Only local system fonts will be searched. Local, } /// A font family name used in font selection. #[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] pub enum FontFamilyName { /// A specific name such as `"Arial"` Specific(Atom), /// A generic name such as `sans-serif` Generic(Atom), } impl FontFamilyName { pub fn name(&self) -> &str { match *self { FontFamilyName::Specific(ref name) => name, FontFamilyName::Generic(ref name) => name, } } } impl<'a> From<&'a SingleFontFamily> for FontFamilyName { fn from(other: &'a SingleFontFamily) -> FontFamilyName { match *other { SingleFontFamily::FamilyName(ref family_name) => { FontFamilyName::Specific(family_name.name.clone()) }, SingleFontFamily::Generic(generic) => FontFamilyName::Generic(match generic { GenericFontFamily::None => panic!("Shouldn't appear in style"), GenericFontFamily::Serif => atom!("serif"), GenericFontFamily::SansSerif => atom!("sans-serif"), GenericFontFamily::Monospace => atom!("monospace"), GenericFontFamily::Cursive => atom!("cursive"), GenericFontFamily::Fantasy => atom!("fantasy"), GenericFontFamily::SystemUi => atom!("system-ui"), }), } } } impl<'a> From<&'a str> for FontFamilyName { fn from(other: &'a str) -> FontFamilyName { FontFamilyName::Specific(Atom::from(other)) } } /// The font family parameters for font selection. #[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] pub struct FontFamilyDescriptor { pub name: FontFamilyName, pub scope: FontSearchScope, } impl FontFamilyDescriptor { pub fn new(name: FontFamilyName, scope: FontSearchScope) -> FontFamilyDescriptor { FontFamilyDescriptor { name, scope } } fn serif() -> FontFamilyDescriptor { FontFamilyDescriptor { name: FontFamilyName::Generic(atom!("serif")), scope: FontSearchScope::Local, } } pub fn name(&self) -> &str { self.name.name() } } /// Given a mapping array `mapping` and a value, map that value onto /// the value specified by the array. For instance, for FontConfig /// values of weights, we would map these onto the CSS [0..1000] range /// by creating an array as below. Values that fall between two mapped /// values, will be adjusted by the weighted mean. /// /// ```rust /// let mapping = [ /// (0., 0.), /// (FC_WEIGHT_REGULAR as f64, 400 as f64), /// (FC_WEIGHT_BOLD as f64, 700 as f64), /// (FC_WEIGHT_EXTRABLACK as f64, 1000 as f64), /// ]; /// let mapped_weight = apply_font_config_to_style_mapping(&mapping, weight as f64); /// ``` #[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 }