/* 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 http://mozilla.org/MPL/2.0/. */ //! Specified values for font properties use Atom; use app_units::Au; use byteorder::{BigEndian, ByteOrder}; use cssparser::{Parser, Token}; #[cfg(feature = "gecko")] use gecko_bindings::bindings; #[cfg(feature = "gecko")] use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use parser::{Parse, ParserContext}; use properties::longhands::system_font::SystemFont; use std::fmt::{self, Write}; use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss}; use values::CustomIdent; use values::computed::{font as computed, Context, Length, NonNegativeLength, ToComputedValue}; use values::computed::font::{SingleFontFamily, FontFamilyList, FamilyName}; use values::generics::font::{FontSettings, FontTag, FeatureTagValue}; use values::generics::font::{KeywordInfo as GenericKeywordInfo, KeywordSize, VariationValue}; use values::specified::{AllowQuirks, Integer, LengthOrPercentage, NoCalcLength, Number}; use values::specified::length::{AU_PER_PT, AU_PER_PX, FontBaseSize}; const DEFAULT_SCRIPT_MIN_SIZE_PT: u32 = 8; const DEFAULT_SCRIPT_SIZE_MULTIPLIER: f64 = 0.71; #[derive(Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToCss)] /// A specified font-weight value pub enum FontWeight { /// Normal variant Normal, /// Bold variant Bold, /// Bolder variant Bolder, /// Lighter variant Lighter, /// Computed weight variant Weight(computed::FontWeight), /// System font varaint System(SystemFont), } impl FontWeight { /// Get a specified FontWeight from a gecko keyword pub fn from_gecko_keyword(kw: u32) -> Self { computed::FontWeight::from_int(kw as i32).map(FontWeight::Weight) .expect("Found unexpected value in style struct for font-weight property") } /// Get a specified FontWeight from a SystemFont pub fn system_font(f: SystemFont) -> Self { FontWeight::System(f) } /// Retreive a SystemFont from FontWeight pub fn get_system(&self) -> Option { if let FontWeight::System(s) = *self { Some(s) } else { None } } } impl Parse for FontWeight { fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { let result = match *input.next()? { Token::Ident(ref ident) => { match_ignore_ascii_case! { ident, "normal" => Ok(FontWeight::Normal), "bold" => Ok(FontWeight::Bold), "bolder" => Ok(FontWeight::Bolder), "lighter" => Ok(FontWeight::Lighter), _ => Err(()), } } Token::Number { int_value: Some(value), .. } => { computed::FontWeight::from_int(value).map(FontWeight::Weight) }, _ => Err(()), }; result.map_err(|_| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } } impl ToComputedValue for FontWeight { type ComputedValue = computed::FontWeight; #[inline] fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { match *self { FontWeight::Weight(weight) => weight, FontWeight::Normal => computed::FontWeight::normal(), FontWeight::Bold => computed::FontWeight::bold(), FontWeight::Bolder => { context.builder.get_parent_font().clone_font_weight().bolder() }, FontWeight::Lighter => { context.builder.get_parent_font().clone_font_weight().lighter() }, #[cfg(feature = "gecko")] FontWeight::System(_) => { context.cached_system_font.as_ref().unwrap().font_weight.clone() }, #[cfg(not(feature = "gecko"))] FontWeight::System(_) => unreachable!(), } } #[inline] fn from_computed_value(computed: &computed::FontWeight) -> Self { FontWeight::Weight(*computed) } } #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)] /// A specified font-size value pub enum FontSize { /// A length; e.g. 10px. Length(LengthOrPercentage), /// A keyword value, along with a ratio and absolute offset. /// The ratio in any specified keyword value /// will be 1 (with offset 0), but we cascade keywordness even /// after font-relative (percent and em) values /// have been applied, which is where the ratio /// comes in. The offset comes in if we cascaded a calc value, /// where the font-relative portion (em and percentage) will /// go into the ratio, and the remaining units all computed together /// will go into the offset. /// See bug 1355707. Keyword(KeywordInfo), /// font-size: smaller Smaller, /// font-size: larger Larger, /// Derived from a specified system font. System(SystemFont) } impl From for FontSize { fn from(other: LengthOrPercentage) -> Self { FontSize::Length(other) } } /// Specifies a prioritized list of font family names or generic family names. #[derive(Clone, Debug, Eq, Hash, PartialEq, ToCss)] pub enum FontFamily { /// List of `font-family` #[css(comma)] Values(#[css(iterable)] FontFamilyList), /// System font System(SystemFont), } impl FontFamily { /// Get `font-family` with system font pub fn system_font(f: SystemFont) -> Self { FontFamily::System(f) } /// Get system font pub fn get_system(&self) -> Option { if let FontFamily::System(s) = *self { Some(s) } else { None } } /// Parse a specified font-family value pub fn parse_specified<'i, 't>(input: &mut Parser<'i, 't>) -> Result> { input.parse_comma_separated(|input| SingleFontFamily::parse(input)).map(|v| { FontFamily::Values(FontFamilyList::new(v.into_boxed_slice())) }) } #[cfg(feature = "gecko")] /// Return the generic ID if it is a single generic font pub fn single_generic(&self) -> Option { match *self { FontFamily::Values(ref values) => values.single_generic(), _ => None, } } } impl ToComputedValue for FontFamily { type ComputedValue = computed::FontFamily; fn to_computed_value(&self, _cx: &Context) -> Self::ComputedValue { match *self { FontFamily::Values(ref v) => computed::FontFamily(v.clone()), FontFamily::System(_) => { #[cfg(feature = "gecko")] { _cx.cached_system_font.as_ref().unwrap().font_family.clone() } #[cfg(feature = "servo")] { unreachable!() } } } } fn from_computed_value(other: &computed::FontFamily) -> Self { FontFamily::Values(other.0.clone()) } } #[cfg(feature = "gecko")] impl MallocSizeOf for FontFamily { fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { match *self { FontFamily::Values(ref v) => { // Although a SharedFontList object is refcounted, we always // attribute its size to the specified value. unsafe { bindings::Gecko_SharedFontList_SizeOfIncludingThis( v.0.get() ) } } FontFamily::System(_) => 0, } } } impl Parse for FontFamily { /// # /// = | [ + ] /// TODO: fn parse<'i, 't>( _: &ParserContext, input: &mut Parser<'i, 't> ) -> Result> { FontFamily::parse_specified(input) } } /// `FamilyName::parse` is based on `SingleFontFamily::parse` and not the other way around /// because we want the former to exclude generic family keywords. impl Parse for FamilyName { fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { match SingleFontFamily::parse(input) { Ok(SingleFontFamily::FamilyName(name)) => Ok(name), Ok(SingleFontFamily::Generic(_)) => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), Err(e) => Err(e) } } } #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToCss)] /// Preserve the readability of text when font fallback occurs pub enum FontSizeAdjust { /// None variant None, /// Number variant Number(Number), /// system font System(SystemFont), } impl FontSizeAdjust { #[inline] /// Default value of font-size-adjust pub fn none() -> Self { FontSizeAdjust::None } /// Get font-size-adjust with SystemFont pub fn system_font(f: SystemFont) -> Self { FontSizeAdjust::System(f) } /// Get SystemFont variant pub fn get_system(&self) -> Option { if let FontSizeAdjust::System(s) = *self { Some(s) } else { None } } } impl ToComputedValue for FontSizeAdjust { type ComputedValue = computed::FontSizeAdjust; fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { match *self { FontSizeAdjust::None => computed::FontSizeAdjust::None, FontSizeAdjust::Number(ref n) => computed::FontSizeAdjust::Number(n.to_computed_value(context)), FontSizeAdjust::System(_) => { #[cfg(feature = "gecko")] { context.cached_system_font.as_ref().unwrap().font_size_adjust } #[cfg(feature = "servo")] { unreachable!() } } } } fn from_computed_value(computed: &computed::FontSizeAdjust) -> Self { match *computed { computed::FontSizeAdjust::None => FontSizeAdjust::None, computed::FontSizeAdjust::Number(ref v) => FontSizeAdjust::Number(Number::from_computed_value(v)), } } } impl Parse for FontSizeAdjust { /// none | fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { if input.try(|input| input.expect_ident_matching("none")).is_ok() { return Ok(FontSizeAdjust::None); } Ok(FontSizeAdjust::Number(Number::parse_non_negative(context, input)?)) } } /// Additional information for specified keyword-derived font sizes. pub type KeywordInfo = GenericKeywordInfo; impl KeywordInfo { /// Computes the final size for this font-size keyword, accounting for /// text-zoom. pub fn to_computed_value(&self, context: &Context) -> NonNegativeLength { let base = context.maybe_zoom_text(self.kw.to_computed_value(context)); base.scale_by(self.factor) + context.maybe_zoom_text(self.offset) } /// Given a parent keyword info (self), apply an additional factor/offset to it pub fn compose(self, factor: f32, offset: NonNegativeLength) -> Self { KeywordInfo { kw: self.kw, factor: self.factor * factor, offset: self.offset.scale_by(factor) + offset, } } } impl KeywordSize { /// Parses a keyword size. pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result> { try_match_ident_ignore_ascii_case! { input, "xx-small" => Ok(KeywordSize::XXSmall), "x-small" => Ok(KeywordSize::XSmall), "small" => Ok(KeywordSize::Small), "medium" => Ok(KeywordSize::Medium), "large" => Ok(KeywordSize::Large), "x-large" => Ok(KeywordSize::XLarge), "xx-large" => Ok(KeywordSize::XXLarge), } } } /// This is the ratio applied for font-size: larger /// and smaller by both Firefox and Chrome const LARGER_FONT_SIZE_RATIO: f32 = 1.2; /// The default font size. pub const FONT_MEDIUM_PX: i32 = 16; #[cfg(feature = "servo")] impl ToComputedValue for KeywordSize { type ComputedValue = NonNegativeLength; #[inline] fn to_computed_value(&self, _: &Context) -> NonNegativeLength { // https://drafts.csswg.org/css-fonts-3/#font-size-prop match *self { KeywordSize::XXSmall => Au::from_px(FONT_MEDIUM_PX) * 3 / 5, KeywordSize::XSmall => Au::from_px(FONT_MEDIUM_PX) * 3 / 4, KeywordSize::Small => Au::from_px(FONT_MEDIUM_PX) * 8 / 9, KeywordSize::Medium => Au::from_px(FONT_MEDIUM_PX), KeywordSize::Large => Au::from_px(FONT_MEDIUM_PX) * 6 / 5, KeywordSize::XLarge => Au::from_px(FONT_MEDIUM_PX) * 3 / 2, KeywordSize::XXLarge => Au::from_px(FONT_MEDIUM_PX) * 2, KeywordSize::XXXLarge => Au::from_px(FONT_MEDIUM_PX) * 3, }.into() } #[inline] fn from_computed_value(_: &NonNegativeLength) -> Self { unreachable!() } } #[cfg(feature = "gecko")] impl ToComputedValue for KeywordSize { type ComputedValue = NonNegativeLength; #[inline] fn to_computed_value(&self, cx: &Context) -> NonNegativeLength { use context::QuirksMode; use values::specified::length::au_to_int_px; // Data from nsRuleNode.cpp in Gecko // Mapping from base size and HTML size to pixels // The first index is (base_size - 9), the second is the // HTML size. "0" is CSS keyword xx-small, not HTML size 0, // since HTML size 0 is the same as 1. // // xxs xs s m l xl xxl - // - 0/1 2 3 4 5 6 7 static FONT_SIZE_MAPPING: [[i32; 8]; 8] = [ [9, 9, 9, 9, 11, 14, 18, 27], [9, 9, 9, 10, 12, 15, 20, 30], [9, 9, 10, 11, 13, 17, 22, 33], [9, 9, 10, 12, 14, 18, 24, 36], [9, 10, 12, 13, 16, 20, 26, 39], [9, 10, 12, 14, 17, 21, 28, 42], [9, 10, 13, 15, 18, 23, 30, 45], [9, 10, 13, 16, 18, 24, 32, 48] ]; // Data from nsRuleNode.cpp in Gecko // (https://dxr.mozilla.org/mozilla-central/rev/35fbf14b9/layout/style/nsRuleNode.cpp#3303) // // This table gives us compatibility with WinNav4 for the default fonts only. // In WinNav4, the default fonts were: // // Times/12pt == Times/16px at 96ppi // Courier/10pt == Courier/13px at 96ppi // // xxs xs s m l xl xxl - // - 1 2 3 4 5 6 7 static QUIRKS_FONT_SIZE_MAPPING: [[i32; 8]; 8] = [ [9, 9, 9, 9, 11, 14, 18, 28], [9, 9, 9, 10, 12, 15, 20, 31], [9, 9, 9, 11, 13, 17, 22, 34], [9, 9, 10, 12, 14, 18, 24, 37], [9, 9, 10, 13, 16, 20, 26, 40], [9, 9, 11, 14, 17, 21, 28, 42], [9, 10, 12, 15, 17, 23, 30, 45], [9, 10, 13, 16, 18, 24, 32, 48] ]; static FONT_SIZE_FACTORS: [i32; 8] = [60, 75, 89, 100, 120, 150, 200, 300]; let ref gecko_font = cx.style().get_font().gecko(); let base_size = unsafe { Atom::with(gecko_font.mLanguage.mRawPtr, |atom| { cx.font_metrics_provider.get_size(atom, gecko_font.mGenericID).0 }) }; let base_size_px = au_to_int_px(base_size as f32); let html_size = self.html_size() as usize; if base_size_px >= 9 && base_size_px <= 16 { let mapping = if cx.quirks_mode == QuirksMode::Quirks { QUIRKS_FONT_SIZE_MAPPING } else { FONT_SIZE_MAPPING }; Au::from_px(mapping[(base_size_px - 9) as usize][html_size]).into() } else { Au(FONT_SIZE_FACTORS[html_size] * base_size / 100).into() } } #[inline] fn from_computed_value(_: &NonNegativeLength) -> Self { unreachable!() } } impl FontSize { /// pub fn from_html_size(size: u8) -> Self { FontSize::Keyword(match size { // If value is less than 1, let it be 1. 0 | 1 => KeywordSize::XSmall, 2 => KeywordSize::Small, 3 => KeywordSize::Medium, 4 => KeywordSize::Large, 5 => KeywordSize::XLarge, 6 => KeywordSize::XXLarge, // If value is greater than 7, let it be 7. _ => KeywordSize::XXXLarge, }.into()) } /// Compute it against a given base font size pub fn to_computed_value_against( &self, context: &Context, base_size: FontBaseSize, ) -> computed::FontSize { use values::specified::length::FontRelativeLength; let compose_keyword = |factor| { context.style().get_parent_font() .clone_font_size().keyword_info .map(|i| i.compose(factor, Au(0).into())) }; let mut info = None; let size = match *self { FontSize::Length(LengthOrPercentage::Length( NoCalcLength::FontRelative(value))) => { if let FontRelativeLength::Em(em) = value { // If the parent font was keyword-derived, this is too. // Tack the em unit onto the factor info = compose_keyword(em); } value.to_computed_value(context, base_size).into() } FontSize::Length(LengthOrPercentage::Length( NoCalcLength::ServoCharacterWidth(value))) => { value.to_computed_value(base_size.resolve(context)).into() } FontSize::Length(LengthOrPercentage::Length( NoCalcLength::Absolute(ref l))) => { context.maybe_zoom_text(l.to_computed_value(context).into()) } FontSize::Length(LengthOrPercentage::Length(ref l)) => { l.to_computed_value(context).into() } FontSize::Length(LengthOrPercentage::Percentage(pc)) => { // If the parent font was keyword-derived, this is too. // Tack the % onto the factor info = compose_keyword(pc.0); base_size.resolve(context).scale_by(pc.0).into() } FontSize::Length(LengthOrPercentage::Calc(ref calc)) => { let parent = context.style().get_parent_font().clone_font_size(); // if we contain em/% units and the parent was keyword derived, this is too // Extract the ratio/offset and compose it if (calc.em.is_some() || calc.percentage.is_some()) && parent.keyword_info.is_some() { let ratio = calc.em.unwrap_or(0.) + calc.percentage.map_or(0., |pc| pc.0); // Compute it, but shave off the font-relative part (em, %). // // This will mean that other font-relative units like ex and // ch will be computed against the old parent font even when // the font changes. // // There's no particular "right answer" for what to do here, // Gecko recascades as if the font had changed, we instead // track the changes and reapply, which means that we carry // over old computed ex/ch values whilst Gecko recomputes // new ones. // // This is enough of an edge case to not really matter. let abs = calc.to_computed_value_zoomed( context, FontBaseSize::InheritedStyleButStripEmUnits, ).length_component(); info = parent.keyword_info.map(|i| i.compose(ratio, abs.into())); } let calc = calc.to_computed_value_zoomed(context, base_size); calc.to_used_value(Some(base_size.resolve(context))).unwrap().into() } FontSize::Keyword(i) => { // As a specified keyword, this is keyword derived info = Some(i); i.to_computed_value(context) } FontSize::Smaller => { info = compose_keyword(1. / LARGER_FONT_SIZE_RATIO); FontRelativeLength::Em(1. / LARGER_FONT_SIZE_RATIO) .to_computed_value(context, base_size).into() } FontSize::Larger => { info = compose_keyword(LARGER_FONT_SIZE_RATIO); FontRelativeLength::Em(LARGER_FONT_SIZE_RATIO) .to_computed_value(context, base_size).into() } FontSize::System(_) => { #[cfg(feature = "servo")] { unreachable!() } #[cfg(feature = "gecko")] { context.cached_system_font.as_ref().unwrap().font_size.size } } }; computed::FontSize { size: size, keyword_info: info, } } } impl ToComputedValue for FontSize { type ComputedValue = computed::FontSize; #[inline] fn to_computed_value(&self, context: &Context) -> computed::FontSize { self.to_computed_value_against(context, FontBaseSize::InheritedStyle) } #[inline] fn from_computed_value(computed: &computed::FontSize) -> Self { FontSize::Length(LengthOrPercentage::Length( ToComputedValue::from_computed_value(&computed.size.0) )) } } impl FontSize { /// Construct a system font value. pub fn system_font(f: SystemFont) -> Self { FontSize::System(f) } /// Obtain the system font, if any pub fn get_system(&self) -> Option { if let FontSize::System(s) = *self { Some(s) } else { None } } #[inline] /// Get initial value for specified font size. pub fn medium() -> Self { FontSize::Keyword(KeywordInfo::medium()) } /// Parses a font-size, with quirks. pub fn parse_quirky<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, allow_quirks: AllowQuirks ) -> Result> { if let Ok(lop) = input.try(|i| LengthOrPercentage::parse_non_negative_quirky(context, i, allow_quirks)) { return Ok(FontSize::Length(lop)) } if let Ok(kw) = input.try(KeywordSize::parse) { return Ok(FontSize::Keyword(kw.into())) } try_match_ident_ignore_ascii_case! { input, "smaller" => Ok(FontSize::Smaller), "larger" => Ok(FontSize::Larger), } } #[allow(unused_mut)] /// Cascade `font-size` with specified value pub fn cascade_specified_font_size( context: &mut Context, specified_value: &FontSize, mut computed: computed::FontSize ) { // we could use clone_language and clone_font_family() here but that's // expensive. Do it only in gecko mode for now. #[cfg(feature = "gecko")] { // if the language or generic changed, we need to recalculate // the font size from the stored font-size origin information. if context.builder.get_font().gecko().mLanguage.mRawPtr != context.builder.get_parent_font().gecko().mLanguage.mRawPtr || context.builder.get_font().gecko().mGenericID != context.builder.get_parent_font().gecko().mGenericID { if let Some(info) = computed.keyword_info { computed.size = info.to_computed_value(context); } } } let device = context.builder.device; let mut font = context.builder.take_font(); let parent_unconstrained = { let parent_font = context.builder.get_parent_font(); font.apply_font_size(computed, parent_font, device) }; context.builder.put_font(font); if let Some(parent) = parent_unconstrained { let new_unconstrained = specified_value.to_computed_value_against(context, FontBaseSize::Custom(Au::from(parent))); context.builder .mutate_font() .apply_unconstrained_font_size(new_unconstrained.size); } } } impl Parse for FontSize { /// | | | fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { FontSize::parse_quirky(context, input, AllowQuirks::No) } } bitflags! { #[cfg_attr(feature = "servo", derive(MallocSizeOf))] /// Flags of variant alternates in bit struct VariantAlternatesParsingFlags: u8 { /// None of variant alternates enabled const NORMAL = 0; /// Historical forms const HISTORICAL_FORMS = 0x01; /// Stylistic Alternates const STYLISTIC = 0x02; /// Stylistic Sets const STYLESET = 0x04; /// Character Variant const CHARACTER_VARIANT = 0x08; /// Swash glyphs const SWASH = 0x10; /// Ornaments glyphs const ORNAMENTS = 0x20; /// Annotation forms const ANNOTATION = 0x40; } } #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)] /// Set of variant alternates pub enum VariantAlternates { /// Enables display of stylistic alternates #[css(function)] Stylistic(CustomIdent), /// Enables display with stylistic sets #[css(comma, function)] Styleset(#[css(iterable)] Box<[CustomIdent]>), /// Enables display of specific character variants #[css(comma, function)] CharacterVariant(#[css(iterable)] Box<[CustomIdent]>), /// Enables display of swash glyphs #[css(function)] Swash(CustomIdent), /// Enables replacement of default glyphs with ornaments #[css(function)] Ornaments(CustomIdent), /// Enables display of alternate annotation forms #[css(function)] Annotation(CustomIdent), /// Enables display of historical forms HistoricalForms, } #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)] /// List of Variant Alternates pub struct VariantAlternatesList( #[css(if_empty = "normal", iterable)] pub Box<[VariantAlternates]>, ); impl VariantAlternatesList { /// Returns the length of all variant alternates. pub fn len(&self) -> usize { self.0.iter().fold(0, |acc, alternate| { match *alternate { VariantAlternates::Swash(_) | VariantAlternates::Stylistic(_) | VariantAlternates::Ornaments(_) | VariantAlternates::Annotation(_) => { acc + 1 }, VariantAlternates::Styleset(ref slice) | VariantAlternates::CharacterVariant(ref slice) => { acc + slice.len() }, _ => acc, } }) } } #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)] /// Control over the selection of these alternate glyphs pub enum FontVariantAlternates { /// Use alternative glyph from value Value(VariantAlternatesList), /// Use system font glyph System(SystemFont) } impl FontVariantAlternates { #[inline] /// Get initial specified value with VariantAlternatesList pub fn get_initial_specified_value() -> Self { FontVariantAlternates::Value(VariantAlternatesList(vec![].into_boxed_slice())) } /// Get FontVariantAlternates with system font pub fn system_font(f: SystemFont) -> Self { FontVariantAlternates::System(f) } /// Get SystemFont of FontVariantAlternates pub fn get_system(&self) -> Option { if let FontVariantAlternates::System(s) = *self { Some(s) } else { None } } } impl ToComputedValue for FontVariantAlternates { type ComputedValue = computed::FontVariantAlternates; fn to_computed_value(&self, _context: &Context) -> computed::FontVariantAlternates { match *self { FontVariantAlternates::Value(ref v) => v.clone(), FontVariantAlternates::System(_) => { #[cfg(feature = "gecko")] { _context.cached_system_font.as_ref().unwrap().font_variant_alternates.clone() } #[cfg(feature = "servo")] { unreachable!() } } } } fn from_computed_value(other: &computed::FontVariantAlternates) -> Self { FontVariantAlternates::Value(other.clone()) } } impl Parse for FontVariantAlternates { /// normal | /// [ stylistic() || /// historical-forms || /// styleset( #) || /// character-variant( #) || /// swash() || /// ornaments() || /// annotation() ] fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { let mut alternates = Vec::new(); if input.try(|input| input.expect_ident_matching("normal")).is_ok() { return Ok(FontVariantAlternates::Value(VariantAlternatesList(alternates.into_boxed_slice()))); } let mut parsed_alternates = VariantAlternatesParsingFlags::empty(); macro_rules! check_if_parsed( ($input:expr, $flag:path) => ( if parsed_alternates.contains($flag) { return Err($input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } parsed_alternates |= $flag; ) ); while let Ok(_) = input.try(|input| { // FIXME: remove clone() when lifetimes are non-lexical match input.next()?.clone() { Token::Ident(ref value) if value.eq_ignore_ascii_case("historical-forms") => { check_if_parsed!(input, VariantAlternatesParsingFlags::HISTORICAL_FORMS); alternates.push(VariantAlternates::HistoricalForms); Ok(()) }, Token::Function(ref name) => { input.parse_nested_block(|i| { match_ignore_ascii_case! { &name, "swash" => { check_if_parsed!(i, VariantAlternatesParsingFlags::SWASH); let location = i.current_source_location(); let ident = CustomIdent::from_ident(location, i.expect_ident()?, &[])?; alternates.push(VariantAlternates::Swash(ident)); Ok(()) }, "stylistic" => { check_if_parsed!(i, VariantAlternatesParsingFlags::STYLISTIC); let location = i.current_source_location(); let ident = CustomIdent::from_ident(location, i.expect_ident()?, &[])?; alternates.push(VariantAlternates::Stylistic(ident)); Ok(()) }, "ornaments" => { check_if_parsed!(i, VariantAlternatesParsingFlags::ORNAMENTS); let location = i.current_source_location(); let ident = CustomIdent::from_ident(location, i.expect_ident()?, &[])?; alternates.push(VariantAlternates::Ornaments(ident)); Ok(()) }, "annotation" => { check_if_parsed!(i, VariantAlternatesParsingFlags::ANNOTATION); let location = i.current_source_location(); let ident = CustomIdent::from_ident(location, i.expect_ident()?, &[])?; alternates.push(VariantAlternates::Annotation(ident)); Ok(()) }, "styleset" => { check_if_parsed!(i, VariantAlternatesParsingFlags::STYLESET); let idents = i.parse_comma_separated(|i| { let location = i.current_source_location(); CustomIdent::from_ident(location, i.expect_ident()?, &[]) })?; alternates.push(VariantAlternates::Styleset(idents.into_boxed_slice())); Ok(()) }, "character-variant" => { check_if_parsed!(i, VariantAlternatesParsingFlags::CHARACTER_VARIANT); let idents = i.parse_comma_separated(|i| { let location = i.current_source_location(); CustomIdent::from_ident(location, i.expect_ident()?, &[]) })?; alternates.push(VariantAlternates::CharacterVariant(idents.into_boxed_slice())); Ok(()) }, _ => return Err(i.new_custom_error(StyleParseErrorKind::UnspecifiedError)), } }) }, _ => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), } }) { } if parsed_alternates.is_empty() { return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); } Ok(FontVariantAlternates::Value(VariantAlternatesList(alternates.into_boxed_slice()))) } } bitflags! { #[derive(MallocSizeOf)] /// Vairants for east asian variant pub struct VariantEastAsian: u16 { /// None of the features const NORMAL = 0; /// Enables rendering of JIS78 forms (OpenType feature: jp78) const JIS78 = 0x01; /// Enables rendering of JIS83 forms (OpenType feature: jp83). const JIS83 = 0x02; /// Enables rendering of JIS90 forms (OpenType feature: jp90). const JIS90 = 0x04; /// Enables rendering of JIS2004 forms (OpenType feature: jp04). const JIS04 = 0x08; /// Enables rendering of simplified forms (OpenType feature: smpl). const SIMPLIFIED = 0x10; /// Enables rendering of traditional forms (OpenType feature: trad). const TRADITIONAL = 0x20; /// Enables rendering of full-width variants (OpenType feature: fwid). const FULL_WIDTH = 0x40; /// Enables rendering of proportionally-spaced variants (OpenType feature: pwid). const PROPORTIONAL_WIDTH = 0x80; /// Enables display of ruby variant glyphs (OpenType feature: ruby). const RUBY = 0x100; } } #[cfg(feature = "gecko")] impl VariantEastAsian { /// Obtain a specified value from a Gecko keyword value /// /// Intended for use with presentation attributes, not style structs pub fn from_gecko_keyword(kw: u16) -> Self { Self::from_bits_truncate(kw) } /// Transform into gecko keyword pub fn to_gecko_keyword(self) -> u16 { self.bits() } } impl ToCss for VariantEastAsian { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { if self.is_empty() { return dest.write_str("normal") } let mut has_any = false; macro_rules! write_value { ($ident:path => $str:expr) => { if self.intersects($ident) { if has_any { dest.write_str(" ")?; } has_any = true; dest.write_str($str)?; } } } write_value!(VariantEastAsian::JIS78 => "jis78"); write_value!(VariantEastAsian::JIS83 => "jis83"); write_value!(VariantEastAsian::JIS90 => "jis90"); write_value!(VariantEastAsian::JIS04 => "jis04"); write_value!(VariantEastAsian::SIMPLIFIED => "simplified"); write_value!(VariantEastAsian::TRADITIONAL => "traditional"); write_value!(VariantEastAsian::FULL_WIDTH => "full-width"); write_value!(VariantEastAsian::PROPORTIONAL_WIDTH => "proportional-width"); write_value!(VariantEastAsian::RUBY => "ruby"); debug_assert!(has_any); Ok(()) } } #[cfg(feature = "gecko")] impl_gecko_keyword_conversions!(VariantEastAsian, u16); /// Asserts that all variant-east-asian matches its NS_FONT_VARIANT_EAST_ASIAN_* value. #[cfg(feature = "gecko")] #[inline] pub fn assert_variant_east_asian_matches() { use gecko_bindings::structs; macro_rules! check_variant_east_asian { ( $( $a:ident => $b:path),*, ) => { if cfg!(debug_assertions) { $( assert_eq!(structs::$a as u16, $b.bits()); )* } } } check_variant_east_asian! { NS_FONT_VARIANT_EAST_ASIAN_FULL_WIDTH => VariantEastAsian::FULL_WIDTH, NS_FONT_VARIANT_EAST_ASIAN_JIS04 => VariantEastAsian::JIS04, NS_FONT_VARIANT_EAST_ASIAN_JIS78 => VariantEastAsian::JIS78, NS_FONT_VARIANT_EAST_ASIAN_JIS83 => VariantEastAsian::JIS83, NS_FONT_VARIANT_EAST_ASIAN_JIS90 => VariantEastAsian::JIS90, NS_FONT_VARIANT_EAST_ASIAN_PROP_WIDTH => VariantEastAsian::PROPORTIONAL_WIDTH, NS_FONT_VARIANT_EAST_ASIAN_RUBY => VariantEastAsian::RUBY, NS_FONT_VARIANT_EAST_ASIAN_SIMPLIFIED => VariantEastAsian::SIMPLIFIED, NS_FONT_VARIANT_EAST_ASIAN_TRADITIONAL => VariantEastAsian::TRADITIONAL, } } #[cfg_attr(feature = "gecko", derive(MallocSizeOf))] #[derive(Clone, Debug, PartialEq, ToCss)] /// Allows control of glyph substitution and sizing in East Asian text. pub enum FontVariantEastAsian { /// Value variant with `variant-east-asian` Value(VariantEastAsian), /// System font variant System(SystemFont) } impl FontVariantEastAsian { #[inline] /// Get default `font-variant-east-asian` with `empty` variant pub fn empty() -> Self { FontVariantEastAsian::Value(VariantEastAsian::empty()) } /// Get `font-variant-east-asian` with system font pub fn system_font(f: SystemFont) -> Self { FontVariantEastAsian::System(f) } /// Get system font pub fn get_system(&self) -> Option { if let FontVariantEastAsian::System(s) = *self { Some(s) } else { None } } } impl ToComputedValue for FontVariantEastAsian { type ComputedValue = computed::FontVariantEastAsian; fn to_computed_value(&self, _context: &Context) -> computed::FontVariantEastAsian { match *self { FontVariantEastAsian::Value(ref v) => v.clone(), FontVariantEastAsian::System(_) => { #[cfg(feature = "gecko")] { _context.cached_system_font.as_ref().unwrap().font_variant_east_asian.clone() } #[cfg(feature = "servo")] { unreachable!() } } } } fn from_computed_value(other: &computed::FontVariantEastAsian) -> Self { FontVariantEastAsian::Value(other.clone()) } } impl Parse for FontVariantEastAsian { /// normal | [ || || ruby ] /// = [ jis78 | jis83 | jis90 | jis04 | simplified | traditional ] /// = [ full-width | proportional-width ] fn parse<'i, 't>( _context: &ParserContext, input: &mut Parser<'i, 't> ) -> Result> { let mut result = VariantEastAsian::empty(); if input.try(|input| input.expect_ident_matching("normal")).is_ok() { return Ok(FontVariantEastAsian::Value(result)) } while let Ok(flag) = input.try(|input| { Ok(match_ignore_ascii_case! { &input.expect_ident().map_err(|_| ())?, "jis78" => exclusive_value!((result, VariantEastAsian::JIS78 | VariantEastAsian::JIS83 | VariantEastAsian::JIS90 | VariantEastAsian::JIS04 | VariantEastAsian::SIMPLIFIED | VariantEastAsian::TRADITIONAL ) => VariantEastAsian::JIS78), "jis83" => exclusive_value!((result, VariantEastAsian::JIS78 | VariantEastAsian::JIS83 | VariantEastAsian::JIS90 | VariantEastAsian::JIS04 | VariantEastAsian::SIMPLIFIED | VariantEastAsian::TRADITIONAL ) => VariantEastAsian::JIS83), "jis90" => exclusive_value!((result, VariantEastAsian::JIS78 | VariantEastAsian::JIS83 | VariantEastAsian::JIS90 | VariantEastAsian::JIS04 | VariantEastAsian::SIMPLIFIED | VariantEastAsian::TRADITIONAL ) => VariantEastAsian::JIS90), "jis04" => exclusive_value!((result, VariantEastAsian::JIS78 | VariantEastAsian::JIS83 | VariantEastAsian::JIS90 | VariantEastAsian::JIS04 | VariantEastAsian::SIMPLIFIED | VariantEastAsian::TRADITIONAL ) => VariantEastAsian::JIS04), "simplified" => exclusive_value!((result, VariantEastAsian::JIS78 | VariantEastAsian::JIS83 | VariantEastAsian::JIS90 | VariantEastAsian::JIS04 | VariantEastAsian::SIMPLIFIED | VariantEastAsian::TRADITIONAL ) => VariantEastAsian::SIMPLIFIED), "traditional" => exclusive_value!((result, VariantEastAsian::JIS78 | VariantEastAsian::JIS83 | VariantEastAsian::JIS90 | VariantEastAsian::JIS04 | VariantEastAsian::SIMPLIFIED | VariantEastAsian::TRADITIONAL ) => VariantEastAsian::TRADITIONAL), "full-width" => exclusive_value!((result, VariantEastAsian::FULL_WIDTH | VariantEastAsian::PROPORTIONAL_WIDTH ) => VariantEastAsian::FULL_WIDTH), "proportional-width" => exclusive_value!((result, VariantEastAsian::FULL_WIDTH | VariantEastAsian::PROPORTIONAL_WIDTH ) => VariantEastAsian::PROPORTIONAL_WIDTH), "ruby" => exclusive_value!((result, VariantEastAsian::RUBY) => VariantEastAsian::RUBY), _ => return Err(()), }) }) { result.insert(flag); } if !result.is_empty() { Ok(FontVariantEastAsian::Value(result)) } else { Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } } } bitflags! { #[derive(MallocSizeOf)] /// Variants of ligatures pub struct VariantLigatures: u16 { /// Specifies that common default features are enabled const NORMAL = 0; /// Specifies that all types of ligatures and contextual forms /// covered by this property are explicitly disabled const NONE = 0x01; /// Enables display of common ligatures const COMMON_LIGATURES = 0x02; /// Disables display of common ligatures const NO_COMMON_LIGATURES = 0x04; /// Enables display of discretionary ligatures const DISCRETIONARY_LIGATURES = 0x08; /// Disables display of discretionary ligatures const NO_DISCRETIONARY_LIGATURES = 0x10; /// Enables display of historical ligatures const HISTORICAL_LIGATURES = 0x20; /// Disables display of historical ligatures const NO_HISTORICAL_LIGATURES = 0x40; /// Enables display of contextual alternates const CONTEXTUAL = 0x80; /// Disables display of contextual alternates const NO_CONTEXTUAL = 0x100; } } #[cfg(feature = "gecko")] impl VariantLigatures { /// Obtain a specified value from a Gecko keyword value /// /// Intended for use with presentation attributes, not style structs pub fn from_gecko_keyword(kw: u16) -> Self { Self::from_bits_truncate(kw) } /// Transform into gecko keyword pub fn to_gecko_keyword(self) -> u16 { self.bits() } } impl ToCss for VariantLigatures { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { if self.is_empty() { return dest.write_str("normal") } if self.contains(VariantLigatures::NONE) { return dest.write_str("none") } let mut has_any = false; macro_rules! write_value { ($ident:path => $str:expr) => { if self.intersects($ident) { if has_any { dest.write_str(" ")?; } has_any = true; dest.write_str($str)?; } } } write_value!(VariantLigatures::COMMON_LIGATURES => "common-ligatures"); write_value!(VariantLigatures::NO_COMMON_LIGATURES => "no-common-ligatures"); write_value!(VariantLigatures::DISCRETIONARY_LIGATURES => "discretionary-ligatures"); write_value!(VariantLigatures::NO_DISCRETIONARY_LIGATURES => "no-discretionary-ligatures"); write_value!(VariantLigatures::HISTORICAL_LIGATURES => "historical-ligatures"); write_value!(VariantLigatures::NO_HISTORICAL_LIGATURES => "no-historical-ligatures"); write_value!(VariantLigatures::CONTEXTUAL => "contextual"); write_value!(VariantLigatures::NO_CONTEXTUAL => "no-contextual"); debug_assert!(has_any); Ok(()) } } #[cfg(feature = "gecko")] impl_gecko_keyword_conversions!(VariantLigatures, u16); /// Asserts that all variant-east-asian matches its NS_FONT_VARIANT_EAST_ASIAN_* value. #[cfg(feature = "gecko")] #[inline] pub fn assert_variant_ligatures_matches() { use gecko_bindings::structs; macro_rules! check_variant_ligatures { ( $( $a:ident => $b:path),*, ) => { if cfg!(debug_assertions) { $( assert_eq!(structs::$a as u16, $b.bits()); )* } } } check_variant_ligatures! { NS_FONT_VARIANT_LIGATURES_NONE => VariantLigatures::NONE, NS_FONT_VARIANT_LIGATURES_COMMON => VariantLigatures::COMMON_LIGATURES, NS_FONT_VARIANT_LIGATURES_NO_COMMON => VariantLigatures::NO_COMMON_LIGATURES, NS_FONT_VARIANT_LIGATURES_DISCRETIONARY => VariantLigatures::DISCRETIONARY_LIGATURES, NS_FONT_VARIANT_LIGATURES_NO_DISCRETIONARY => VariantLigatures::NO_DISCRETIONARY_LIGATURES, NS_FONT_VARIANT_LIGATURES_HISTORICAL => VariantLigatures::HISTORICAL_LIGATURES, NS_FONT_VARIANT_LIGATURES_NO_HISTORICAL => VariantLigatures::NO_HISTORICAL_LIGATURES, NS_FONT_VARIANT_LIGATURES_CONTEXTUAL => VariantLigatures::CONTEXTUAL, NS_FONT_VARIANT_LIGATURES_NO_CONTEXTUAL => VariantLigatures::NO_CONTEXTUAL, } } #[cfg_attr(feature = "gecko", derive(MallocSizeOf))] #[derive(Clone, Debug, PartialEq, ToCss)] /// Ligatures and contextual forms are ways of combining glyphs /// to produce more harmonized forms pub enum FontVariantLigatures { /// Value variant with `variant-ligatures` Value(VariantLigatures), /// System font variant System(SystemFont) } impl FontVariantLigatures { /// Get `font-variant-ligatures` with system font pub fn system_font(f: SystemFont) -> Self { FontVariantLigatures::System(f) } /// Get system font pub fn get_system(&self) -> Option { if let FontVariantLigatures::System(s) = *self { Some(s) } else { None } } #[inline] /// Default value of `font-variant-ligatures` as `empty` pub fn empty() -> FontVariantLigatures { FontVariantLigatures::Value(VariantLigatures::empty()) } #[inline] /// Get `none` variant of `font-variant-ligatures` pub fn none() -> FontVariantLigatures { FontVariantLigatures::Value(VariantLigatures::NONE) } } impl ToComputedValue for FontVariantLigatures { type ComputedValue = computed::FontVariantLigatures; fn to_computed_value(&self, _context: &Context) -> computed::FontVariantLigatures { match *self { FontVariantLigatures::Value(ref v) => v.clone(), FontVariantLigatures::System(_) => { #[cfg(feature = "gecko")] { _context.cached_system_font.as_ref().unwrap().font_variant_ligatures.clone() } #[cfg(feature = "servo")] { unreachable!() } } } } fn from_computed_value(other: &computed::FontVariantLigatures) -> Self { FontVariantLigatures::Value(other.clone()) } } impl Parse for FontVariantLigatures { /// normal | none | /// [ || /// || /// || /// ] /// = [ common-ligatures | no-common-ligatures ] /// = [ discretionary-ligatures | no-discretionary-ligatures ] /// = [ historical-ligatures | no-historical-ligatures ] /// = [ contextual | no-contextual ] fn parse<'i, 't> ( _context: &ParserContext, input: &mut Parser<'i, 't> ) -> Result> { let mut result = VariantLigatures::empty(); if input.try(|input| input.expect_ident_matching("normal")).is_ok() { return Ok(FontVariantLigatures::Value(result)) } if input.try(|input| input.expect_ident_matching("none")).is_ok() { return Ok(FontVariantLigatures::Value(VariantLigatures::NONE)) } while let Ok(flag) = input.try(|input| { Ok(match_ignore_ascii_case! { &input.expect_ident().map_err(|_| ())?, "common-ligatures" => exclusive_value!((result, VariantLigatures::COMMON_LIGATURES | VariantLigatures::NO_COMMON_LIGATURES ) => VariantLigatures::COMMON_LIGATURES), "no-common-ligatures" => exclusive_value!((result, VariantLigatures::COMMON_LIGATURES | VariantLigatures::NO_COMMON_LIGATURES ) => VariantLigatures::NO_COMMON_LIGATURES), "discretionary-ligatures" => exclusive_value!((result, VariantLigatures::DISCRETIONARY_LIGATURES | VariantLigatures::NO_DISCRETIONARY_LIGATURES ) => VariantLigatures::DISCRETIONARY_LIGATURES), "no-discretionary-ligatures" => exclusive_value!((result, VariantLigatures::DISCRETIONARY_LIGATURES | VariantLigatures::NO_DISCRETIONARY_LIGATURES ) => VariantLigatures::NO_DISCRETIONARY_LIGATURES), "historical-ligatures" => exclusive_value!((result, VariantLigatures::HISTORICAL_LIGATURES | VariantLigatures::NO_HISTORICAL_LIGATURES ) => VariantLigatures::HISTORICAL_LIGATURES), "no-historical-ligatures" => exclusive_value!((result, VariantLigatures::HISTORICAL_LIGATURES | VariantLigatures::NO_HISTORICAL_LIGATURES ) => VariantLigatures::NO_HISTORICAL_LIGATURES), "contextual" => exclusive_value!((result, VariantLigatures::CONTEXTUAL | VariantLigatures::NO_CONTEXTUAL ) => VariantLigatures::CONTEXTUAL), "no-contextual" => exclusive_value!((result, VariantLigatures::CONTEXTUAL | VariantLigatures::NO_CONTEXTUAL ) => VariantLigatures::NO_CONTEXTUAL), _ => return Err(()), }) }) { result.insert(flag); } if !result.is_empty() { Ok(FontVariantLigatures::Value(result)) } else { Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } } } bitflags! { #[derive(MallocSizeOf)] /// Vairants of numeric values pub struct VariantNumeric: u8 { /// None of other variants are enabled. const NORMAL = 0; /// Enables display of lining numerals. const LINING_NUMS = 0x01; /// Enables display of old-style numerals. const OLDSTYLE_NUMS = 0x02; /// Enables display of proportional numerals. const PROPORTIONAL_NUMS = 0x04; /// Enables display of tabular numerals. const TABULAR_NUMS = 0x08; /// Enables display of lining diagonal fractions. const DIAGONAL_FRACTIONS = 0x10; /// Enables display of lining stacked fractions. const STACKED_FRACTIONS = 0x20; /// Enables display of letter forms used with ordinal numbers. const ORDINAL = 0x80; /// Enables display of slashed zeros. const SLASHED_ZERO = 0x40; } } #[cfg(feature = "gecko")] impl VariantNumeric { /// Obtain a specified value from a Gecko keyword value /// /// Intended for use with presentation attributes, not style structs pub fn from_gecko_keyword(kw: u8) -> Self { Self::from_bits_truncate(kw) } /// Transform into gecko keyword pub fn to_gecko_keyword(self) -> u8 { self.bits() } } impl ToCss for VariantNumeric { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { if self.is_empty() { return dest.write_str("normal") } let mut has_any = false; macro_rules! write_value { ($ident:path => $str:expr) => { if self.intersects($ident) { if has_any { dest.write_str(" ")?; } has_any = true; dest.write_str($str)?; } } } write_value!(VariantNumeric::LINING_NUMS => "lining-nums"); write_value!(VariantNumeric::OLDSTYLE_NUMS => "oldstyle-nums"); write_value!(VariantNumeric::PROPORTIONAL_NUMS => "proportional-nums"); write_value!(VariantNumeric::TABULAR_NUMS => "tabular-nums"); write_value!(VariantNumeric::DIAGONAL_FRACTIONS => "diagonal-fractions"); write_value!(VariantNumeric::STACKED_FRACTIONS => "stacked-fractions"); write_value!(VariantNumeric::SLASHED_ZERO => "slashed-zero"); write_value!(VariantNumeric::ORDINAL => "ordinal"); debug_assert!(has_any); Ok(()) } } #[cfg(feature = "gecko")] impl_gecko_keyword_conversions!(VariantNumeric, u8); /// Asserts that all variant-east-asian matches its NS_FONT_VARIANT_EAST_ASIAN_* value. #[cfg(feature = "gecko")] #[inline] pub fn assert_variant_numeric_matches() { use gecko_bindings::structs; macro_rules! check_variant_numeric { ( $( $a:ident => $b:path),*, ) => { if cfg!(debug_assertions) { $( assert_eq!(structs::$a as u8, $b.bits()); )* } } } check_variant_numeric! { NS_FONT_VARIANT_NUMERIC_LINING => VariantNumeric::LINING_NUMS, NS_FONT_VARIANT_NUMERIC_OLDSTYLE => VariantNumeric::OLDSTYLE_NUMS, NS_FONT_VARIANT_NUMERIC_PROPORTIONAL => VariantNumeric::PROPORTIONAL_NUMS, NS_FONT_VARIANT_NUMERIC_TABULAR => VariantNumeric::TABULAR_NUMS, NS_FONT_VARIANT_NUMERIC_DIAGONAL_FRACTIONS => VariantNumeric::DIAGONAL_FRACTIONS, NS_FONT_VARIANT_NUMERIC_STACKED_FRACTIONS => VariantNumeric::STACKED_FRACTIONS, NS_FONT_VARIANT_NUMERIC_SLASHZERO => VariantNumeric::SLASHED_ZERO, NS_FONT_VARIANT_NUMERIC_ORDINAL => VariantNumeric::ORDINAL, } } #[cfg_attr(feature = "gecko", derive(MallocSizeOf))] #[derive(Clone, Debug, PartialEq, ToCss)] /// Specifies control over numerical forms. pub enum FontVariantNumeric { /// Value variant with `variant-numeric` Value(VariantNumeric), /// System font System(SystemFont) } impl FontVariantNumeric { #[inline] /// Default value of `font-variant-numeric` as `empty` pub fn empty() -> FontVariantNumeric { FontVariantNumeric::Value(VariantNumeric::empty()) } /// Get `font-variant-numeric` with system font pub fn system_font(f: SystemFont) -> Self { FontVariantNumeric::System(f) } /// Get system font pub fn get_system(&self) -> Option { if let FontVariantNumeric::System(s) = *self { Some(s) } else { None } } } impl ToComputedValue for FontVariantNumeric { type ComputedValue = computed::FontVariantNumeric; fn to_computed_value(&self, _context: &Context) -> computed::FontVariantNumeric { match *self { FontVariantNumeric::Value(ref v) => v.clone(), FontVariantNumeric::System(_) => { #[cfg(feature = "gecko")] { _context.cached_system_font.as_ref().unwrap().font_variant_numeric.clone() } #[cfg(feature = "servo")] { unreachable!() } } } } fn from_computed_value(other: &computed::FontVariantNumeric) -> Self { FontVariantNumeric::Value(other.clone()) } } impl Parse for FontVariantNumeric { /// normal | /// [ || /// || /// || /// ordinal || /// slashed-zero ] /// = [ lining-nums | oldstyle-nums ] /// = [ proportional-nums | tabular-nums ] /// = [ diagonal-fractions | stacked-fractions ] fn parse<'i, 't>( _context: &ParserContext, input: &mut Parser<'i, 't> ) -> Result> { let mut result = VariantNumeric::empty(); if input.try(|input| input.expect_ident_matching("normal")).is_ok() { return Ok(FontVariantNumeric::Value(result)) } while let Ok(flag) = input.try(|input| { Ok(match_ignore_ascii_case! { &input.expect_ident().map_err(|_| ())?, "ordinal" => exclusive_value!((result, VariantNumeric::ORDINAL) => VariantNumeric::ORDINAL), "slashed-zero" => exclusive_value!((result, VariantNumeric::SLASHED_ZERO) => VariantNumeric::SLASHED_ZERO), "lining-nums" => exclusive_value!((result, VariantNumeric::LINING_NUMS | VariantNumeric::OLDSTYLE_NUMS ) => VariantNumeric::LINING_NUMS), "oldstyle-nums" => exclusive_value!((result, VariantNumeric::LINING_NUMS | VariantNumeric::OLDSTYLE_NUMS ) => VariantNumeric::OLDSTYLE_NUMS), "proportional-nums" => exclusive_value!((result, VariantNumeric::PROPORTIONAL_NUMS | VariantNumeric::TABULAR_NUMS ) => VariantNumeric::PROPORTIONAL_NUMS), "tabular-nums" => exclusive_value!((result, VariantNumeric::PROPORTIONAL_NUMS | VariantNumeric::TABULAR_NUMS ) => VariantNumeric::TABULAR_NUMS), "diagonal-fractions" => exclusive_value!((result, VariantNumeric::DIAGONAL_FRACTIONS | VariantNumeric::STACKED_FRACTIONS ) => VariantNumeric::DIAGONAL_FRACTIONS), "stacked-fractions" => exclusive_value!((result, VariantNumeric::DIAGONAL_FRACTIONS | VariantNumeric::STACKED_FRACTIONS ) => VariantNumeric::STACKED_FRACTIONS), _ => return Err(()), }) }) { result.insert(flag); } if !result.is_empty() { Ok(FontVariantNumeric::Value(result)) } else { Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } } } /// This property provides low-level control over OpenType or TrueType font features. pub type SpecifiedFontFeatureSettings = FontSettings>; /// Define initial settings that apply when the font defined by an @font-face /// rule is rendered. #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)] pub enum FontFeatureSettings { /// Value of `FontSettings` Value(SpecifiedFontFeatureSettings), /// System font System(SystemFont) } impl FontFeatureSettings { #[inline] /// Get default value of `font-feature-settings` as normal pub fn normal() -> FontFeatureSettings { FontFeatureSettings::Value(FontSettings::normal()) } /// Get `font-feature-settings` with system font pub fn system_font(f: SystemFont) -> Self { FontFeatureSettings::System(f) } /// Get system font pub fn get_system(&self) -> Option { if let FontFeatureSettings::System(s) = *self { Some(s) } else { None } } } impl ToComputedValue for FontFeatureSettings { type ComputedValue = computed::FontFeatureSettings; fn to_computed_value(&self, context: &Context) -> computed::FontFeatureSettings { match *self { FontFeatureSettings::Value(ref v) => v.to_computed_value(context), FontFeatureSettings::System(_) => { #[cfg(feature = "gecko")] { context.cached_system_font.as_ref().unwrap().font_feature_settings.clone() } #[cfg(feature = "servo")] { unreachable!() } } } } fn from_computed_value(other: &computed::FontFeatureSettings) -> Self { FontFeatureSettings::Value(ToComputedValue::from_computed_value(other)) } } impl Parse for FontFeatureSettings { /// normal | # fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't> ) -> Result> { SpecifiedFontFeatureSettings::parse(context, input).map(FontFeatureSettings::Value) } } #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue)] /// Whether user agents are allowed to synthesize bold or oblique font faces /// when a font family lacks bold or italic faces pub struct FontSynthesis { /// If a `font-weight` is requested that the font family does not contain, /// the user agent may synthesize the requested weight from the weights /// that do exist in the font family. pub weight: bool, /// If a font-style is requested that the font family does not contain, /// the user agent may synthesize the requested style from the normal face in the font family. pub style: bool, } impl FontSynthesis { #[inline] /// Get the default value of font-synthesis pub fn get_initial_value() -> Self { FontSynthesis { weight: true, style: true } } } impl Parse for FontSynthesis { fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { let mut result = FontSynthesis { weight: false, style: false }; try_match_ident_ignore_ascii_case! { input, "none" => Ok(result), "weight" => { result.weight = true; if input.try(|input| input.expect_ident_matching("style")).is_ok() { result.style = true; } Ok(result) }, "style" => { result.style = true; if input.try(|input| input.expect_ident_matching("weight")).is_ok() { result.weight = true; } Ok(result) }, } } } impl ToCss for FontSynthesis { fn to_css(&self, dest: &mut CssWriter) -> fmt::Result where W: Write, { if self.weight && self.style { dest.write_str("weight style") } else if self.style { dest.write_str("style") } else if self.weight { dest.write_str("weight") } else { dest.write_str("none") } } } #[cfg(feature = "gecko")] impl From for FontSynthesis { fn from(bits: u8) -> FontSynthesis { use gecko_bindings::structs; FontSynthesis { weight: bits & structs::NS_FONT_SYNTHESIS_WEIGHT as u8 != 0, style: bits & structs::NS_FONT_SYNTHESIS_STYLE as u8 != 0 } } } #[cfg(feature = "gecko")] impl From for u8 { fn from(v: FontSynthesis) -> u8 { use gecko_bindings::structs; let mut bits: u8 = 0; if v.weight { bits |= structs::NS_FONT_SYNTHESIS_WEIGHT as u8; } if v.style { bits |= structs::NS_FONT_SYNTHESIS_STYLE as u8; } bits } } #[derive(Clone, Debug, Eq, MallocSizeOf, PartialEq, ToCss)] /// Allows authors to explicitly specify the language system of the font, /// overriding the language system implied by the content language pub enum FontLanguageOverride { /// When rendering with OpenType fonts, /// the content language of the element is /// used to infer the OpenType language system Normal, /// Single three-letter case-sensitive OpenType language system tag, /// specifies the OpenType language system to be used instead of /// the language system implied by the language of the element Override(Box), /// Use system font System(SystemFont) } impl FontLanguageOverride { #[inline] /// Get default value with `normal` pub fn normal() -> FontLanguageOverride { FontLanguageOverride::Normal } /// Get `font-language-override` with `system font` pub fn system_font(f: SystemFont) -> Self { FontLanguageOverride::System(f) } /// Get system font pub fn get_system(&self) -> Option { if let FontLanguageOverride::System(s) = *self { Some(s) } else { None } } } impl ToComputedValue for FontLanguageOverride { type ComputedValue = computed::FontLanguageOverride; #[inline] fn to_computed_value(&self, _context: &Context) -> computed::FontLanguageOverride { match *self { FontLanguageOverride::Normal => computed::FontLanguageOverride(0), FontLanguageOverride::Override(ref lang) => { if lang.is_empty() || lang.len() > 4 || !lang.is_ascii() { return computed::FontLanguageOverride(0) } let mut computed_lang = lang.to_string(); while computed_lang.len() < 4 { computed_lang.push(' '); } let bytes = computed_lang.into_bytes(); computed::FontLanguageOverride(BigEndian::read_u32(&bytes)) } FontLanguageOverride::System(_) => { #[cfg(feature = "gecko")] { _context.cached_system_font.as_ref().unwrap().font_language_override } #[cfg(feature = "servo")] { unreachable!() } } } } #[inline] fn from_computed_value(computed: &computed::FontLanguageOverride) -> Self { if computed.0 == 0 { return FontLanguageOverride::Normal } let mut buf = [0; 4]; BigEndian::write_u32(&mut buf, computed.0); FontLanguageOverride::Override( if cfg!(debug_assertions) { String::from_utf8(buf.to_vec()).unwrap() } else { unsafe { String::from_utf8_unchecked(buf.to_vec()) } }.into_boxed_str() ) } } impl Parse for FontLanguageOverride { /// normal | fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { if input.try(|input| input.expect_ident_matching("normal")).is_ok() { return Ok(FontLanguageOverride::Normal) } let string = input.expect_string()?; Ok(FontLanguageOverride::Override(string.as_ref().to_owned().into_boxed_str())) } } /// This property provides low-level control over OpenType or TrueType font /// variations. pub type SpecifiedFontVariationSettings = FontSettings>; /// Define initial settings that apply when the font defined by an @font-face /// rule is rendered. #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToCss)] pub enum FontVariationSettings { /// Value of `FontSettings` Value(SpecifiedFontVariationSettings), /// System font System(SystemFont) } impl FontVariationSettings { #[inline] /// Get default value of `font-variation-settings` as normal pub fn normal() -> FontVariationSettings { FontVariationSettings::Value(FontSettings::normal()) } /// Get `font-variation-settings` with system font pub fn system_font(f: SystemFont) -> Self { FontVariationSettings::System(f) } /// Get system font pub fn get_system(&self) -> Option { if let FontVariationSettings::System(s) = *self { Some(s) } else { None } } } impl ToComputedValue for FontVariationSettings { type ComputedValue = computed::FontVariationSettings; fn to_computed_value(&self, context: &Context) -> computed::FontVariationSettings { match *self { FontVariationSettings::Value(ref v) => v.to_computed_value(context), FontVariationSettings::System(_) => { #[cfg(feature = "gecko")] { context.cached_system_font.as_ref().unwrap().font_variation_settings.clone() } #[cfg(feature = "servo")] { unreachable!() } } } } fn from_computed_value(other: &computed::FontVariationSettings) -> Self { FontVariationSettings::Value(ToComputedValue::from_computed_value(other)) } } impl Parse for FontVariationSettings { /// normal | # fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't> ) -> Result> { SpecifiedFontVariationSettings::parse(context, input).map(FontVariationSettings::Value) } } fn parse_one_feature_value<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { if let Ok(integer) = input.try(|i| Integer::parse_non_negative(context, i)) { return Ok(integer) } try_match_ident_ignore_ascii_case! { input, "on" => Ok(Integer::new(1)), "off" => Ok(Integer::new(0)), } } impl Parse for FeatureTagValue { /// https://drafts.csswg.org/css-fonts-4/#feature-tag-value fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { let tag = FontTag::parse(context, input)?; let value = input.try(|i| parse_one_feature_value(context, i)) .unwrap_or_else(|_| Integer::new(1)); Ok(Self { tag, value }) } } impl Parse for VariationValue { /// This is the ` ` part of the font-variation-settings /// syntax. fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { let tag = FontTag::parse(context, input)?; let value = Number::parse(context, input)?; Ok(Self { tag, value }) } } #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss)] /// text-zoom. Enable if true, disable if false pub struct XTextZoom(#[css(skip)] pub bool); impl Parse for XTextZoom { fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { debug_assert!(false, "Should be set directly by presentation attributes only."); Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } } #[derive(Clone, Debug, MallocSizeOf, PartialEq, ToComputedValue, ToCss)] /// Internal property that reflects the lang attribute pub struct XLang(#[css(skip)] pub Atom); impl XLang { #[inline] /// Get default value for `-x-lang` pub fn get_initial_value() -> XLang { XLang(atom!("")) } } impl Parse for XLang { fn parse<'i, 't>( _: &ParserContext, input: &mut Parser<'i, 't> ) -> Result> { debug_assert!(false, "Should be set directly by presentation attributes only."); Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } } #[cfg_attr(feature = "gecko", derive(MallocSizeOf))] #[derive(Clone, Copy, Debug, PartialEq, ToCss)] /// Specifies the minimum font size allowed due to changes in scriptlevel. /// Ref: https://wiki.mozilla.org/MathML:mstyle pub struct MozScriptMinSize(pub NoCalcLength); impl MozScriptMinSize { #[inline] /// Calculate initial value of -moz-script-min-size. pub fn get_initial_value() -> Length { Length::new(DEFAULT_SCRIPT_MIN_SIZE_PT as f32 * (AU_PER_PT / AU_PER_PX)) } } impl Parse for MozScriptMinSize { fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { debug_assert!(false, "Should be set directly by presentation attributes only."); Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } } #[cfg_attr(feature = "gecko", derive(MallocSizeOf))] #[derive(Clone, Copy, Debug, PartialEq, ToCss)] /// Changes the scriptlevel in effect for the children. /// Ref: https://wiki.mozilla.org/MathML:mstyle /// /// The main effect of scriptlevel is to control the font size. /// https://www.w3.org/TR/MathML3/chapter3.html#presm.scriptlevel pub enum MozScriptLevel { /// Change `font-size` relatively. Relative(i32), /// Change `font-size` absolutely. /// /// Should only be serialized by presentation attributes, so even though /// serialization for this would look the same as for the `Relative` /// variant, it is unexposed, so no big deal. #[css(function)] MozAbsolute(i32), /// Change `font-size` automatically. Auto } impl Parse for MozScriptLevel { fn parse<'i, 't>(_: &ParserContext, input: &mut Parser<'i, 't>) -> Result> { // We don't bother to handle calc here. if let Ok(i) = input.try(|i| i.expect_integer()) { return Ok(MozScriptLevel::Relative(i)) } input.expect_ident_matching("auto")?; Ok(MozScriptLevel::Auto) } } #[cfg_attr(feature = "gecko", derive(MallocSizeOf))] #[derive(Clone, Copy, Debug, PartialEq, ToComputedValue, ToCss)] /// Specifies the multiplier to be used to adjust font size /// due to changes in scriptlevel. /// /// Ref: https://www.w3.org/TR/MathML3/chapter3.html#presm.mstyle.attrs pub struct MozScriptSizeMultiplier(pub f32); impl MozScriptSizeMultiplier { #[inline] /// Get default value of `-moz-script-size-multiplier` pub fn get_initial_value() -> MozScriptSizeMultiplier { MozScriptSizeMultiplier(DEFAULT_SCRIPT_SIZE_MULTIPLIER as f32) } } impl Parse for MozScriptSizeMultiplier { fn parse<'i, 't>( _: &ParserContext, input: &mut Parser<'i, 't> ) -> Result> { debug_assert!(false, "Should be set directly by presentation attributes only."); Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) } } impl From for MozScriptSizeMultiplier { fn from(v: f32) -> Self { MozScriptSizeMultiplier(v) } } impl From for f32 { fn from(v: MozScriptSizeMultiplier) -> f32 { v.0 } }