1use 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
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 .clone_text_align();
573 let ltr = _context.builder.inherited_writing_mode().is_bidi_ltr();
574 match (parent, ltr) {
575 (TextAlignKeyword::Start, true) => TextAlignKeyword::Left,
576 (TextAlignKeyword::Start, false) => TextAlignKeyword::Right,
577 (TextAlignKeyword::End, true) => TextAlignKeyword::Right,
578 (TextAlignKeyword::End, false) => TextAlignKeyword::Left,
579 _ => parent,
580 }
581 },
582 TextAlign::MozCenterOrInherit => {
583 let parent = _context
584 .builder
585 .get_parent_inherited_text()
586 .clone_text_align();
587 if parent == TextAlignKeyword::Start {
588 TextAlignKeyword::Center
589 } else {
590 parent
591 }
592 },
593 }
594 }
595
596 #[inline]
597 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
598 TextAlign::Keyword(*computed)
599 }
600}
601
602fn fill_mode_is_default_and_shape_exists(
603 fill: &TextEmphasisFillMode,
604 shape: &Option<TextEmphasisShapeKeyword>,
605) -> bool {
606 shape.is_some() && fill.is_filled()
607}
608
609#[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().clone_writing_mode()
701 == SpecifiedWritingMode::HorizontalTb
702 {
703 TextEmphasisShapeKeyword::Circle
704 } else {
705 TextEmphasisShapeKeyword::Sesame
706 }
707 });
708 ComputedTextEmphasisStyle::Keyword { fill, shape }
709 },
710 TextEmphasisStyle::None => ComputedTextEmphasisStyle::None,
711 TextEmphasisStyle::String(ref s) => {
712 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 if let Ok(len) = input
992 .try_parse(|i| LengthPercentage::parse_quirky(context, i, AllowQuirks::Yes))
993 {
994 length = Some(len);
995 continue;
996 }
997 }
998
999 if cfg!(feature = "servo") {
1001 break;
1002 }
1003
1004 try_match_ident_ignore_ascii_case! { input,
1006 "hanging" if !hanging => hanging = true,
1007 "each-line" if !each_line => each_line = true,
1008 }
1009 }
1010
1011 if let Some(length) = length {
1013 Ok(Self {
1014 length,
1015 hanging,
1016 each_line,
1017 })
1018 } else {
1019 Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError))
1020 }
1021 }
1022}
1023
1024#[repr(u8)]
1028#[derive(
1029 Clone,
1030 Copy,
1031 Debug,
1032 Deserialize,
1033 Eq,
1034 MallocSizeOf,
1035 Parse,
1036 PartialEq,
1037 Serialize,
1038 SpecifiedValueInfo,
1039 ToComputedValue,
1040 ToCss,
1041 ToResolvedValue,
1042 ToShmem,
1043 ToTyped,
1044)]
1045#[allow(missing_docs)]
1046pub enum TextDecorationSkipInk {
1047 Auto,
1048 None,
1049 All,
1050}
1051
1052pub type TextDecorationLength = GenericTextDecorationLength<LengthPercentage>;
1054
1055impl TextDecorationLength {
1056 #[inline]
1058 pub fn auto() -> Self {
1059 GenericTextDecorationLength::Auto
1060 }
1061
1062 #[inline]
1064 pub fn is_auto(&self) -> bool {
1065 matches!(*self, GenericTextDecorationLength::Auto)
1066 }
1067}
1068
1069pub type TextDecorationInset = GenericTextDecorationInset<LengthPercentage>;
1071
1072impl TextDecorationInset {
1073 #[inline]
1075 pub fn auto() -> Self {
1076 GenericTextDecorationInset::Auto
1077 }
1078
1079 #[inline]
1081 pub fn is_auto(&self) -> bool {
1082 matches!(*self, GenericTextDecorationInset::Auto)
1083 }
1084}
1085
1086fn parse_inset_endpoint(
1087 ctx: &ParserContext,
1088 input: &mut Parser,
1089) -> Result<LengthPercentage, ParseError> {
1090 if !crate::pref!("layout.css.text-decoration-inset-percentage.enabled") {
1091 Length::parse(ctx, input).map(|l| l.into())
1092 } else {
1093 LengthPercentage::parse(ctx, input)
1094 }
1095}
1096
1097impl Parse for TextDecorationInset {
1098 fn parse(ctx: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
1099 if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
1100 return Ok(TextDecorationInset::Auto);
1101 }
1102
1103 let start = parse_inset_endpoint(ctx, input)?;
1104 let end = input
1105 .try_parse(|i| parse_inset_endpoint(ctx, i))
1106 .unwrap_or_else(|_| start.clone());
1107 Ok(TextDecorationInset::LengthPercentage { start, end })
1108 }
1109}
1110
1111#[derive(
1112 Clone,
1113 Copy,
1114 Debug,
1115 Eq,
1116 MallocSizeOf,
1117 Parse,
1118 PartialEq,
1119 SpecifiedValueInfo,
1120 ToComputedValue,
1121 ToResolvedValue,
1122 ToShmem,
1123 ToTyped,
1124)]
1125#[css(bitflags(
1126 single = "auto",
1127 mixed = "from-font,under,left,right",
1128 validate_mixed = "Self::validate_mixed_flags",
1129))]
1130#[repr(C)]
1131pub struct TextUnderlinePosition(u8);
1136bitflags! {
1137 impl TextUnderlinePosition: u8 {
1138 const AUTO = 0;
1140 const FROM_FONT = 1 << 0;
1142 const UNDER = 1 << 1;
1144 const LEFT = 1 << 2;
1146 const RIGHT = 1 << 3;
1148 }
1149}
1150
1151impl TextUnderlinePosition {
1152 fn validate_mixed_flags(&self) -> bool {
1153 if self.contains(Self::LEFT | Self::RIGHT) {
1154 return false;
1156 }
1157 if self.contains(Self::FROM_FONT | Self::UNDER) {
1158 return false;
1160 }
1161 true
1162 }
1163}
1164
1165impl ToCss for TextUnderlinePosition {
1166 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1167 where
1168 W: Write,
1169 {
1170 if self.is_empty() {
1171 return dest.write_str("auto");
1172 }
1173
1174 let mut writer = SequenceWriter::new(dest, " ");
1175 let mut any = false;
1176
1177 macro_rules! maybe_write {
1178 ($ident:ident => $str:expr) => {
1179 if self.contains(TextUnderlinePosition::$ident) {
1180 any = true;
1181 writer.raw_item($str)?;
1182 }
1183 };
1184 }
1185
1186 maybe_write!(FROM_FONT => "from-font");
1187 maybe_write!(UNDER => "under");
1188 maybe_write!(LEFT => "left");
1189 maybe_write!(RIGHT => "right");
1190
1191 debug_assert!(any);
1192
1193 Ok(())
1194 }
1195}
1196
1197#[repr(u8)]
1199#[derive(
1200 Clone,
1201 Copy,
1202 Debug,
1203 Eq,
1204 MallocSizeOf,
1205 PartialEq,
1206 ToComputedValue,
1207 ToResolvedValue,
1208 ToShmem,
1209 ToTyped,
1210)]
1211#[allow(missing_docs)]
1212pub enum RubyPosition {
1213 AlternateOver,
1214 AlternateUnder,
1215 Over,
1216 Under,
1217}
1218
1219impl Parse for RubyPosition {
1220 fn parse(_context: &ParserContext, input: &mut Parser) -> Result<RubyPosition, ParseError> {
1221 let alternate = input
1223 .try_parse(|i| i.expect_ident_matching("alternate"))
1224 .is_ok();
1225 if alternate && input.is_exhausted() {
1226 return Ok(RubyPosition::AlternateOver);
1227 }
1228 let over = try_match_ident_ignore_ascii_case! { input,
1230 "over" => true,
1231 "under" => false,
1232 };
1233 let alternate = alternate
1235 || input
1236 .try_parse(|i| i.expect_ident_matching("alternate"))
1237 .is_ok();
1238
1239 Ok(match (over, alternate) {
1240 (true, true) => RubyPosition::AlternateOver,
1241 (false, true) => RubyPosition::AlternateUnder,
1242 (true, false) => RubyPosition::Over,
1243 (false, false) => RubyPosition::Under,
1244 })
1245 }
1246}
1247
1248impl ToCss for RubyPosition {
1249 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1250 where
1251 W: Write,
1252 {
1253 dest.write_str(match self {
1254 RubyPosition::AlternateOver => "alternate",
1255 RubyPosition::AlternateUnder => "alternate under",
1256 RubyPosition::Over => "over",
1257 RubyPosition::Under => "under",
1258 })
1259 }
1260}
1261
1262impl SpecifiedValueInfo for RubyPosition {
1263 fn collect_completion_keywords(f: KeywordsCollectFn) {
1264 f(&["alternate", "over", "under"])
1265 }
1266}
1267
1268#[derive(
1280 Clone,
1281 Copy,
1282 Debug,
1283 Eq,
1284 MallocSizeOf,
1285 Parse,
1286 PartialEq,
1287 Serialize,
1288 SpecifiedValueInfo,
1289 ToCss,
1290 ToComputedValue,
1291 ToResolvedValue,
1292 ToShmem,
1293 ToTyped,
1294)]
1295#[css(bitflags(
1296 single = "normal,auto,no-autospace",
1297 mixed = "ideograph-alpha,ideograph-numeric,insert",
1300 ))]
1303#[repr(C)]
1304pub struct TextAutospace(u8);
1305bitflags! {
1306 impl TextAutospace: u8 {
1307 const NO_AUTOSPACE = 0;
1309
1310 const AUTO = 1 << 0;
1312
1313 const NORMAL = 1 << 1;
1315
1316 const IDEOGRAPH_ALPHA = 1 << 2;
1318
1319 const IDEOGRAPH_NUMERIC = 1 << 3;
1321
1322 const INSERT = 1 << 5;
1329
1330 }
1335}
1336
1337#[derive(
1346 Clone,
1347 Copy,
1348 Debug,
1349 Eq,
1350 FromPrimitive,
1351 Hash,
1352 MallocSizeOf,
1353 Parse,
1354 PartialEq,
1355 SpecifiedValueInfo,
1356 ToComputedValue,
1357 ToCss,
1358 ToResolvedValue,
1359 ToShmem,
1360 ToTyped,
1361)]
1362#[repr(u8)]
1363pub enum TextEdgeKeyword {
1367 Text,
1369 Ideographic,
1371 IdeographicInk,
1373 Cap,
1375 Ex,
1377 Alphabetic,
1379}
1380
1381impl TextEdgeKeyword {
1382 fn is_valid_for_over(&self) -> bool {
1383 match self {
1384 TextEdgeKeyword::Text
1385 | TextEdgeKeyword::Ideographic
1386 | TextEdgeKeyword::IdeographicInk
1387 | TextEdgeKeyword::Cap
1388 | TextEdgeKeyword::Ex => true,
1389 _ => false,
1390 }
1391 }
1392
1393 fn is_valid_for_under(&self) -> bool {
1394 match self {
1395 TextEdgeKeyword::Text
1396 | TextEdgeKeyword::Ideographic
1397 | TextEdgeKeyword::IdeographicInk
1398 | TextEdgeKeyword::Alphabetic => true,
1399 _ => false,
1400 }
1401 }
1402}
1403
1404#[derive(
1405 Clone,
1406 Copy,
1407 Debug,
1408 Eq,
1409 Hash,
1410 MallocSizeOf,
1411 PartialEq,
1412 SpecifiedValueInfo,
1413 ToComputedValue,
1414 ToResolvedValue,
1415 ToShmem,
1416 ToTyped,
1417)]
1418#[repr(C)]
1419pub struct TextEdge {
1429 pub over: TextEdgeKeyword,
1431 pub under: TextEdgeKeyword,
1433}
1434
1435impl Parse for TextEdge {
1436 fn parse(_context: &ParserContext, input: &mut Parser) -> Result<TextEdge, ParseError> {
1437 let first = TextEdgeKeyword::parse(input)?;
1438
1439 if let Ok(second) = input.try_parse(TextEdgeKeyword::parse) {
1440 if !first.is_valid_for_over() || !second.is_valid_for_under() {
1441 return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
1442 }
1443
1444 return Ok(TextEdge {
1445 over: first,
1446 under: second,
1447 });
1448 }
1449
1450 match (first.is_valid_for_over(), first.is_valid_for_under()) {
1454 (true, true) => Ok(TextEdge {
1455 over: first,
1456 under: first,
1457 }),
1458 (true, false) => Ok(TextEdge {
1459 over: first,
1460 under: TextEdgeKeyword::Text,
1461 }),
1462 (false, true) => Ok(TextEdge {
1463 over: TextEdgeKeyword::Text,
1464 under: first,
1465 }),
1466 _ => unreachable!("Parsed keyword will be valid for at least one edge"),
1467 }
1468 }
1469}
1470
1471impl ToCss for TextEdge {
1472 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1473 where
1474 W: Write,
1475 {
1476 match (self.over, self.under) {
1477 (over, TextEdgeKeyword::Text) if !over.is_valid_for_under() => over.to_css(dest),
1478 (TextEdgeKeyword::Text, under) if !under.is_valid_for_over() => under.to_css(dest),
1479 (over, under) => {
1480 over.to_css(dest)?;
1481
1482 if over != under {
1483 dest.write_char(' ')?;
1484 self.under.to_css(dest)?;
1485 }
1486
1487 Ok(())
1488 },
1489 }
1490 }
1491}
1492
1493#[derive(
1494 Clone,
1495 Copy,
1496 Debug,
1497 Eq,
1498 Hash,
1499 MallocSizeOf,
1500 Parse,
1501 PartialEq,
1502 SpecifiedValueInfo,
1503 ToComputedValue,
1504 ToCss,
1505 ToResolvedValue,
1506 ToShmem,
1507 ToTyped,
1508)]
1509#[repr(C, u8)]
1510pub enum TextBoxEdge {
1514 Auto,
1516 TextEdge(TextEdge),
1518}
1519
1520#[derive(
1521 Clone,
1522 Copy,
1523 Debug,
1524 Eq,
1525 MallocSizeOf,
1526 PartialEq,
1527 Parse,
1528 Serialize,
1529 SpecifiedValueInfo,
1530 ToCss,
1531 ToComputedValue,
1532 ToResolvedValue,
1533 ToShmem,
1534 ToTyped,
1535)]
1536#[css(bitflags(single = "none,trim-start,trim-end,trim-both"))]
1537#[repr(C)]
1538pub struct TextBoxTrim(u8);
1542bitflags! {
1543 impl TextBoxTrim: u8 {
1544 const NONE = 0;
1546 const TRIM_START = 1 << 0;
1548 const TRIM_END = 1 << 1;
1550 const TRIM_BOTH = Self::TRIM_START.0 | Self::TRIM_END.0;
1552 }
1553}
1554
1555impl TextBoxTrim {
1556 #[inline]
1558 pub fn none() -> Self {
1559 TextBoxTrim::NONE
1560 }
1561}