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#[css(bitflags(
284 single = "none,spelling-error,grammar-error",
285 mixed = "underline,overline,line-through,blink",
286))]
287#[repr(C)]
288pub struct TextDecorationLine(u8);
290bitflags! {
291 impl TextDecorationLine: u8 {
292 const NONE = 0;
294 const UNDERLINE = 1 << 0;
296 const OVERLINE = 1 << 1;
298 const LINE_THROUGH = 1 << 2;
300 const BLINK = 1 << 3;
302 const SPELLING_ERROR = 1 << 4;
304 const GRAMMAR_ERROR = 1 << 5;
306 #[cfg(feature = "gecko")]
314 const COLOR_OVERRIDE = 1 << 7;
315 }
316}
317
318impl Default for TextDecorationLine {
319 fn default() -> Self {
320 TextDecorationLine::NONE
321 }
322}
323
324impl TextDecorationLine {
325 #[inline]
326 pub fn none() -> Self {
328 TextDecorationLine::NONE
329 }
330}
331
332#[derive(
333 Clone,
334 Copy,
335 Debug,
336 Eq,
337 MallocSizeOf,
338 PartialEq,
339 SpecifiedValueInfo,
340 ToComputedValue,
341 ToCss,
342 ToResolvedValue,
343 ToShmem,
344)]
345#[repr(C)]
346pub enum TextTransformCase {
348 None,
350 Uppercase,
352 Lowercase,
354 Capitalize,
356 MathAuto,
358}
359
360#[derive(
361 Clone,
362 Copy,
363 Debug,
364 Eq,
365 MallocSizeOf,
366 PartialEq,
367 Parse,
368 Serialize,
369 SpecifiedValueInfo,
370 ToCss,
371 ToComputedValue,
372 ToResolvedValue,
373 ToShmem,
374 ToTyped,
375)]
376#[css(bitflags(
377 single = "none,math-auto",
378 mixed = "uppercase,lowercase,capitalize,full-width,full-size-kana",
379 validate_mixed = "Self::validate_mixed_flags",
380))]
381#[repr(C)]
382pub struct TextTransform(u8);
387bitflags! {
388 impl TextTransform: u8 {
389 const NONE = 0;
391 const UPPERCASE = 1 << 0;
393 const LOWERCASE = 1 << 1;
395 const CAPITALIZE = 1 << 2;
397 const MATH_AUTO = 1 << 3;
399
400 const CASE_TRANSFORMS = Self::UPPERCASE.0 | Self::LOWERCASE.0 | Self::CAPITALIZE.0 | Self::MATH_AUTO.0;
403
404 const FULL_WIDTH = 1 << 4;
406 const FULL_SIZE_KANA = 1 << 5;
408 }
409}
410
411impl TextTransform {
412 #[inline]
414 pub fn none() -> Self {
415 Self::NONE
416 }
417
418 #[inline]
420 pub fn is_none(self) -> bool {
421 self == Self::NONE
422 }
423
424 fn validate_mixed_flags(&self) -> bool {
425 let case = self.intersection(Self::CASE_TRANSFORMS);
426 case.is_empty() || case.bits().is_power_of_two()
428 }
429
430 pub fn case(&self) -> TextTransformCase {
432 match *self & Self::CASE_TRANSFORMS {
433 Self::NONE => TextTransformCase::None,
434 Self::UPPERCASE => TextTransformCase::Uppercase,
435 Self::LOWERCASE => TextTransformCase::Lowercase,
436 Self::CAPITALIZE => TextTransformCase::Capitalize,
437 Self::MATH_AUTO => TextTransformCase::MathAuto,
438 _ => unreachable!("Case bits are exclusive with each other"),
439 }
440 }
441}
442
443#[derive(
445 Clone,
446 Copy,
447 Debug,
448 Eq,
449 FromPrimitive,
450 Hash,
451 MallocSizeOf,
452 Parse,
453 PartialEq,
454 SpecifiedValueInfo,
455 ToComputedValue,
456 ToCss,
457 ToResolvedValue,
458 ToShmem,
459 ToTyped,
460)]
461#[allow(missing_docs)]
462#[repr(u8)]
463pub enum TextAlignLast {
464 Auto,
465 Start,
466 End,
467 Left,
468 Right,
469 Center,
470 Justify,
471}
472
473#[derive(
475 Clone,
476 Copy,
477 Debug,
478 Eq,
479 FromPrimitive,
480 Hash,
481 MallocSizeOf,
482 Parse,
483 PartialEq,
484 SpecifiedValueInfo,
485 ToComputedValue,
486 ToCss,
487 ToResolvedValue,
488 ToShmem,
489 ToTyped,
490)]
491#[allow(missing_docs)]
492#[repr(u8)]
493pub enum TextAlignKeyword {
494 Start,
495 Left,
496 Right,
497 Center,
498 Justify,
499 End,
500 #[parse(aliases = "-webkit-center")]
501 MozCenter,
502 #[parse(aliases = "-webkit-left")]
503 MozLeft,
504 #[parse(aliases = "-webkit-right")]
505 MozRight,
506}
507
508#[derive(
510 Clone,
511 Copy,
512 Debug,
513 Eq,
514 Hash,
515 MallocSizeOf,
516 Parse,
517 PartialEq,
518 SpecifiedValueInfo,
519 ToCss,
520 ToShmem,
521 ToTyped,
522)]
523pub enum TextAlign {
524 Keyword(TextAlignKeyword),
526 MatchParent,
529 #[parse(condition = "ParserContext::chrome_rules_enabled")]
542 MozCenterOrInherit,
543}
544
545impl ToComputedValue for TextAlign {
546 type ComputedValue = TextAlignKeyword;
547
548 #[inline]
549 fn to_computed_value(&self, _context: &Context) -> Self::ComputedValue {
550 match *self {
551 TextAlign::Keyword(key) => key,
552 TextAlign::MatchParent => {
553 if _context.builder.is_root_element {
560 return TextAlignKeyword::Start;
561 }
562 let parent = *_context
563 .builder
564 .get_parent_inherited_text()
565 .get_text_align();
566 let ltr = _context.builder.inherited_writing_mode().is_bidi_ltr();
567 match (parent, ltr) {
568 (TextAlignKeyword::Start, true) => TextAlignKeyword::Left,
569 (TextAlignKeyword::Start, false) => TextAlignKeyword::Right,
570 (TextAlignKeyword::End, true) => TextAlignKeyword::Right,
571 (TextAlignKeyword::End, false) => TextAlignKeyword::Left,
572 _ => parent,
573 }
574 },
575 TextAlign::MozCenterOrInherit => {
576 let parent = *_context
577 .builder
578 .get_parent_inherited_text()
579 .get_text_align();
580 if parent == TextAlignKeyword::Start {
581 TextAlignKeyword::Center
582 } else {
583 parent
584 }
585 },
586 }
587 }
588
589 #[inline]
590 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
591 TextAlign::Keyword(*computed)
592 }
593}
594
595fn fill_mode_is_default_and_shape_exists(
596 fill: &TextEmphasisFillMode,
597 shape: &Option<TextEmphasisShapeKeyword>,
598) -> bool {
599 shape.is_some() && fill.is_filled()
600}
601
602#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
606#[allow(missing_docs)]
607#[typed(todo_derive_fields)]
608pub enum TextEmphasisStyle {
609 Keyword {
611 #[css(contextual_skip_if = "fill_mode_is_default_and_shape_exists")]
612 fill: TextEmphasisFillMode,
613 shape: Option<TextEmphasisShapeKeyword>,
614 },
615 None,
617 String(crate::OwnedStr),
619}
620
621#[derive(
623 Clone,
624 Copy,
625 Debug,
626 MallocSizeOf,
627 Parse,
628 PartialEq,
629 SpecifiedValueInfo,
630 ToCss,
631 ToComputedValue,
632 ToResolvedValue,
633 ToShmem,
634)]
635#[repr(u8)]
636pub enum TextEmphasisFillMode {
637 Filled,
639 Open,
641}
642
643impl TextEmphasisFillMode {
644 #[inline]
646 pub fn is_filled(&self) -> bool {
647 matches!(*self, TextEmphasisFillMode::Filled)
648 }
649}
650
651#[derive(
653 Clone,
654 Copy,
655 Debug,
656 Eq,
657 MallocSizeOf,
658 Parse,
659 PartialEq,
660 SpecifiedValueInfo,
661 ToCss,
662 ToComputedValue,
663 ToResolvedValue,
664 ToShmem,
665)]
666#[repr(u8)]
667pub enum TextEmphasisShapeKeyword {
668 Dot,
670 Circle,
672 DoubleCircle,
674 Triangle,
676 Sesame,
678}
679
680impl ToComputedValue for TextEmphasisStyle {
681 type ComputedValue = ComputedTextEmphasisStyle;
682
683 #[inline]
684 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
685 match *self {
686 TextEmphasisStyle::Keyword { fill, shape } => {
687 let shape = shape.unwrap_or_else(|| {
688 if *context.style().get_inherited_box().get_writing_mode()
694 == SpecifiedWritingMode::HorizontalTb
695 {
696 TextEmphasisShapeKeyword::Circle
697 } else {
698 TextEmphasisShapeKeyword::Sesame
699 }
700 });
701 ComputedTextEmphasisStyle::Keyword { fill, shape }
702 },
703 TextEmphasisStyle::None => ComputedTextEmphasisStyle::None,
704 TextEmphasisStyle::String(ref s) => {
705 let first_grapheme_end = GraphemeClusterSegmenter::new()
711 .segment_str(s)
712 .nth(1)
713 .unwrap_or(0);
714 ComputedTextEmphasisStyle::String(s[0..first_grapheme_end].to_string().into())
715 },
716 }
717 }
718
719 #[inline]
720 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
721 match *computed {
722 ComputedTextEmphasisStyle::Keyword { fill, shape } => TextEmphasisStyle::Keyword {
723 fill,
724 shape: Some(shape),
725 },
726 ComputedTextEmphasisStyle::None => TextEmphasisStyle::None,
727 ComputedTextEmphasisStyle::String(ref string) => {
728 TextEmphasisStyle::String(string.clone())
729 },
730 }
731 }
732}
733
734impl Parse for TextEmphasisStyle {
735 fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
736 if input
737 .try_parse(|input| input.expect_ident_matching("none"))
738 .is_ok()
739 {
740 return Ok(TextEmphasisStyle::None);
741 }
742
743 if let Ok(s) = input.try_parse(|i| i.expect_string().map(|s| s.as_ref().to_owned())) {
744 return Ok(TextEmphasisStyle::String(s.into()));
746 }
747
748 let mut shape = input.try_parse(TextEmphasisShapeKeyword::parse).ok();
750 let fill = input.try_parse(TextEmphasisFillMode::parse).ok();
751 if shape.is_none() {
752 shape = input.try_parse(TextEmphasisShapeKeyword::parse).ok();
753 }
754
755 if shape.is_none() && fill.is_none() {
756 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
757 }
758
759 let fill = fill.unwrap_or(TextEmphasisFillMode::Filled);
762
763 Ok(TextEmphasisStyle::Keyword { fill, shape })
766 }
767}
768
769#[derive(
770 Clone,
771 Copy,
772 Debug,
773 Eq,
774 MallocSizeOf,
775 PartialEq,
776 Parse,
777 Serialize,
778 SpecifiedValueInfo,
779 ToCss,
780 ToComputedValue,
781 ToResolvedValue,
782 ToShmem,
783 ToTyped,
784)]
785#[repr(C)]
786#[css(bitflags(
787 single = "auto",
788 mixed = "over,under,left,right",
789 validate_mixed = "Self::validate_and_simplify"
790))]
791pub struct TextEmphasisPosition(u8);
794bitflags! {
795 impl TextEmphasisPosition: u8 {
796 const AUTO = 1 << 0;
798 const OVER = 1 << 1;
800 const UNDER = 1 << 2;
802 const LEFT = 1 << 3;
804 const RIGHT = 1 << 4;
806 }
807}
808
809impl TextEmphasisPosition {
810 fn validate_and_simplify(&mut self) -> bool {
811 if self.intersects(Self::OVER) == self.intersects(Self::UNDER) {
813 return false;
814 }
815
816 if self.intersects(Self::LEFT) {
818 return !self.intersects(Self::RIGHT);
819 }
820
821 self.remove(Self::RIGHT); true
823 }
824}
825
826fn word_break_break_word_enabled(_: &ParserContext) -> bool {
827 crate::pref!("layout.css.word-break.break-word.enabled", gecko = true)
828}
829
830#[repr(u8)]
832#[derive(
833 Clone,
834 Copy,
835 Debug,
836 Eq,
837 MallocSizeOf,
838 Parse,
839 PartialEq,
840 SpecifiedValueInfo,
841 ToComputedValue,
842 ToCss,
843 ToResolvedValue,
844 ToShmem,
845 ToTyped,
846)]
847#[allow(missing_docs)]
848pub enum WordBreak {
849 Normal,
850 BreakAll,
851 KeepAll,
852 #[parse(condition = "word_break_break_word_enabled")]
857 BreakWord,
858}
859
860#[repr(u8)]
862#[derive(
863 Clone,
864 Copy,
865 Debug,
866 Eq,
867 MallocSizeOf,
868 Parse,
869 PartialEq,
870 SpecifiedValueInfo,
871 ToComputedValue,
872 ToCss,
873 ToResolvedValue,
874 ToShmem,
875 ToTyped,
876)]
877#[allow(missing_docs)]
878pub enum TextJustify {
879 Auto,
880 None,
881 InterWord,
882 #[parse(aliases = "distribute")]
885 InterCharacter,
886}
887
888#[repr(u8)]
890#[derive(
891 Clone,
892 Copy,
893 Debug,
894 Eq,
895 MallocSizeOf,
896 Parse,
897 PartialEq,
898 SpecifiedValueInfo,
899 ToComputedValue,
900 ToCss,
901 ToResolvedValue,
902 ToShmem,
903 ToTyped,
904)]
905#[allow(missing_docs)]
906pub enum MozControlCharacterVisibility {
907 Hidden,
908 Visible,
909}
910
911#[cfg(feature = "gecko")]
912impl Default for MozControlCharacterVisibility {
913 fn default() -> Self {
914 if crate::pref!("layout.css.control-characters.visible") {
915 Self::Visible
916 } else {
917 Self::Hidden
918 }
919 }
920}
921
922#[repr(u8)]
924#[derive(
925 Clone,
926 Copy,
927 Debug,
928 Eq,
929 MallocSizeOf,
930 Parse,
931 PartialEq,
932 SpecifiedValueInfo,
933 ToComputedValue,
934 ToCss,
935 ToResolvedValue,
936 ToShmem,
937 ToTyped,
938)]
939#[allow(missing_docs)]
940pub enum LineBreak {
941 Auto,
942 Loose,
943 Normal,
944 Strict,
945 Anywhere,
946}
947
948#[repr(u8)]
950#[derive(
951 Clone,
952 Copy,
953 Debug,
954 Eq,
955 MallocSizeOf,
956 Parse,
957 PartialEq,
958 SpecifiedValueInfo,
959 ToComputedValue,
960 ToCss,
961 ToResolvedValue,
962 ToShmem,
963 ToTyped,
964)]
965#[allow(missing_docs)]
966pub enum OverflowWrap {
967 Normal,
968 BreakWord,
969 Anywhere,
970}
971
972pub type TextIndent = GenericTextIndent<LengthPercentage>;
977
978impl Parse for TextIndent {
979 fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
980 let mut length = None;
981 let mut hanging = false;
982 let mut each_line = false;
983
984 while !input.is_exhausted() {
986 if length.is_none()
988 && let Ok(len) = input
989 .try_parse(|i| LengthPercentage::parse_quirky(context, i, AllowQuirks::Yes))
990 {
991 length = Some(len);
992 continue;
993 }
994
995 if !crate::pref!("layout.css.text-indent-keywords.enabled", gecko = true) {
997 break;
998 }
999
1000 try_match_ident_ignore_ascii_case! { input,
1002 "hanging" if !hanging => hanging = true,
1003 "each-line" if !each_line => each_line = true,
1004 }
1005 }
1006
1007 if let Some(length) = length {
1009 Ok(Self {
1010 length,
1011 hanging,
1012 each_line,
1013 })
1014 } else {
1015 Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError))
1016 }
1017 }
1018}
1019
1020#[repr(u8)]
1024#[derive(
1025 Clone,
1026 Copy,
1027 Debug,
1028 Deserialize,
1029 Eq,
1030 MallocSizeOf,
1031 Parse,
1032 PartialEq,
1033 Serialize,
1034 SpecifiedValueInfo,
1035 ToComputedValue,
1036 ToCss,
1037 ToResolvedValue,
1038 ToShmem,
1039 ToTyped,
1040)]
1041#[allow(missing_docs)]
1042pub enum TextDecorationSkipInk {
1043 Auto,
1044 None,
1045 All,
1046}
1047
1048pub type TextDecorationLength = GenericTextDecorationLength<LengthPercentage>;
1050
1051impl TextDecorationLength {
1052 #[inline]
1054 pub fn auto() -> Self {
1055 GenericTextDecorationLength::Auto
1056 }
1057
1058 #[inline]
1060 pub fn is_auto(&self) -> bool {
1061 matches!(*self, GenericTextDecorationLength::Auto)
1062 }
1063}
1064
1065pub type TextDecorationInset = GenericTextDecorationInset<LengthPercentage>;
1067
1068impl TextDecorationInset {
1069 #[inline]
1071 pub fn auto() -> Self {
1072 GenericTextDecorationInset::Auto
1073 }
1074
1075 #[inline]
1077 pub fn is_auto(&self) -> bool {
1078 matches!(*self, GenericTextDecorationInset::Auto)
1079 }
1080}
1081
1082fn parse_inset_endpoint(
1083 ctx: &ParserContext,
1084 input: &mut Parser,
1085) -> Result<LengthPercentage, ParseError> {
1086 if !crate::pref!("layout.css.text-decoration-inset-percentage.enabled") {
1087 Length::parse(ctx, input).map(|l| l.into())
1088 } else {
1089 LengthPercentage::parse(ctx, input)
1090 }
1091}
1092
1093impl Parse for TextDecorationInset {
1094 fn parse(ctx: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
1095 if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
1096 return Ok(TextDecorationInset::Auto);
1097 }
1098
1099 let start = parse_inset_endpoint(ctx, input)?;
1100 let end = input
1101 .try_parse(|i| parse_inset_endpoint(ctx, i))
1102 .unwrap_or_else(|_| start.clone());
1103 Ok(TextDecorationInset::LengthPercentage { start, end })
1104 }
1105}
1106
1107#[derive(
1108 Clone,
1109 Copy,
1110 Debug,
1111 Eq,
1112 MallocSizeOf,
1113 Parse,
1114 PartialEq,
1115 SpecifiedValueInfo,
1116 ToComputedValue,
1117 ToResolvedValue,
1118 ToShmem,
1119 ToTyped,
1120)]
1121#[css(bitflags(
1122 single = "auto",
1123 mixed = "from-font,under,left,right",
1124 validate_mixed = "Self::validate_mixed_flags",
1125))]
1126#[repr(C)]
1127pub struct TextUnderlinePosition(u8);
1132bitflags! {
1133 impl TextUnderlinePosition: u8 {
1134 const AUTO = 0;
1136 const FROM_FONT = 1 << 0;
1138 const UNDER = 1 << 1;
1140 const LEFT = 1 << 2;
1142 const RIGHT = 1 << 3;
1144 }
1145}
1146
1147impl TextUnderlinePosition {
1148 fn validate_mixed_flags(&self) -> bool {
1149 if self.contains(Self::LEFT | Self::RIGHT) {
1150 return false;
1152 }
1153 if self.contains(Self::FROM_FONT | Self::UNDER) {
1154 return false;
1156 }
1157 true
1158 }
1159}
1160
1161impl ToCss for TextUnderlinePosition {
1162 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1163 where
1164 W: Write,
1165 {
1166 if self.is_empty() {
1167 return dest.write_str("auto");
1168 }
1169
1170 let mut writer = SequenceWriter::new(dest, " ");
1171 let mut any = false;
1172
1173 macro_rules! maybe_write {
1174 ($ident:ident => $str:expr) => {
1175 if self.contains(TextUnderlinePosition::$ident) {
1176 any = true;
1177 writer.raw_item($str)?;
1178 }
1179 };
1180 }
1181
1182 maybe_write!(FROM_FONT => "from-font");
1183 maybe_write!(UNDER => "under");
1184 maybe_write!(LEFT => "left");
1185 maybe_write!(RIGHT => "right");
1186
1187 debug_assert!(any);
1188
1189 Ok(())
1190 }
1191}
1192
1193#[repr(u8)]
1195#[derive(
1196 Clone,
1197 Copy,
1198 Debug,
1199 Eq,
1200 MallocSizeOf,
1201 PartialEq,
1202 ToComputedValue,
1203 ToResolvedValue,
1204 ToShmem,
1205 ToTyped,
1206)]
1207#[allow(missing_docs)]
1208pub enum RubyPosition {
1209 AlternateOver,
1210 AlternateUnder,
1211 Over,
1212 Under,
1213}
1214
1215impl Parse for RubyPosition {
1216 fn parse(_context: &ParserContext, input: &mut Parser) -> Result<RubyPosition, ParseError> {
1217 let alternate = input
1219 .try_parse(|i| i.expect_ident_matching("alternate"))
1220 .is_ok();
1221 if alternate && input.is_exhausted() {
1222 return Ok(RubyPosition::AlternateOver);
1223 }
1224 let over = try_match_ident_ignore_ascii_case! { input,
1226 "over" => true,
1227 "under" => false,
1228 };
1229 let alternate = alternate
1231 || input
1232 .try_parse(|i| i.expect_ident_matching("alternate"))
1233 .is_ok();
1234
1235 Ok(match (over, alternate) {
1236 (true, true) => RubyPosition::AlternateOver,
1237 (false, true) => RubyPosition::AlternateUnder,
1238 (true, false) => RubyPosition::Over,
1239 (false, false) => RubyPosition::Under,
1240 })
1241 }
1242}
1243
1244impl ToCss for RubyPosition {
1245 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1246 where
1247 W: Write,
1248 {
1249 dest.write_str(match self {
1250 RubyPosition::AlternateOver => "alternate",
1251 RubyPosition::AlternateUnder => "alternate under",
1252 RubyPosition::Over => "over",
1253 RubyPosition::Under => "under",
1254 })
1255 }
1256}
1257
1258impl SpecifiedValueInfo for RubyPosition {
1259 fn collect_completion_keywords(f: KeywordsCollectFn) {
1260 f(&["alternate", "over", "under"])
1261 }
1262}
1263
1264#[derive(
1276 Clone,
1277 Copy,
1278 Debug,
1279 Eq,
1280 MallocSizeOf,
1281 Parse,
1282 PartialEq,
1283 Serialize,
1284 SpecifiedValueInfo,
1285 ToCss,
1286 ToComputedValue,
1287 ToResolvedValue,
1288 ToShmem,
1289 ToTyped,
1290)]
1291#[css(bitflags(
1292 single = "normal,auto,no-autospace",
1293 mixed = "ideograph-alpha,ideograph-numeric,insert",
1296 ))]
1299#[repr(C)]
1300pub struct TextAutospace(u8);
1301bitflags! {
1302 impl TextAutospace: u8 {
1303 const NO_AUTOSPACE = 0;
1305
1306 const AUTO = 1 << 0;
1308
1309 const NORMAL = 1 << 1;
1311
1312 const IDEOGRAPH_ALPHA = 1 << 2;
1314
1315 const IDEOGRAPH_NUMERIC = 1 << 3;
1317
1318 const INSERT = 1 << 5;
1325
1326 }
1331}
1332
1333#[derive(
1342 Clone,
1343 Copy,
1344 Debug,
1345 Eq,
1346 FromPrimitive,
1347 Hash,
1348 MallocSizeOf,
1349 Parse,
1350 PartialEq,
1351 SpecifiedValueInfo,
1352 ToComputedValue,
1353 ToCss,
1354 ToResolvedValue,
1355 ToShmem,
1356 ToTyped,
1357)]
1358#[repr(u8)]
1359pub enum TextEdgeKeyword {
1363 Text,
1365 Ideographic,
1367 IdeographicInk,
1369 Cap,
1371 Ex,
1373 Alphabetic,
1375}
1376
1377impl TextEdgeKeyword {
1378 fn is_valid_for_over(&self) -> bool {
1379 match self {
1380 TextEdgeKeyword::Text
1381 | TextEdgeKeyword::Ideographic
1382 | TextEdgeKeyword::IdeographicInk
1383 | TextEdgeKeyword::Cap
1384 | TextEdgeKeyword::Ex => true,
1385 _ => false,
1386 }
1387 }
1388
1389 fn is_valid_for_under(&self) -> bool {
1390 match self {
1391 TextEdgeKeyword::Text
1392 | TextEdgeKeyword::Ideographic
1393 | TextEdgeKeyword::IdeographicInk
1394 | TextEdgeKeyword::Alphabetic => true,
1395 _ => false,
1396 }
1397 }
1398}
1399
1400#[derive(
1401 Clone,
1402 Copy,
1403 Debug,
1404 Eq,
1405 Hash,
1406 MallocSizeOf,
1407 PartialEq,
1408 SpecifiedValueInfo,
1409 ToComputedValue,
1410 ToResolvedValue,
1411 ToShmem,
1412 ToTyped,
1413)]
1414#[repr(C)]
1415pub struct TextEdge {
1425 pub over: TextEdgeKeyword,
1427 pub under: TextEdgeKeyword,
1429}
1430
1431impl Parse for TextEdge {
1432 fn parse(_context: &ParserContext, input: &mut Parser) -> Result<TextEdge, ParseError> {
1433 let first = TextEdgeKeyword::parse(input)?;
1434
1435 if let Ok(second) = input.try_parse(TextEdgeKeyword::parse) {
1436 if !first.is_valid_for_over() || !second.is_valid_for_under() {
1437 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1438 }
1439
1440 return Ok(TextEdge {
1441 over: first,
1442 under: second,
1443 });
1444 }
1445
1446 match (first.is_valid_for_over(), first.is_valid_for_under()) {
1450 (true, true) => Ok(TextEdge {
1451 over: first,
1452 under: first,
1453 }),
1454 (true, false) => Ok(TextEdge {
1455 over: first,
1456 under: TextEdgeKeyword::Text,
1457 }),
1458 (false, true) => Ok(TextEdge {
1459 over: TextEdgeKeyword::Text,
1460 under: first,
1461 }),
1462 _ => unreachable!("Parsed keyword will be valid for at least one edge"),
1463 }
1464 }
1465}
1466
1467impl ToCss for TextEdge {
1468 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1469 where
1470 W: Write,
1471 {
1472 match (self.over, self.under) {
1473 (over, TextEdgeKeyword::Text) if !over.is_valid_for_under() => over.to_css(dest),
1474 (TextEdgeKeyword::Text, under) if !under.is_valid_for_over() => under.to_css(dest),
1475 (over, under) => {
1476 over.to_css(dest)?;
1477
1478 if over != under {
1479 dest.write_char(' ')?;
1480 self.under.to_css(dest)?;
1481 }
1482
1483 Ok(())
1484 },
1485 }
1486 }
1487}
1488
1489#[derive(
1490 Clone,
1491 Copy,
1492 Debug,
1493 Eq,
1494 Hash,
1495 MallocSizeOf,
1496 Parse,
1497 PartialEq,
1498 SpecifiedValueInfo,
1499 ToComputedValue,
1500 ToCss,
1501 ToResolvedValue,
1502 ToShmem,
1503 ToTyped,
1504)]
1505#[repr(C, u8)]
1506pub enum TextBoxEdge {
1510 Auto,
1512 TextEdge(TextEdge),
1514}
1515
1516#[derive(
1517 Clone,
1518 Copy,
1519 Debug,
1520 Eq,
1521 MallocSizeOf,
1522 PartialEq,
1523 Parse,
1524 Serialize,
1525 SpecifiedValueInfo,
1526 ToCss,
1527 ToComputedValue,
1528 ToResolvedValue,
1529 ToShmem,
1530 ToTyped,
1531)]
1532#[css(bitflags(single = "none,trim-start,trim-end,trim-both"))]
1533#[repr(C)]
1534pub struct TextBoxTrim(u8);
1538bitflags! {
1539 impl TextBoxTrim: u8 {
1540 const NONE = 0;
1542 const TRIM_START = 1 << 0;
1544 const TRIM_END = 1 << 1;
1546 const TRIM_BOTH = Self::TRIM_START.0 | Self::TRIM_END.0;
1548 }
1549}
1550
1551impl TextBoxTrim {
1552 #[inline]
1554 pub fn none() -> Self {
1555 TextBoxTrim::NONE
1556 }
1557}
1558
1559#[allow(missing_docs)]
1561#[derive(
1562 Clone,
1563 Copy,
1564 Debug,
1565 Deserialize,
1566 Eq,
1567 FromPrimitive,
1568 Hash,
1569 MallocSizeOf,
1570 Parse,
1571 PartialEq,
1572 Serialize,
1573 SpecifiedValueInfo,
1574 ToComputedValue,
1575 ToCss,
1576 ToResolvedValue,
1577 ToShmem,
1578 ToTyped,
1579)]
1580#[repr(u8)]
1581pub enum Hyphens {
1582 Manual,
1583 None,
1584 Auto,
1585}
1586
1587#[allow(missing_docs)]
1589#[derive(
1590 Clone,
1591 Copy,
1592 Debug,
1593 Deserialize,
1594 Eq,
1595 FromPrimitive,
1596 Hash,
1597 MallocSizeOf,
1598 Parse,
1599 PartialEq,
1600 Serialize,
1601 SpecifiedValueInfo,
1602 ToComputedValue,
1603 ToCss,
1604 ToResolvedValue,
1605 ToShmem,
1606 ToTyped,
1607)]
1608#[repr(u8)]
1609pub enum TextSizeAdjust {
1610 Auto,
1611 None,
1612}
1613
1614#[allow(missing_docs)]
1616#[derive(
1617 Clone,
1618 Copy,
1619 Debug,
1620 Deserialize,
1621 Eq,
1622 FromPrimitive,
1623 Hash,
1624 MallocSizeOf,
1625 Parse,
1626 PartialEq,
1627 Serialize,
1628 SpecifiedValueInfo,
1629 ToComputedValue,
1630 ToCss,
1631 ToResolvedValue,
1632 ToShmem,
1633 ToTyped,
1634)]
1635#[repr(u8)]
1636pub enum RubyAlign {
1637 SpaceAround,
1638 Start,
1639 Center,
1640 SpaceBetween,
1641}
1642
1643#[allow(missing_docs)]
1645#[derive(
1646 Clone,
1647 Copy,
1648 Debug,
1649 Deserialize,
1650 Eq,
1651 FromPrimitive,
1652 Hash,
1653 MallocSizeOf,
1654 Parse,
1655 PartialEq,
1656 Serialize,
1657 SpecifiedValueInfo,
1658 ToComputedValue,
1659 ToCss,
1660 ToResolvedValue,
1661 ToShmem,
1662 ToTyped,
1663)]
1664#[repr(u8)]
1665pub enum TextCombineUpright {
1666 None,
1667 All,
1668}
1669
1670#[allow(missing_docs)]
1672#[derive(
1673 Clone,
1674 Copy,
1675 Debug,
1676 Deserialize,
1677 Eq,
1678 FromPrimitive,
1679 Hash,
1680 MallocSizeOf,
1681 Parse,
1682 PartialEq,
1683 Serialize,
1684 SpecifiedValueInfo,
1685 ToComputedValue,
1686 ToCss,
1687 ToResolvedValue,
1688 ToShmem,
1689 ToTyped,
1690)]
1691#[repr(u8)]
1692pub enum TextRendering {
1693 Auto,
1694 Optimizespeed,
1695 Optimizelegibility,
1696 Geometricprecision,
1697}
1698
1699#[allow(missing_docs)]
1701#[derive(
1702 Clone,
1703 Copy,
1704 Debug,
1705 Deserialize,
1706 Eq,
1707 FromPrimitive,
1708 Hash,
1709 MallocSizeOf,
1710 Parse,
1711 PartialEq,
1712 Serialize,
1713 SpecifiedValueInfo,
1714 ToComputedValue,
1715 ToCss,
1716 ToResolvedValue,
1717 ToShmem,
1718 ToTyped,
1719)]
1720#[repr(u8)]
1721pub enum TextSecurity {
1722 None,
1723 Circle,
1724 Disc,
1725 Square,
1726}
1727
1728#[allow(missing_docs)]
1730#[derive(
1731 Clone,
1732 Copy,
1733 Debug,
1734 Deserialize,
1735 Eq,
1736 FromPrimitive,
1737 Hash,
1738 MallocSizeOf,
1739 Parse,
1740 PartialEq,
1741 Serialize,
1742 SpecifiedValueInfo,
1743 ToComputedValue,
1744 ToCss,
1745 ToResolvedValue,
1746 ToShmem,
1747 ToTyped,
1748)]
1749#[repr(u8)]
1750pub enum TextWrapMode {
1751 Wrap,
1752 Nowrap,
1753}
1754
1755fn text_wrap_style_balance_enabled(_: &ParserContext) -> bool {
1756 crate::pref!("layout.css.text-wrap-style.balance.enabled", gecko = true)
1757}
1758
1759fn text_wrap_style_pretty_enabled(_: &ParserContext) -> bool {
1760 crate::pref!("layout.css.text-wrap-style.pretty.enabled", gecko = false)
1761}
1762
1763#[allow(missing_docs)]
1765#[derive(
1766 Clone,
1767 Copy,
1768 Debug,
1769 Deserialize,
1770 Eq,
1771 FromPrimitive,
1772 Hash,
1773 MallocSizeOf,
1774 Parse,
1775 PartialEq,
1776 Serialize,
1777 SpecifiedValueInfo,
1778 ToComputedValue,
1779 ToCss,
1780 ToResolvedValue,
1781 ToShmem,
1782 ToTyped,
1783)]
1784#[repr(u8)]
1785pub enum TextWrapStyle {
1786 Auto,
1787 Stable,
1788 #[parse(condition = "text_wrap_style_balance_enabled")]
1789 Balance,
1790 #[parse(condition = "text_wrap_style_pretty_enabled")]
1791 Pretty,
1792}
1793
1794#[allow(missing_docs)]
1796#[derive(
1797 Clone,
1798 Copy,
1799 Debug,
1800 Deserialize,
1801 Eq,
1802 FromPrimitive,
1803 Hash,
1804 MallocSizeOf,
1805 Parse,
1806 PartialEq,
1807 Serialize,
1808 SpecifiedValueInfo,
1809 ToComputedValue,
1810 ToCss,
1811 ToResolvedValue,
1812 ToShmem,
1813 ToTyped,
1814)]
1815#[repr(u8)]
1816pub enum UnicodeBidi {
1817 Normal,
1818 Embed,
1819 Isolate,
1820 BidiOverride,
1821 IsolateOverride,
1822 Plaintext,
1823}
1824
1825#[allow(missing_docs)]
1827#[derive(
1828 Clone,
1829 Copy,
1830 Debug,
1831 Deserialize,
1832 Eq,
1833 FromPrimitive,
1834 Hash,
1835 MallocSizeOf,
1836 Parse,
1837 PartialEq,
1838 Serialize,
1839 SpecifiedValueInfo,
1840 ToComputedValue,
1841 ToCss,
1842 ToResolvedValue,
1843 ToShmem,
1844 ToTyped,
1845)]
1846#[repr(u8)]
1847pub enum TextDecorationStyle {
1848 #[css(keyword = "-moz-none")]
1851 None = 0,
1852 Dotted = 1,
1853 Dashed = 2,
1854 Solid = 3,
1855 Double = 4,
1856 Wavy = 5,
1857}
1858
1859pub const MAX_LINE_STYLE: i32 = 5;
1861
1862fn white_space_collapse_preserve_spaces_enabled(_: &ParserContext) -> bool {
1863 crate::pref!(
1864 "layout.css.white-space-collapse.preserve-spaces.enabled",
1865 gecko = true
1866 )
1867}
1868
1869#[allow(missing_docs)]
1871#[derive(
1872 Clone,
1873 Copy,
1874 Debug,
1875 Deserialize,
1876 Eq,
1877 FromPrimitive,
1878 Hash,
1879 MallocSizeOf,
1880 Parse,
1881 PartialEq,
1882 Serialize,
1883 SpecifiedValueInfo,
1884 ToComputedValue,
1885 ToCss,
1886 ToResolvedValue,
1887 ToShmem,
1888 ToTyped,
1889)]
1890#[repr(u8)]
1891pub enum WhiteSpaceCollapse {
1892 Collapse,
1893 Preserve,
1895 PreserveBreaks,
1896 BreakSpaces,
1897 #[parse(condition = "white_space_collapse_preserve_spaces_enabled")]
1898 #[cfg_attr(feature = "gecko", parse(aliases = "-moz-pre-space"))]
1899 PreserveSpaces,
1900}