--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -274,16 +274,34 @@ def set_gecko_property(ffi_name, expr):
% endfor
% if keyword.gecko_inexhaustive:
x => panic!("Found unexpected value in style struct for ${ident} property: {:?}", x),
% endif
}
}
</%def>
+<%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;
+ % 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};
+ }
+</%def>
+
/// Convert a Servo color into an nscolor; with currentColor as 0
///
/// Call sites will need to be updated after https://bugzilla.mozilla.org/show_bug.cgi?id=760345
fn color_to_nscolor_zero_currentcolor(color: Color) -> structs::nscolor {
match color {
Color::RGBA(rgba) => {
convert_rgba_to_nscolor(&rgba)
@@ -1240,18 +1258,21 @@ fn static_assert() {
corner.y_index) %>
% endfor
pub fn outline_has_nonzero_width(&self) -> bool {
self.gecko.mActualOutlineWidth != 0
}
</%self:impl_trait>
-<%self:impl_trait style_struct_name="Font"
- skip_longhands="font-family font-size font-size-adjust font-weight font-synthesis -x-lang font-language-override"
+<% skip_font_longhands= """font-family font-size font-size-adjust font-weight
+ 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}"
skip_additionals="*">
pub fn set_font_family(&mut self, v: longhands::font_family::computed_value::T) {
use properties::longhands::font_family::computed_value::FontFamily;
use gecko_bindings::structs::FontFamilyType;
let list = &mut self.gecko.mFont.fontlist;
unsafe { Gecko_FontFamilyList_Clear(list); }
@@ -1384,16 +1405,86 @@ fn static_assert() {
Gecko_nsStyleFont_CopyLangFrom(&mut self.gecko, &other.gecko);
}
}
pub fn set_font_language_override(&mut self, v: longhands::font_language_override::computed_value::T) {
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')}
</%self:impl_trait>
<%def name="impl_copy_animation_or_transition_value(type, ident, gecko_ffi_name)">
#[allow(non_snake_case)]
pub fn copy_${type}_${ident}_from(&mut self, other: &Self) {
unsafe { self.gecko.m${type.capitalize()}s.ensure_len(other.gecko.m${type.capitalize()}s.len()) };
let count = other.gecko.m${type.capitalize()}${gecko_ffi_name}Count;
--- a/servo/components/style/properties/longhand/font.mako.rs
+++ b/servo/components/style/properties/longhand/font.mako.rs
@@ -854,16 +854,501 @@
${helpers.single_keyword("font-kerning",
"auto none normal",
products="gecko",
gecko_ffi_name="mFont.kerning",
gecko_constant_prefix="NS_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! {
+ 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<W>(&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(<feature-value-name>) ||
+ /// historical-forms ||
+ /// styleset(<feature-value-name> #) ||
+ /// character-variant(<feature-value-name> #) ||
+ /// swash(<feature-value-name>) ||
+ /// ornaments(<feature-value-name>) ||
+ /// annotation(<feature-value-name>) ]
+ pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
+ 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(())
+ }
+ }
+</%helpers:longhand>
+
+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! {
+ 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<W>(&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 | [ <east-asian-variant-values> || <east-asian-width-values> || ruby ]
+ /// <east-asian-variant-values> = [ jis78 | jis83 | jis90 | jis04 | simplified | traditional ]
+ /// <east-asian-width-values> = [ 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<SpecifiedValue, ()> {
+ 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>
+
+<%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! {
+ 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<W>(&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-lig-values> ||
+ /// <discretionary-lig-values> ||
+ /// <historical-lig-values> ||
+ /// <contextual-alt-values> ]
+ /// <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 ]
+ <% 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<SpecifiedValue, ()> {
+ 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>
+
+<%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! {
+ 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<W>(&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 |
+ /// [ <numeric-figure-values> ||
+ /// <numeric-spacing-values> ||
+ /// <numeric-fraction-values> ||
+ /// ordinal ||
+ /// slashed-zero ]
+ /// <numeric-figure-values> = [ lining-nums | oldstyle-nums ]
+ /// <numeric-spacing-values> = [ proportional-nums | tabular-nums ]
+ /// <numeric-fraction-values> = [ 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<SpecifiedValue, ()> {
+ 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:longhand>
+
${helpers.single_keyword("font-variant-position",
"normal sub super",
products="gecko",
gecko_ffi_name="mFont.variantPosition",
gecko_constant_prefix="NS_FONT_VARIANT_POSITION",
spec="https://drafts.csswg.org/css-fonts/#propdef-font-variant-position",
animation_type="none")}