/* 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/. */ //! Computed values for font properties use crate::parser::{Parse, ParserContext}; use crate::values::animated::ToAnimatedValue; use crate::values::computed::{ Angle, Context, Integer, Length, NonNegativeLength, NonNegativeNumber, Number, Percentage, ToComputedValue, Zoom, }; use crate::values::generics::font::{ FeatureTagValue, FontSettings, TaggedFontValue, VariationValue, }; use crate::values::generics::{font as generics, NonNegative}; use crate::values::resolved::{Context as ResolvedContext, ToResolvedValue}; use crate::values::specified::font::{ self as specified, KeywordInfo, MAX_FONT_WEIGHT, MIN_FONT_WEIGHT, }; use crate::values::specified::length::{FontBaseSize, LineHeightBase, NoCalcLength}; use crate::Atom; use cssparser::{serialize_identifier, CssStringWriter, Parser}; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use num_traits::abs; use num_traits::cast::AsPrimitive; use std::fmt::{self, Write}; use style_traits::{CssWriter, ParseError, ToCss}; pub use crate::values::computed::Length as MozScriptMinSize; pub use crate::values::specified::font::MozScriptSizeMultiplier; pub use crate::values::specified::font::{FontPalette, FontSynthesis}; pub use crate::values::specified::font::{ FontVariantAlternates, FontVariantEastAsian, FontVariantLigatures, FontVariantNumeric, XLang, XTextScale, }; pub use crate::values::specified::Integer as SpecifiedInteger; pub use crate::values::specified::Number as SpecifiedNumber; /// Generic template for font property type classes that use a fixed-point /// internal representation with `FRACTION_BITS` for the fractional part. /// /// Values are constructed from and exposed as floating-point, but stored /// internally as fixed point, so there will be a quantization effect on /// fractional values, depending on the number of fractional bits used. /// /// Using (16-bit) fixed-point types rather than floats for these style /// attributes reduces the memory footprint of gfxFontEntry and gfxFontStyle; it /// will also tend to reduce the number of distinct font instances that get /// created, particularly when styles are animated or set to arbitrary values /// (e.g. by sliders in the UI), which should reduce pressure on graphics /// resources and improve cache hit rates. /// /// cbindgen:derive-lt /// cbindgen:derive-lte /// cbindgen:derive-gt /// cbindgen:derive-gte #[repr(C)] #[derive( Clone, ComputeSquaredDistance, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq, PartialOrd, ToResolvedValue, )] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] pub struct FixedPoint { /// The actual representation. pub value: T, } impl FixedPoint where T: AsPrimitive, f32: AsPrimitive, u16: AsPrimitive, { const SCALE: u16 = 1 << FRACTION_BITS; const INVERSE_SCALE: f32 = 1.0 / Self::SCALE as f32; /// Returns a fixed-point bit from a floating-point context. pub fn from_float(v: f32) -> Self { Self { value: (v * Self::SCALE as f32).round().as_(), } } /// Returns the floating-point representation. pub fn to_float(&self) -> f32 { self.value.as_() * Self::INVERSE_SCALE } } // We implement this and mul below only for u16 types, because u32 types might need more care about // overflow. But it's not hard to implement in either case. impl std::ops::Div for FixedPoint { type Output = Self; fn div(self, rhs: Self) -> Self { Self { value: (((self.value as u32) << (FRACTION_BITS as u32)) / (rhs.value as u32)) as u16, } } } impl std::ops::Mul for FixedPoint { type Output = Self; fn mul(self, rhs: Self) -> Self { Self { value: (((self.value as u32) * (rhs.value as u32)) >> (FRACTION_BITS as u32)) as u16, } } } /// font-weight: range 1..1000, fractional values permitted; keywords /// 'normal', 'bold' aliased to 400, 700 respectively. /// /// We use an unsigned 10.6 fixed-point value (range 0.0 - 1023.984375) pub const FONT_WEIGHT_FRACTION_BITS: u16 = 6; /// This is an alias which is useful mostly as a cbindgen / C++ inference /// workaround. pub type FontWeightFixedPoint = FixedPoint; /// A value for the font-weight property per: /// /// https://drafts.csswg.org/css-fonts-4/#propdef-font-weight /// /// cbindgen:derive-lt /// cbindgen:derive-lte /// cbindgen:derive-gt /// cbindgen:derive-gte #[derive( Clone, ComputeSquaredDistance, Copy, Debug, Hash, MallocSizeOf, PartialEq, PartialOrd, ToResolvedValue, )] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] #[repr(C)] pub struct FontWeight(FontWeightFixedPoint); impl ToAnimatedValue for FontWeight { type AnimatedValue = Number; #[inline] fn to_animated_value(self, _: &crate::values::animated::Context) -> Self::AnimatedValue { self.value() } #[inline] fn from_animated_value(animated: Self::AnimatedValue) -> Self { FontWeight::from_float(animated) } } impl ToCss for FontWeight { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: fmt::Write, { self.value().to_css(dest) } } impl FontWeight { /// The `normal` keyword. pub const NORMAL: FontWeight = FontWeight(FontWeightFixedPoint { value: 400 << FONT_WEIGHT_FRACTION_BITS, }); /// The `bold` value. pub const BOLD: FontWeight = FontWeight(FontWeightFixedPoint { value: 700 << FONT_WEIGHT_FRACTION_BITS, }); /// The threshold from which we consider a font bold. pub const BOLD_THRESHOLD: FontWeight = FontWeight(FontWeightFixedPoint { value: 600 << FONT_WEIGHT_FRACTION_BITS, }); /// Returns the `normal` keyword value. pub fn normal() -> Self { Self::NORMAL } /// Weither this weight is bold pub fn is_bold(&self) -> bool { *self >= Self::BOLD_THRESHOLD } /// Returns the value as a float. pub fn value(&self) -> f32 { self.0.to_float() } /// Construct a valid weight from a float value. pub fn from_float(v: f32) -> Self { Self(FixedPoint::from_float( v.max(MIN_FONT_WEIGHT).min(MAX_FONT_WEIGHT), )) } /// Return the bolder weight. /// /// See the table in: /// https://drafts.csswg.org/css-fonts-4/#font-weight-numeric-values pub fn bolder(self) -> Self { let value = self.value(); if value < 350. { return Self::NORMAL; } if value < 550. { return Self::BOLD; } Self::from_float(value.max(900.)) } /// Return the lighter weight. /// /// See the table in: /// https://drafts.csswg.org/css-fonts-4/#font-weight-numeric-values pub fn lighter(self) -> Self { let value = self.value(); if value < 550. { return Self::from_float(value.min(100.)); } if value < 750. { return Self::NORMAL; } Self::BOLD } } #[derive( Animate, Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf, PartialEq, ToAnimatedZero, ToCss, )] #[cfg_attr(feature = "servo", derive(Serialize, Deserialize))] /// The computed value of font-size pub struct FontSize { /// The computed size, that we use to compute ems etc. This accounts for /// e.g., text-zoom. pub computed_size: NonNegativeLength, /// The actual used size. This is the computed font size, potentially /// constrained by other factors like minimum font-size settings and so on. #[css(skip)] pub used_size: NonNegativeLength, /// If derived from a keyword, the keyword and additional transformations applied to it #[css(skip)] pub keyword_info: KeywordInfo, } impl FontSize { /// The actual computed font size. #[inline] pub fn computed_size(&self) -> Length { self.computed_size.0 } /// The actual used font size. #[inline] pub fn used_size(&self) -> Length { self.used_size.0 } /// Apply zoom to the font-size. This is usually done by ToComputedValue. #[inline] pub fn zoom(&self, zoom: Zoom) -> Self { Self { computed_size: NonNegative(Length::new(zoom.zoom(self.computed_size.0.px()))), used_size: NonNegative(Length::new(zoom.zoom(self.used_size.0.px()))), keyword_info: self.keyword_info, } } #[inline] /// Get default value of font size. pub fn medium() -> Self { Self { computed_size: NonNegative(Length::new(specified::FONT_MEDIUM_PX)), used_size: NonNegative(Length::new(specified::FONT_MEDIUM_PX)), keyword_info: KeywordInfo::medium(), } } } impl ToAnimatedValue for FontSize { type AnimatedValue = Length; #[inline] fn to_animated_value(self, context: &crate::values::animated::Context) -> Self::AnimatedValue { self.computed_size.0.to_animated_value(context) } #[inline] fn from_animated_value(animated: Self::AnimatedValue) -> Self { FontSize { computed_size: NonNegative(animated.clamp_to_non_negative()), used_size: NonNegative(animated.clamp_to_non_negative()), keyword_info: KeywordInfo::none(), } } } impl ToResolvedValue for FontSize { type ResolvedValue = NonNegativeLength; #[inline] fn to_resolved_value(self, context: &ResolvedContext) -> Self::ResolvedValue { self.computed_size.to_resolved_value(context) } #[inline] fn from_resolved_value(resolved: Self::ResolvedValue) -> Self { let computed_size = NonNegativeLength::from_resolved_value(resolved); Self { computed_size, used_size: computed_size, keyword_info: KeywordInfo::none(), } } } #[derive(Clone, Debug, Eq, PartialEq, ToComputedValue, ToResolvedValue)] #[cfg_attr(feature = "servo", derive(Hash, Serialize, Deserialize))] /// Specifies a prioritized list of font family names or generic family names. #[repr(C)] pub struct FontFamily { /// The actual list of family names. pub families: FontFamilyList, /// Whether this font-family came from a specified system-font. pub is_system_font: bool, /// Whether this is the initial font-family that might react to language /// changes. pub is_initial: bool, } macro_rules! static_font_family { ($ident:ident, $family:expr) => { lazy_static! { static ref $ident: FontFamily = FontFamily { families: FontFamilyList { list: crate::ArcSlice::from_iter_leaked(std::iter::once($family)), }, is_system_font: false, is_initial: false, }; } }; } impl FontFamily { #[inline] /// Get default font family as `serif` which is a generic font-family pub fn serif() -> Self { Self::generic(GenericFontFamily::Serif).clone() } /// Returns the font family for `-moz-bullet-font`. #[cfg(feature = "gecko")] pub(crate) fn moz_bullet() -> &'static Self { static_font_family!( MOZ_BULLET, SingleFontFamily::FamilyName(FamilyName { name: atom!("-moz-bullet-font"), syntax: FontFamilyNameSyntax::Identifiers, }) ); &*MOZ_BULLET } /// Returns a font family for a single system font. #[cfg(feature = "gecko")] pub fn for_system_font(name: &str) -> Self { Self { families: FontFamilyList { list: crate::ArcSlice::from_iter(std::iter::once(SingleFontFamily::FamilyName( FamilyName { name: Atom::from(name), syntax: FontFamilyNameSyntax::Identifiers, }, ))), }, is_system_font: true, is_initial: false, } } /// Returns a generic font family. pub fn generic(generic: GenericFontFamily) -> &'static Self { macro_rules! generic_font_family { ($ident:ident, $family:ident) => { static_font_family!( $ident, SingleFontFamily::Generic(GenericFontFamily::$family) ) }; } generic_font_family!(SERIF, Serif); generic_font_family!(SANS_SERIF, SansSerif); generic_font_family!(MONOSPACE, Monospace); generic_font_family!(CURSIVE, Cursive); generic_font_family!(FANTASY, Fantasy); #[cfg(feature = "gecko")] generic_font_family!(MOZ_EMOJI, MozEmoji); generic_font_family!(SYSTEM_UI, SystemUi); let family = match generic { GenericFontFamily::None => { debug_assert!(false, "Bogus caller!"); &*SERIF }, GenericFontFamily::Serif => &*SERIF, GenericFontFamily::SansSerif => &*SANS_SERIF, GenericFontFamily::Monospace => &*MONOSPACE, GenericFontFamily::Cursive => &*CURSIVE, GenericFontFamily::Fantasy => &*FANTASY, #[cfg(feature = "gecko")] GenericFontFamily::MozEmoji => &*MOZ_EMOJI, GenericFontFamily::SystemUi => &*SYSTEM_UI, }; debug_assert_eq!( *family.families.iter().next().unwrap(), SingleFontFamily::Generic(generic) ); family } } impl MallocSizeOf for FontFamily { fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { use malloc_size_of::MallocUnconditionalSizeOf; // SharedFontList objects are generally measured from the pointer stored // in the specified value. So only count this if the SharedFontList is // unshared. let shared_font_list = &self.families.list; if shared_font_list.is_unique() { shared_font_list.unconditional_size_of(ops) } else { 0 } } } impl ToCss for FontFamily { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: fmt::Write, { let mut iter = self.families.iter(); match iter.next() { Some(f) => f.to_css(dest)?, None => return Ok(()), } for family in iter { dest.write_str(", ")?; family.to_css(dest)?; } Ok(()) } } /// The name of a font family of choice. #[derive( Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, )] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] #[repr(C)] pub struct FamilyName { /// Name of the font family. pub name: Atom, /// Syntax of the font family. pub syntax: FontFamilyNameSyntax, } #[cfg(feature = "gecko")] impl FamilyName { fn is_known_icon_font_family(&self) -> bool { use crate::gecko_bindings::bindings; unsafe { bindings::Gecko_IsKnownIconFontFamily(self.name.as_ptr()) } } } #[cfg(feature = "servo")] impl FamilyName { fn is_known_icon_font_family(&self) -> bool { false } } impl ToCss for FamilyName { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: fmt::Write, { match self.syntax { FontFamilyNameSyntax::Quoted => { dest.write_char('"')?; write!(CssStringWriter::new(dest), "{}", self.name)?; dest.write_char('"') }, FontFamilyNameSyntax::Identifiers => { let mut first = true; for ident in self.name.to_string().split(' ') { if first { first = false; } else { dest.write_char(' ')?; } debug_assert!( !ident.is_empty(), "Family name with leading, \ trailing, or consecutive white spaces should \ have been marked quoted by the parser" ); serialize_identifier(ident, dest)?; } Ok(()) }, } } } #[derive( Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem, )] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] /// Font family names must either be given quoted as strings, /// or unquoted as a sequence of one or more identifiers. #[repr(u8)] pub enum FontFamilyNameSyntax { /// The family name was specified in a quoted form, e.g. "Font Name" /// or 'Font Name'. Quoted, /// The family name was specified in an unquoted form as a sequence of /// identifiers. Identifiers, } /// A set of faces that vary in weight, width or slope. /// cbindgen:derive-mut-casts=true #[derive( Clone, Debug, Eq, MallocSizeOf, PartialEq, ToCss, ToComputedValue, ToResolvedValue, ToShmem, )] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize, Hash))] #[repr(u8)] pub enum SingleFontFamily { /// The name of a font family of choice. FamilyName(FamilyName), /// Generic family name. Generic(GenericFontFamily), } fn system_ui_enabled(_: &ParserContext) -> bool { static_prefs::pref!("layout.css.system-ui.enabled") } /// A generic font-family name. /// /// The order here is important, if you change it make sure that /// `gfxPlatformFontList.h`s ranged array and `gfxFontFamilyList`'s /// sSingleGenerics are updated as well. /// /// NOTE(emilio): Should be u8, but it's a u32 because of ABI issues between GCC /// and LLVM see https://bugs.llvm.org/show_bug.cgi?id=44228 / bug 1600735 / /// bug 1726515. #[derive( Clone, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq, Parse, ToCss, ToComputedValue, ToResolvedValue, ToShmem, )] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] #[repr(u32)] #[allow(missing_docs)] pub enum GenericFontFamily { /// No generic family specified, only for internal usage. /// /// NOTE(emilio): Gecko code relies on this variant being zero. #[css(skip)] None = 0, Serif, SansSerif, #[parse(aliases = "-moz-fixed")] Monospace, Cursive, Fantasy, #[parse(condition = "system_ui_enabled")] SystemUi, /// An internal value for emoji font selection. #[css(skip)] #[cfg(feature = "gecko")] MozEmoji, } impl GenericFontFamily { /// When we disallow websites to override fonts, we ignore some generic /// families that the website might specify, since they're not configured by /// the user. See bug 789788 and bug 1730098. pub(crate) fn valid_for_user_font_prioritization(self) -> bool { match self { Self::None | Self::Fantasy | Self::Cursive | Self::SystemUi => false, #[cfg(feature = "gecko")] Self::MozEmoji => false, Self::Serif | Self::SansSerif | Self::Monospace => true, } } } impl Parse for SingleFontFamily { /// Parse a font-family value. fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { if let Ok(value) = input.try_parse(|i| i.expect_string_cloned()) { return Ok(SingleFontFamily::FamilyName(FamilyName { name: Atom::from(&*value), syntax: FontFamilyNameSyntax::Quoted, })); } if let Ok(generic) = input.try_parse(|i| GenericFontFamily::parse(context, i)) { return Ok(SingleFontFamily::Generic(generic)); } let first_ident = input.expect_ident_cloned()?; let reserved = match_ignore_ascii_case! { &first_ident, // https://drafts.csswg.org/css-fonts/#propdef-font-family // "Font family names that happen to be the same as a keyword value // (`inherit`, `serif`, `sans-serif`, `monospace`, `fantasy`, and `cursive`) // must be quoted to prevent confusion with the keywords with the same names. // The keywords ‘initial’ and ‘default’ are reserved for future use // and must also be quoted when used as font names. // UAs must not consider these keywords as matching the type." "inherit" | "initial" | "unset" | "revert" | "default" => true, _ => false, }; let mut value = first_ident.as_ref().to_owned(); let mut serialize_quoted = value.contains(' '); // These keywords are not allowed by themselves. // The only way this value can be valid with with another keyword. if reserved { let ident = input.expect_ident()?; serialize_quoted = serialize_quoted || ident.contains(' '); value.push(' '); value.push_str(&ident); } while let Ok(ident) = input.try_parse(|i| i.expect_ident_cloned()) { serialize_quoted = serialize_quoted || ident.contains(' '); value.push(' '); value.push_str(&ident); } let syntax = if serialize_quoted { // For font family names which contains special white spaces, e.g. // `font-family: \ a\ \ b\ \ c\ ;`, it is tricky to serialize them // as identifiers correctly. Just mark them quoted so we don't need // to worry about them in serialization code. FontFamilyNameSyntax::Quoted } else { FontFamilyNameSyntax::Identifiers }; Ok(SingleFontFamily::FamilyName(FamilyName { name: Atom::from(value), syntax, })) } } /// A list of font families. #[derive(Clone, Debug, ToComputedValue, ToResolvedValue, ToShmem, PartialEq, Eq)] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize, Hash))] #[repr(C)] pub struct FontFamilyList { /// The actual list of font families specified. pub list: crate::ArcSlice, } impl FontFamilyList { /// Return iterator of SingleFontFamily pub fn iter(&self) -> impl Iterator { self.list.iter() } /// If there's a generic font family on the list which is suitable for user /// font prioritization, then move it ahead of the other families in the list, /// except for any families known to be ligature-based icon fonts, where using a /// generic instead of the site's specified font may cause substantial breakage. /// If no suitable generic is found in the list, insert the default generic ahead /// of all the listed families except for known ligature-based icon fonts. #[cfg_attr(feature = "servo", allow(unused))] pub(crate) fn prioritize_first_generic_or_prepend(&mut self, generic: GenericFontFamily) { let mut index_of_first_generic = None; let mut target_index = None; for (i, f) in self.iter().enumerate() { match &*f { SingleFontFamily::Generic(f) => { if index_of_first_generic.is_none() && f.valid_for_user_font_prioritization() { // If we haven't found a target position, there's nothing to do; // this entry is already ahead of everything except any whitelisted // icon fonts. if target_index.is_none() { return; } index_of_first_generic = Some(i); break; } // A non-prioritized generic (e.g. cursive, fantasy) becomes the target // position for prioritization, just like arbitrary named families. if target_index.is_none() { target_index = Some(i); } }, SingleFontFamily::FamilyName(fam) => { // Target position for the first generic is in front of the first // non-whitelisted icon font family we find. if target_index.is_none() && !fam.is_known_icon_font_family() { target_index = Some(i); } }, } } let mut new_list = self.list.iter().cloned().collect::>(); let first_generic = match index_of_first_generic { Some(i) => new_list.remove(i), None => SingleFontFamily::Generic(generic), }; if let Some(i) = target_index { new_list.insert(i, first_generic); } else { new_list.push(first_generic); } self.list = crate::ArcSlice::from_iter(new_list.into_iter()); } /// Returns whether we need to prioritize user fonts. #[cfg_attr(feature = "servo", allow(unused))] pub(crate) fn needs_user_font_prioritization(&self) -> bool { self.iter().next().map_or(true, |f| match f { SingleFontFamily::Generic(f) => !f.valid_for_user_font_prioritization(), _ => true, }) } /// Return the generic ID if it is a single generic font pub fn single_generic(&self) -> Option { let mut iter = self.iter(); if let Some(SingleFontFamily::Generic(f)) = iter.next() { if iter.next().is_none() { return Some(*f); } } None } } /// Preserve the readability of text when font fallback occurs. pub type FontSizeAdjust = generics::GenericFontSizeAdjust; impl FontSizeAdjust { #[inline] /// Default value of font-size-adjust pub fn none() -> Self { FontSizeAdjust::None } } impl ToComputedValue for specified::FontSizeAdjust { type ComputedValue = FontSizeAdjust; fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { use crate::font_metrics::FontMetricsOrientation; let font_metrics = |vertical| { let orient = if vertical { FontMetricsOrientation::MatchContextPreferVertical } else { FontMetricsOrientation::Horizontal }; let metrics = context.query_font_metrics(FontBaseSize::CurrentStyle, orient, false); let font_size = context.style().get_font().clone_font_size().used_size.0; (metrics, font_size) }; // Macro to resolve a from-font value using the given metric field. If not present, // returns the fallback value, or if that is negative, resolves using ascent instead // of the missing field (this is the fallback for cap-height). macro_rules! resolve { ($basis:ident, $value:expr, $vertical:expr, $field:ident, $fallback:expr) => {{ match $value { specified::FontSizeAdjustFactor::Number(f) => { FontSizeAdjust::$basis(f.to_computed_value(context)) }, specified::FontSizeAdjustFactor::FromFont => { let (metrics, font_size) = font_metrics($vertical); let ratio = if let Some(metric) = metrics.$field { metric / font_size } else if $fallback >= 0.0 { $fallback } else { metrics.ascent / font_size }; if ratio.is_nan() { FontSizeAdjust::$basis(NonNegative(abs($fallback))) } else { FontSizeAdjust::$basis(NonNegative(ratio)) } }, } }}; } match *self { Self::None => FontSizeAdjust::None, Self::ExHeight(val) => resolve!(ExHeight, val, false, x_height, 0.5), Self::CapHeight(val) => { resolve!(CapHeight, val, false, cap_height, -1.0 /* fall back to ascent */) }, Self::ChWidth(val) => resolve!(ChWidth, val, false, zero_advance_measure, 0.5), Self::IcWidth(val) => resolve!(IcWidth, val, false, ic_width, 1.0), Self::IcHeight(val) => resolve!(IcHeight, val, true, ic_width, 1.0), } } fn from_computed_value(computed: &Self::ComputedValue) -> Self { macro_rules! case { ($basis:ident, $val:expr) => { Self::$basis(specified::FontSizeAdjustFactor::Number( ToComputedValue::from_computed_value($val), )) }; } match *computed { FontSizeAdjust::None => Self::None, FontSizeAdjust::ExHeight(ref val) => case!(ExHeight, val), FontSizeAdjust::CapHeight(ref val) => case!(CapHeight, val), FontSizeAdjust::ChWidth(ref val) => case!(ChWidth, val), FontSizeAdjust::IcWidth(ref val) => case!(IcWidth, val), FontSizeAdjust::IcHeight(ref val) => case!(IcHeight, val), } } } /// Use FontSettings as computed type of FontFeatureSettings. pub type FontFeatureSettings = FontSettings>; /// The computed value for font-variation-settings. pub type FontVariationSettings = FontSettings>; // The computed value of font-{feature,variation}-settings discards values // with duplicate tags, keeping only the last occurrence of each tag. fn dedup_font_settings(settings_list: &mut Vec) where T: TaggedFontValue, { if settings_list.len() > 1 { settings_list.sort_by_key(|k| k.tag().0); // dedup() keeps the first of any duplicates, but we want the last, // so we implement it manually here. let mut prev_tag = settings_list.last().unwrap().tag(); for i in (0..settings_list.len() - 1).rev() { let cur_tag = settings_list[i].tag(); if cur_tag == prev_tag { settings_list.remove(i); } prev_tag = cur_tag; } } } impl ToComputedValue for FontSettings where T: ToComputedValue, ::ComputedValue: TaggedFontValue, { type ComputedValue = FontSettings; fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { let mut v = self .0 .iter() .map(|item| item.to_computed_value(context)) .collect::>(); dedup_font_settings(&mut v); FontSettings(v.into_boxed_slice()) } fn from_computed_value(computed: &Self::ComputedValue) -> Self { Self( computed .0 .iter() .map(T::from_computed_value) .collect() ) } } /// font-language-override can only have a single 1-4 ASCII character /// OpenType "language system" tag, so we should be able to compute /// it and store it as a 32-bit integer /// (see http://www.microsoft.com/typography/otspec/languagetags.htm). #[derive( Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem, )] #[repr(C)] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] #[value_info(other_values = "normal")] pub struct FontLanguageOverride(pub u32); impl FontLanguageOverride { #[inline] /// Get computed default value of `font-language-override` with 0 pub fn normal() -> FontLanguageOverride { FontLanguageOverride(0) } /// Returns this value as a `&str`, backed by `storage`. #[inline] pub(crate) fn to_str(self, storage: &mut [u8; 4]) -> &str { *storage = u32::to_be_bytes(self.0); // Safe because we ensure it's ASCII during parsing let slice = if cfg!(debug_assertions) { std::str::from_utf8(&storage[..]).unwrap() } else { unsafe { std::str::from_utf8_unchecked(&storage[..]) } }; slice.trim_end() } /// Unsafe because `Self::to_str` requires the value to represent a UTF-8 /// string. #[inline] pub unsafe fn from_u32(value: u32) -> Self { Self(value) } } impl ToCss for FontLanguageOverride { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: fmt::Write, { if self.0 == 0 { return dest.write_str("normal"); } self.to_str(&mut [0; 4]).to_css(dest) } } impl ToComputedValue for specified::MozScriptMinSize { type ComputedValue = MozScriptMinSize; fn to_computed_value(&self, cx: &Context) -> MozScriptMinSize { // this value is used in the computation of font-size, so // we use the parent size let base_size = FontBaseSize::InheritedStyle; let line_height_base = LineHeightBase::InheritedStyle; match self.0 { NoCalcLength::FontRelative(value) => { value.to_computed_value(cx, base_size, line_height_base) }, NoCalcLength::ServoCharacterWidth(value) => { value.to_computed_value(base_size.resolve(cx).computed_size()) }, ref l => l.to_computed_value(cx), } } fn from_computed_value(other: &MozScriptMinSize) -> Self { specified::MozScriptMinSize(ToComputedValue::from_computed_value(other)) } } /// The computed value of the math-depth property. pub type MathDepth = i8; #[cfg(feature = "gecko")] impl ToComputedValue for specified::MathDepth { type ComputedValue = MathDepth; fn to_computed_value(&self, cx: &Context) -> i8 { use crate::properties::longhands::math_style::SpecifiedValue as MathStyleValue; use std::{cmp, i8}; let int = match *self { specified::MathDepth::AutoAdd => { let parent = cx.builder.get_parent_font().clone_math_depth() as i32; let style = cx.builder.get_parent_font().clone_math_style(); if style == MathStyleValue::Compact { parent.saturating_add(1) } else { parent } }, specified::MathDepth::Add(rel) => { let parent = cx.builder.get_parent_font().clone_math_depth(); (parent as i32).saturating_add(rel.to_computed_value(cx)) }, specified::MathDepth::Absolute(abs) => abs.to_computed_value(cx), }; cmp::min(int, i8::MAX as i32) as i8 } fn from_computed_value(other: &i8) -> Self { let computed_value = *other as i32; specified::MathDepth::Absolute(SpecifiedInteger::from_computed_value(&computed_value)) } } /// - Use a signed 8.8 fixed-point value (representable range -128.0..128) /// /// Values of below -90 or above 90 not permitted, so we use out of /// range values to represent normal | oblique pub const FONT_STYLE_FRACTION_BITS: u16 = 8; /// This is an alias which is useful mostly as a cbindgen / C++ inference /// workaround. pub type FontStyleFixedPoint = FixedPoint; /// The computed value of `font-style`. /// /// - Define out of range values min value (-128.0) as meaning 'normal' /// - Define max value (127.99609375) as 'italic' /// - Other values represent 'oblique ' /// - Note that 'oblique 0deg' is distinct from 'normal' (should it be?) /// /// cbindgen:derive-lt /// cbindgen:derive-lte /// cbindgen:derive-gt /// cbindgen:derive-gte #[derive( Clone, ComputeSquaredDistance, Copy, Debug, Eq, Hash, MallocSizeOf, PartialEq, PartialOrd, ToResolvedValue, )] #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] #[repr(C)] pub struct FontStyle(FontStyleFixedPoint); impl FontStyle { /// The normal keyword. pub const NORMAL: FontStyle = FontStyle(FontStyleFixedPoint { value: 100 << FONT_STYLE_FRACTION_BITS, }); /// The italic keyword. pub const ITALIC: FontStyle = FontStyle(FontStyleFixedPoint { value: 101 << FONT_STYLE_FRACTION_BITS, }); /// The default angle for `font-style: oblique`. /// See also https://github.com/w3c/csswg-drafts/issues/2295 pub const DEFAULT_OBLIQUE_DEGREES: i16 = 14; /// The `oblique` keyword with the default degrees. pub const OBLIQUE: FontStyle = FontStyle(FontStyleFixedPoint { value: Self::DEFAULT_OBLIQUE_DEGREES << FONT_STYLE_FRACTION_BITS, }); /// The `normal` value. #[inline] pub fn normal() -> Self { Self::NORMAL } /// Returns the oblique angle for this style. pub fn oblique(degrees: f32) -> Self { Self(FixedPoint::from_float( degrees .max(specified::FONT_STYLE_OBLIQUE_MIN_ANGLE_DEGREES) .min(specified::FONT_STYLE_OBLIQUE_MAX_ANGLE_DEGREES), )) } /// Returns the oblique angle for this style. pub fn oblique_degrees(&self) -> f32 { debug_assert_ne!(*self, Self::NORMAL); debug_assert_ne!(*self, Self::ITALIC); self.0.to_float() } } impl ToCss for FontStyle { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: fmt::Write, { if *self == Self::NORMAL { return dest.write_str("normal"); } if *self == Self::ITALIC { return dest.write_str("italic"); } if *self == Self::OBLIQUE { return dest.write_str("oblique"); } dest.write_str("oblique ")?; let angle = Angle::from_degrees(self.oblique_degrees()); angle.to_css(dest)?; Ok(()) } } impl ToAnimatedValue for FontStyle { type AnimatedValue = generics::FontStyle; #[inline] fn to_animated_value(self, _: &crate::values::animated::Context) -> Self::AnimatedValue { if self == Self::NORMAL { // This allows us to animate between normal and oblique values. Per spec, // https://drafts.csswg.org/css-fonts-4/#font-style-prop: // Animation type: by computed value type; 'normal' animates as 'oblique 0deg' return generics::FontStyle::Oblique(Angle::from_degrees(0.0)); } if self == Self::ITALIC { return generics::FontStyle::Italic; } generics::FontStyle::Oblique(Angle::from_degrees(self.oblique_degrees())) } #[inline] fn from_animated_value(animated: Self::AnimatedValue) -> Self { match animated { generics::FontStyle::Normal => Self::NORMAL, generics::FontStyle::Italic => Self::ITALIC, generics::FontStyle::Oblique(ref angle) => { if angle.degrees() == 0.0 { // Reverse the conversion done in to_animated_value() Self::NORMAL } else { Self::oblique(angle.degrees()) } }, } } } /// font-stretch is a percentage relative to normal. /// /// We use an unsigned 10.6 fixed-point value (range 0.0 - 1023.984375) /// /// We arbitrarily limit here to 1000%. (If that becomes a problem, we could /// reduce the number of fractional bits and increase the limit.) pub const FONT_STRETCH_FRACTION_BITS: u16 = 6; /// This is an alias which is useful mostly as a cbindgen / C++ inference /// workaround. pub type FontStretchFixedPoint = FixedPoint; /// A value for the font-stretch property per: /// /// https://drafts.csswg.org/css-fonts-4/#propdef-font-stretch /// /// cbindgen:derive-lt /// cbindgen:derive-lte /// cbindgen:derive-gt /// cbindgen:derive-gte #[derive( Clone, ComputeSquaredDistance, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd, ToResolvedValue, )] #[cfg_attr(feature = "servo", derive(Deserialize, Hash, Serialize))] #[repr(C)] pub struct FontStretch(pub FontStretchFixedPoint); impl FontStretch { /// The fraction bits, as an easy-to-access-constant. pub const FRACTION_BITS: u16 = FONT_STRETCH_FRACTION_BITS; /// 0.5 in our floating point representation. pub const HALF: u16 = 1 << (Self::FRACTION_BITS - 1); /// The `ultra-condensed` keyword. pub const ULTRA_CONDENSED: FontStretch = FontStretch(FontStretchFixedPoint { value: 50 << Self::FRACTION_BITS, }); /// The `extra-condensed` keyword. pub const EXTRA_CONDENSED: FontStretch = FontStretch(FontStretchFixedPoint { value: (62 << Self::FRACTION_BITS) + Self::HALF, }); /// The `condensed` keyword. pub const CONDENSED: FontStretch = FontStretch(FontStretchFixedPoint { value: 75 << Self::FRACTION_BITS, }); /// The `semi-condensed` keyword. pub const SEMI_CONDENSED: FontStretch = FontStretch(FontStretchFixedPoint { value: (87 << Self::FRACTION_BITS) + Self::HALF, }); /// The `normal` keyword. pub const NORMAL: FontStretch = FontStretch(FontStretchFixedPoint { value: 100 << Self::FRACTION_BITS, }); /// The `semi-expanded` keyword. pub const SEMI_EXPANDED: FontStretch = FontStretch(FontStretchFixedPoint { value: (112 << Self::FRACTION_BITS) + Self::HALF, }); /// The `expanded` keyword. pub const EXPANDED: FontStretch = FontStretch(FontStretchFixedPoint { value: 125 << Self::FRACTION_BITS, }); /// The `extra-expanded` keyword. pub const EXTRA_EXPANDED: FontStretch = FontStretch(FontStretchFixedPoint { value: 150 << Self::FRACTION_BITS, }); /// The `ultra-expanded` keyword. pub const ULTRA_EXPANDED: FontStretch = FontStretch(FontStretchFixedPoint { value: 200 << Self::FRACTION_BITS, }); /// 100% pub fn hundred() -> Self { Self::NORMAL } /// Converts to a computed percentage. #[inline] pub fn to_percentage(&self) -> Percentage { Percentage(self.0.to_float() / 100.0) } /// Converts from a computed percentage value. pub fn from_percentage(p: f32) -> Self { Self(FixedPoint::from_float((p * 100.).max(0.0).min(1000.0))) } /// Returns a relevant stretch value from a keyword. /// https://drafts.csswg.org/css-fonts-4/#font-stretch-prop pub fn from_keyword(kw: specified::FontStretchKeyword) -> Self { use specified::FontStretchKeyword::*; match kw { UltraCondensed => Self::ULTRA_CONDENSED, ExtraCondensed => Self::EXTRA_CONDENSED, Condensed => Self::CONDENSED, SemiCondensed => Self::SEMI_CONDENSED, Normal => Self::NORMAL, SemiExpanded => Self::SEMI_EXPANDED, Expanded => Self::EXPANDED, ExtraExpanded => Self::EXTRA_EXPANDED, UltraExpanded => Self::ULTRA_EXPANDED, } } /// Returns the stretch keyword if we map to one of the relevant values. pub fn as_keyword(&self) -> Option { use specified::FontStretchKeyword::*; // TODO: Can we use match here? if *self == Self::ULTRA_CONDENSED { return Some(UltraCondensed); } if *self == Self::EXTRA_CONDENSED { return Some(ExtraCondensed); } if *self == Self::CONDENSED { return Some(Condensed); } if *self == Self::SEMI_CONDENSED { return Some(SemiCondensed); } if *self == Self::NORMAL { return Some(Normal); } if *self == Self::SEMI_EXPANDED { return Some(SemiExpanded); } if *self == Self::EXPANDED { return Some(Expanded); } if *self == Self::EXTRA_EXPANDED { return Some(ExtraExpanded); } if *self == Self::ULTRA_EXPANDED { return Some(UltraExpanded); } None } } impl ToCss for FontStretch { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: fmt::Write, { self.to_percentage().to_css(dest) } } impl ToAnimatedValue for FontStretch { type AnimatedValue = Percentage; #[inline] fn to_animated_value(self, _: &crate::values::animated::Context) -> Self::AnimatedValue { self.to_percentage() } #[inline] fn from_animated_value(animated: Self::AnimatedValue) -> Self { Self::from_percentage(animated.0) } } /// A computed value for the `line-height` property. pub type LineHeight = generics::GenericLineHeight; impl ToResolvedValue for LineHeight { type ResolvedValue = Self; fn to_resolved_value(self, context: &ResolvedContext) -> Self::ResolvedValue { #[cfg(feature = "gecko")] { // Resolve to an absolute based on font size. if matches!(self, Self::Normal | Self::MozBlockHeight) { return self; } let wm = context.style.writing_mode; Self::Length( context .device .calc_line_height( context.style.get_font(), wm, Some(context.element_info.element), ) .to_resolved_value(context), ) } #[cfg(feature = "servo")] { if let LineHeight::Number(num) = &self { let size = context.style.get_font().clone_font_size().computed_size(); LineHeight::Length(NonNegativeLength::new(size.px() * num.0)) } else { self } } } #[inline] fn from_resolved_value(value: Self::ResolvedValue) -> Self { value } }