1
0
Fork 0
firefox/servo/components/style/values/computed/font.rs
Daniel Baumann 5e9a113729
Adding upstream version 140.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-25 09:37:52 +02:00

1379 lines
45 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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, FontSynthesisStyle};
pub use crate::values::specified::font::{
FontVariantAlternates, FontVariantEastAsian, FontVariantLigatures, FontVariantNumeric,
QueryFontMetricsFlags, 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<T, const FRACTION_BITS: u16> {
/// The actual representation.
pub value: T,
}
impl<T, const FRACTION_BITS: u16> FixedPoint<T, FRACTION_BITS>
where
T: AsPrimitive<f32>,
f32: AsPrimitive<T>,
u16: AsPrimitive<T>,
{
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<const FRACTION_BITS: u16> std::ops::Div for FixedPoint<u16, FRACTION_BITS> {
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<const FRACTION_BITS: u16> std::ops::Mul for FixedPoint<u16, FRACTION_BITS> {
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<u16, FONT_WEIGHT_FRACTION_BITS>;
/// 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<W>(&self, dest: &mut CssWriter<W>) -> 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<W>(&self, dest: &mut CssWriter<W>) -> 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<W>(&self, dest: &mut CssWriter<W>) -> 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<Self, ParseError<'i>> {
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 <family-name> 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<SingleFontFamily>,
}
impl FontFamilyList {
/// Return iterator of SingleFontFamily
pub fn iter(&self) -> impl Iterator<Item = &SingleFontFamily> {
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::<Vec<_>>();
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<GenericFontFamily> {
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<NonNegativeNumber>;
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, flags| {
let orient = if vertical {
FontMetricsOrientation::MatchContextPreferVertical
} else {
FontMetricsOrientation::Horizontal
};
let metrics = context.query_font_metrics(FontBaseSize::CurrentStyle, orient, flags);
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, $flags:expr) => {{
match $value {
specified::FontSizeAdjustFactor::Number(f) => {
FontSizeAdjust::$basis(f.to_computed_value(context))
},
specified::FontSizeAdjustFactor::FromFont => {
let (metrics, font_size) = font_metrics($vertical, $flags);
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, QueryFontMetricsFlags::empty())
},
Self::CapHeight(val) => {
resolve!(CapHeight, val, false, cap_height, -1.0 /* fall back to ascent */, QueryFontMetricsFlags::empty())
},
Self::ChWidth(val) => {
resolve!(ChWidth, val, false, zero_advance_measure, 0.5, QueryFontMetricsFlags::NEEDS_CH)
},
Self::IcWidth(val) => {
resolve!(IcWidth, val, false, ic_width, 1.0, QueryFontMetricsFlags::NEEDS_IC)
},
Self::IcHeight(val) => {
resolve!(IcHeight, val, true, ic_width, 1.0, QueryFontMetricsFlags::NEEDS_IC)
},
}
}
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<FeatureTagValue<Integer>>;
/// The computed value for font-variation-settings.
pub type FontVariationSettings = FontSettings<VariationValue<Number>>;
// 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<T>(settings_list: &mut Vec<T>)
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<T> ToComputedValue for FontSettings<T>
where
T: ToComputedValue,
<T as ToComputedValue>::ComputedValue: TaggedFontValue,
{
type ComputedValue = FontSettings<T::ComputedValue>;
fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
let mut v = self
.0
.iter()
.map(|item| item.to_computed_value(context))
.collect::<Vec<_>>();
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<W>(&self, dest: &mut CssWriter<W>) -> 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 <angle> below -90 or above 90 are not permitted, so we use an out
/// of range value to represent `italic`.
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<i16, FONT_STYLE_FRACTION_BITS>;
/// The computed value of `font-style`.
///
/// - Define angle of zero degrees as `normal`
/// - Define out-of-range value 100 degrees as `italic`
/// - Other values represent `oblique <angle>`
///
/// 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, equal to `oblique` with angle zero.
pub const NORMAL: FontStyle = FontStyle(FontStyleFixedPoint {
value: 0 << FONT_STYLE_FRACTION_BITS,
});
/// The italic keyword.
pub const ITALIC: FontStyle = FontStyle(FontStyleFixedPoint {
value: 100 << 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::ITALIC);
self.0.to_float()
}
}
impl ToCss for FontStyle {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: fmt::Write,
{
if *self == Self::NORMAL {
return dest.write_str("normal");
}
if *self == Self::ITALIC {
return dest.write_str("italic");
}
dest.write_str("oblique")?;
if *self != Self::OBLIQUE {
// It's not the default oblique amount, so append the angle in degrees.
dest.write_char(' ')?;
Angle::from_degrees(self.oblique_degrees()).to_css(dest)?;
}
Ok(())
}
}
impl ToAnimatedValue for FontStyle {
type AnimatedValue = generics::FontStyle<Angle>;
#[inline]
fn to_animated_value(self, _: &crate::values::animated::Context) -> Self::AnimatedValue {
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::Italic => Self::ITALIC,
generics::FontStyle::Oblique(ref angle) => 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<u16, FONT_STRETCH_FRACTION_BITS>;
/// 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<specified::FontStretchKeyword> {
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<W>(&self, dest: &mut CssWriter<W>) -> 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<NonNegativeNumber, NonNegativeLength>;
impl ToResolvedValue for LineHeight {
type ResolvedValue = Self;
fn to_resolved_value(self, context: &ResolvedContext) -> Self::ResolvedValue {
#[cfg(feature = "gecko")]
{
// Resolve <number> to an absolute <length> 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
}
}