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