1379 lines
45 KiB
Rust
1379 lines
45 KiB
Rust
/* 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
|
||
}
|
||
}
|