diff --git a/components/style/properties/gecko.mako.rs b/components/style/properties/gecko.mako.rs index bf8a2b5f19b..cbf3937a6d0 100644 --- a/components/style/properties/gecko.mako.rs +++ b/components/style/properties/gecko.mako.rs @@ -293,6 +293,26 @@ def set_gecko_property(ffi_name, expr): } +<%def name="impl_bitflags_setter(ident, gecko_ffi_name, bit_map, gecko_bit_prefix, cast_type='u8')"> + #[allow(non_snake_case)] + pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) { + % for gecko_bit in bit_map.values(): + use gecko_bindings::structs::${gecko_bit_prefix}${gecko_bit}; + % endfor + + let mut bits: ${cast_type} = 0; + // FIXME: if we ensure that the Servo bitflags storage is the same + // as Gecko's one, we can just copy it. + % for servo_bit, gecko_bit in bit_map.iteritems(): + if v.contains(longhands::${ident}::${servo_bit}) { + bits |= ${gecko_bit_prefix}${gecko_bit} as ${cast_type}; + } + % endfor + + self.gecko.${gecko_ffi_name} = bits as ${cast_type}; + } + + /// Convert a Servo color into an nscolor; with currentColor as 0 /// @@ -1262,7 +1282,9 @@ fn static_assert() { <% skip_font_longhands = """font-family font-size font-size-adjust font-weight - font-synthesis -x-lang font-language-override""" + font-synthesis -x-lang font-variant-alternates + font-variant-east-asian font-variant-ligatures + font-variant-numeric font-language-override""" %> <%self:impl_trait style_struct_name="Font" skip_longhands="${skip_font_longhands}" @@ -1408,6 +1430,76 @@ fn static_assert() { self.gecko.mFont.languageOverride = v.0; } ${impl_simple_copy('font_language_override', 'mFont.languageOverride')} + + <% font_variant_alternates_map = { "HISTORICAL_FORMS": "HISTORICAL", + "STYLISTIC": "STYLISTIC", + "STYLESET": "STYLESET", + "CHARACTER_VARIANT": "CHARACTER_VARIANT", + "SWASH": "SWASH", + "ORNAMENTS": "ORNAMENTS", + "ANNOTATION": "ANNOTATION" } %> + // FIXME: Set alternateValues as well. + // self.gecko.mFont.alternateValues = xxx; + ${impl_bitflags_setter('font_variant_alternates', + 'mFont.variantAlternates', + font_variant_alternates_map, + 'NS_FONT_VARIANT_ALTERNATES_', + cast_type='u16')} + #[allow(non_snake_case)] + pub fn copy_font_variant_alternates_from(&mut self, other: &Self) { + self.gecko.mFont.variantAlternates = other.gecko.mFont.variantAlternates; + // FIXME: Copy alternateValues as well. + // self.gecko.mFont.alternateValues = other.gecko.mFont.alternateValues; + } + + // servo_bit: gecko_bit + <% font_variant_ligatures_map = { "NONE": "NONE", + "COMMON_LIGATURES": "COMMON", + "NO_COMMON_LIGATURES": "NO_COMMON", + "DISCRETIONARY_LIGATURES": "DISCRETIONARY", + "NO_DISCRETIONARY_LIGATURES": "NO_DISCRETIONARY", + "HISTORICAL_LIGATURES": "HISTORICAL", + "NO_HISTORICAL_LIGATURES": "NO_HISTORICAL", + "CONTEXTUAL": "CONTEXTUAL", + "NO_CONTEXTUAL": "NO_CONTEXTUAL" } %> + ${impl_bitflags_setter('font_variant_ligatures', + 'mFont.variantLigatures', + font_variant_ligatures_map, + 'NS_FONT_VARIANT_LIGATURES_', + cast_type='u16')} + ${impl_simple_copy('font_variant_ligatures', 'mFont.variantLigatures')} + + // servo_bit: gecko_bit + <% font_variant_east_asian_map = { "JIS78": "JIS78", + "JIS83": "JIS83", + "JIS90": "JIS90", + "JIS04": "JIS04", + "SIMPLIFIED": "SIMPLIFIED", + "TRADITIONAL": "TRADITIONAL", + "FULL_WIDTH": "FULL_WIDTH", + "PROPORTIONAL_WIDTH": "PROP_WIDTH", + "RUBY": "RUBY" } %> + ${impl_bitflags_setter('font_variant_east_asian', + 'mFont.variantEastAsian', + font_variant_east_asian_map, + 'NS_FONT_VARIANT_EAST_ASIAN_', + cast_type='u16')} + ${impl_simple_copy('font_variant_east_asian', 'mFont.variantEastAsian')} + + // servo_bit: gecko_bit + <% font_variant_numeric_map = { "LINING_NUMS": "LINING", + "OLDSTYLE_NUMS": "OLDSTYLE", + "PROPORTIONAL_NUMS": "PROPORTIONAL", + "TABULAR_NUMS": "TABULAR", + "DIAGONAL_FRACTIONS": "DIAGONAL_FRACTIONS", + "STACKED_FRACTIONS": "STACKED_FRACTIONS", + "SLASHED_ZERO": "SLASHZERO", + "ORDINAL": "ORDINAL" } %> + ${impl_bitflags_setter('font_variant_numeric', + 'mFont.variantNumeric', + font_variant_numeric_map, + 'NS_FONT_VARIANT_NUMERIC_')} + ${impl_simple_copy('font_variant_numeric', 'mFont.variantNumeric')} <%def name="impl_copy_animation_or_transition_value(type, ident, gecko_ffi_name)"> diff --git a/components/style/properties/longhand/font.mako.rs b/components/style/properties/longhand/font.mako.rs index 1e5cc783bd3..2490c3d81ab 100644 --- a/components/style/properties/longhand/font.mako.rs +++ b/components/style/properties/longhand/font.mako.rs @@ -893,6 +893,495 @@ ${helpers.single_keyword("font-kerning", spec="https://drafts.csswg.org/css-fonts/#propdef-font-stretch", animation_type="none")} +/// FIXME: Implement proper handling of each values. +/// https://github.com/servo/servo/issues/15957 +<%helpers:longhand name="font-variant-alternates" products="gecko" animation_type="none" + spec="https://drafts.csswg.org/css-fonts/#propdef-font-variant-alternates"> + use std::fmt; + use style_traits::ToCss; + use values::HasViewportPercentage; + use values::computed::ComputedValueAsSpecified; + + impl ComputedValueAsSpecified for SpecifiedValue {} + no_viewport_percentage!(SpecifiedValue); + + bitflags! { + #[cfg_attr(feature = "servo", derive(HeapSizeOf))] + pub flags SpecifiedValue: u8 { + const NORMAL = 0, + const HISTORICAL_FORMS = 0x01, + const STYLISTIC = 0x02, + const STYLESET = 0x04, + const CHARACTER_VARIANT = 0x08, + const SWASH = 0x10, + const ORNAMENTS = 0x20, + const ANNOTATION = 0x40, + } + } + + impl ToCss for SpecifiedValue { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + if self.is_empty() { + return dest.write_str("normal") + } + + let mut has_any = false; + + macro_rules! write_value { + ($ident:ident => $str:expr) => { + if self.intersects($ident) { + if has_any { + try!(dest.write_str(" ")); + } + has_any = true; + try!(dest.write_str($str)); + } + } + } + + write_value!(HISTORICAL_FORMS => "historical-forms"); + write_value!(STYLISTIC => "stylistic"); + write_value!(STYLESET => "styleset"); + write_value!(CHARACTER_VARIANT => "character-variant"); + write_value!(SWASH => "swash"); + write_value!(ORNAMENTS => "ornaments"); + write_value!(ANNOTATION => "annotation"); + + debug_assert!(has_any); + Ok(()) + } + } + + pub mod computed_value { + pub type T = super::SpecifiedValue; + } + #[inline] + pub fn get_initial_value() -> computed_value::T { + computed_value::T::empty() + } + #[inline] + pub fn get_initial_specified_value() -> SpecifiedValue { + SpecifiedValue::empty() + } + + /// normal | + /// [ stylistic() || + /// historical-forms || + /// styleset( #) || + /// character-variant( #) || + /// swash() || + /// ornaments() || + /// annotation() ] + pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result { + let mut result = SpecifiedValue::empty(); + + if input.try(|input| input.expect_ident_matching("normal")).is_ok() { + return Ok(result) + } + + while let Ok(ident) = input.try(|input| input.expect_ident()) { + let flag = match_ignore_ascii_case! { &ident, + "stylistic" => STYLISTIC, + "historical-forms" => HISTORICAL_FORMS, + "styleset" => STYLESET, + "character-variant" => CHARACTER_VARIANT, + "swash" => SWASH, + "ornaments" => ORNAMENTS, + "annotation" => ANNOTATION, + _ => return Err(()), + }; + if result.intersects(flag) { + return Err(()) + } + result.insert(flag); + } + + if !result.is_empty() { + Ok(result) + } else { + Err(()) + } + } + + +macro_rules! exclusive_value { + (($value:ident, $set:expr) => $ident:ident) => { + if $value.intersects($set) { + return Err(()) + } else { + $ident + } + } +} + +<%helpers:longhand name="font-variant-east-asian" products="gecko" animation_type="none" + spec="https://drafts.csswg.org/css-fonts/#propdef-font-variant-east-asian"> + use std::fmt; + use style_traits::ToCss; + use values::HasViewportPercentage; + use values::computed::ComputedValueAsSpecified; + + impl ComputedValueAsSpecified for SpecifiedValue {} + no_viewport_percentage!(SpecifiedValue); + + bitflags! { + #[cfg_attr(feature = "servo", derive(HeapSizeOf))] + pub flags SpecifiedValue: u16 { + const NORMAL = 0, + const JIS78 = 0x01, + const JIS83 = 0x02, + const JIS90 = 0x04, + const JIS04 = 0x08, + const SIMPLIFIED = 0x10, + const TRADITIONAL = 0x20, + const FULL_WIDTH = 0x40, + const PROPORTIONAL_WIDTH = 0x80, + const RUBY = 0x100, + } + } + + impl ToCss for SpecifiedValue { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + if self.is_empty() { + return dest.write_str("normal") + } + + let mut has_any = false; + + macro_rules! write_value { + ($ident:ident => $str:expr) => { + if self.intersects($ident) { + if has_any { + try!(dest.write_str(" ")); + } + has_any = true; + try!(dest.write_str($str)); + } + } + } + + write_value!(JIS78 => "jis78"); + write_value!(JIS83 => "jis83"); + write_value!(JIS90 => "jis90"); + write_value!(JIS04 => "jis04"); + write_value!(SIMPLIFIED => "simplified"); + write_value!(TRADITIONAL => "traditional"); + write_value!(FULL_WIDTH => "full-width"); + write_value!(PROPORTIONAL_WIDTH => "proportional-width"); + write_value!(RUBY => "ruby"); + + debug_assert!(has_any); + Ok(()) + } + } + + pub mod computed_value { + pub type T = super::SpecifiedValue; + } + #[inline] + pub fn get_initial_value() -> computed_value::T { + computed_value::T::empty() + } + #[inline] + pub fn get_initial_specified_value() -> SpecifiedValue { + SpecifiedValue::empty() + } + + /// normal | [ || || ruby ] + /// = [ jis78 | jis83 | jis90 | jis04 | simplified | traditional ] + /// = [ full-width | proportional-width ] + <% east_asian_variant_values = "JIS78 | JIS83 | JIS90 | JIS04 | SIMPLIFIED | TRADITIONAL" %> + <% east_asian_width_values = "FULL_WIDTH | PROPORTIONAL_WIDTH" %> + pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result { + let mut result = SpecifiedValue::empty(); + + if input.try(|input| input.expect_ident_matching("normal")).is_ok() { + return Ok(result) + } + + while let Ok(ident) = input.try(|input| input.expect_ident()) { + let flag = match_ignore_ascii_case! { &ident, + "jis78" => + exclusive_value!((result, ${east_asian_variant_values}) => JIS78), + "jis83" => + exclusive_value!((result, ${east_asian_variant_values}) => JIS83), + "jis90" => + exclusive_value!((result, ${east_asian_variant_values}) => JIS90), + "jis04" => + exclusive_value!((result, ${east_asian_variant_values}) => JIS04), + "simplified" => + exclusive_value!((result, ${east_asian_variant_values}) => SIMPLIFIED), + "traditional" => + exclusive_value!((result, ${east_asian_variant_values}) => TRADITIONAL), + "full-width" => + exclusive_value!((result, ${east_asian_width_values}) => FULL_WIDTH), + "proportional-width" => + exclusive_value!((result, ${east_asian_width_values}) => PROPORTIONAL_WIDTH), + "ruby" => + exclusive_value!((result, RUBY) => RUBY), + _ => return Err(()), + }; + result.insert(flag); + } + + if !result.is_empty() { + Ok(result) + } else { + Err(()) + } + } + + +<%helpers:longhand name="font-variant-ligatures" products="gecko" animation_type="none" + spec="https://drafts.csswg.org/css-fonts/#propdef-font-variant-ligatures"> + use std::fmt; + use style_traits::ToCss; + use values::HasViewportPercentage; + use values::computed::ComputedValueAsSpecified; + + impl ComputedValueAsSpecified for SpecifiedValue {} + no_viewport_percentage!(SpecifiedValue); + + bitflags! { + #[cfg_attr(feature = "servo", derive(HeapSizeOf))] + pub flags SpecifiedValue: u16 { + const NORMAL = 0, + const NONE = 0x01, + const COMMON_LIGATURES = 0x02, + const NO_COMMON_LIGATURES = 0x04, + const DISCRETIONARY_LIGATURES = 0x08, + const NO_DISCRETIONARY_LIGATURES = 0x10, + const HISTORICAL_LIGATURES = 0x20, + const NO_HISTORICAL_LIGATURES = 0x40, + const CONTEXTUAL = 0x80, + const NO_CONTEXTUAL = 0x100, + } + } + + impl ToCss for SpecifiedValue { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + if self.is_empty() { + return dest.write_str("normal") + } + if self.contains(NONE) { + return dest.write_str("none") + } + + let mut has_any = false; + + macro_rules! write_value { + ($ident:ident => $str:expr) => { + if self.intersects($ident) { + if has_any { + try!(dest.write_str(" ")); + } + has_any = true; + try!(dest.write_str($str)); + } + } + } + + write_value!(COMMON_LIGATURES => "common-ligatures"); + write_value!(NO_COMMON_LIGATURES => "no-common-ligatures"); + write_value!(DISCRETIONARY_LIGATURES => "discretionary-ligatures"); + write_value!(NO_DISCRETIONARY_LIGATURES => "no-discretionary-ligatures"); + write_value!(HISTORICAL_LIGATURES => "historical-ligatures"); + write_value!(NO_HISTORICAL_LIGATURES => "no-historical-ligatures"); + write_value!(CONTEXTUAL => "contextual"); + write_value!(NO_CONTEXTUAL => "no-contextual"); + + debug_assert!(has_any); + Ok(()) + } + } + + pub mod computed_value { + pub type T = super::SpecifiedValue; + } + #[inline] + pub fn get_initial_value() -> computed_value::T { + computed_value::T::empty() + } + #[inline] + pub fn get_initial_specified_value() -> SpecifiedValue { + SpecifiedValue::empty() + } + + /// normal | none | + /// [ || + /// || + /// || + /// ] + /// = [ common-ligatures | no-common-ligatures ] + /// = [ discretionary-ligatures | no-discretionary-ligatures ] + /// = [ historical-ligatures | no-historical-ligatures ] + /// = [ contextual | no-contextual ] + <% common_lig_values = "COMMON_LIGATURES | NO_COMMON_LIGATURES" %> + <% discretionary_lig_values = "DISCRETIONARY_LIGATURES | NO_DISCRETIONARY_LIGATURES" %> + <% historical_lig_values = "HISTORICAL_LIGATURES | NO_HISTORICAL_LIGATURES" %> + <% contextual_alt_values = "CONTEXTUAL | NO_CONTEXTUAL" %> + pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result { + let mut result = SpecifiedValue::empty(); + + if input.try(|input| input.expect_ident_matching("normal")).is_ok() { + return Ok(result) + } + if input.try(|input| input.expect_ident_matching("none")).is_ok() { + return Ok(NONE) + } + + while let Ok(ident) = input.try(|input| input.expect_ident()) { + let flag = match_ignore_ascii_case! { &ident, + "common-ligatures" => + exclusive_value!((result, ${common_lig_values}) => COMMON_LIGATURES), + "no-common-ligatures" => + exclusive_value!((result, ${common_lig_values}) => NO_COMMON_LIGATURES), + "discretionary-ligatures" => + exclusive_value!((result, ${discretionary_lig_values}) => DISCRETIONARY_LIGATURES), + "no-discretionary-ligatures" => + exclusive_value!((result, ${discretionary_lig_values}) => NO_DISCRETIONARY_LIGATURES), + "historical-ligatures" => + exclusive_value!((result, ${historical_lig_values}) => HISTORICAL_LIGATURES), + "no-historical-ligatures" => + exclusive_value!((result, ${historical_lig_values}) => NO_HISTORICAL_LIGATURES), + "contextual" => + exclusive_value!((result, ${contextual_alt_values}) => CONTEXTUAL), + "no-contextual" => + exclusive_value!((result, ${contextual_alt_values}) => NO_CONTEXTUAL), + _ => return Err(()), + }; + result.insert(flag); + } + + if !result.is_empty() { + Ok(result) + } else { + Err(()) + } + } + + +<%helpers:longhand name="font-variant-numeric" products="gecko" animation_type="none" + spec="https://drafts.csswg.org/css-fonts/#propdef-font-variant-numeric"> + use std::fmt; + use style_traits::ToCss; + use values::HasViewportPercentage; + use values::computed::ComputedValueAsSpecified; + + impl ComputedValueAsSpecified for SpecifiedValue {} + no_viewport_percentage!(SpecifiedValue); + + bitflags! { + #[cfg_attr(feature = "servo", derive(HeapSizeOf))] + pub flags SpecifiedValue: u8 { + const NORMAL = 0, + const LINING_NUMS = 0x01, + const OLDSTYLE_NUMS = 0x02, + const PROPORTIONAL_NUMS = 0x04, + const TABULAR_NUMS = 0x08, + const DIAGONAL_FRACTIONS = 0x10, + const STACKED_FRACTIONS = 0x20, + const SLASHED_ZERO = 0x40, + const ORDINAL = 0x80, + } + } + + impl ToCss for SpecifiedValue { + fn to_css(&self, dest: &mut W) -> fmt::Result where W: fmt::Write { + if self.is_empty() { + return dest.write_str("normal") + } + + let mut has_any = false; + + macro_rules! write_value { + ($ident:ident => $str:expr) => { + if self.intersects($ident) { + if has_any { + try!(dest.write_str(" ")); + } + has_any = true; + try!(dest.write_str($str)); + } + } + } + + write_value!(LINING_NUMS => "lining-nums"); + write_value!(OLDSTYLE_NUMS => "oldstyle-nums"); + write_value!(PROPORTIONAL_NUMS => "proportional-nums"); + write_value!(TABULAR_NUMS => "tabular-nums"); + write_value!(DIAGONAL_FRACTIONS => "diagonal-fractions"); + write_value!(STACKED_FRACTIONS => "stacked-fractions"); + write_value!(SLASHED_ZERO => "slashed-zero"); + write_value!(ORDINAL => "ordinal"); + + debug_assert!(has_any); + Ok(()) + } + } + + pub mod computed_value { + pub type T = super::SpecifiedValue; + } + #[inline] + pub fn get_initial_value() -> computed_value::T { + computed_value::T::empty() + } + #[inline] + pub fn get_initial_specified_value() -> SpecifiedValue { + SpecifiedValue::empty() + } + + /// normal | + /// [ || + /// || + /// || + /// ordinal || + /// slashed-zero ] + /// = [ lining-nums | oldstyle-nums ] + /// = [ proportional-nums | tabular-nums ] + /// = [ diagonal-fractions | stacked-fractions ] + <% numeric_figure_values = "LINING_NUMS | OLDSTYLE_NUMS" %> + <% numeric_spacing_values = "PROPORTIONAL_NUMS | TABULAR_NUMS" %> + <% numeric_fraction_values = "DIAGONAL_FRACTIONS | STACKED_FRACTIONS" %> + pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result { + let mut result = SpecifiedValue::empty(); + + if input.try(|input| input.expect_ident_matching("normal")).is_ok() { + return Ok(result) + } + + while let Ok(ident) = input.try(|input| input.expect_ident()) { + let flag = match_ignore_ascii_case! { &ident, + "ordinal" => + exclusive_value!((result, ORDINAL) => ORDINAL), + "slashed-zero" => + exclusive_value!((result, SLASHED_ZERO) => SLASHED_ZERO), + "lining-nums" => + exclusive_value!((result, ${numeric_figure_values}) => LINING_NUMS ), + "oldstyle-nums" => + exclusive_value!((result, ${numeric_figure_values}) => OLDSTYLE_NUMS ), + "proportional-nums" => + exclusive_value!((result, ${numeric_spacing_values}) => PROPORTIONAL_NUMS ), + "tabular-nums" => + exclusive_value!((result, ${numeric_spacing_values}) => TABULAR_NUMS ), + "diagonal-fractions" => + exclusive_value!((result, ${numeric_fraction_values}) => DIAGONAL_FRACTIONS ), + "stacked-fractions" => + exclusive_value!((result, ${numeric_fraction_values}) => STACKED_FRACTIONS ), + _ => return Err(()), + }; + result.insert(flag); + } + + if !result.is_empty() { + Ok(result) + } else { + Err(()) + } + } + + ${helpers.single_keyword("font-variant-position", "normal sub super", products="gecko",