diff --git a/components/style/properties/data.py b/components/style/properties/data.py index 407cc5fcdb0..b20a8deaaea 100644 --- a/components/style/properties/data.py +++ b/components/style/properties/data.py @@ -347,6 +347,7 @@ class Longhand(object): "TextAlign", "TextDecorationLine", "TextEmphasisPosition", + "TextTransform", "TouchAction", "TransformStyle", "UserSelect", diff --git a/components/style/properties/longhands/inherited_text.mako.rs b/components/style/properties/longhands/inherited_text.mako.rs index f61c8754b5e..cdd7df93256 100644 --- a/components/style/properties/longhands/inherited_text.mako.rs +++ b/components/style/properties/longhands/inherited_text.mako.rs @@ -19,11 +19,10 @@ ${helpers.predefined_type( // CSS Text Module Level 3 -// TODO(pcwalton): `full-width` -${helpers.single_keyword( +${helpers.predefined_type( "text-transform", - "none capitalize uppercase lowercase", - extra_gecko_values="full-width full-size-kana", + "TextTransform", + "computed::TextTransform::none()", animation_value_type="discrete", flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER", spec="https://drafts.csswg.org/css-text/#propdef-text-transform", diff --git a/components/style/values/computed/mod.rs b/components/style/values/computed/mod.rs index 3b32f8fecd7..78384ea6b94 100644 --- a/components/style/values/computed/mod.rs +++ b/components/style/values/computed/mod.rs @@ -87,6 +87,7 @@ pub use self::transform::{TransformOrigin, TransformStyle, Translate}; pub use self::ui::CursorImage; pub use self::ui::{Cursor, MozForceBrokenImageIcon, UserSelect}; pub use super::specified::{BorderStyle, TextDecorationLine}; +pub use super::specified::TextTransform; pub use super::{Auto, Either, None_}; pub use app_units::Au; diff --git a/components/style/values/computed/text.rs b/components/style/values/computed/text.rs index 2fdb1e48c4f..b88ba850536 100644 --- a/components/style/values/computed/text.rs +++ b/components/style/values/computed/text.rs @@ -21,6 +21,7 @@ use style_traits::{CssWriter, ToCss}; pub use crate::values::specified::TextAlignKeyword as TextAlign; pub use crate::values::specified::{OverflowWrap, WordBreak}; pub use crate::values::specified::{TextDecorationLine, TextEmphasisPosition}; +pub use crate::values::specified::TextTransform; /// A computed value for the `initial-letter` property. pub type InitialLetter = GenericInitialLetter; diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index 64a76ccc582..64a45231c03 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -83,6 +83,7 @@ pub use self::table::XSpan; pub use self::text::{InitialLetter, LetterSpacing, LineHeight, TextAlign}; pub use self::text::{OverflowWrap, TextEmphasisPosition, TextEmphasisStyle, WordBreak}; pub use self::text::{TextAlignKeyword, TextDecorationLine, TextOverflow, WordSpacing}; +pub use self::text::TextTransform; pub use self::time::Time; pub use self::transform::{Rotate, Scale, Transform}; pub use self::transform::{TransformOrigin, TransformStyle, Translate}; diff --git a/components/style/values/specified/text.rs b/components/style/values/specified/text.rs index 9d4c6ae649c..bf4c5b288e1 100644 --- a/components/style/values/specified/text.rs +++ b/components/style/values/specified/text.rs @@ -351,6 +351,175 @@ impl TextDecorationLine { } } +#[derive( + Clone, + Copy, + Debug, + Eq, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToResolvedValue, + ToShmem)] +#[repr(C)] +/// Specified value of the text-transform property, stored in two parts: +/// the case-related transforms (mutually exclusive, only one may be in effect), and others (non-exclusive). +pub struct TextTransform { + /// Case transform, if any. + pub case_: TextTransformCase, + /// Non-case transforms. + pub other_: TextTransformOther, +} + +impl TextTransform { + #[inline] + /// Returns the initial value of text-transform + pub fn none() -> Self { + TextTransform { + case_: TextTransformCase::None, + other_: TextTransformOther::empty(), + } + } + #[inline] + /// Returns whether the value is 'none' + pub fn is_none(&self) -> bool { + self.case_ == TextTransformCase::None && self.other_.is_empty() + } +} + +impl Parse for TextTransform { + fn parse<'i, 't>( + _context: &ParserContext, + input: &mut Parser<'i, 't>, + ) -> Result> { + let mut result = TextTransform::none(); + + // Case keywords are mutually exclusive; other transforms may co-occur. + loop { + let location = input.current_source_location(); + let ident = match input.next() { + Ok(&Token::Ident(ref ident)) => ident, + Ok(other) => return Err(location.new_unexpected_token_error(other.clone())), + Err(..) => break, + }; + + match_ignore_ascii_case! { ident, + "none" if result.is_none() => { + return Ok(result); + }, + "uppercase" if result.case_ == TextTransformCase::None => { + result.case_ = TextTransformCase::Uppercase + }, + "lowercase" if result.case_ == TextTransformCase::None => { + result.case_ = TextTransformCase::Lowercase + }, + "capitalize" if result.case_ == TextTransformCase::None => { + result.case_ = TextTransformCase::Capitalize + }, + "full-width" if !result.other_.intersects(TextTransformOther::FULL_WIDTH) => { + result.other_.insert(TextTransformOther::FULL_WIDTH) + }, + "full-size-kana" if !result.other_.intersects(TextTransformOther::FULL_SIZE_KANA) => { + result.other_.insert(TextTransformOther::FULL_SIZE_KANA) + } + _ => return Err(location.new_custom_error( + SelectorParseErrorKind::UnexpectedIdent(ident.clone()) + )), + } + } + + if result.is_none() { + Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) + } else { + Ok(result) + } + } +} + +impl ToCss for TextTransform { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + if self.is_none() { + return dest.write_str("none"); + } + + if self.case_ != TextTransformCase::None { + self.case_.to_css(dest)?; + if !self.other_.is_empty() { + dest.write_str(" ")?; + } + } + + self.other_.to_css(dest) + } +} + +#[derive( + Clone, + Copy, + Debug, + Eq, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem)] +#[repr(C)] +/// Specified keyword values for case transforms in the text-transform property. (These are exclusive.) +pub enum TextTransformCase { + /// No case transform. + None, + /// All uppercase. + Uppercase, + /// All lowercase. + Lowercase, + /// Capitalize each word. + Capitalize, +} + +bitflags! { + #[derive(MallocSizeOf, SpecifiedValueInfo, ToComputedValue, ToResolvedValue, ToShmem)] + #[value_info(other_values = "none,full-width,full-size-kana")] + #[repr(C)] + /// Specified keyword values for non-case transforms in the text-transform property. (Non-exclusive.) + pub struct TextTransformOther: u8 { + /// full-width + const FULL_WIDTH = 1 << 0; + /// full-size-kana + const FULL_SIZE_KANA = 1 << 1; + } +} + +impl ToCss for TextTransformOther { + fn to_css(&self, dest: &mut CssWriter) -> fmt::Result + where + W: Write, + { + let mut writer = SequenceWriter::new(dest, " "); + let mut any = false; + macro_rules! maybe_write { + ($ident:ident => $str:expr) => { + if self.contains(TextTransformOther::$ident) { + writer.raw_item($str)?; + any = true; + } + }; + } + + maybe_write!(FULL_WIDTH => "full-width"); + maybe_write!(FULL_SIZE_KANA => "full-size-kana"); + + debug_assert!(any || self.is_empty()); + + Ok(()) + } +} + /// Specified value of text-align keyword value. #[derive( Clone,