Skip to main content

style/values/specified/
text.rs

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
7use crate::Zero;
8use crate::derives::*;
9use crate::parser::{Parse, ParserContext};
10use crate::properties::longhands::writing_mode::computed_value::T as SpecifiedWritingMode;
11use crate::values::computed;
12use crate::values::computed::text::TextEmphasisStyle as ComputedTextEmphasisStyle;
13use crate::values::computed::{Context, ToComputedValue};
14use crate::values::generics::NumberOrAuto;
15use crate::values::generics::text::{
16    GenericHyphenateLimitChars, GenericInitialLetter, GenericTextDecorationInset,
17    GenericTextDecorationLength, GenericTextIndent,
18};
19use crate::values::specified::length::{Length, LengthPercentage};
20use crate::values::specified::{AllowQuirks, Integer, Number};
21use cssparser::Parser;
22use icu_segmenter::GraphemeClusterSegmenter;
23use std::fmt::{self, Write};
24use style_traits::values::SequenceWriter;
25use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
26use style_traits::{KeywordsCollectFn, SpecifiedValueInfo};
27
28/// A specified type for the `initial-letter` property.
29pub 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)]
33pub enum Spacing {
34    /// `normal`
35    Normal,
36    /// `<value>`
37    Value(LengthPercentage),
38}
39
40impl Parse for Spacing {
41    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
42        if input
43            .try_parse(|i| i.expect_ident_matching("normal"))
44            .is_ok()
45        {
46            return Ok(Spacing::Normal);
47        }
48        LengthPercentage::parse_quirky(context, input, AllowQuirks::Yes).map(Spacing::Value)
49    }
50}
51
52/// A specified value for the `letter-spacing` property.
53#[derive(
54    Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
55)]
56pub struct LetterSpacing(pub Spacing);
57
58impl ToComputedValue for LetterSpacing {
59    type ComputedValue = computed::LetterSpacing;
60
61    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
62        use computed::text::GenericLetterSpacing;
63        match self.0 {
64            Spacing::Normal => GenericLetterSpacing(computed::LengthPercentage::zero()),
65            Spacing::Value(ref v) => GenericLetterSpacing(v.to_computed_value(context)),
66        }
67    }
68
69    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
70        if computed.0.is_zero() {
71            return LetterSpacing(Spacing::Normal);
72        }
73        LetterSpacing(Spacing::Value(ToComputedValue::from_computed_value(
74            &computed.0,
75        )))
76    }
77}
78
79/// A specified value for the `word-spacing` property.
80#[derive(
81    Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
82)]
83pub struct WordSpacing(pub Spacing);
84
85impl ToComputedValue for WordSpacing {
86    type ComputedValue = computed::WordSpacing;
87
88    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
89        match self.0 {
90            Spacing::Normal => computed::LengthPercentage::zero(),
91            Spacing::Value(ref v) => v.to_computed_value(context),
92        }
93    }
94
95    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
96        WordSpacing(Spacing::Value(ToComputedValue::from_computed_value(
97            computed,
98        )))
99    }
100}
101
102/// A value for the `hyphenate-character` property.
103#[derive(
104    Clone,
105    Debug,
106    MallocSizeOf,
107    Parse,
108    PartialEq,
109    SpecifiedValueInfo,
110    ToComputedValue,
111    ToCss,
112    ToResolvedValue,
113    ToShmem,
114    ToTyped,
115)]
116#[repr(C, u8)]
117#[typed(todo_derive_fields)]
118pub enum HyphenateCharacter {
119    /// `auto`
120    Auto,
121    /// `<string>`
122    String(crate::OwnedStr),
123}
124
125/// A value for the `hyphenate-limit-chars` property.
126pub type HyphenateLimitChars = GenericHyphenateLimitChars<Integer>;
127
128impl Parse for HyphenateLimitChars {
129    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
130        type IntegerOrAuto = NumberOrAuto<Integer>;
131
132        let total_word_length = IntegerOrAuto::parse(context, input)?;
133        let pre_hyphen_length = input
134            .try_parse(|i| IntegerOrAuto::parse(context, i))
135            .unwrap_or(IntegerOrAuto::Auto);
136        let post_hyphen_length = input
137            .try_parse(|i| IntegerOrAuto::parse(context, i))
138            .unwrap_or_else(|_| pre_hyphen_length.clone());
139        Ok(Self {
140            total_word_length,
141            pre_hyphen_length,
142            post_hyphen_length,
143        })
144    }
145}
146
147impl Parse for InitialLetter {
148    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
149        if input
150            .try_parse(|i| i.expect_ident_matching("normal"))
151            .is_ok()
152        {
153            return Ok(Self::normal());
154        }
155        let size = Number::parse_at_least_one(context, input)?;
156        let sink = input
157            .try_parse(|i| Integer::parse_positive(context, i))
158            .unwrap_or_else(|_| crate::Zero::zero());
159        Ok(Self { size, sink })
160    }
161}
162
163/// A generic value for the `text-overflow` property.
164#[derive(
165    Clone,
166    Debug,
167    Eq,
168    MallocSizeOf,
169    PartialEq,
170    Parse,
171    SpecifiedValueInfo,
172    ToComputedValue,
173    ToCss,
174    ToResolvedValue,
175    ToShmem,
176)]
177#[repr(C, u8)]
178pub enum TextOverflowSide {
179    /// Clip inline content.
180    Clip,
181    /// Render ellipsis to represent clipped inline content.
182    Ellipsis,
183    /// Render a given string to represent clipped inline content.
184    String(crate::values::AtomString),
185}
186
187#[derive(
188    Clone,
189    Debug,
190    Eq,
191    MallocSizeOf,
192    PartialEq,
193    SpecifiedValueInfo,
194    ToComputedValue,
195    ToResolvedValue,
196    ToShmem,
197    ToTyped,
198)]
199#[repr(C)]
200#[typed(todo_derive_fields)]
201/// text-overflow.
202/// When the specified value only has one side, that's the "second"
203/// side, and the sides are logical, so "second" means "end".  The
204/// start side is Clip in that case.
205///
206/// When the specified value has two sides, those are our "first"
207/// and "second" sides, and they are physical sides ("left" and
208/// "right").
209pub struct TextOverflow {
210    /// First side
211    pub first: TextOverflowSide,
212    /// Second side
213    pub second: TextOverflowSide,
214    /// True if the specified value only has one side.
215    pub sides_are_logical: bool,
216}
217
218impl Parse for TextOverflow {
219    fn parse(context: &ParserContext, input: &mut Parser) -> Result<TextOverflow, ParseError> {
220        let first = TextOverflowSide::parse(context, input)?;
221        Ok(
222            if let Ok(second) = input.try_parse(|input| TextOverflowSide::parse(context, input)) {
223                Self {
224                    first,
225                    second,
226                    sides_are_logical: false,
227                }
228            } else {
229                Self {
230                    first: TextOverflowSide::Clip,
231                    second: first,
232                    sides_are_logical: true,
233                }
234            },
235        )
236    }
237}
238
239impl TextOverflow {
240    /// Returns the initial `text-overflow` value
241    pub fn get_initial_value() -> TextOverflow {
242        TextOverflow {
243            first: TextOverflowSide::Clip,
244            second: TextOverflowSide::Clip,
245            sides_are_logical: true,
246        }
247    }
248}
249
250impl ToCss for TextOverflow {
251    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
252    where
253        W: Write,
254    {
255        if self.sides_are_logical {
256            debug_assert_eq!(self.first, TextOverflowSide::Clip);
257            self.second.to_css(dest)?;
258        } else {
259            self.first.to_css(dest)?;
260            dest.write_char(' ')?;
261            self.second.to_css(dest)?;
262        }
263        Ok(())
264    }
265}
266
267#[derive(
268    Clone,
269    Copy,
270    Debug,
271    Eq,
272    MallocSizeOf,
273    PartialEq,
274    Parse,
275    Serialize,
276    SpecifiedValueInfo,
277    ToCss,
278    ToComputedValue,
279    ToResolvedValue,
280    ToShmem,
281    ToTyped,
282)]
283#[css(bitflags(
284    single = "none,spelling-error,grammar-error",
285    mixed = "underline,overline,line-through,blink",
286))]
287#[repr(C)]
288/// Specified keyword values for the text-decoration-line property.
289pub struct TextDecorationLine(u8);
290bitflags! {
291    impl TextDecorationLine: u8 {
292        /// No text decoration line is specified.
293        const NONE = 0;
294        /// underline
295        const UNDERLINE = 1 << 0;
296        /// overline
297        const OVERLINE = 1 << 1;
298        /// line-through
299        const LINE_THROUGH = 1 << 2;
300        /// blink
301        const BLINK = 1 << 3;
302        /// spelling-error
303        const SPELLING_ERROR = 1 << 4;
304        /// grammar-error
305        const GRAMMAR_ERROR = 1 << 5;
306        /// Only set by presentation attributes
307        ///
308        /// Setting this will mean that text-decorations use the color
309        /// specified by `color` in quirks mode.
310        ///
311        /// For example, this gives <a href=foo><font color="red">text</font></a>
312        /// a red text decoration
313        #[cfg(feature = "gecko")]
314        const COLOR_OVERRIDE = 1 << 7;
315    }
316}
317
318impl Default for TextDecorationLine {
319    fn default() -> Self {
320        TextDecorationLine::NONE
321    }
322}
323
324impl TextDecorationLine {
325    #[inline]
326    /// Returns the initial value of text-decoration-line
327    pub fn none() -> Self {
328        TextDecorationLine::NONE
329    }
330}
331
332#[derive(
333    Clone,
334    Copy,
335    Debug,
336    Eq,
337    MallocSizeOf,
338    PartialEq,
339    SpecifiedValueInfo,
340    ToComputedValue,
341    ToCss,
342    ToResolvedValue,
343    ToShmem,
344)]
345#[repr(C)]
346/// Specified keyword values for case transforms in the text-transform property. (These are exclusive.)
347pub enum TextTransformCase {
348    /// No case transform.
349    None,
350    /// All uppercase.
351    Uppercase,
352    /// All lowercase.
353    Lowercase,
354    /// Capitalize each word.
355    Capitalize,
356    /// Automatic italicization of math variables.
357    MathAuto,
358}
359
360#[derive(
361    Clone,
362    Copy,
363    Debug,
364    Eq,
365    MallocSizeOf,
366    PartialEq,
367    Parse,
368    Serialize,
369    SpecifiedValueInfo,
370    ToCss,
371    ToComputedValue,
372    ToResolvedValue,
373    ToShmem,
374    ToTyped,
375)]
376#[css(bitflags(
377    single = "none,math-auto",
378    mixed = "uppercase,lowercase,capitalize,full-width,full-size-kana",
379    validate_mixed = "Self::validate_mixed_flags",
380))]
381#[repr(C)]
382/// Specified value for the text-transform property.
383/// (The spec grammar gives
384/// `none | math-auto | [capitalize | uppercase | lowercase] || full-width || full-size-kana`.)
385/// https://drafts.csswg.org/css-text-4/#text-transform-property
386pub struct TextTransform(u8);
387bitflags! {
388    impl TextTransform: u8 {
389        /// none
390        const NONE = 0;
391        /// All uppercase.
392        const UPPERCASE = 1 << 0;
393        /// All lowercase.
394        const LOWERCASE = 1 << 1;
395        /// Capitalize each word.
396        const CAPITALIZE = 1 << 2;
397        /// Automatic italicization of math variables.
398        const MATH_AUTO = 1 << 3;
399
400        /// All the case transforms, which are exclusive with each other.
401        /// Except for math-auto, they can be mixed with full-width or full-size-kana.
402        const CASE_TRANSFORMS = Self::UPPERCASE.0 | Self::LOWERCASE.0 | Self::CAPITALIZE.0 | Self::MATH_AUTO.0;
403
404        /// full-width
405        const FULL_WIDTH = 1 << 4;
406        /// full-size-kana
407        const FULL_SIZE_KANA = 1 << 5;
408    }
409}
410
411impl TextTransform {
412    /// Returns the initial value of text-transform
413    #[inline]
414    pub fn none() -> Self {
415        Self::NONE
416    }
417
418    /// Returns whether the value is 'none'
419    #[inline]
420    pub fn is_none(self) -> bool {
421        self == Self::NONE
422    }
423
424    fn validate_mixed_flags(&self) -> bool {
425        let case = self.intersection(Self::CASE_TRANSFORMS);
426        // Case bits are exclusive with each other.
427        case.is_empty() || case.bits().is_power_of_two()
428    }
429
430    /// Returns the corresponding TextTransformCase.
431    pub fn case(&self) -> TextTransformCase {
432        match *self & Self::CASE_TRANSFORMS {
433            Self::NONE => TextTransformCase::None,
434            Self::UPPERCASE => TextTransformCase::Uppercase,
435            Self::LOWERCASE => TextTransformCase::Lowercase,
436            Self::CAPITALIZE => TextTransformCase::Capitalize,
437            Self::MATH_AUTO => TextTransformCase::MathAuto,
438            _ => unreachable!("Case bits are exclusive with each other"),
439        }
440    }
441}
442
443/// Specified and computed value of text-align-last.
444#[derive(
445    Clone,
446    Copy,
447    Debug,
448    Eq,
449    FromPrimitive,
450    Hash,
451    MallocSizeOf,
452    Parse,
453    PartialEq,
454    SpecifiedValueInfo,
455    ToComputedValue,
456    ToCss,
457    ToResolvedValue,
458    ToShmem,
459    ToTyped,
460)]
461#[allow(missing_docs)]
462#[repr(u8)]
463pub enum TextAlignLast {
464    Auto,
465    Start,
466    End,
467    Left,
468    Right,
469    Center,
470    Justify,
471}
472
473/// Specified value of text-align keyword value.
474#[derive(
475    Clone,
476    Copy,
477    Debug,
478    Eq,
479    FromPrimitive,
480    Hash,
481    MallocSizeOf,
482    Parse,
483    PartialEq,
484    SpecifiedValueInfo,
485    ToComputedValue,
486    ToCss,
487    ToResolvedValue,
488    ToShmem,
489    ToTyped,
490)]
491#[allow(missing_docs)]
492#[repr(u8)]
493pub enum TextAlignKeyword {
494    Start,
495    Left,
496    Right,
497    Center,
498    Justify,
499    End,
500    #[parse(aliases = "-webkit-center")]
501    MozCenter,
502    #[parse(aliases = "-webkit-left")]
503    MozLeft,
504    #[parse(aliases = "-webkit-right")]
505    MozRight,
506}
507
508/// Specified value of text-align property.
509#[derive(
510    Clone,
511    Copy,
512    Debug,
513    Eq,
514    Hash,
515    MallocSizeOf,
516    Parse,
517    PartialEq,
518    SpecifiedValueInfo,
519    ToCss,
520    ToShmem,
521    ToTyped,
522)]
523pub enum TextAlign {
524    /// Keyword value of text-align property.
525    Keyword(TextAlignKeyword),
526    /// `match-parent` value of text-align property. It has a different handling
527    /// unlike other keywords.
528    MatchParent,
529    /// This is how we implement the following HTML behavior from
530    /// https://html.spec.whatwg.org/#tables-2:
531    ///
532    ///     User agents are expected to have a rule in their user agent style sheet
533    ///     that matches th elements that have a parent node whose computed value
534    ///     for the 'text-align' property is its initial value, whose declaration
535    ///     block consists of just a single declaration that sets the 'text-align'
536    ///     property to the value 'center'.
537    ///
538    /// Since selectors can't depend on the ancestor styles, we implement it with a
539    /// magic value that computes to the right thing. Since this is an
540    /// implementation detail, it shouldn't be exposed to web content.
541    #[parse(condition = "ParserContext::chrome_rules_enabled")]
542    MozCenterOrInherit,
543}
544
545impl ToComputedValue for TextAlign {
546    type ComputedValue = TextAlignKeyword;
547
548    #[inline]
549    fn to_computed_value(&self, _context: &Context) -> Self::ComputedValue {
550        match *self {
551            TextAlign::Keyword(key) => key,
552            TextAlign::MatchParent => {
553                // on the root <html> element we should still respect the dir
554                // but the parent dir of that element is LTR even if it's <html dir=rtl>
555                // and will only be RTL if certain prefs have been set.
556                // In that case, the default behavior here will set it to left,
557                // but we want to set it to right -- instead set it to the default (`start`),
558                // which will do the right thing in this case (but not the general case)
559                if _context.builder.is_root_element {
560                    return TextAlignKeyword::Start;
561                }
562                let parent = *_context
563                    .builder
564                    .get_parent_inherited_text()
565                    .get_text_align();
566                let ltr = _context.builder.inherited_writing_mode().is_bidi_ltr();
567                match (parent, ltr) {
568                    (TextAlignKeyword::Start, true) => TextAlignKeyword::Left,
569                    (TextAlignKeyword::Start, false) => TextAlignKeyword::Right,
570                    (TextAlignKeyword::End, true) => TextAlignKeyword::Right,
571                    (TextAlignKeyword::End, false) => TextAlignKeyword::Left,
572                    _ => parent,
573                }
574            },
575            TextAlign::MozCenterOrInherit => {
576                let parent = *_context
577                    .builder
578                    .get_parent_inherited_text()
579                    .get_text_align();
580                if parent == TextAlignKeyword::Start {
581                    TextAlignKeyword::Center
582                } else {
583                    parent
584                }
585            },
586        }
587    }
588
589    #[inline]
590    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
591        TextAlign::Keyword(*computed)
592    }
593}
594
595fn fill_mode_is_default_and_shape_exists(
596    fill: &TextEmphasisFillMode,
597    shape: &Option<TextEmphasisShapeKeyword>,
598) -> bool {
599    shape.is_some() && fill.is_filled()
600}
601
602/// Specified value of text-emphasis-style property.
603///
604/// https://drafts.csswg.org/css-text-decor/#propdef-text-emphasis-style
605#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
606#[allow(missing_docs)]
607#[typed(todo_derive_fields)]
608pub enum TextEmphasisStyle {
609    /// [ <fill> || <shape> ]
610    Keyword {
611        #[css(contextual_skip_if = "fill_mode_is_default_and_shape_exists")]
612        fill: TextEmphasisFillMode,
613        shape: Option<TextEmphasisShapeKeyword>,
614    },
615    /// `none`
616    None,
617    /// `<string>` (of which only the first grapheme cluster will be used).
618    String(crate::OwnedStr),
619}
620
621/// Fill mode for the text-emphasis-style property
622#[derive(
623    Clone,
624    Copy,
625    Debug,
626    MallocSizeOf,
627    Parse,
628    PartialEq,
629    SpecifiedValueInfo,
630    ToCss,
631    ToComputedValue,
632    ToResolvedValue,
633    ToShmem,
634)]
635#[repr(u8)]
636pub enum TextEmphasisFillMode {
637    /// `filled`
638    Filled,
639    /// `open`
640    Open,
641}
642
643impl TextEmphasisFillMode {
644    /// Whether the value is `filled`.
645    #[inline]
646    pub fn is_filled(&self) -> bool {
647        matches!(*self, TextEmphasisFillMode::Filled)
648    }
649}
650
651/// Shape keyword for the text-emphasis-style property
652#[derive(
653    Clone,
654    Copy,
655    Debug,
656    Eq,
657    MallocSizeOf,
658    Parse,
659    PartialEq,
660    SpecifiedValueInfo,
661    ToCss,
662    ToComputedValue,
663    ToResolvedValue,
664    ToShmem,
665)]
666#[repr(u8)]
667pub enum TextEmphasisShapeKeyword {
668    /// `dot`
669    Dot,
670    /// `circle`
671    Circle,
672    /// `double-circle`
673    DoubleCircle,
674    /// `triangle`
675    Triangle,
676    /// `sesame`
677    Sesame,
678}
679
680impl ToComputedValue for TextEmphasisStyle {
681    type ComputedValue = ComputedTextEmphasisStyle;
682
683    #[inline]
684    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
685        match *self {
686            TextEmphasisStyle::Keyword { fill, shape } => {
687                let shape = shape.unwrap_or_else(|| {
688                    // FIXME(emilio, bug 1572958): This should set the
689                    // rule_cache_conditions properly.
690                    //
691                    // Also should probably use WritingMode::is_vertical rather
692                    // than the computed value of the `writing-mode` property.
693                    if *context.style().get_inherited_box().get_writing_mode()
694                        == SpecifiedWritingMode::HorizontalTb
695                    {
696                        TextEmphasisShapeKeyword::Circle
697                    } else {
698                        TextEmphasisShapeKeyword::Sesame
699                    }
700                });
701                ComputedTextEmphasisStyle::Keyword { fill, shape }
702            },
703            TextEmphasisStyle::None => ComputedTextEmphasisStyle::None,
704            TextEmphasisStyle::String(ref s) => {
705                // FIXME(emilio): Doing this at computed value time seems wrong.
706                // The spec doesn't say that this should be a computed-value
707                // time operation. This is observable from getComputedStyle().
708                //
709                // Note that the first grapheme cluster boundary should always be the start of the string.
710                let first_grapheme_end = GraphemeClusterSegmenter::new()
711                    .segment_str(s)
712                    .nth(1)
713                    .unwrap_or(0);
714                ComputedTextEmphasisStyle::String(s[0..first_grapheme_end].to_string().into())
715            },
716        }
717    }
718
719    #[inline]
720    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
721        match *computed {
722            ComputedTextEmphasisStyle::Keyword { fill, shape } => TextEmphasisStyle::Keyword {
723                fill,
724                shape: Some(shape),
725            },
726            ComputedTextEmphasisStyle::None => TextEmphasisStyle::None,
727            ComputedTextEmphasisStyle::String(ref string) => {
728                TextEmphasisStyle::String(string.clone())
729            },
730        }
731    }
732}
733
734impl Parse for TextEmphasisStyle {
735    fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
736        if input
737            .try_parse(|input| input.expect_ident_matching("none"))
738            .is_ok()
739        {
740            return Ok(TextEmphasisStyle::None);
741        }
742
743        if let Ok(s) = input.try_parse(|i| i.expect_string().map(|s| s.as_ref().to_owned())) {
744            // Handle <string>
745            return Ok(TextEmphasisStyle::String(s.into()));
746        }
747
748        // Handle a pair of keywords
749        let mut shape = input.try_parse(TextEmphasisShapeKeyword::parse).ok();
750        let fill = input.try_parse(TextEmphasisFillMode::parse).ok();
751        if shape.is_none() {
752            shape = input.try_parse(TextEmphasisShapeKeyword::parse).ok();
753        }
754
755        if shape.is_none() && fill.is_none() {
756            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
757        }
758
759        // If a shape keyword is specified but neither filled nor open is
760        // specified, filled is assumed.
761        let fill = fill.unwrap_or(TextEmphasisFillMode::Filled);
762
763        // We cannot do the same because the default `<shape>` depends on the
764        // computed writing-mode.
765        Ok(TextEmphasisStyle::Keyword { fill, shape })
766    }
767}
768
769#[derive(
770    Clone,
771    Copy,
772    Debug,
773    Eq,
774    MallocSizeOf,
775    PartialEq,
776    Parse,
777    Serialize,
778    SpecifiedValueInfo,
779    ToCss,
780    ToComputedValue,
781    ToResolvedValue,
782    ToShmem,
783    ToTyped,
784)]
785#[repr(C)]
786#[css(bitflags(
787    single = "auto",
788    mixed = "over,under,left,right",
789    validate_mixed = "Self::validate_and_simplify"
790))]
791/// Values for text-emphasis-position:
792/// <https://drafts.csswg.org/css-text-decor/#text-emphasis-position-property>
793pub struct TextEmphasisPosition(u8);
794bitflags! {
795    impl TextEmphasisPosition: u8 {
796        /// Automatically choose mark position based on language.
797        const AUTO = 1 << 0;
798        /// Draw marks over the text in horizontal writing mode.
799        const OVER = 1 << 1;
800        /// Draw marks under the text in horizontal writing mode.
801        const UNDER = 1 << 2;
802        /// Draw marks to the left of the text in vertical writing mode.
803        const LEFT = 1 << 3;
804        /// Draw marks to the right of the text in vertical writing mode.
805        const RIGHT = 1 << 4;
806    }
807}
808
809impl TextEmphasisPosition {
810    fn validate_and_simplify(&mut self) -> bool {
811        // Require one but not both of 'over' and 'under'.
812        if self.intersects(Self::OVER) == self.intersects(Self::UNDER) {
813            return false;
814        }
815
816        // If 'left' is present, 'right' must be absent.
817        if self.intersects(Self::LEFT) {
818            return !self.intersects(Self::RIGHT);
819        }
820
821        self.remove(Self::RIGHT); // Right is the default
822        true
823    }
824}
825
826fn word_break_break_word_enabled(_: &ParserContext) -> bool {
827    crate::pref!("layout.css.word-break.break-word.enabled", gecko = true)
828}
829
830/// Values for the `word-break` property.
831#[repr(u8)]
832#[derive(
833    Clone,
834    Copy,
835    Debug,
836    Eq,
837    MallocSizeOf,
838    Parse,
839    PartialEq,
840    SpecifiedValueInfo,
841    ToComputedValue,
842    ToCss,
843    ToResolvedValue,
844    ToShmem,
845    ToTyped,
846)]
847#[allow(missing_docs)]
848pub enum WordBreak {
849    Normal,
850    BreakAll,
851    KeepAll,
852    /// The break-word value, needed for compat.
853    ///
854    /// Specifying `word-break: break-word` makes `overflow-wrap` behave as
855    /// `anywhere`, and `word-break` behave like `normal`.
856    #[parse(condition = "word_break_break_word_enabled")]
857    BreakWord,
858}
859
860/// Values for the `text-justify` CSS property.
861#[repr(u8)]
862#[derive(
863    Clone,
864    Copy,
865    Debug,
866    Eq,
867    MallocSizeOf,
868    Parse,
869    PartialEq,
870    SpecifiedValueInfo,
871    ToComputedValue,
872    ToCss,
873    ToResolvedValue,
874    ToShmem,
875    ToTyped,
876)]
877#[allow(missing_docs)]
878pub enum TextJustify {
879    Auto,
880    None,
881    InterWord,
882    // See https://drafts.csswg.org/css-text-3/#valdef-text-justify-distribute
883    // and https://github.com/w3c/csswg-drafts/issues/6156 for the alias.
884    #[parse(aliases = "distribute")]
885    InterCharacter,
886}
887
888/// Values for the `-moz-control-character-visibility` CSS property.
889#[repr(u8)]
890#[derive(
891    Clone,
892    Copy,
893    Debug,
894    Eq,
895    MallocSizeOf,
896    Parse,
897    PartialEq,
898    SpecifiedValueInfo,
899    ToComputedValue,
900    ToCss,
901    ToResolvedValue,
902    ToShmem,
903    ToTyped,
904)]
905#[allow(missing_docs)]
906pub enum MozControlCharacterVisibility {
907    Hidden,
908    Visible,
909}
910
911#[cfg(feature = "gecko")]
912impl Default for MozControlCharacterVisibility {
913    fn default() -> Self {
914        if crate::pref!("layout.css.control-characters.visible") {
915            Self::Visible
916        } else {
917            Self::Hidden
918        }
919    }
920}
921
922/// Values for the `line-break` property.
923#[repr(u8)]
924#[derive(
925    Clone,
926    Copy,
927    Debug,
928    Eq,
929    MallocSizeOf,
930    Parse,
931    PartialEq,
932    SpecifiedValueInfo,
933    ToComputedValue,
934    ToCss,
935    ToResolvedValue,
936    ToShmem,
937    ToTyped,
938)]
939#[allow(missing_docs)]
940pub enum LineBreak {
941    Auto,
942    Loose,
943    Normal,
944    Strict,
945    Anywhere,
946}
947
948/// Values for the `overflow-wrap` property.
949#[repr(u8)]
950#[derive(
951    Clone,
952    Copy,
953    Debug,
954    Eq,
955    MallocSizeOf,
956    Parse,
957    PartialEq,
958    SpecifiedValueInfo,
959    ToComputedValue,
960    ToCss,
961    ToResolvedValue,
962    ToShmem,
963    ToTyped,
964)]
965#[allow(missing_docs)]
966pub enum OverflowWrap {
967    Normal,
968    BreakWord,
969    Anywhere,
970}
971
972/// A specified value for the `text-indent` property
973/// which takes the grammar of [<length-percentage>] && hanging? && each-line?
974///
975/// https://drafts.csswg.org/css-text/#propdef-text-indent
976pub type TextIndent = GenericTextIndent<LengthPercentage>;
977
978impl Parse for TextIndent {
979    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
980        let mut length = None;
981        let mut hanging = false;
982        let mut each_line = false;
983
984        // The length-percentage and the two possible keywords can occur in any order.
985        while !input.is_exhausted() {
986            // If we haven't seen a length yet, try to parse one.
987            if length.is_none()
988                && let Ok(len) = input
989                    .try_parse(|i| LengthPercentage::parse_quirky(context, i, AllowQuirks::Yes))
990            {
991                length = Some(len);
992                continue;
993            }
994
995            // If the keywords are disabled, just break and let the caller deal with it.
996            if !crate::pref!("layout.css.text-indent-keywords.enabled", gecko = true) {
997                break;
998            }
999
1000            // Check for the keywords (boolean flags).
1001            try_match_ident_ignore_ascii_case! { input,
1002                "hanging" if !hanging => hanging = true,
1003                "each-line" if !each_line => each_line = true,
1004            }
1005        }
1006
1007        // The length-percentage value is required for the declaration to be valid.
1008        if let Some(length) = length {
1009            Ok(Self {
1010                length,
1011                hanging,
1012                each_line,
1013            })
1014        } else {
1015            Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError))
1016        }
1017    }
1018}
1019
1020/// Implements text-decoration-skip-ink which takes the keywords auto | none | all
1021///
1022/// https://drafts.csswg.org/css-text-decor-4/#text-decoration-skip-ink-property
1023#[repr(u8)]
1024#[derive(
1025    Clone,
1026    Copy,
1027    Debug,
1028    Deserialize,
1029    Eq,
1030    MallocSizeOf,
1031    Parse,
1032    PartialEq,
1033    Serialize,
1034    SpecifiedValueInfo,
1035    ToComputedValue,
1036    ToCss,
1037    ToResolvedValue,
1038    ToShmem,
1039    ToTyped,
1040)]
1041#[allow(missing_docs)]
1042pub enum TextDecorationSkipInk {
1043    Auto,
1044    None,
1045    All,
1046}
1047
1048/// Implements type for `text-decoration-thickness` property
1049pub type TextDecorationLength = GenericTextDecorationLength<LengthPercentage>;
1050
1051impl TextDecorationLength {
1052    /// `Auto` value.
1053    #[inline]
1054    pub fn auto() -> Self {
1055        GenericTextDecorationLength::Auto
1056    }
1057
1058    /// Whether this is the `Auto` value.
1059    #[inline]
1060    pub fn is_auto(&self) -> bool {
1061        matches!(*self, GenericTextDecorationLength::Auto)
1062    }
1063}
1064
1065/// Implements type for `text-decoration-inset` property
1066pub type TextDecorationInset = GenericTextDecorationInset<LengthPercentage>;
1067
1068impl TextDecorationInset {
1069    /// `Auto` value.
1070    #[inline]
1071    pub fn auto() -> Self {
1072        GenericTextDecorationInset::Auto
1073    }
1074
1075    /// Whether this is the `Auto` value.
1076    #[inline]
1077    pub fn is_auto(&self) -> bool {
1078        matches!(*self, GenericTextDecorationInset::Auto)
1079    }
1080}
1081
1082fn parse_inset_endpoint(
1083    ctx: &ParserContext,
1084    input: &mut Parser,
1085) -> Result<LengthPercentage, ParseError> {
1086    if !crate::pref!("layout.css.text-decoration-inset-percentage.enabled") {
1087        Length::parse(ctx, input).map(|l| l.into())
1088    } else {
1089        LengthPercentage::parse(ctx, input)
1090    }
1091}
1092
1093impl Parse for TextDecorationInset {
1094    fn parse(ctx: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
1095        if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
1096            return Ok(TextDecorationInset::Auto);
1097        }
1098
1099        let start = parse_inset_endpoint(ctx, input)?;
1100        let end = input
1101            .try_parse(|i| parse_inset_endpoint(ctx, i))
1102            .unwrap_or_else(|_| start.clone());
1103        Ok(TextDecorationInset::LengthPercentage { start, end })
1104    }
1105}
1106
1107#[derive(
1108    Clone,
1109    Copy,
1110    Debug,
1111    Eq,
1112    MallocSizeOf,
1113    Parse,
1114    PartialEq,
1115    SpecifiedValueInfo,
1116    ToComputedValue,
1117    ToResolvedValue,
1118    ToShmem,
1119    ToTyped,
1120)]
1121#[css(bitflags(
1122    single = "auto",
1123    mixed = "from-font,under,left,right",
1124    validate_mixed = "Self::validate_mixed_flags",
1125))]
1126#[repr(C)]
1127/// Specified keyword values for the text-underline-position property.
1128/// (Non-exclusive, but not all combinations are allowed: the spec grammar gives
1129/// `auto | [ from-font | under ] || [ left | right ]`.)
1130/// https://drafts.csswg.org/css-text-decor-4/#text-underline-position-property
1131pub struct TextUnderlinePosition(u8);
1132bitflags! {
1133    impl TextUnderlinePosition: u8 {
1134        /// Use automatic positioning below the alphabetic baseline.
1135        const AUTO = 0;
1136        /// Use underline position from the first available font.
1137        const FROM_FONT = 1 << 0;
1138        /// Below the glyph box.
1139        const UNDER = 1 << 1;
1140        /// In vertical mode, place to the left of the text.
1141        const LEFT = 1 << 2;
1142        /// In vertical mode, place to the right of the text.
1143        const RIGHT = 1 << 3;
1144    }
1145}
1146
1147impl TextUnderlinePosition {
1148    fn validate_mixed_flags(&self) -> bool {
1149        if self.contains(Self::LEFT | Self::RIGHT) {
1150            // left and right can't be mixed together.
1151            return false;
1152        }
1153        if self.contains(Self::FROM_FONT | Self::UNDER) {
1154            // from-font and under can't be mixed together either.
1155            return false;
1156        }
1157        true
1158    }
1159}
1160
1161impl ToCss for TextUnderlinePosition {
1162    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1163    where
1164        W: Write,
1165    {
1166        if self.is_empty() {
1167            return dest.write_str("auto");
1168        }
1169
1170        let mut writer = SequenceWriter::new(dest, " ");
1171        let mut any = false;
1172
1173        macro_rules! maybe_write {
1174            ($ident:ident => $str:expr) => {
1175                if self.contains(TextUnderlinePosition::$ident) {
1176                    any = true;
1177                    writer.raw_item($str)?;
1178                }
1179            };
1180        }
1181
1182        maybe_write!(FROM_FONT => "from-font");
1183        maybe_write!(UNDER => "under");
1184        maybe_write!(LEFT => "left");
1185        maybe_write!(RIGHT => "right");
1186
1187        debug_assert!(any);
1188
1189        Ok(())
1190    }
1191}
1192
1193/// Values for `ruby-position` property
1194#[repr(u8)]
1195#[derive(
1196    Clone,
1197    Copy,
1198    Debug,
1199    Eq,
1200    MallocSizeOf,
1201    PartialEq,
1202    ToComputedValue,
1203    ToResolvedValue,
1204    ToShmem,
1205    ToTyped,
1206)]
1207#[allow(missing_docs)]
1208pub enum RubyPosition {
1209    AlternateOver,
1210    AlternateUnder,
1211    Over,
1212    Under,
1213}
1214
1215impl Parse for RubyPosition {
1216    fn parse(_context: &ParserContext, input: &mut Parser) -> Result<RubyPosition, ParseError> {
1217        // Parse alternate before
1218        let alternate = input
1219            .try_parse(|i| i.expect_ident_matching("alternate"))
1220            .is_ok();
1221        if alternate && input.is_exhausted() {
1222            return Ok(RubyPosition::AlternateOver);
1223        }
1224        // Parse over / under
1225        let over = try_match_ident_ignore_ascii_case! { input,
1226            "over" => true,
1227            "under" => false,
1228        };
1229        // Parse alternate after
1230        let alternate = alternate
1231            || input
1232                .try_parse(|i| i.expect_ident_matching("alternate"))
1233                .is_ok();
1234
1235        Ok(match (over, alternate) {
1236            (true, true) => RubyPosition::AlternateOver,
1237            (false, true) => RubyPosition::AlternateUnder,
1238            (true, false) => RubyPosition::Over,
1239            (false, false) => RubyPosition::Under,
1240        })
1241    }
1242}
1243
1244impl ToCss for RubyPosition {
1245    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1246    where
1247        W: Write,
1248    {
1249        dest.write_str(match self {
1250            RubyPosition::AlternateOver => "alternate",
1251            RubyPosition::AlternateUnder => "alternate under",
1252            RubyPosition::Over => "over",
1253            RubyPosition::Under => "under",
1254        })
1255    }
1256}
1257
1258impl SpecifiedValueInfo for RubyPosition {
1259    fn collect_completion_keywords(f: KeywordsCollectFn) {
1260        f(&["alternate", "over", "under"])
1261    }
1262}
1263
1264/// Specified value for the text-autospace property
1265/// which takes the grammar:
1266///     normal | <autospace> | auto
1267/// where:
1268///     <autospace> = no-autospace |
1269///                   [ ideograph-alpha || ideograph-numeric || punctuation ]
1270///                   || [ insert | replace ]
1271///
1272/// https://drafts.csswg.org/css-text-4/#text-autospace-property
1273///
1274/// Bug 1980111: 'replace' value is not supported yet.
1275#[derive(
1276    Clone,
1277    Copy,
1278    Debug,
1279    Eq,
1280    MallocSizeOf,
1281    Parse,
1282    PartialEq,
1283    Serialize,
1284    SpecifiedValueInfo,
1285    ToCss,
1286    ToComputedValue,
1287    ToResolvedValue,
1288    ToShmem,
1289    ToTyped,
1290)]
1291#[css(bitflags(
1292    single = "normal,auto,no-autospace",
1293    // Bug 1980111: add 'replace' to 'mixed' in the future so that it parses correctly.
1294    // Bug 1986500: add 'punctuation' to 'mixed' in the future so that it parses correctly.
1295    mixed = "ideograph-alpha,ideograph-numeric,insert",
1296    // Bug 1980111: Uncomment 'validate_mixed' to support 'replace' value.
1297    // validate_mixed = "Self::validate_mixed_flags",
1298))]
1299#[repr(C)]
1300pub struct TextAutospace(u8);
1301bitflags! {
1302    impl TextAutospace: u8 {
1303        /// No automatic space is inserted.
1304        const NO_AUTOSPACE = 0;
1305
1306        /// The user agent chooses a set of typographically high quality spacing values.
1307        const AUTO = 1 << 0;
1308
1309        /// Same behavior as ideograph-alpha ideograph-numeric.
1310        const NORMAL = 1 << 1;
1311
1312        /// 1/8ic space between ideographic characters and non-ideographic letters.
1313        const IDEOGRAPH_ALPHA = 1 << 2;
1314
1315        /// 1/8ic space between ideographic characters and non-ideographic decimal numerals.
1316        const IDEOGRAPH_NUMERIC = 1 << 3;
1317
1318        /* Bug 1986500: Uncomment the following to support the 'punctuation' value.
1319        /// Apply special spacing between letters and punctuation (French).
1320        const PUNCTUATION = 1 << 4;
1321        */
1322
1323        /// Auto-spacing is only inserted if no space character is present in the text.
1324        const INSERT = 1 << 5;
1325
1326        /* Bug 1980111: Uncomment the following to support 'replace' value.
1327        /// Auto-spacing may replace an existing U+0020 space with custom space.
1328        const REPLACE = 1 << 6;
1329        */
1330    }
1331}
1332
1333/* Bug 1980111: Uncomment the following to support 'replace' value.
1334impl TextAutospace {
1335    fn validate_mixed_flags(&self) -> bool {
1336        // It's not valid to have both INSERT and REPLACE set.
1337        !self.contains(TextAutospace::INSERT | TextAutospace::REPLACE)
1338    }
1339}
1340*/
1341#[derive(
1342    Clone,
1343    Copy,
1344    Debug,
1345    Eq,
1346    FromPrimitive,
1347    Hash,
1348    MallocSizeOf,
1349    Parse,
1350    PartialEq,
1351    SpecifiedValueInfo,
1352    ToComputedValue,
1353    ToCss,
1354    ToResolvedValue,
1355    ToShmem,
1356    ToTyped,
1357)]
1358#[repr(u8)]
1359/// Identifies specific font metrics for use in the <text-edge> typedef.
1360///
1361/// https://drafts.csswg.org/css-inline-3/#typedef-text-edge
1362pub enum TextEdgeKeyword {
1363    /// Use the text-over baseline/text-under baseline as the over/under edge.
1364    Text,
1365    /// Use the ideographic-over baseline/ideographic-under baseline as the over/under edge.
1366    Ideographic,
1367    /// Use the ideographic-ink-over baseline/ideographic-ink-under baseline as the over/under edge.
1368    IdeographicInk,
1369    /// Use the cap-height baseline as the over edge.
1370    Cap,
1371    /// Use the x-height baseline as the over edge.
1372    Ex,
1373    /// Use the alphabetic baseline as the under edge.
1374    Alphabetic,
1375}
1376
1377impl TextEdgeKeyword {
1378    fn is_valid_for_over(&self) -> bool {
1379        match self {
1380            TextEdgeKeyword::Text
1381            | TextEdgeKeyword::Ideographic
1382            | TextEdgeKeyword::IdeographicInk
1383            | TextEdgeKeyword::Cap
1384            | TextEdgeKeyword::Ex => true,
1385            _ => false,
1386        }
1387    }
1388
1389    fn is_valid_for_under(&self) -> bool {
1390        match self {
1391            TextEdgeKeyword::Text
1392            | TextEdgeKeyword::Ideographic
1393            | TextEdgeKeyword::IdeographicInk
1394            | TextEdgeKeyword::Alphabetic => true,
1395            _ => false,
1396        }
1397    }
1398}
1399
1400#[derive(
1401    Clone,
1402    Copy,
1403    Debug,
1404    Eq,
1405    Hash,
1406    MallocSizeOf,
1407    PartialEq,
1408    SpecifiedValueInfo,
1409    ToComputedValue,
1410    ToResolvedValue,
1411    ToShmem,
1412    ToTyped,
1413)]
1414#[repr(C)]
1415/// The <text-edge> typedef, used by the `line-fit-edge` and
1416/// `text-box-edge` properties.
1417///
1418/// The first value specifies the text over edge; the second value
1419/// specifies the text under edge. If only one value is specified,
1420/// both edges are assigned that same keyword if possible; else
1421/// text is assumed as the missing value.
1422///
1423/// https://drafts.csswg.org/css-inline-3/#typedef-text-edge
1424pub struct TextEdge {
1425    /// Font metric to use for the text over edge.
1426    pub over: TextEdgeKeyword,
1427    /// Font metric to use for the text under edge.
1428    pub under: TextEdgeKeyword,
1429}
1430
1431impl Parse for TextEdge {
1432    fn parse(_context: &ParserContext, input: &mut Parser) -> Result<TextEdge, ParseError> {
1433        let first = TextEdgeKeyword::parse(input)?;
1434
1435        if let Ok(second) = input.try_parse(TextEdgeKeyword::parse) {
1436            if !first.is_valid_for_over() || !second.is_valid_for_under() {
1437                return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1438            }
1439
1440            return Ok(TextEdge {
1441                over: first,
1442                under: second,
1443            });
1444        }
1445
1446        // https://drafts.csswg.org/css-inline-3/#typedef-text-edge
1447        // > If only one value is specified, both edges are assigned that same
1448        // > keyword if possible; else 'text' is assumed as the missing value.
1449        match (first.is_valid_for_over(), first.is_valid_for_under()) {
1450            (true, true) => Ok(TextEdge {
1451                over: first,
1452                under: first,
1453            }),
1454            (true, false) => Ok(TextEdge {
1455                over: first,
1456                under: TextEdgeKeyword::Text,
1457            }),
1458            (false, true) => Ok(TextEdge {
1459                over: TextEdgeKeyword::Text,
1460                under: first,
1461            }),
1462            _ => unreachable!("Parsed keyword will be valid for at least one edge"),
1463        }
1464    }
1465}
1466
1467impl ToCss for TextEdge {
1468    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1469    where
1470        W: Write,
1471    {
1472        match (self.over, self.under) {
1473            (over, TextEdgeKeyword::Text) if !over.is_valid_for_under() => over.to_css(dest),
1474            (TextEdgeKeyword::Text, under) if !under.is_valid_for_over() => under.to_css(dest),
1475            (over, under) => {
1476                over.to_css(dest)?;
1477
1478                if over != under {
1479                    dest.write_char(' ')?;
1480                    self.under.to_css(dest)?;
1481                }
1482
1483                Ok(())
1484            },
1485        }
1486    }
1487}
1488
1489#[derive(
1490    Clone,
1491    Copy,
1492    Debug,
1493    Eq,
1494    Hash,
1495    MallocSizeOf,
1496    Parse,
1497    PartialEq,
1498    SpecifiedValueInfo,
1499    ToComputedValue,
1500    ToCss,
1501    ToResolvedValue,
1502    ToShmem,
1503    ToTyped,
1504)]
1505#[repr(C, u8)]
1506/// Specified value for the `text-box-edge` property.
1507///
1508/// https://drafts.csswg.org/css-inline-3/#text-box-edge
1509pub enum TextBoxEdge {
1510    /// Uses the value of `line-fit-edge`, interpreting `leading` (the initial value) as `text`.
1511    Auto,
1512    /// Uses the specified font metrics.
1513    TextEdge(TextEdge),
1514}
1515
1516#[derive(
1517    Clone,
1518    Copy,
1519    Debug,
1520    Eq,
1521    MallocSizeOf,
1522    PartialEq,
1523    Parse,
1524    Serialize,
1525    SpecifiedValueInfo,
1526    ToCss,
1527    ToComputedValue,
1528    ToResolvedValue,
1529    ToShmem,
1530    ToTyped,
1531)]
1532#[css(bitflags(single = "none,trim-start,trim-end,trim-both"))]
1533#[repr(C)]
1534/// Specified value for the `text-box-trim` property.
1535///
1536/// https://drafts.csswg.org/css-inline-3/#text-box-edge
1537pub struct TextBoxTrim(u8);
1538bitflags! {
1539    impl TextBoxTrim: u8 {
1540        /// NONE
1541        const NONE = 0;
1542        /// TRIM_START
1543        const TRIM_START = 1 << 0;
1544        /// TRIM_END
1545        const TRIM_END = 1 << 1;
1546        /// TRIM_BOTH
1547        const TRIM_BOTH = Self::TRIM_START.0 | Self::TRIM_END.0;
1548    }
1549}
1550
1551impl TextBoxTrim {
1552    /// Returns the initial value of text-box-trim
1553    #[inline]
1554    pub fn none() -> Self {
1555        TextBoxTrim::NONE
1556    }
1557}
1558
1559/// https://drafts.csswg.org/css-text/#propdef-hyphens
1560#[allow(missing_docs)]
1561#[derive(
1562    Clone,
1563    Copy,
1564    Debug,
1565    Deserialize,
1566    Eq,
1567    FromPrimitive,
1568    Hash,
1569    MallocSizeOf,
1570    Parse,
1571    PartialEq,
1572    Serialize,
1573    SpecifiedValueInfo,
1574    ToComputedValue,
1575    ToCss,
1576    ToResolvedValue,
1577    ToShmem,
1578    ToTyped,
1579)]
1580#[repr(u8)]
1581pub enum Hyphens {
1582    Manual,
1583    None,
1584    Auto,
1585}
1586
1587/// https://drafts.csswg.org/css-size-adjust/#adjustment-control
1588#[allow(missing_docs)]
1589#[derive(
1590    Clone,
1591    Copy,
1592    Debug,
1593    Deserialize,
1594    Eq,
1595    FromPrimitive,
1596    Hash,
1597    MallocSizeOf,
1598    Parse,
1599    PartialEq,
1600    Serialize,
1601    SpecifiedValueInfo,
1602    ToComputedValue,
1603    ToCss,
1604    ToResolvedValue,
1605    ToShmem,
1606    ToTyped,
1607)]
1608#[repr(u8)]
1609pub enum TextSizeAdjust {
1610    Auto,
1611    None,
1612}
1613
1614/// https://drafts.csswg.org/css-ruby/#ruby-align-property
1615#[allow(missing_docs)]
1616#[derive(
1617    Clone,
1618    Copy,
1619    Debug,
1620    Deserialize,
1621    Eq,
1622    FromPrimitive,
1623    Hash,
1624    MallocSizeOf,
1625    Parse,
1626    PartialEq,
1627    Serialize,
1628    SpecifiedValueInfo,
1629    ToComputedValue,
1630    ToCss,
1631    ToResolvedValue,
1632    ToShmem,
1633    ToTyped,
1634)]
1635#[repr(u8)]
1636pub enum RubyAlign {
1637    SpaceAround,
1638    Start,
1639    Center,
1640    SpaceBetween,
1641}
1642
1643/// https://drafts.csswg.org/css-writing-modes-3/#text-combine-upright
1644#[allow(missing_docs)]
1645#[derive(
1646    Clone,
1647    Copy,
1648    Debug,
1649    Deserialize,
1650    Eq,
1651    FromPrimitive,
1652    Hash,
1653    MallocSizeOf,
1654    Parse,
1655    PartialEq,
1656    Serialize,
1657    SpecifiedValueInfo,
1658    ToComputedValue,
1659    ToCss,
1660    ToResolvedValue,
1661    ToShmem,
1662    ToTyped,
1663)]
1664#[repr(u8)]
1665pub enum TextCombineUpright {
1666    None,
1667    All,
1668}
1669
1670/// https://svgwg.org/svg2-draft/painting.html#TextRenderingProperty
1671#[allow(missing_docs)]
1672#[derive(
1673    Clone,
1674    Copy,
1675    Debug,
1676    Deserialize,
1677    Eq,
1678    FromPrimitive,
1679    Hash,
1680    MallocSizeOf,
1681    Parse,
1682    PartialEq,
1683    Serialize,
1684    SpecifiedValueInfo,
1685    ToComputedValue,
1686    ToCss,
1687    ToResolvedValue,
1688    ToShmem,
1689    ToTyped,
1690)]
1691#[repr(u8)]
1692pub enum TextRendering {
1693    Auto,
1694    Optimizespeed,
1695    Optimizelegibility,
1696    Geometricprecision,
1697}
1698
1699/// https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/-webkit-text-security
1700#[allow(missing_docs)]
1701#[derive(
1702    Clone,
1703    Copy,
1704    Debug,
1705    Deserialize,
1706    Eq,
1707    FromPrimitive,
1708    Hash,
1709    MallocSizeOf,
1710    Parse,
1711    PartialEq,
1712    Serialize,
1713    SpecifiedValueInfo,
1714    ToComputedValue,
1715    ToCss,
1716    ToResolvedValue,
1717    ToShmem,
1718    ToTyped,
1719)]
1720#[repr(u8)]
1721pub enum TextSecurity {
1722    None,
1723    Circle,
1724    Disc,
1725    Square,
1726}
1727
1728/// https://drafts.csswg.org/css-text-4/#propdef-text-wrap-mode
1729#[allow(missing_docs)]
1730#[derive(
1731    Clone,
1732    Copy,
1733    Debug,
1734    Deserialize,
1735    Eq,
1736    FromPrimitive,
1737    Hash,
1738    MallocSizeOf,
1739    Parse,
1740    PartialEq,
1741    Serialize,
1742    SpecifiedValueInfo,
1743    ToComputedValue,
1744    ToCss,
1745    ToResolvedValue,
1746    ToShmem,
1747    ToTyped,
1748)]
1749#[repr(u8)]
1750pub enum TextWrapMode {
1751    Wrap,
1752    Nowrap,
1753}
1754
1755fn text_wrap_style_balance_enabled(_: &ParserContext) -> bool {
1756    crate::pref!("layout.css.text-wrap-style.balance.enabled", gecko = true)
1757}
1758
1759fn text_wrap_style_pretty_enabled(_: &ParserContext) -> bool {
1760    crate::pref!("layout.css.text-wrap-style.pretty.enabled", gecko = false)
1761}
1762
1763/// https://drafts.csswg.org/css-text-4/#text-wrap-style
1764#[allow(missing_docs)]
1765#[derive(
1766    Clone,
1767    Copy,
1768    Debug,
1769    Deserialize,
1770    Eq,
1771    FromPrimitive,
1772    Hash,
1773    MallocSizeOf,
1774    Parse,
1775    PartialEq,
1776    Serialize,
1777    SpecifiedValueInfo,
1778    ToComputedValue,
1779    ToCss,
1780    ToResolvedValue,
1781    ToShmem,
1782    ToTyped,
1783)]
1784#[repr(u8)]
1785pub enum TextWrapStyle {
1786    Auto,
1787    Stable,
1788    #[parse(condition = "text_wrap_style_balance_enabled")]
1789    Balance,
1790    #[parse(condition = "text_wrap_style_pretty_enabled")]
1791    Pretty,
1792}
1793
1794/// https://drafts.csswg.org/css-writing-modes/#propdef-unicode-bidi
1795#[allow(missing_docs)]
1796#[derive(
1797    Clone,
1798    Copy,
1799    Debug,
1800    Deserialize,
1801    Eq,
1802    FromPrimitive,
1803    Hash,
1804    MallocSizeOf,
1805    Parse,
1806    PartialEq,
1807    Serialize,
1808    SpecifiedValueInfo,
1809    ToComputedValue,
1810    ToCss,
1811    ToResolvedValue,
1812    ToShmem,
1813    ToTyped,
1814)]
1815#[repr(u8)]
1816pub enum UnicodeBidi {
1817    Normal,
1818    Embed,
1819    Isolate,
1820    BidiOverride,
1821    IsolateOverride,
1822    Plaintext,
1823}
1824
1825/// https://drafts.csswg.org/css-text-decor/#propdef-text-decoration-style
1826#[allow(missing_docs)]
1827#[derive(
1828    Clone,
1829    Copy,
1830    Debug,
1831    Deserialize,
1832    Eq,
1833    FromPrimitive,
1834    Hash,
1835    MallocSizeOf,
1836    Parse,
1837    PartialEq,
1838    Serialize,
1839    SpecifiedValueInfo,
1840    ToComputedValue,
1841    ToCss,
1842    ToResolvedValue,
1843    ToShmem,
1844    ToTyped,
1845)]
1846#[repr(u8)]
1847pub enum TextDecorationStyle {
1848    // The discriminants are exposed through the LookAndFeel underline style
1849    // integers, so keep them stable.
1850    #[css(keyword = "-moz-none")]
1851    None = 0,
1852    Dotted = 1,
1853    Dashed = 2,
1854    Solid = 3,
1855    Double = 4,
1856    Wavy = 5,
1857}
1858
1859/// Max valid value for TextDecorationStyle, see above.
1860pub const MAX_LINE_STYLE: i32 = 5;
1861
1862fn white_space_collapse_preserve_spaces_enabled(_: &ParserContext) -> bool {
1863    crate::pref!(
1864        "layout.css.white-space-collapse.preserve-spaces.enabled",
1865        gecko = true
1866    )
1867}
1868
1869/// https://drafts.csswg.org/css-text-4/#propdef-white-space-collapse
1870#[allow(missing_docs)]
1871#[derive(
1872    Clone,
1873    Copy,
1874    Debug,
1875    Deserialize,
1876    Eq,
1877    FromPrimitive,
1878    Hash,
1879    MallocSizeOf,
1880    Parse,
1881    PartialEq,
1882    Serialize,
1883    SpecifiedValueInfo,
1884    ToComputedValue,
1885    ToCss,
1886    ToResolvedValue,
1887    ToShmem,
1888    ToTyped,
1889)]
1890#[repr(u8)]
1891pub enum WhiteSpaceCollapse {
1892    Collapse,
1893    // TODO: Discard is not yet supported.
1894    Preserve,
1895    PreserveBreaks,
1896    BreakSpaces,
1897    #[parse(condition = "white_space_collapse_preserve_spaces_enabled")]
1898    #[cfg_attr(feature = "gecko", parse(aliases = "-moz-pre-space"))]
1899    PreserveSpaces,
1900}