tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

text.rs (37500B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
      4 
      5 //! Specified types for text properties.
      6 
      7 use crate::derives::*;
      8 use crate::parser::{Parse, ParserContext};
      9 use crate::properties::longhands::writing_mode::computed_value::T as SpecifiedWritingMode;
     10 use crate::values::computed;
     11 use crate::values::computed::text::TextEmphasisStyle as ComputedTextEmphasisStyle;
     12 use crate::values::computed::{Context, ToComputedValue};
     13 use crate::values::generics::text::{
     14    GenericHyphenateLimitChars, GenericInitialLetter, GenericTextDecorationInset,
     15    GenericTextDecorationLength, GenericTextIndent,
     16 };
     17 use crate::values::generics::NumberOrAuto;
     18 use crate::values::specified::length::{Length, LengthPercentage};
     19 use crate::values::specified::{AllowQuirks, Integer, Number};
     20 use crate::Zero;
     21 use cssparser::Parser;
     22 use icu_segmenter::GraphemeClusterSegmenter;
     23 use std::fmt::{self, Write};
     24 use style_traits::values::SequenceWriter;
     25 use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
     26 use style_traits::{KeywordsCollectFn, SpecifiedValueInfo};
     27 
     28 /// A specified type for the `initial-letter` property.
     29 pub type InitialLetter = GenericInitialLetter<Number, Integer>;
     30 
     31 /// A spacing value used by either the `letter-spacing` or `word-spacing` properties.
     32 #[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
     33 pub enum Spacing {
     34    /// `normal`
     35    Normal,
     36    /// `<value>`
     37    Value(LengthPercentage),
     38 }
     39 
     40 impl Parse for Spacing {
     41    fn parse<'i, 't>(
     42        context: &ParserContext,
     43        input: &mut Parser<'i, 't>,
     44    ) -> Result<Self, ParseError<'i>> {
     45        if input
     46            .try_parse(|i| i.expect_ident_matching("normal"))
     47            .is_ok()
     48        {
     49            return Ok(Spacing::Normal);
     50        }
     51        LengthPercentage::parse_quirky(context, input, AllowQuirks::Yes).map(Spacing::Value)
     52    }
     53 }
     54 
     55 /// A specified value for the `letter-spacing` property.
     56 #[derive(
     57    Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
     58 )]
     59 #[typed_value(derive_fields)]
     60 pub struct LetterSpacing(pub Spacing);
     61 
     62 impl ToComputedValue for LetterSpacing {
     63    type ComputedValue = computed::LetterSpacing;
     64 
     65    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
     66        use computed::text::GenericLetterSpacing;
     67        match self.0 {
     68            Spacing::Normal => GenericLetterSpacing(computed::LengthPercentage::zero()),
     69            Spacing::Value(ref v) => GenericLetterSpacing(v.to_computed_value(context)),
     70        }
     71    }
     72 
     73    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
     74        if computed.0.is_zero() {
     75            return LetterSpacing(Spacing::Normal);
     76        }
     77        LetterSpacing(Spacing::Value(ToComputedValue::from_computed_value(
     78            &computed.0,
     79        )))
     80    }
     81 }
     82 
     83 /// A specified value for the `word-spacing` property.
     84 #[derive(
     85    Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
     86 )]
     87 pub struct WordSpacing(pub Spacing);
     88 
     89 impl ToComputedValue for WordSpacing {
     90    type ComputedValue = computed::WordSpacing;
     91 
     92    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
     93        match self.0 {
     94            Spacing::Normal => computed::LengthPercentage::zero(),
     95            Spacing::Value(ref v) => v.to_computed_value(context),
     96        }
     97    }
     98 
     99    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
    100        WordSpacing(Spacing::Value(ToComputedValue::from_computed_value(
    101            computed,
    102        )))
    103    }
    104 }
    105 
    106 /// A value for the `hyphenate-character` property.
    107 #[derive(
    108    Clone,
    109    Debug,
    110    MallocSizeOf,
    111    Parse,
    112    PartialEq,
    113    SpecifiedValueInfo,
    114    ToComputedValue,
    115    ToCss,
    116    ToResolvedValue,
    117    ToShmem,
    118    ToTyped,
    119 )]
    120 #[repr(C, u8)]
    121 pub enum HyphenateCharacter {
    122    /// `auto`
    123    Auto,
    124    /// `<string>`
    125    String(crate::OwnedStr),
    126 }
    127 
    128 /// A value for the `hyphenate-limit-chars` property.
    129 pub type HyphenateLimitChars = GenericHyphenateLimitChars<Integer>;
    130 
    131 impl Parse for HyphenateLimitChars {
    132    fn parse<'i, 't>(
    133        context: &ParserContext,
    134        input: &mut Parser<'i, 't>,
    135    ) -> Result<Self, ParseError<'i>> {
    136        type IntegerOrAuto = NumberOrAuto<Integer>;
    137 
    138        let total_word_length = IntegerOrAuto::parse(context, input)?;
    139        let pre_hyphen_length = input
    140            .try_parse(|i| IntegerOrAuto::parse(context, i))
    141            .unwrap_or(IntegerOrAuto::Auto);
    142        let post_hyphen_length = input
    143            .try_parse(|i| IntegerOrAuto::parse(context, i))
    144            .unwrap_or(pre_hyphen_length);
    145        Ok(Self {
    146            total_word_length,
    147            pre_hyphen_length,
    148            post_hyphen_length,
    149        })
    150    }
    151 }
    152 
    153 impl Parse for InitialLetter {
    154    fn parse<'i, 't>(
    155        context: &ParserContext,
    156        input: &mut Parser<'i, 't>,
    157    ) -> Result<Self, ParseError<'i>> {
    158        if input
    159            .try_parse(|i| i.expect_ident_matching("normal"))
    160            .is_ok()
    161        {
    162            return Ok(Self::normal());
    163        }
    164        let size = Number::parse_at_least_one(context, input)?;
    165        let sink = input
    166            .try_parse(|i| Integer::parse_positive(context, i))
    167            .unwrap_or_else(|_| crate::Zero::zero());
    168        Ok(Self { size, sink })
    169    }
    170 }
    171 
    172 /// A generic value for the `text-overflow` property.
    173 #[derive(
    174    Clone,
    175    Debug,
    176    Eq,
    177    MallocSizeOf,
    178    PartialEq,
    179    Parse,
    180    SpecifiedValueInfo,
    181    ToComputedValue,
    182    ToCss,
    183    ToResolvedValue,
    184    ToShmem,
    185 )]
    186 #[repr(C, u8)]
    187 pub enum TextOverflowSide {
    188    /// Clip inline content.
    189    Clip,
    190    /// Render ellipsis to represent clipped inline content.
    191    Ellipsis,
    192    /// Render a given string to represent clipped inline content.
    193    String(crate::values::AtomString),
    194 }
    195 
    196 #[derive(
    197    Clone,
    198    Debug,
    199    Eq,
    200    MallocSizeOf,
    201    PartialEq,
    202    SpecifiedValueInfo,
    203    ToComputedValue,
    204    ToResolvedValue,
    205    ToShmem,
    206    ToTyped,
    207 )]
    208 #[repr(C)]
    209 /// text-overflow.
    210 /// When the specified value only has one side, that's the "second"
    211 /// side, and the sides are logical, so "second" means "end".  The
    212 /// start side is Clip in that case.
    213 ///
    214 /// When the specified value has two sides, those are our "first"
    215 /// and "second" sides, and they are physical sides ("left" and
    216 /// "right").
    217 pub struct TextOverflow {
    218    /// First side
    219    pub first: TextOverflowSide,
    220    /// Second side
    221    pub second: TextOverflowSide,
    222    /// True if the specified value only has one side.
    223    pub sides_are_logical: bool,
    224 }
    225 
    226 impl Parse for TextOverflow {
    227    fn parse<'i, 't>(
    228        context: &ParserContext,
    229        input: &mut Parser<'i, 't>,
    230    ) -> Result<TextOverflow, ParseError<'i>> {
    231        let first = TextOverflowSide::parse(context, input)?;
    232        Ok(
    233            if let Ok(second) = input.try_parse(|input| TextOverflowSide::parse(context, input)) {
    234                Self {
    235                    first,
    236                    second,
    237                    sides_are_logical: false,
    238                }
    239            } else {
    240                Self {
    241                    first: TextOverflowSide::Clip,
    242                    second: first,
    243                    sides_are_logical: true,
    244                }
    245            },
    246        )
    247    }
    248 }
    249 
    250 impl TextOverflow {
    251    /// Returns the initial `text-overflow` value
    252    pub fn get_initial_value() -> TextOverflow {
    253        TextOverflow {
    254            first: TextOverflowSide::Clip,
    255            second: TextOverflowSide::Clip,
    256            sides_are_logical: true,
    257        }
    258    }
    259 }
    260 
    261 impl ToCss for TextOverflow {
    262    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
    263    where
    264        W: Write,
    265    {
    266        if self.sides_are_logical {
    267            debug_assert_eq!(self.first, TextOverflowSide::Clip);
    268            self.second.to_css(dest)?;
    269        } else {
    270            self.first.to_css(dest)?;
    271            dest.write_char(' ')?;
    272            self.second.to_css(dest)?;
    273        }
    274        Ok(())
    275    }
    276 }
    277 
    278 #[derive(
    279    Clone,
    280    Copy,
    281    Debug,
    282    Eq,
    283    MallocSizeOf,
    284    PartialEq,
    285    Parse,
    286    Serialize,
    287    SpecifiedValueInfo,
    288    ToCss,
    289    ToComputedValue,
    290    ToResolvedValue,
    291    ToShmem,
    292    ToTyped,
    293 )]
    294 #[cfg_attr(
    295    feature = "gecko",
    296    css(bitflags(
    297        single = "none,spelling-error,grammar-error",
    298        mixed = "underline,overline,line-through,blink",
    299    ))
    300 )]
    301 #[cfg_attr(
    302    not(feature = "gecko"),
    303    css(bitflags(single = "none", mixed = "underline,overline,line-through,blink",))
    304 )]
    305 #[repr(C)]
    306 /// Specified keyword values for the text-decoration-line property.
    307 pub struct TextDecorationLine(u8);
    308 bitflags! {
    309    impl TextDecorationLine: u8 {
    310        /// No text decoration line is specified.
    311        const NONE = 0;
    312        /// underline
    313        const UNDERLINE = 1 << 0;
    314        /// overline
    315        const OVERLINE = 1 << 1;
    316        /// line-through
    317        const LINE_THROUGH = 1 << 2;
    318        /// blink
    319        const BLINK = 1 << 3;
    320        /// spelling-error
    321        const SPELLING_ERROR = 1 << 4;
    322        /// grammar-error
    323        const GRAMMAR_ERROR = 1 << 5;
    324        /// Only set by presentation attributes
    325        ///
    326        /// Setting this will mean that text-decorations use the color
    327        /// specified by `color` in quirks mode.
    328        ///
    329        /// For example, this gives <a href=foo><font color="red">text</font></a>
    330        /// a red text decoration
    331        #[cfg(feature = "gecko")]
    332        const COLOR_OVERRIDE = 1 << 7;
    333    }
    334 }
    335 
    336 impl Default for TextDecorationLine {
    337    fn default() -> Self {
    338        TextDecorationLine::NONE
    339    }
    340 }
    341 
    342 impl TextDecorationLine {
    343    #[inline]
    344    /// Returns the initial value of text-decoration-line
    345    pub fn none() -> Self {
    346        TextDecorationLine::NONE
    347    }
    348 }
    349 
    350 #[derive(
    351    Clone,
    352    Copy,
    353    Debug,
    354    Eq,
    355    MallocSizeOf,
    356    PartialEq,
    357    SpecifiedValueInfo,
    358    ToComputedValue,
    359    ToCss,
    360    ToResolvedValue,
    361    ToShmem,
    362 )]
    363 #[repr(C)]
    364 /// Specified keyword values for case transforms in the text-transform property. (These are exclusive.)
    365 pub enum TextTransformCase {
    366    /// No case transform.
    367    None,
    368    /// All uppercase.
    369    Uppercase,
    370    /// All lowercase.
    371    Lowercase,
    372    /// Capitalize each word.
    373    Capitalize,
    374    /// Automatic italicization of math variables.
    375    #[cfg(feature = "gecko")]
    376    MathAuto,
    377 }
    378 
    379 #[derive(
    380    Clone,
    381    Copy,
    382    Debug,
    383    Eq,
    384    MallocSizeOf,
    385    PartialEq,
    386    Parse,
    387    Serialize,
    388    SpecifiedValueInfo,
    389    ToCss,
    390    ToComputedValue,
    391    ToResolvedValue,
    392    ToShmem,
    393    ToTyped,
    394 )]
    395 #[cfg_attr(
    396    feature = "gecko",
    397    css(bitflags(
    398        single = "none,math-auto",
    399        mixed = "uppercase,lowercase,capitalize,full-width,full-size-kana",
    400        validate_mixed = "Self::validate_mixed_flags",
    401    ))
    402 )]
    403 #[cfg_attr(
    404    not(feature = "gecko"),
    405    css(bitflags(
    406        single = "none",
    407        mixed = "uppercase,lowercase,capitalize,full-width,full-size-kana",
    408        validate_mixed = "Self::validate_mixed_flags",
    409    ))
    410 )]
    411 #[repr(C)]
    412 /// Specified value for the text-transform property.
    413 /// (The spec grammar gives
    414 /// `none | math-auto | [capitalize | uppercase | lowercase] || full-width || full-size-kana`.)
    415 /// https://drafts.csswg.org/css-text-4/#text-transform-property
    416 pub struct TextTransform(u8);
    417 bitflags! {
    418    impl TextTransform: u8 {
    419        /// none
    420        const NONE = 0;
    421        /// All uppercase.
    422        const UPPERCASE = 1 << 0;
    423        /// All lowercase.
    424        const LOWERCASE = 1 << 1;
    425        /// Capitalize each word.
    426        const CAPITALIZE = 1 << 2;
    427        /// Automatic italicization of math variables.
    428        const MATH_AUTO = 1 << 3;
    429 
    430        /// All the case transforms, which are exclusive with each other.
    431        const CASE_TRANSFORMS = Self::UPPERCASE.0 | Self::LOWERCASE.0 | Self::CAPITALIZE.0 | Self::MATH_AUTO.0;
    432 
    433        /// full-width
    434        const FULL_WIDTH = 1 << 4;
    435        /// full-size-kana
    436        const FULL_SIZE_KANA = 1 << 5;
    437    }
    438 }
    439 
    440 impl TextTransform {
    441    /// Returns the initial value of text-transform
    442    #[inline]
    443    pub fn none() -> Self {
    444        Self::NONE
    445    }
    446 
    447    /// Returns whether the value is 'none'
    448    #[inline]
    449    pub fn is_none(self) -> bool {
    450        self == Self::NONE
    451    }
    452 
    453    fn validate_mixed_flags(&self) -> bool {
    454        let case = self.intersection(Self::CASE_TRANSFORMS);
    455        // Case bits are exclusive with each other.
    456        case.is_empty() || case.bits().is_power_of_two()
    457    }
    458 
    459    /// Returns the corresponding TextTransformCase.
    460    pub fn case(&self) -> TextTransformCase {
    461        match *self & Self::CASE_TRANSFORMS {
    462            Self::NONE => TextTransformCase::None,
    463            Self::UPPERCASE => TextTransformCase::Uppercase,
    464            Self::LOWERCASE => TextTransformCase::Lowercase,
    465            Self::CAPITALIZE => TextTransformCase::Capitalize,
    466            Self::MATH_AUTO => TextTransformCase::MathAuto,
    467            _ => unreachable!("Case bits are exclusive with each other"),
    468        }
    469    }
    470 }
    471 
    472 /// Specified and computed value of text-align-last.
    473 #[derive(
    474    Clone,
    475    Copy,
    476    Debug,
    477    Eq,
    478    FromPrimitive,
    479    Hash,
    480    MallocSizeOf,
    481    Parse,
    482    PartialEq,
    483    SpecifiedValueInfo,
    484    ToComputedValue,
    485    ToCss,
    486    ToResolvedValue,
    487    ToShmem,
    488    ToTyped,
    489 )]
    490 #[allow(missing_docs)]
    491 #[repr(u8)]
    492 pub enum TextAlignLast {
    493    Auto,
    494    Start,
    495    End,
    496    Left,
    497    Right,
    498    Center,
    499    Justify,
    500 }
    501 
    502 /// Specified value of text-align keyword value.
    503 #[derive(
    504    Clone,
    505    Copy,
    506    Debug,
    507    Eq,
    508    FromPrimitive,
    509    Hash,
    510    MallocSizeOf,
    511    Parse,
    512    PartialEq,
    513    SpecifiedValueInfo,
    514    ToComputedValue,
    515    ToCss,
    516    ToResolvedValue,
    517    ToShmem,
    518    ToTyped,
    519 )]
    520 #[allow(missing_docs)]
    521 #[repr(u8)]
    522 pub enum TextAlignKeyword {
    523    Start,
    524    Left,
    525    Right,
    526    Center,
    527    Justify,
    528    End,
    529    #[parse(aliases = "-webkit-center")]
    530    MozCenter,
    531    #[parse(aliases = "-webkit-left")]
    532    MozLeft,
    533    #[parse(aliases = "-webkit-right")]
    534    MozRight,
    535 }
    536 
    537 /// Specified value of text-align property.
    538 #[derive(
    539    Clone,
    540    Copy,
    541    Debug,
    542    Eq,
    543    Hash,
    544    MallocSizeOf,
    545    Parse,
    546    PartialEq,
    547    SpecifiedValueInfo,
    548    ToCss,
    549    ToShmem,
    550    ToTyped,
    551 )]
    552 pub enum TextAlign {
    553    /// Keyword value of text-align property.
    554    Keyword(TextAlignKeyword),
    555    /// `match-parent` value of text-align property. It has a different handling
    556    /// unlike other keywords.
    557    #[cfg(feature = "gecko")]
    558    MatchParent,
    559    /// This is how we implement the following HTML behavior from
    560    /// https://html.spec.whatwg.org/#tables-2:
    561    ///
    562    ///     User agents are expected to have a rule in their user agent style sheet
    563    ///     that matches th elements that have a parent node whose computed value
    564    ///     for the 'text-align' property is its initial value, whose declaration
    565    ///     block consists of just a single declaration that sets the 'text-align'
    566    ///     property to the value 'center'.
    567    ///
    568    /// Since selectors can't depend on the ancestor styles, we implement it with a
    569    /// magic value that computes to the right thing. Since this is an
    570    /// implementation detail, it shouldn't be exposed to web content.
    571    #[parse(condition = "ParserContext::chrome_rules_enabled")]
    572    MozCenterOrInherit,
    573 }
    574 
    575 impl ToComputedValue for TextAlign {
    576    type ComputedValue = TextAlignKeyword;
    577 
    578    #[inline]
    579    fn to_computed_value(&self, _context: &Context) -> Self::ComputedValue {
    580        match *self {
    581            TextAlign::Keyword(key) => key,
    582            #[cfg(feature = "gecko")]
    583            TextAlign::MatchParent => {
    584                // on the root <html> element we should still respect the dir
    585                // but the parent dir of that element is LTR even if it's <html dir=rtl>
    586                // and will only be RTL if certain prefs have been set.
    587                // In that case, the default behavior here will set it to left,
    588                // but we want to set it to right -- instead set it to the default (`start`),
    589                // which will do the right thing in this case (but not the general case)
    590                if _context.builder.is_root_element {
    591                    return TextAlignKeyword::Start;
    592                }
    593                let parent = _context
    594                    .builder
    595                    .get_parent_inherited_text()
    596                    .clone_text_align();
    597                let ltr = _context.builder.inherited_writing_mode().is_bidi_ltr();
    598                match (parent, ltr) {
    599                    (TextAlignKeyword::Start, true) => TextAlignKeyword::Left,
    600                    (TextAlignKeyword::Start, false) => TextAlignKeyword::Right,
    601                    (TextAlignKeyword::End, true) => TextAlignKeyword::Right,
    602                    (TextAlignKeyword::End, false) => TextAlignKeyword::Left,
    603                    _ => parent,
    604                }
    605            },
    606            TextAlign::MozCenterOrInherit => {
    607                let parent = _context
    608                    .builder
    609                    .get_parent_inherited_text()
    610                    .clone_text_align();
    611                if parent == TextAlignKeyword::Start {
    612                    TextAlignKeyword::Center
    613                } else {
    614                    parent
    615                }
    616            },
    617        }
    618    }
    619 
    620    #[inline]
    621    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
    622        TextAlign::Keyword(*computed)
    623    }
    624 }
    625 
    626 fn fill_mode_is_default_and_shape_exists(
    627    fill: &TextEmphasisFillMode,
    628    shape: &Option<TextEmphasisShapeKeyword>,
    629 ) -> bool {
    630    shape.is_some() && fill.is_filled()
    631 }
    632 
    633 /// Specified value of text-emphasis-style property.
    634 ///
    635 /// https://drafts.csswg.org/css-text-decor/#propdef-text-emphasis-style
    636 #[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
    637 #[allow(missing_docs)]
    638 pub enum TextEmphasisStyle {
    639    /// [ <fill> || <shape> ]
    640    Keyword {
    641        #[css(contextual_skip_if = "fill_mode_is_default_and_shape_exists")]
    642        fill: TextEmphasisFillMode,
    643        shape: Option<TextEmphasisShapeKeyword>,
    644    },
    645    /// `none`
    646    None,
    647    /// `<string>` (of which only the first grapheme cluster will be used).
    648    String(crate::OwnedStr),
    649 }
    650 
    651 /// Fill mode for the text-emphasis-style property
    652 #[derive(
    653    Clone,
    654    Copy,
    655    Debug,
    656    MallocSizeOf,
    657    Parse,
    658    PartialEq,
    659    SpecifiedValueInfo,
    660    ToCss,
    661    ToComputedValue,
    662    ToResolvedValue,
    663    ToShmem,
    664 )]
    665 #[repr(u8)]
    666 pub enum TextEmphasisFillMode {
    667    /// `filled`
    668    Filled,
    669    /// `open`
    670    Open,
    671 }
    672 
    673 impl TextEmphasisFillMode {
    674    /// Whether the value is `filled`.
    675    #[inline]
    676    pub fn is_filled(&self) -> bool {
    677        matches!(*self, TextEmphasisFillMode::Filled)
    678    }
    679 }
    680 
    681 /// Shape keyword for the text-emphasis-style property
    682 #[derive(
    683    Clone,
    684    Copy,
    685    Debug,
    686    Eq,
    687    MallocSizeOf,
    688    Parse,
    689    PartialEq,
    690    SpecifiedValueInfo,
    691    ToCss,
    692    ToComputedValue,
    693    ToResolvedValue,
    694    ToShmem,
    695 )]
    696 #[repr(u8)]
    697 pub enum TextEmphasisShapeKeyword {
    698    /// `dot`
    699    Dot,
    700    /// `circle`
    701    Circle,
    702    /// `double-circle`
    703    DoubleCircle,
    704    /// `triangle`
    705    Triangle,
    706    /// `sesame`
    707    Sesame,
    708 }
    709 
    710 impl ToComputedValue for TextEmphasisStyle {
    711    type ComputedValue = ComputedTextEmphasisStyle;
    712 
    713    #[inline]
    714    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
    715        match *self {
    716            TextEmphasisStyle::Keyword { fill, shape } => {
    717                let shape = shape.unwrap_or_else(|| {
    718                    // FIXME(emilio, bug 1572958): This should set the
    719                    // rule_cache_conditions properly.
    720                    //
    721                    // Also should probably use WritingMode::is_vertical rather
    722                    // than the computed value of the `writing-mode` property.
    723                    if context.style().get_inherited_box().clone_writing_mode()
    724                        == SpecifiedWritingMode::HorizontalTb
    725                    {
    726                        TextEmphasisShapeKeyword::Circle
    727                    } else {
    728                        TextEmphasisShapeKeyword::Sesame
    729                    }
    730                });
    731                ComputedTextEmphasisStyle::Keyword { fill, shape }
    732            },
    733            TextEmphasisStyle::None => ComputedTextEmphasisStyle::None,
    734            TextEmphasisStyle::String(ref s) => {
    735                // FIXME(emilio): Doing this at computed value time seems wrong.
    736                // The spec doesn't say that this should be a computed-value
    737                // time operation. This is observable from getComputedStyle().
    738                //
    739                // Note that the first grapheme cluster boundary should always be the start of the string.
    740                let first_grapheme_end = GraphemeClusterSegmenter::new()
    741                    .segment_str(s)
    742                    .nth(1)
    743                    .unwrap_or(0);
    744                ComputedTextEmphasisStyle::String(s[0..first_grapheme_end].to_string().into())
    745            },
    746        }
    747    }
    748 
    749    #[inline]
    750    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
    751        match *computed {
    752            ComputedTextEmphasisStyle::Keyword { fill, shape } => TextEmphasisStyle::Keyword {
    753                fill,
    754                shape: Some(shape),
    755            },
    756            ComputedTextEmphasisStyle::None => TextEmphasisStyle::None,
    757            ComputedTextEmphasisStyle::String(ref string) => {
    758                TextEmphasisStyle::String(string.clone())
    759            },
    760        }
    761    }
    762 }
    763 
    764 impl Parse for TextEmphasisStyle {
    765    fn parse<'i, 't>(
    766        _context: &ParserContext,
    767        input: &mut Parser<'i, 't>,
    768    ) -> Result<Self, ParseError<'i>> {
    769        if input
    770            .try_parse(|input| input.expect_ident_matching("none"))
    771            .is_ok()
    772        {
    773            return Ok(TextEmphasisStyle::None);
    774        }
    775 
    776        if let Ok(s) = input.try_parse(|i| i.expect_string().map(|s| s.as_ref().to_owned())) {
    777            // Handle <string>
    778            return Ok(TextEmphasisStyle::String(s.into()));
    779        }
    780 
    781        // Handle a pair of keywords
    782        let mut shape = input.try_parse(TextEmphasisShapeKeyword::parse).ok();
    783        let fill = input.try_parse(TextEmphasisFillMode::parse).ok();
    784        if shape.is_none() {
    785            shape = input.try_parse(TextEmphasisShapeKeyword::parse).ok();
    786        }
    787 
    788        if shape.is_none() && fill.is_none() {
    789            return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
    790        }
    791 
    792        // If a shape keyword is specified but neither filled nor open is
    793        // specified, filled is assumed.
    794        let fill = fill.unwrap_or(TextEmphasisFillMode::Filled);
    795 
    796        // We cannot do the same because the default `<shape>` depends on the
    797        // computed writing-mode.
    798        Ok(TextEmphasisStyle::Keyword { fill, shape })
    799    }
    800 }
    801 
    802 #[derive(
    803    Clone,
    804    Copy,
    805    Debug,
    806    Eq,
    807    MallocSizeOf,
    808    PartialEq,
    809    Parse,
    810    Serialize,
    811    SpecifiedValueInfo,
    812    ToCss,
    813    ToComputedValue,
    814    ToResolvedValue,
    815    ToShmem,
    816    ToTyped,
    817 )]
    818 #[repr(C)]
    819 #[css(bitflags(
    820    single = "auto",
    821    mixed = "over,under,left,right",
    822    validate_mixed = "Self::validate_and_simplify"
    823 ))]
    824 /// Values for text-emphasis-position:
    825 /// <https://drafts.csswg.org/css-text-decor/#text-emphasis-position-property>
    826 pub struct TextEmphasisPosition(u8);
    827 bitflags! {
    828    impl TextEmphasisPosition: u8 {
    829        /// Automatically choose mark position based on language.
    830        const AUTO = 1 << 0;
    831        /// Draw marks over the text in horizontal writing mode.
    832        const OVER = 1 << 1;
    833        /// Draw marks under the text in horizontal writing mode.
    834        const UNDER = 1 << 2;
    835        /// Draw marks to the left of the text in vertical writing mode.
    836        const LEFT = 1 << 3;
    837        /// Draw marks to the right of the text in vertical writing mode.
    838        const RIGHT = 1 << 4;
    839    }
    840 }
    841 
    842 impl TextEmphasisPosition {
    843    fn validate_and_simplify(&mut self) -> bool {
    844        // Require one but not both of 'over' and 'under'.
    845        if self.intersects(Self::OVER) == self.intersects(Self::UNDER) {
    846            return false;
    847        }
    848 
    849        // If 'left' is present, 'right' must be absent.
    850        if self.intersects(Self::LEFT) {
    851            return !self.intersects(Self::RIGHT);
    852        }
    853 
    854        self.remove(Self::RIGHT); // Right is the default
    855        true
    856    }
    857 }
    858 
    859 /// Values for the `word-break` property.
    860 #[repr(u8)]
    861 #[derive(
    862    Clone,
    863    Copy,
    864    Debug,
    865    Eq,
    866    MallocSizeOf,
    867    Parse,
    868    PartialEq,
    869    SpecifiedValueInfo,
    870    ToComputedValue,
    871    ToCss,
    872    ToResolvedValue,
    873    ToShmem,
    874    ToTyped,
    875 )]
    876 #[allow(missing_docs)]
    877 pub enum WordBreak {
    878    Normal,
    879    BreakAll,
    880    KeepAll,
    881    /// The break-word value, needed for compat.
    882    ///
    883    /// Specifying `word-break: break-word` makes `overflow-wrap` behave as
    884    /// `anywhere`, and `word-break` behave like `normal`.
    885    #[cfg(feature = "gecko")]
    886    BreakWord,
    887 }
    888 
    889 /// Values for the `text-justify` CSS property.
    890 #[repr(u8)]
    891 #[derive(
    892    Clone,
    893    Copy,
    894    Debug,
    895    Eq,
    896    MallocSizeOf,
    897    Parse,
    898    PartialEq,
    899    SpecifiedValueInfo,
    900    ToComputedValue,
    901    ToCss,
    902    ToResolvedValue,
    903    ToShmem,
    904    ToTyped,
    905 )]
    906 #[allow(missing_docs)]
    907 pub enum TextJustify {
    908    Auto,
    909    None,
    910    InterWord,
    911    // See https://drafts.csswg.org/css-text-3/#valdef-text-justify-distribute
    912    // and https://github.com/w3c/csswg-drafts/issues/6156 for the alias.
    913    #[parse(aliases = "distribute")]
    914    InterCharacter,
    915 }
    916 
    917 /// Values for the `-moz-control-character-visibility` CSS property.
    918 #[repr(u8)]
    919 #[derive(
    920    Clone,
    921    Copy,
    922    Debug,
    923    Eq,
    924    MallocSizeOf,
    925    Parse,
    926    PartialEq,
    927    SpecifiedValueInfo,
    928    ToComputedValue,
    929    ToCss,
    930    ToResolvedValue,
    931    ToShmem,
    932    ToTyped,
    933 )]
    934 #[allow(missing_docs)]
    935 pub enum MozControlCharacterVisibility {
    936    Hidden,
    937    Visible,
    938 }
    939 
    940 #[cfg(feature = "gecko")]
    941 impl Default for MozControlCharacterVisibility {
    942    fn default() -> Self {
    943        if static_prefs::pref!("layout.css.control-characters.visible") {
    944            Self::Visible
    945        } else {
    946            Self::Hidden
    947        }
    948    }
    949 }
    950 
    951 /// Values for the `line-break` property.
    952 #[repr(u8)]
    953 #[derive(
    954    Clone,
    955    Copy,
    956    Debug,
    957    Eq,
    958    MallocSizeOf,
    959    Parse,
    960    PartialEq,
    961    SpecifiedValueInfo,
    962    ToComputedValue,
    963    ToCss,
    964    ToResolvedValue,
    965    ToShmem,
    966    ToTyped,
    967 )]
    968 #[allow(missing_docs)]
    969 pub enum LineBreak {
    970    Auto,
    971    Loose,
    972    Normal,
    973    Strict,
    974    Anywhere,
    975 }
    976 
    977 /// Values for the `overflow-wrap` property.
    978 #[repr(u8)]
    979 #[derive(
    980    Clone,
    981    Copy,
    982    Debug,
    983    Eq,
    984    MallocSizeOf,
    985    Parse,
    986    PartialEq,
    987    SpecifiedValueInfo,
    988    ToComputedValue,
    989    ToCss,
    990    ToResolvedValue,
    991    ToShmem,
    992    ToTyped,
    993 )]
    994 #[allow(missing_docs)]
    995 pub enum OverflowWrap {
    996    Normal,
    997    BreakWord,
    998    Anywhere,
    999 }
   1000 
   1001 /// A specified value for the `text-indent` property
   1002 /// which takes the grammar of [<length-percentage>] && hanging? && each-line?
   1003 ///
   1004 /// https://drafts.csswg.org/css-text/#propdef-text-indent
   1005 pub type TextIndent = GenericTextIndent<LengthPercentage>;
   1006 
   1007 impl Parse for TextIndent {
   1008    fn parse<'i, 't>(
   1009        context: &ParserContext,
   1010        input: &mut Parser<'i, 't>,
   1011    ) -> Result<Self, ParseError<'i>> {
   1012        let mut length = None;
   1013        let mut hanging = false;
   1014        let mut each_line = false;
   1015 
   1016        // The length-percentage and the two possible keywords can occur in any order.
   1017        while !input.is_exhausted() {
   1018            // If we haven't seen a length yet, try to parse one.
   1019            if length.is_none() {
   1020                if let Ok(len) = input
   1021                    .try_parse(|i| LengthPercentage::parse_quirky(context, i, AllowQuirks::Yes))
   1022                {
   1023                    length = Some(len);
   1024                    continue;
   1025                }
   1026            }
   1027 
   1028            // Servo doesn't support the keywords, so just break and let the caller deal with it.
   1029            if cfg!(feature = "servo") {
   1030                break;
   1031            }
   1032 
   1033            // Check for the keywords (boolean flags).
   1034            try_match_ident_ignore_ascii_case! { input,
   1035                "hanging" if !hanging => hanging = true,
   1036                "each-line" if !each_line => each_line = true,
   1037            }
   1038        }
   1039 
   1040        // The length-percentage value is required for the declaration to be valid.
   1041        if let Some(length) = length {
   1042            Ok(Self {
   1043                length,
   1044                hanging,
   1045                each_line,
   1046            })
   1047        } else {
   1048            Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
   1049        }
   1050    }
   1051 }
   1052 
   1053 /// Implements text-decoration-skip-ink which takes the keywords auto | none | all
   1054 ///
   1055 /// https://drafts.csswg.org/css-text-decor-4/#text-decoration-skip-ink-property
   1056 #[repr(u8)]
   1057 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
   1058 #[derive(
   1059    Clone,
   1060    Copy,
   1061    Debug,
   1062    Eq,
   1063    MallocSizeOf,
   1064    Parse,
   1065    PartialEq,
   1066    SpecifiedValueInfo,
   1067    ToComputedValue,
   1068    ToCss,
   1069    ToResolvedValue,
   1070    ToShmem,
   1071    ToTyped,
   1072 )]
   1073 #[allow(missing_docs)]
   1074 pub enum TextDecorationSkipInk {
   1075    Auto,
   1076    None,
   1077    All,
   1078 }
   1079 
   1080 /// Implements type for `text-decoration-thickness` property
   1081 pub type TextDecorationLength = GenericTextDecorationLength<LengthPercentage>;
   1082 
   1083 impl TextDecorationLength {
   1084    /// `Auto` value.
   1085    #[inline]
   1086    pub fn auto() -> Self {
   1087        GenericTextDecorationLength::Auto
   1088    }
   1089 
   1090    /// Whether this is the `Auto` value.
   1091    #[inline]
   1092    pub fn is_auto(&self) -> bool {
   1093        matches!(*self, GenericTextDecorationLength::Auto)
   1094    }
   1095 }
   1096 
   1097 /// Implements type for `text-decoration-inset` property
   1098 pub type TextDecorationInset = GenericTextDecorationInset<Length>;
   1099 
   1100 impl TextDecorationInset {
   1101    /// `Auto` value.
   1102    #[inline]
   1103    pub fn auto() -> Self {
   1104        GenericTextDecorationInset::Auto
   1105    }
   1106 
   1107    /// Whether this is the `Auto` value.
   1108    #[inline]
   1109    pub fn is_auto(&self) -> bool {
   1110        matches!(*self, GenericTextDecorationInset::Auto)
   1111    }
   1112 }
   1113 
   1114 impl Parse for TextDecorationInset {
   1115    fn parse<'i, 't>(
   1116        ctx: &ParserContext,
   1117        input: &mut Parser<'i, 't>,
   1118    ) -> Result<Self, ParseError<'i>> {
   1119        if let Ok(start) = input.try_parse(|i| Length::parse(ctx, i)) {
   1120            let end = input.try_parse(|i| Length::parse(ctx, i));
   1121            let end = end.unwrap_or_else(|_| start.clone());
   1122            return Ok(TextDecorationInset::Length { start, end });
   1123        }
   1124        input.expect_ident_matching("auto")?;
   1125        Ok(TextDecorationInset::Auto)
   1126    }
   1127 }
   1128 
   1129 #[derive(
   1130    Clone,
   1131    Copy,
   1132    Debug,
   1133    Eq,
   1134    MallocSizeOf,
   1135    Parse,
   1136    PartialEq,
   1137    SpecifiedValueInfo,
   1138    ToComputedValue,
   1139    ToResolvedValue,
   1140    ToShmem,
   1141    ToTyped,
   1142 )]
   1143 #[css(bitflags(
   1144    single = "auto",
   1145    mixed = "from-font,under,left,right",
   1146    validate_mixed = "Self::validate_mixed_flags",
   1147 ))]
   1148 #[repr(C)]
   1149 /// Specified keyword values for the text-underline-position property.
   1150 /// (Non-exclusive, but not all combinations are allowed: the spec grammar gives
   1151 /// `auto | [ from-font | under ] || [ left | right ]`.)
   1152 /// https://drafts.csswg.org/css-text-decor-4/#text-underline-position-property
   1153 pub struct TextUnderlinePosition(u8);
   1154 bitflags! {
   1155    impl TextUnderlinePosition: u8 {
   1156        /// Use automatic positioning below the alphabetic baseline.
   1157        const AUTO = 0;
   1158        /// Use underline position from the first available font.
   1159        const FROM_FONT = 1 << 0;
   1160        /// Below the glyph box.
   1161        const UNDER = 1 << 1;
   1162        /// In vertical mode, place to the left of the text.
   1163        const LEFT = 1 << 2;
   1164        /// In vertical mode, place to the right of the text.
   1165        const RIGHT = 1 << 3;
   1166    }
   1167 }
   1168 
   1169 impl TextUnderlinePosition {
   1170    fn validate_mixed_flags(&self) -> bool {
   1171        if self.contains(Self::LEFT | Self::RIGHT) {
   1172            // left and right can't be mixed together.
   1173            return false;
   1174        }
   1175        if self.contains(Self::FROM_FONT | Self::UNDER) {
   1176            // from-font and under can't be mixed together either.
   1177            return false;
   1178        }
   1179        true
   1180    }
   1181 }
   1182 
   1183 impl ToCss for TextUnderlinePosition {
   1184    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
   1185    where
   1186        W: Write,
   1187    {
   1188        if self.is_empty() {
   1189            return dest.write_str("auto");
   1190        }
   1191 
   1192        let mut writer = SequenceWriter::new(dest, " ");
   1193        let mut any = false;
   1194 
   1195        macro_rules! maybe_write {
   1196            ($ident:ident => $str:expr) => {
   1197                if self.contains(TextUnderlinePosition::$ident) {
   1198                    any = true;
   1199                    writer.raw_item($str)?;
   1200                }
   1201            };
   1202        }
   1203 
   1204        maybe_write!(FROM_FONT => "from-font");
   1205        maybe_write!(UNDER => "under");
   1206        maybe_write!(LEFT => "left");
   1207        maybe_write!(RIGHT => "right");
   1208 
   1209        debug_assert!(any);
   1210 
   1211        Ok(())
   1212    }
   1213 }
   1214 
   1215 /// Values for `ruby-position` property
   1216 #[repr(u8)]
   1217 #[derive(
   1218    Clone,
   1219    Copy,
   1220    Debug,
   1221    Eq,
   1222    MallocSizeOf,
   1223    PartialEq,
   1224    ToComputedValue,
   1225    ToResolvedValue,
   1226    ToShmem,
   1227    ToTyped,
   1228 )]
   1229 #[allow(missing_docs)]
   1230 pub enum RubyPosition {
   1231    AlternateOver,
   1232    AlternateUnder,
   1233    Over,
   1234    Under,
   1235 }
   1236 
   1237 impl Parse for RubyPosition {
   1238    fn parse<'i, 't>(
   1239        _context: &ParserContext,
   1240        input: &mut Parser<'i, 't>,
   1241    ) -> Result<RubyPosition, ParseError<'i>> {
   1242        // Parse alternate before
   1243        let alternate = input
   1244            .try_parse(|i| i.expect_ident_matching("alternate"))
   1245            .is_ok();
   1246        if alternate && input.is_exhausted() {
   1247            return Ok(RubyPosition::AlternateOver);
   1248        }
   1249        // Parse over / under
   1250        let over = try_match_ident_ignore_ascii_case! { input,
   1251            "over" => true,
   1252            "under" => false,
   1253        };
   1254        // Parse alternate after
   1255        let alternate = alternate
   1256            || input
   1257                .try_parse(|i| i.expect_ident_matching("alternate"))
   1258                .is_ok();
   1259 
   1260        Ok(match (over, alternate) {
   1261            (true, true) => RubyPosition::AlternateOver,
   1262            (false, true) => RubyPosition::AlternateUnder,
   1263            (true, false) => RubyPosition::Over,
   1264            (false, false) => RubyPosition::Under,
   1265        })
   1266    }
   1267 }
   1268 
   1269 impl ToCss for RubyPosition {
   1270    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
   1271    where
   1272        W: Write,
   1273    {
   1274        dest.write_str(match self {
   1275            RubyPosition::AlternateOver => "alternate",
   1276            RubyPosition::AlternateUnder => "alternate under",
   1277            RubyPosition::Over => "over",
   1278            RubyPosition::Under => "under",
   1279        })
   1280    }
   1281 }
   1282 
   1283 impl SpecifiedValueInfo for RubyPosition {
   1284    fn collect_completion_keywords(f: KeywordsCollectFn) {
   1285        f(&["alternate", "over", "under"])
   1286    }
   1287 }
   1288 
   1289 /// Specified value for the text-autospace property
   1290 /// which takes the grammar:
   1291 ///     normal | <autospace> | auto
   1292 /// where:
   1293 ///     <autospace> = no-autospace |
   1294 ///                   [ ideograph-alpha || ideograph-numeric || punctuation ]
   1295 ///                   || [ insert | replace ]
   1296 ///
   1297 /// https://drafts.csswg.org/css-text-4/#text-autospace-property
   1298 ///
   1299 /// Bug 1980111: 'replace' value is not supported yet.
   1300 #[derive(
   1301    Clone,
   1302    Copy,
   1303    Debug,
   1304    Eq,
   1305    MallocSizeOf,
   1306    Parse,
   1307    PartialEq,
   1308    Serialize,
   1309    SpecifiedValueInfo,
   1310    ToCss,
   1311    ToComputedValue,
   1312    ToResolvedValue,
   1313    ToShmem,
   1314    ToTyped,
   1315 )]
   1316 #[css(bitflags(
   1317    single = "normal,auto,no-autospace",
   1318    // Bug 1980111: add 'replace' to 'mixed' in the future so that it parses correctly.
   1319    // Bug 1986500: add 'punctuation' to 'mixed' in the future so that it parses correctly.
   1320    mixed = "ideograph-alpha,ideograph-numeric,insert",
   1321    // Bug 1980111: Uncomment 'validate_mixed' to support 'replace' value.
   1322    // validate_mixed = "Self::validate_mixed_flags",
   1323 ))]
   1324 #[repr(C)]
   1325 pub struct TextAutospace(u8);
   1326 bitflags! {
   1327    impl TextAutospace: u8 {
   1328        /// No automatic space is inserted.
   1329        const NO_AUTOSPACE = 0;
   1330 
   1331        /// The user agent chooses a set of typographically high quality spacing values.
   1332        const AUTO = 1 << 0;
   1333 
   1334        /// Same behavior as ideograph-alpha ideograph-numeric.
   1335        const NORMAL = 1 << 1;
   1336 
   1337        /// 1/8ic space between ideographic characters and non-ideographic letters.
   1338        const IDEOGRAPH_ALPHA = 1 << 2;
   1339 
   1340        /// 1/8ic space between ideographic characters and non-ideographic decimal numerals.
   1341        const IDEOGRAPH_NUMERIC = 1 << 3;
   1342 
   1343        /* Bug 1986500: Uncomment the following to support the 'punctuation' value.
   1344        /// Apply special spacing between letters and punctuation (French).
   1345        const PUNCTUATION = 1 << 4;
   1346        */
   1347 
   1348        /// Auto-spacing is only inserted if no space character is present in the text.
   1349        const INSERT = 1 << 5;
   1350 
   1351        /* Bug 1980111: Uncomment the following to support 'replace' value.
   1352        /// Auto-spacing may replace an existing U+0020 space with custom space.
   1353        const REPLACE = 1 << 6;
   1354        */
   1355    }
   1356 }
   1357 
   1358 /* Bug 1980111: Uncomment the following to support 'replace' value.
   1359 impl TextAutospace {
   1360    fn validate_mixed_flags(&self) -> bool {
   1361        // It's not valid to have both INSERT and REPLACE set.
   1362        !self.contains(TextAutospace::INSERT | TextAutospace::REPLACE)
   1363    }
   1364 }
   1365 */