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