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::derives::*;
8use crate::parser::{Parse, ParserContext};
9use crate::properties::longhands::writing_mode::computed_value::T as SpecifiedWritingMode;
10use crate::values::computed;
11use crate::values::computed::text::TextEmphasisStyle as ComputedTextEmphasisStyle;
12use crate::values::computed::{Context, ToComputedValue};
13use crate::values::generics::text::{
14    GenericHyphenateLimitChars, GenericInitialLetter, GenericTextDecorationInset,
15    GenericTextDecorationLength, GenericTextIndent,
16};
17use crate::values::generics::NumberOrAuto;
18use crate::values::specified::length::{Length, LengthPercentage};
19use crate::values::specified::{AllowQuirks, Integer, Number};
20use crate::Zero;
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                    .clone_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                    .clone_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().clone_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                if 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
999            // Servo doesn't support the keywords, so just break and let the caller deal with it.
1000            if cfg!(feature = "servo") {
1001                break;
1002            }
1003
1004            // Check for the keywords (boolean flags).
1005            try_match_ident_ignore_ascii_case! { input,
1006                "hanging" if !hanging => hanging = true,
1007                "each-line" if !each_line => each_line = true,
1008            }
1009        }
1010
1011        // The length-percentage value is required for the declaration to be valid.
1012        if let Some(length) = length {
1013            Ok(Self {
1014                length,
1015                hanging,
1016                each_line,
1017            })
1018        } else {
1019            Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError))
1020        }
1021    }
1022}
1023
1024/// Implements text-decoration-skip-ink which takes the keywords auto | none | all
1025///
1026/// https://drafts.csswg.org/css-text-decor-4/#text-decoration-skip-ink-property
1027#[repr(u8)]
1028#[derive(
1029    Clone,
1030    Copy,
1031    Debug,
1032    Deserialize,
1033    Eq,
1034    MallocSizeOf,
1035    Parse,
1036    PartialEq,
1037    Serialize,
1038    SpecifiedValueInfo,
1039    ToComputedValue,
1040    ToCss,
1041    ToResolvedValue,
1042    ToShmem,
1043    ToTyped,
1044)]
1045#[allow(missing_docs)]
1046pub enum TextDecorationSkipInk {
1047    Auto,
1048    None,
1049    All,
1050}
1051
1052/// Implements type for `text-decoration-thickness` property
1053pub type TextDecorationLength = GenericTextDecorationLength<LengthPercentage>;
1054
1055impl TextDecorationLength {
1056    /// `Auto` value.
1057    #[inline]
1058    pub fn auto() -> Self {
1059        GenericTextDecorationLength::Auto
1060    }
1061
1062    /// Whether this is the `Auto` value.
1063    #[inline]
1064    pub fn is_auto(&self) -> bool {
1065        matches!(*self, GenericTextDecorationLength::Auto)
1066    }
1067}
1068
1069/// Implements type for `text-decoration-inset` property
1070pub type TextDecorationInset = GenericTextDecorationInset<LengthPercentage>;
1071
1072impl TextDecorationInset {
1073    /// `Auto` value.
1074    #[inline]
1075    pub fn auto() -> Self {
1076        GenericTextDecorationInset::Auto
1077    }
1078
1079    /// Whether this is the `Auto` value.
1080    #[inline]
1081    pub fn is_auto(&self) -> bool {
1082        matches!(*self, GenericTextDecorationInset::Auto)
1083    }
1084}
1085
1086fn parse_inset_endpoint(
1087    ctx: &ParserContext,
1088    input: &mut Parser,
1089) -> Result<LengthPercentage, ParseError> {
1090    if !crate::pref!("layout.css.text-decoration-inset-percentage.enabled") {
1091        Length::parse(ctx, input).map(|l| l.into())
1092    } else {
1093        LengthPercentage::parse(ctx, input)
1094    }
1095}
1096
1097impl Parse for TextDecorationInset {
1098    fn parse(ctx: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
1099        if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
1100            return Ok(TextDecorationInset::Auto);
1101        }
1102
1103        let start = parse_inset_endpoint(ctx, input)?;
1104        let end = input
1105            .try_parse(|i| parse_inset_endpoint(ctx, i))
1106            .unwrap_or_else(|_| start.clone());
1107        Ok(TextDecorationInset::LengthPercentage { start, end })
1108    }
1109}
1110
1111#[derive(
1112    Clone,
1113    Copy,
1114    Debug,
1115    Eq,
1116    MallocSizeOf,
1117    Parse,
1118    PartialEq,
1119    SpecifiedValueInfo,
1120    ToComputedValue,
1121    ToResolvedValue,
1122    ToShmem,
1123    ToTyped,
1124)]
1125#[css(bitflags(
1126    single = "auto",
1127    mixed = "from-font,under,left,right",
1128    validate_mixed = "Self::validate_mixed_flags",
1129))]
1130#[repr(C)]
1131/// Specified keyword values for the text-underline-position property.
1132/// (Non-exclusive, but not all combinations are allowed: the spec grammar gives
1133/// `auto | [ from-font | under ] || [ left | right ]`.)
1134/// https://drafts.csswg.org/css-text-decor-4/#text-underline-position-property
1135pub struct TextUnderlinePosition(u8);
1136bitflags! {
1137    impl TextUnderlinePosition: u8 {
1138        /// Use automatic positioning below the alphabetic baseline.
1139        const AUTO = 0;
1140        /// Use underline position from the first available font.
1141        const FROM_FONT = 1 << 0;
1142        /// Below the glyph box.
1143        const UNDER = 1 << 1;
1144        /// In vertical mode, place to the left of the text.
1145        const LEFT = 1 << 2;
1146        /// In vertical mode, place to the right of the text.
1147        const RIGHT = 1 << 3;
1148    }
1149}
1150
1151impl TextUnderlinePosition {
1152    fn validate_mixed_flags(&self) -> bool {
1153        if self.contains(Self::LEFT | Self::RIGHT) {
1154            // left and right can't be mixed together.
1155            return false;
1156        }
1157        if self.contains(Self::FROM_FONT | Self::UNDER) {
1158            // from-font and under can't be mixed together either.
1159            return false;
1160        }
1161        true
1162    }
1163}
1164
1165impl ToCss for TextUnderlinePosition {
1166    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1167    where
1168        W: Write,
1169    {
1170        if self.is_empty() {
1171            return dest.write_str("auto");
1172        }
1173
1174        let mut writer = SequenceWriter::new(dest, " ");
1175        let mut any = false;
1176
1177        macro_rules! maybe_write {
1178            ($ident:ident => $str:expr) => {
1179                if self.contains(TextUnderlinePosition::$ident) {
1180                    any = true;
1181                    writer.raw_item($str)?;
1182                }
1183            };
1184        }
1185
1186        maybe_write!(FROM_FONT => "from-font");
1187        maybe_write!(UNDER => "under");
1188        maybe_write!(LEFT => "left");
1189        maybe_write!(RIGHT => "right");
1190
1191        debug_assert!(any);
1192
1193        Ok(())
1194    }
1195}
1196
1197/// Values for `ruby-position` property
1198#[repr(u8)]
1199#[derive(
1200    Clone,
1201    Copy,
1202    Debug,
1203    Eq,
1204    MallocSizeOf,
1205    PartialEq,
1206    ToComputedValue,
1207    ToResolvedValue,
1208    ToShmem,
1209    ToTyped,
1210)]
1211#[allow(missing_docs)]
1212pub enum RubyPosition {
1213    AlternateOver,
1214    AlternateUnder,
1215    Over,
1216    Under,
1217}
1218
1219impl Parse for RubyPosition {
1220    fn parse(_context: &ParserContext, input: &mut Parser) -> Result<RubyPosition, ParseError> {
1221        // Parse alternate before
1222        let alternate = input
1223            .try_parse(|i| i.expect_ident_matching("alternate"))
1224            .is_ok();
1225        if alternate && input.is_exhausted() {
1226            return Ok(RubyPosition::AlternateOver);
1227        }
1228        // Parse over / under
1229        let over = try_match_ident_ignore_ascii_case! { input,
1230            "over" => true,
1231            "under" => false,
1232        };
1233        // Parse alternate after
1234        let alternate = alternate
1235            || input
1236                .try_parse(|i| i.expect_ident_matching("alternate"))
1237                .is_ok();
1238
1239        Ok(match (over, alternate) {
1240            (true, true) => RubyPosition::AlternateOver,
1241            (false, true) => RubyPosition::AlternateUnder,
1242            (true, false) => RubyPosition::Over,
1243            (false, false) => RubyPosition::Under,
1244        })
1245    }
1246}
1247
1248impl ToCss for RubyPosition {
1249    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1250    where
1251        W: Write,
1252    {
1253        dest.write_str(match self {
1254            RubyPosition::AlternateOver => "alternate",
1255            RubyPosition::AlternateUnder => "alternate under",
1256            RubyPosition::Over => "over",
1257            RubyPosition::Under => "under",
1258        })
1259    }
1260}
1261
1262impl SpecifiedValueInfo for RubyPosition {
1263    fn collect_completion_keywords(f: KeywordsCollectFn) {
1264        f(&["alternate", "over", "under"])
1265    }
1266}
1267
1268/// Specified value for the text-autospace property
1269/// which takes the grammar:
1270///     normal | <autospace> | auto
1271/// where:
1272///     <autospace> = no-autospace |
1273///                   [ ideograph-alpha || ideograph-numeric || punctuation ]
1274///                   || [ insert | replace ]
1275///
1276/// https://drafts.csswg.org/css-text-4/#text-autospace-property
1277///
1278/// Bug 1980111: 'replace' value is not supported yet.
1279#[derive(
1280    Clone,
1281    Copy,
1282    Debug,
1283    Eq,
1284    MallocSizeOf,
1285    Parse,
1286    PartialEq,
1287    Serialize,
1288    SpecifiedValueInfo,
1289    ToCss,
1290    ToComputedValue,
1291    ToResolvedValue,
1292    ToShmem,
1293    ToTyped,
1294)]
1295#[css(bitflags(
1296    single = "normal,auto,no-autospace",
1297    // Bug 1980111: add 'replace' to 'mixed' in the future so that it parses correctly.
1298    // Bug 1986500: add 'punctuation' to 'mixed' in the future so that it parses correctly.
1299    mixed = "ideograph-alpha,ideograph-numeric,insert",
1300    // Bug 1980111: Uncomment 'validate_mixed' to support 'replace' value.
1301    // validate_mixed = "Self::validate_mixed_flags",
1302))]
1303#[repr(C)]
1304pub struct TextAutospace(u8);
1305bitflags! {
1306    impl TextAutospace: u8 {
1307        /// No automatic space is inserted.
1308        const NO_AUTOSPACE = 0;
1309
1310        /// The user agent chooses a set of typographically high quality spacing values.
1311        const AUTO = 1 << 0;
1312
1313        /// Same behavior as ideograph-alpha ideograph-numeric.
1314        const NORMAL = 1 << 1;
1315
1316        /// 1/8ic space between ideographic characters and non-ideographic letters.
1317        const IDEOGRAPH_ALPHA = 1 << 2;
1318
1319        /// 1/8ic space between ideographic characters and non-ideographic decimal numerals.
1320        const IDEOGRAPH_NUMERIC = 1 << 3;
1321
1322        /* Bug 1986500: Uncomment the following to support the 'punctuation' value.
1323        /// Apply special spacing between letters and punctuation (French).
1324        const PUNCTUATION = 1 << 4;
1325        */
1326
1327        /// Auto-spacing is only inserted if no space character is present in the text.
1328        const INSERT = 1 << 5;
1329
1330        /* Bug 1980111: Uncomment the following to support 'replace' value.
1331        /// Auto-spacing may replace an existing U+0020 space with custom space.
1332        const REPLACE = 1 << 6;
1333        */
1334    }
1335}
1336
1337/* Bug 1980111: Uncomment the following to support 'replace' value.
1338impl TextAutospace {
1339    fn validate_mixed_flags(&self) -> bool {
1340        // It's not valid to have both INSERT and REPLACE set.
1341        !self.contains(TextAutospace::INSERT | TextAutospace::REPLACE)
1342    }
1343}
1344*/
1345#[derive(
1346    Clone,
1347    Copy,
1348    Debug,
1349    Eq,
1350    FromPrimitive,
1351    Hash,
1352    MallocSizeOf,
1353    Parse,
1354    PartialEq,
1355    SpecifiedValueInfo,
1356    ToComputedValue,
1357    ToCss,
1358    ToResolvedValue,
1359    ToShmem,
1360    ToTyped,
1361)]
1362#[repr(u8)]
1363/// Identifies specific font metrics for use in the <text-edge> typedef.
1364///
1365/// https://drafts.csswg.org/css-inline-3/#typedef-text-edge
1366pub enum TextEdgeKeyword {
1367    /// Use the text-over baseline/text-under baseline as the over/under edge.
1368    Text,
1369    /// Use the ideographic-over baseline/ideographic-under baseline as the over/under edge.
1370    Ideographic,
1371    /// Use the ideographic-ink-over baseline/ideographic-ink-under baseline as the over/under edge.
1372    IdeographicInk,
1373    /// Use the cap-height baseline as the over edge.
1374    Cap,
1375    /// Use the x-height baseline as the over edge.
1376    Ex,
1377    /// Use the alphabetic baseline as the under edge.
1378    Alphabetic,
1379}
1380
1381impl TextEdgeKeyword {
1382    fn is_valid_for_over(&self) -> bool {
1383        match self {
1384            TextEdgeKeyword::Text
1385            | TextEdgeKeyword::Ideographic
1386            | TextEdgeKeyword::IdeographicInk
1387            | TextEdgeKeyword::Cap
1388            | TextEdgeKeyword::Ex => true,
1389            _ => false,
1390        }
1391    }
1392
1393    fn is_valid_for_under(&self) -> bool {
1394        match self {
1395            TextEdgeKeyword::Text
1396            | TextEdgeKeyword::Ideographic
1397            | TextEdgeKeyword::IdeographicInk
1398            | TextEdgeKeyword::Alphabetic => true,
1399            _ => false,
1400        }
1401    }
1402}
1403
1404#[derive(
1405    Clone,
1406    Copy,
1407    Debug,
1408    Eq,
1409    Hash,
1410    MallocSizeOf,
1411    PartialEq,
1412    SpecifiedValueInfo,
1413    ToComputedValue,
1414    ToResolvedValue,
1415    ToShmem,
1416    ToTyped,
1417)]
1418#[repr(C)]
1419/// The <text-edge> typedef, used by the `line-fit-edge` and
1420/// `text-box-edge` properties.
1421///
1422/// The first value specifies the text over edge; the second value
1423/// specifies the text under edge. If only one value is specified,
1424/// both edges are assigned that same keyword if possible; else
1425/// text is assumed as the missing value.
1426///
1427/// https://drafts.csswg.org/css-inline-3/#typedef-text-edge
1428pub struct TextEdge {
1429    /// Font metric to use for the text over edge.
1430    pub over: TextEdgeKeyword,
1431    /// Font metric to use for the text under edge.
1432    pub under: TextEdgeKeyword,
1433}
1434
1435impl Parse for TextEdge {
1436    fn parse(_context: &ParserContext, input: &mut Parser) -> Result<TextEdge, ParseError> {
1437        let first = TextEdgeKeyword::parse(input)?;
1438
1439        if let Ok(second) = input.try_parse(TextEdgeKeyword::parse) {
1440            if !first.is_valid_for_over() || !second.is_valid_for_under() {
1441                return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1442            }
1443
1444            return Ok(TextEdge {
1445                over: first,
1446                under: second,
1447            });
1448        }
1449
1450        // https://drafts.csswg.org/css-inline-3/#typedef-text-edge
1451        // > If only one value is specified, both edges are assigned that same
1452        // > keyword if possible; else 'text' is assumed as the missing value.
1453        match (first.is_valid_for_over(), first.is_valid_for_under()) {
1454            (true, true) => Ok(TextEdge {
1455                over: first,
1456                under: first,
1457            }),
1458            (true, false) => Ok(TextEdge {
1459                over: first,
1460                under: TextEdgeKeyword::Text,
1461            }),
1462            (false, true) => Ok(TextEdge {
1463                over: TextEdgeKeyword::Text,
1464                under: first,
1465            }),
1466            _ => unreachable!("Parsed keyword will be valid for at least one edge"),
1467        }
1468    }
1469}
1470
1471impl ToCss for TextEdge {
1472    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1473    where
1474        W: Write,
1475    {
1476        match (self.over, self.under) {
1477            (over, TextEdgeKeyword::Text) if !over.is_valid_for_under() => over.to_css(dest),
1478            (TextEdgeKeyword::Text, under) if !under.is_valid_for_over() => under.to_css(dest),
1479            (over, under) => {
1480                over.to_css(dest)?;
1481
1482                if over != under {
1483                    dest.write_char(' ')?;
1484                    self.under.to_css(dest)?;
1485                }
1486
1487                Ok(())
1488            },
1489        }
1490    }
1491}
1492
1493#[derive(
1494    Clone,
1495    Copy,
1496    Debug,
1497    Eq,
1498    Hash,
1499    MallocSizeOf,
1500    Parse,
1501    PartialEq,
1502    SpecifiedValueInfo,
1503    ToComputedValue,
1504    ToCss,
1505    ToResolvedValue,
1506    ToShmem,
1507    ToTyped,
1508)]
1509#[repr(C, u8)]
1510/// Specified value for the `text-box-edge` property.
1511///
1512/// https://drafts.csswg.org/css-inline-3/#text-box-edge
1513pub enum TextBoxEdge {
1514    /// Uses the value of `line-fit-edge`, interpreting `leading` (the initial value) as `text`.
1515    Auto,
1516    /// Uses the specified font metrics.
1517    TextEdge(TextEdge),
1518}
1519
1520#[derive(
1521    Clone,
1522    Copy,
1523    Debug,
1524    Eq,
1525    MallocSizeOf,
1526    PartialEq,
1527    Parse,
1528    Serialize,
1529    SpecifiedValueInfo,
1530    ToCss,
1531    ToComputedValue,
1532    ToResolvedValue,
1533    ToShmem,
1534    ToTyped,
1535)]
1536#[css(bitflags(single = "none,trim-start,trim-end,trim-both"))]
1537#[repr(C)]
1538/// Specified value for the `text-box-trim` property.
1539///
1540/// https://drafts.csswg.org/css-inline-3/#text-box-edge
1541pub struct TextBoxTrim(u8);
1542bitflags! {
1543    impl TextBoxTrim: u8 {
1544        /// NONE
1545        const NONE = 0;
1546        /// TRIM_START
1547        const TRIM_START = 1 << 0;
1548        /// TRIM_END
1549        const TRIM_END = 1 << 1;
1550        /// TRIM_BOTH
1551        const TRIM_BOTH = Self::TRIM_START.0 | Self::TRIM_END.0;
1552    }
1553}
1554
1555impl TextBoxTrim {
1556    /// Returns the initial value of text-box-trim
1557    #[inline]
1558    pub fn none() -> Self {
1559        TextBoxTrim::NONE
1560    }
1561}