1use crate::Zero;
8use crate::derives::*;
9use crate::parser::{Parse, ParserContext};
10use crate::properties::longhands::writing_mode::computed_value::T as SpecifiedWritingMode;
11use crate::values::computed;
12use crate::values::computed::text::TextEmphasisStyle as ComputedTextEmphasisStyle;
13use crate::values::computed::{Context, ToComputedValue};
14use crate::values::generics::NumberOrAuto;
15use crate::values::generics::text::{
16 GenericHyphenateLimitChars, GenericInitialLetter, GenericTextDecorationInset,
17 GenericTextDecorationLength, GenericTextIndent,
18};
19use crate::values::specified::length::{Length, LengthPercentage};
20use crate::values::specified::{AllowQuirks, Integer, Number};
21use cssparser::Parser;
22use icu_segmenter::GraphemeClusterSegmenter;
23use std::fmt::{self, Write};
24use style_traits::values::SequenceWriter;
25use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
26use style_traits::{KeywordsCollectFn, SpecifiedValueInfo};
27
28pub type InitialLetter = GenericInitialLetter<Number, Integer>;
30
31#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
33pub enum Spacing {
34 Normal,
36 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#[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#[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#[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,
121 String(crate::OwnedStr),
123}
124
125pub 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#[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,
181 Ellipsis,
183 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)]
201pub struct TextOverflow {
210 pub first: TextOverflowSide,
212 pub second: TextOverflowSide,
214 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 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)]
295pub struct TextDecorationLine(u8);
297bitflags! {
298 impl TextDecorationLine: u8 {
299 const NONE = 0;
301 const UNDERLINE = 1 << 0;
303 const OVERLINE = 1 << 1;
305 const LINE_THROUGH = 1 << 2;
307 const BLINK = 1 << 3;
309 const SPELLING_ERROR = 1 << 4;
311 const GRAMMAR_ERROR = 1 << 5;
313 #[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 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)]
353pub enum TextTransformCase {
355 None,
357 Uppercase,
359 Lowercase,
361 Capitalize,
363 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)]
389pub struct TextTransform(u8);
394bitflags! {
395 impl TextTransform: u8 {
396 const NONE = 0;
398 const UPPERCASE = 1 << 0;
400 const LOWERCASE = 1 << 1;
402 const CAPITALIZE = 1 << 2;
404 const MATH_AUTO = 1 << 3;
406
407 const CASE_TRANSFORMS = Self::UPPERCASE.0 | Self::LOWERCASE.0 | Self::CAPITALIZE.0 | Self::MATH_AUTO.0;
410
411 const FULL_WIDTH = 1 << 4;
413 const FULL_SIZE_KANA = 1 << 5;
415 }
416}
417
418impl TextTransform {
419 #[inline]
421 pub fn none() -> Self {
422 Self::NONE
423 }
424
425 #[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.is_empty() || case.bits().is_power_of_two()
435 }
436
437 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#[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#[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#[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(TextAlignKeyword),
533 MatchParent,
536 #[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 if _context.builder.is_root_element {
567 return TextAlignKeyword::Start;
568 }
569 let parent = *_context
570 .builder
571 .get_parent_inherited_text()
572 .get_text_align();
573 let ltr = _context.builder.inherited_writing_mode().is_bidi_ltr();
574 match (parent, ltr) {
575 (TextAlignKeyword::Start, true) => TextAlignKeyword::Left,
576 (TextAlignKeyword::Start, false) => TextAlignKeyword::Right,
577 (TextAlignKeyword::End, true) => TextAlignKeyword::Right,
578 (TextAlignKeyword::End, false) => TextAlignKeyword::Left,
579 _ => parent,
580 }
581 },
582 TextAlign::MozCenterOrInherit => {
583 let parent = *_context
584 .builder
585 .get_parent_inherited_text()
586 .get_text_align();
587 if parent == TextAlignKeyword::Start {
588 TextAlignKeyword::Center
589 } else {
590 parent
591 }
592 },
593 }
594 }
595
596 #[inline]
597 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
598 TextAlign::Keyword(*computed)
599 }
600}
601
602fn fill_mode_is_default_and_shape_exists(
603 fill: &TextEmphasisFillMode,
604 shape: &Option<TextEmphasisShapeKeyword>,
605) -> bool {
606 shape.is_some() && fill.is_filled()
607}
608
609#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
613#[allow(missing_docs)]
614#[typed(todo_derive_fields)]
615pub enum TextEmphasisStyle {
616 Keyword {
618 #[css(contextual_skip_if = "fill_mode_is_default_and_shape_exists")]
619 fill: TextEmphasisFillMode,
620 shape: Option<TextEmphasisShapeKeyword>,
621 },
622 None,
624 String(crate::OwnedStr),
626}
627
628#[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,
646 Open,
648}
649
650impl TextEmphasisFillMode {
651 #[inline]
653 pub fn is_filled(&self) -> bool {
654 matches!(*self, TextEmphasisFillMode::Filled)
655 }
656}
657
658#[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,
677 Circle,
679 DoubleCircle,
681 Triangle,
683 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 if *context.style().get_inherited_box().get_writing_mode()
701 == SpecifiedWritingMode::HorizontalTb
702 {
703 TextEmphasisShapeKeyword::Circle
704 } else {
705 TextEmphasisShapeKeyword::Sesame
706 }
707 });
708 ComputedTextEmphasisStyle::Keyword { fill, shape }
709 },
710 TextEmphasisStyle::None => ComputedTextEmphasisStyle::None,
711 TextEmphasisStyle::String(ref s) => {
712 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 return Ok(TextEmphasisStyle::String(s.into()));
753 }
754
755 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 let fill = fill.unwrap_or(TextEmphasisFillMode::Filled);
769
770 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))]
798pub struct TextEmphasisPosition(u8);
801bitflags! {
802 impl TextEmphasisPosition: u8 {
803 const AUTO = 1 << 0;
805 const OVER = 1 << 1;
807 const UNDER = 1 << 2;
809 const LEFT = 1 << 3;
811 const RIGHT = 1 << 4;
813 }
814}
815
816impl TextEmphasisPosition {
817 fn validate_and_simplify(&mut self) -> bool {
818 if self.intersects(Self::OVER) == self.intersects(Self::UNDER) {
820 return false;
821 }
822
823 if self.intersects(Self::LEFT) {
825 return !self.intersects(Self::RIGHT);
826 }
827
828 self.remove(Self::RIGHT); true
830 }
831}
832
833#[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 #[cfg(feature = "gecko")]
860 BreakWord,
861}
862
863#[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 #[parse(aliases = "distribute")]
888 InterCharacter,
889}
890
891#[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#[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#[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
975pub 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 while !input.is_exhausted() {
989 if length.is_none()
991 && let Ok(len) = input
992 .try_parse(|i| LengthPercentage::parse_quirky(context, i, AllowQuirks::Yes))
993 {
994 length = Some(len);
995 continue;
996 }
997
998 if !crate::pref!("layout.css.text-indent-keywords.enabled", gecko = true) {
1000 break;
1001 }
1002
1003 try_match_ident_ignore_ascii_case! { input,
1005 "hanging" if !hanging => hanging = true,
1006 "each-line" if !each_line => each_line = true,
1007 }
1008 }
1009
1010 if let Some(length) = length {
1012 Ok(Self {
1013 length,
1014 hanging,
1015 each_line,
1016 })
1017 } else {
1018 Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError))
1019 }
1020 }
1021}
1022
1023#[repr(u8)]
1027#[derive(
1028 Clone,
1029 Copy,
1030 Debug,
1031 Deserialize,
1032 Eq,
1033 MallocSizeOf,
1034 Parse,
1035 PartialEq,
1036 Serialize,
1037 SpecifiedValueInfo,
1038 ToComputedValue,
1039 ToCss,
1040 ToResolvedValue,
1041 ToShmem,
1042 ToTyped,
1043)]
1044#[allow(missing_docs)]
1045pub enum TextDecorationSkipInk {
1046 Auto,
1047 None,
1048 All,
1049}
1050
1051pub type TextDecorationLength = GenericTextDecorationLength<LengthPercentage>;
1053
1054impl TextDecorationLength {
1055 #[inline]
1057 pub fn auto() -> Self {
1058 GenericTextDecorationLength::Auto
1059 }
1060
1061 #[inline]
1063 pub fn is_auto(&self) -> bool {
1064 matches!(*self, GenericTextDecorationLength::Auto)
1065 }
1066}
1067
1068pub type TextDecorationInset = GenericTextDecorationInset<LengthPercentage>;
1070
1071impl TextDecorationInset {
1072 #[inline]
1074 pub fn auto() -> Self {
1075 GenericTextDecorationInset::Auto
1076 }
1077
1078 #[inline]
1080 pub fn is_auto(&self) -> bool {
1081 matches!(*self, GenericTextDecorationInset::Auto)
1082 }
1083}
1084
1085fn parse_inset_endpoint(
1086 ctx: &ParserContext,
1087 input: &mut Parser,
1088) -> Result<LengthPercentage, ParseError> {
1089 if !crate::pref!("layout.css.text-decoration-inset-percentage.enabled") {
1090 Length::parse(ctx, input).map(|l| l.into())
1091 } else {
1092 LengthPercentage::parse(ctx, input)
1093 }
1094}
1095
1096impl Parse for TextDecorationInset {
1097 fn parse(ctx: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
1098 if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
1099 return Ok(TextDecorationInset::Auto);
1100 }
1101
1102 let start = parse_inset_endpoint(ctx, input)?;
1103 let end = input
1104 .try_parse(|i| parse_inset_endpoint(ctx, i))
1105 .unwrap_or_else(|_| start.clone());
1106 Ok(TextDecorationInset::LengthPercentage { start, end })
1107 }
1108}
1109
1110#[derive(
1111 Clone,
1112 Copy,
1113 Debug,
1114 Eq,
1115 MallocSizeOf,
1116 Parse,
1117 PartialEq,
1118 SpecifiedValueInfo,
1119 ToComputedValue,
1120 ToResolvedValue,
1121 ToShmem,
1122 ToTyped,
1123)]
1124#[css(bitflags(
1125 single = "auto",
1126 mixed = "from-font,under,left,right",
1127 validate_mixed = "Self::validate_mixed_flags",
1128))]
1129#[repr(C)]
1130pub struct TextUnderlinePosition(u8);
1135bitflags! {
1136 impl TextUnderlinePosition: u8 {
1137 const AUTO = 0;
1139 const FROM_FONT = 1 << 0;
1141 const UNDER = 1 << 1;
1143 const LEFT = 1 << 2;
1145 const RIGHT = 1 << 3;
1147 }
1148}
1149
1150impl TextUnderlinePosition {
1151 fn validate_mixed_flags(&self) -> bool {
1152 if self.contains(Self::LEFT | Self::RIGHT) {
1153 return false;
1155 }
1156 if self.contains(Self::FROM_FONT | Self::UNDER) {
1157 return false;
1159 }
1160 true
1161 }
1162}
1163
1164impl ToCss for TextUnderlinePosition {
1165 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1166 where
1167 W: Write,
1168 {
1169 if self.is_empty() {
1170 return dest.write_str("auto");
1171 }
1172
1173 let mut writer = SequenceWriter::new(dest, " ");
1174 let mut any = false;
1175
1176 macro_rules! maybe_write {
1177 ($ident:ident => $str:expr) => {
1178 if self.contains(TextUnderlinePosition::$ident) {
1179 any = true;
1180 writer.raw_item($str)?;
1181 }
1182 };
1183 }
1184
1185 maybe_write!(FROM_FONT => "from-font");
1186 maybe_write!(UNDER => "under");
1187 maybe_write!(LEFT => "left");
1188 maybe_write!(RIGHT => "right");
1189
1190 debug_assert!(any);
1191
1192 Ok(())
1193 }
1194}
1195
1196#[repr(u8)]
1198#[derive(
1199 Clone,
1200 Copy,
1201 Debug,
1202 Eq,
1203 MallocSizeOf,
1204 PartialEq,
1205 ToComputedValue,
1206 ToResolvedValue,
1207 ToShmem,
1208 ToTyped,
1209)]
1210#[allow(missing_docs)]
1211pub enum RubyPosition {
1212 AlternateOver,
1213 AlternateUnder,
1214 Over,
1215 Under,
1216}
1217
1218impl Parse for RubyPosition {
1219 fn parse(_context: &ParserContext, input: &mut Parser) -> Result<RubyPosition, ParseError> {
1220 let alternate = input
1222 .try_parse(|i| i.expect_ident_matching("alternate"))
1223 .is_ok();
1224 if alternate && input.is_exhausted() {
1225 return Ok(RubyPosition::AlternateOver);
1226 }
1227 let over = try_match_ident_ignore_ascii_case! { input,
1229 "over" => true,
1230 "under" => false,
1231 };
1232 let alternate = alternate
1234 || input
1235 .try_parse(|i| i.expect_ident_matching("alternate"))
1236 .is_ok();
1237
1238 Ok(match (over, alternate) {
1239 (true, true) => RubyPosition::AlternateOver,
1240 (false, true) => RubyPosition::AlternateUnder,
1241 (true, false) => RubyPosition::Over,
1242 (false, false) => RubyPosition::Under,
1243 })
1244 }
1245}
1246
1247impl ToCss for RubyPosition {
1248 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1249 where
1250 W: Write,
1251 {
1252 dest.write_str(match self {
1253 RubyPosition::AlternateOver => "alternate",
1254 RubyPosition::AlternateUnder => "alternate under",
1255 RubyPosition::Over => "over",
1256 RubyPosition::Under => "under",
1257 })
1258 }
1259}
1260
1261impl SpecifiedValueInfo for RubyPosition {
1262 fn collect_completion_keywords(f: KeywordsCollectFn) {
1263 f(&["alternate", "over", "under"])
1264 }
1265}
1266
1267#[derive(
1279 Clone,
1280 Copy,
1281 Debug,
1282 Eq,
1283 MallocSizeOf,
1284 Parse,
1285 PartialEq,
1286 Serialize,
1287 SpecifiedValueInfo,
1288 ToCss,
1289 ToComputedValue,
1290 ToResolvedValue,
1291 ToShmem,
1292 ToTyped,
1293)]
1294#[css(bitflags(
1295 single = "normal,auto,no-autospace",
1296 mixed = "ideograph-alpha,ideograph-numeric,insert",
1299 ))]
1302#[repr(C)]
1303pub struct TextAutospace(u8);
1304bitflags! {
1305 impl TextAutospace: u8 {
1306 const NO_AUTOSPACE = 0;
1308
1309 const AUTO = 1 << 0;
1311
1312 const NORMAL = 1 << 1;
1314
1315 const IDEOGRAPH_ALPHA = 1 << 2;
1317
1318 const IDEOGRAPH_NUMERIC = 1 << 3;
1320
1321 const INSERT = 1 << 5;
1328
1329 }
1334}
1335
1336#[derive(
1345 Clone,
1346 Copy,
1347 Debug,
1348 Eq,
1349 FromPrimitive,
1350 Hash,
1351 MallocSizeOf,
1352 Parse,
1353 PartialEq,
1354 SpecifiedValueInfo,
1355 ToComputedValue,
1356 ToCss,
1357 ToResolvedValue,
1358 ToShmem,
1359 ToTyped,
1360)]
1361#[repr(u8)]
1362pub enum TextEdgeKeyword {
1366 Text,
1368 Ideographic,
1370 IdeographicInk,
1372 Cap,
1374 Ex,
1376 Alphabetic,
1378}
1379
1380impl TextEdgeKeyword {
1381 fn is_valid_for_over(&self) -> bool {
1382 match self {
1383 TextEdgeKeyword::Text
1384 | TextEdgeKeyword::Ideographic
1385 | TextEdgeKeyword::IdeographicInk
1386 | TextEdgeKeyword::Cap
1387 | TextEdgeKeyword::Ex => true,
1388 _ => false,
1389 }
1390 }
1391
1392 fn is_valid_for_under(&self) -> bool {
1393 match self {
1394 TextEdgeKeyword::Text
1395 | TextEdgeKeyword::Ideographic
1396 | TextEdgeKeyword::IdeographicInk
1397 | TextEdgeKeyword::Alphabetic => true,
1398 _ => false,
1399 }
1400 }
1401}
1402
1403#[derive(
1404 Clone,
1405 Copy,
1406 Debug,
1407 Eq,
1408 Hash,
1409 MallocSizeOf,
1410 PartialEq,
1411 SpecifiedValueInfo,
1412 ToComputedValue,
1413 ToResolvedValue,
1414 ToShmem,
1415 ToTyped,
1416)]
1417#[repr(C)]
1418pub struct TextEdge {
1428 pub over: TextEdgeKeyword,
1430 pub under: TextEdgeKeyword,
1432}
1433
1434impl Parse for TextEdge {
1435 fn parse(_context: &ParserContext, input: &mut Parser) -> Result<TextEdge, ParseError> {
1436 let first = TextEdgeKeyword::parse(input)?;
1437
1438 if let Ok(second) = input.try_parse(TextEdgeKeyword::parse) {
1439 if !first.is_valid_for_over() || !second.is_valid_for_under() {
1440 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1441 }
1442
1443 return Ok(TextEdge {
1444 over: first,
1445 under: second,
1446 });
1447 }
1448
1449 match (first.is_valid_for_over(), first.is_valid_for_under()) {
1453 (true, true) => Ok(TextEdge {
1454 over: first,
1455 under: first,
1456 }),
1457 (true, false) => Ok(TextEdge {
1458 over: first,
1459 under: TextEdgeKeyword::Text,
1460 }),
1461 (false, true) => Ok(TextEdge {
1462 over: TextEdgeKeyword::Text,
1463 under: first,
1464 }),
1465 _ => unreachable!("Parsed keyword will be valid for at least one edge"),
1466 }
1467 }
1468}
1469
1470impl ToCss for TextEdge {
1471 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1472 where
1473 W: Write,
1474 {
1475 match (self.over, self.under) {
1476 (over, TextEdgeKeyword::Text) if !over.is_valid_for_under() => over.to_css(dest),
1477 (TextEdgeKeyword::Text, under) if !under.is_valid_for_over() => under.to_css(dest),
1478 (over, under) => {
1479 over.to_css(dest)?;
1480
1481 if over != under {
1482 dest.write_char(' ')?;
1483 self.under.to_css(dest)?;
1484 }
1485
1486 Ok(())
1487 },
1488 }
1489 }
1490}
1491
1492#[derive(
1493 Clone,
1494 Copy,
1495 Debug,
1496 Eq,
1497 Hash,
1498 MallocSizeOf,
1499 Parse,
1500 PartialEq,
1501 SpecifiedValueInfo,
1502 ToComputedValue,
1503 ToCss,
1504 ToResolvedValue,
1505 ToShmem,
1506 ToTyped,
1507)]
1508#[repr(C, u8)]
1509pub enum TextBoxEdge {
1513 Auto,
1515 TextEdge(TextEdge),
1517}
1518
1519#[derive(
1520 Clone,
1521 Copy,
1522 Debug,
1523 Eq,
1524 MallocSizeOf,
1525 PartialEq,
1526 Parse,
1527 Serialize,
1528 SpecifiedValueInfo,
1529 ToCss,
1530 ToComputedValue,
1531 ToResolvedValue,
1532 ToShmem,
1533 ToTyped,
1534)]
1535#[css(bitflags(single = "none,trim-start,trim-end,trim-both"))]
1536#[repr(C)]
1537pub struct TextBoxTrim(u8);
1541bitflags! {
1542 impl TextBoxTrim: u8 {
1543 const NONE = 0;
1545 const TRIM_START = 1 << 0;
1547 const TRIM_END = 1 << 1;
1549 const TRIM_BOTH = Self::TRIM_START.0 | Self::TRIM_END.0;
1551 }
1552}
1553
1554impl TextBoxTrim {
1555 #[inline]
1557 pub fn none() -> Self {
1558 TextBoxTrim::NONE
1559 }
1560}
1561
1562#[allow(missing_docs)]
1564#[derive(
1565 Clone,
1566 Copy,
1567 Debug,
1568 Deserialize,
1569 Eq,
1570 FromPrimitive,
1571 Hash,
1572 MallocSizeOf,
1573 Parse,
1574 PartialEq,
1575 Serialize,
1576 SpecifiedValueInfo,
1577 ToComputedValue,
1578 ToCss,
1579 ToResolvedValue,
1580 ToShmem,
1581 ToTyped,
1582)]
1583#[repr(u8)]
1584pub enum Hyphens {
1585 Manual,
1586 None,
1587 Auto,
1588}
1589
1590#[allow(missing_docs)]
1592#[derive(
1593 Clone,
1594 Copy,
1595 Debug,
1596 Deserialize,
1597 Eq,
1598 FromPrimitive,
1599 Hash,
1600 MallocSizeOf,
1601 Parse,
1602 PartialEq,
1603 Serialize,
1604 SpecifiedValueInfo,
1605 ToComputedValue,
1606 ToCss,
1607 ToResolvedValue,
1608 ToShmem,
1609 ToTyped,
1610)]
1611#[repr(u8)]
1612pub enum TextSizeAdjust {
1613 Auto,
1614 None,
1615}
1616
1617#[allow(missing_docs)]
1619#[derive(
1620 Clone,
1621 Copy,
1622 Debug,
1623 Deserialize,
1624 Eq,
1625 FromPrimitive,
1626 Hash,
1627 MallocSizeOf,
1628 Parse,
1629 PartialEq,
1630 Serialize,
1631 SpecifiedValueInfo,
1632 ToComputedValue,
1633 ToCss,
1634 ToResolvedValue,
1635 ToShmem,
1636 ToTyped,
1637)]
1638#[repr(u8)]
1639pub enum RubyAlign {
1640 SpaceAround,
1641 Start,
1642 Center,
1643 SpaceBetween,
1644}
1645
1646#[allow(missing_docs)]
1648#[derive(
1649 Clone,
1650 Copy,
1651 Debug,
1652 Deserialize,
1653 Eq,
1654 FromPrimitive,
1655 Hash,
1656 MallocSizeOf,
1657 Parse,
1658 PartialEq,
1659 Serialize,
1660 SpecifiedValueInfo,
1661 ToComputedValue,
1662 ToCss,
1663 ToResolvedValue,
1664 ToShmem,
1665 ToTyped,
1666)]
1667#[repr(u8)]
1668pub enum TextCombineUpright {
1669 None,
1670 All,
1671}
1672
1673#[allow(missing_docs)]
1675#[derive(
1676 Clone,
1677 Copy,
1678 Debug,
1679 Deserialize,
1680 Eq,
1681 FromPrimitive,
1682 Hash,
1683 MallocSizeOf,
1684 Parse,
1685 PartialEq,
1686 Serialize,
1687 SpecifiedValueInfo,
1688 ToComputedValue,
1689 ToCss,
1690 ToResolvedValue,
1691 ToShmem,
1692 ToTyped,
1693)]
1694#[repr(u8)]
1695pub enum TextRendering {
1696 Auto,
1697 Optimizespeed,
1698 Optimizelegibility,
1699 Geometricprecision,
1700}
1701
1702#[allow(missing_docs)]
1704#[derive(
1705 Clone,
1706 Copy,
1707 Debug,
1708 Deserialize,
1709 Eq,
1710 FromPrimitive,
1711 Hash,
1712 MallocSizeOf,
1713 Parse,
1714 PartialEq,
1715 Serialize,
1716 SpecifiedValueInfo,
1717 ToComputedValue,
1718 ToCss,
1719 ToResolvedValue,
1720 ToShmem,
1721 ToTyped,
1722)]
1723#[repr(u8)]
1724pub enum TextSecurity {
1725 None,
1726 Circle,
1727 Disc,
1728 Square,
1729}
1730
1731#[allow(missing_docs)]
1733#[derive(
1734 Clone,
1735 Copy,
1736 Debug,
1737 Deserialize,
1738 Eq,
1739 FromPrimitive,
1740 Hash,
1741 MallocSizeOf,
1742 Parse,
1743 PartialEq,
1744 Serialize,
1745 SpecifiedValueInfo,
1746 ToComputedValue,
1747 ToCss,
1748 ToResolvedValue,
1749 ToShmem,
1750 ToTyped,
1751)]
1752#[repr(u8)]
1753pub enum TextWrapMode {
1754 Wrap,
1755 Nowrap,
1756}
1757
1758#[allow(missing_docs)]
1760#[derive(
1761 Clone,
1762 Copy,
1763 Debug,
1764 Deserialize,
1765 Eq,
1766 FromPrimitive,
1767 Hash,
1768 MallocSizeOf,
1769 Parse,
1770 PartialEq,
1771 Serialize,
1772 SpecifiedValueInfo,
1773 ToComputedValue,
1774 ToCss,
1775 ToResolvedValue,
1776 ToShmem,
1777 ToTyped,
1778)]
1779#[repr(u8)]
1780pub enum TextWrapStyle {
1781 Auto,
1782 Stable,
1783 Balance,
1784}
1785
1786#[allow(missing_docs)]
1788#[derive(
1789 Clone,
1790 Copy,
1791 Debug,
1792 Deserialize,
1793 Eq,
1794 FromPrimitive,
1795 Hash,
1796 MallocSizeOf,
1797 Parse,
1798 PartialEq,
1799 Serialize,
1800 SpecifiedValueInfo,
1801 ToComputedValue,
1802 ToCss,
1803 ToResolvedValue,
1804 ToShmem,
1805 ToTyped,
1806)]
1807#[repr(u8)]
1808pub enum UnicodeBidi {
1809 Normal,
1810 Embed,
1811 Isolate,
1812 BidiOverride,
1813 IsolateOverride,
1814 Plaintext,
1815}
1816
1817#[allow(missing_docs)]
1819#[derive(
1820 Clone,
1821 Copy,
1822 Debug,
1823 Deserialize,
1824 Eq,
1825 FromPrimitive,
1826 Hash,
1827 MallocSizeOf,
1828 Parse,
1829 PartialEq,
1830 Serialize,
1831 SpecifiedValueInfo,
1832 ToComputedValue,
1833 ToCss,
1834 ToResolvedValue,
1835 ToShmem,
1836 ToTyped,
1837)]
1838#[repr(u8)]
1839pub enum TextDecorationStyle {
1840 #[css(keyword = "-moz-none")]
1843 None = 0,
1844 Dotted = 1,
1845 Dashed = 2,
1846 Solid = 3,
1847 Double = 4,
1848 Wavy = 5,
1849}
1850
1851pub const MAX_LINE_STYLE: i32 = 5;
1853
1854#[allow(missing_docs)]
1856#[derive(
1857 Clone,
1858 Copy,
1859 Debug,
1860 Deserialize,
1861 Eq,
1862 FromPrimitive,
1863 Hash,
1864 MallocSizeOf,
1865 Parse,
1866 PartialEq,
1867 Serialize,
1868 SpecifiedValueInfo,
1869 ToComputedValue,
1870 ToCss,
1871 ToResolvedValue,
1872 ToShmem,
1873 ToTyped,
1874)]
1875#[repr(u8)]
1876pub enum WhiteSpaceCollapse {
1877 Collapse,
1878 Preserve,
1880 PreserveBreaks,
1881 BreakSpaces,
1882 #[cfg(feature = "gecko")]
1883 #[cfg_attr(feature = "gecko", parse(aliases = "-moz-pre-space"))]
1884 PreserveSpaces,
1885}