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)]
33#[typed_value(derive_fields)]
34pub enum Spacing {
35 Normal,
37 Value(LengthPercentage),
39}
40
41impl Parse for Spacing {
42 fn parse<'i, 't>(
43 context: &ParserContext,
44 input: &mut Parser<'i, 't>,
45 ) -> Result<Self, ParseError<'i>> {
46 if input
47 .try_parse(|i| i.expect_ident_matching("normal"))
48 .is_ok()
49 {
50 return Ok(Spacing::Normal);
51 }
52 LengthPercentage::parse_quirky(context, input, AllowQuirks::Yes).map(Spacing::Value)
53 }
54}
55
56#[derive(
58 Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
59)]
60#[typed_value(derive_fields)]
61pub struct LetterSpacing(pub Spacing);
62
63impl ToComputedValue for LetterSpacing {
64 type ComputedValue = computed::LetterSpacing;
65
66 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
67 use computed::text::GenericLetterSpacing;
68 match self.0 {
69 Spacing::Normal => GenericLetterSpacing(computed::LengthPercentage::zero()),
70 Spacing::Value(ref v) => GenericLetterSpacing(v.to_computed_value(context)),
71 }
72 }
73
74 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
75 if computed.0.is_zero() {
76 return LetterSpacing(Spacing::Normal);
77 }
78 LetterSpacing(Spacing::Value(ToComputedValue::from_computed_value(
79 &computed.0,
80 )))
81 }
82}
83
84#[derive(
86 Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
87)]
88#[typed_value(derive_fields)]
89pub struct WordSpacing(pub Spacing);
90
91impl ToComputedValue for WordSpacing {
92 type ComputedValue = computed::WordSpacing;
93
94 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
95 match self.0 {
96 Spacing::Normal => computed::LengthPercentage::zero(),
97 Spacing::Value(ref v) => v.to_computed_value(context),
98 }
99 }
100
101 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
102 WordSpacing(Spacing::Value(ToComputedValue::from_computed_value(
103 computed,
104 )))
105 }
106}
107
108#[derive(
110 Clone,
111 Debug,
112 MallocSizeOf,
113 Parse,
114 PartialEq,
115 SpecifiedValueInfo,
116 ToComputedValue,
117 ToCss,
118 ToResolvedValue,
119 ToShmem,
120 ToTyped,
121)]
122#[repr(C, u8)]
123pub enum HyphenateCharacter {
124 Auto,
126 String(crate::OwnedStr),
128}
129
130pub type HyphenateLimitChars = GenericHyphenateLimitChars<Integer>;
132
133impl Parse for HyphenateLimitChars {
134 fn parse<'i, 't>(
135 context: &ParserContext,
136 input: &mut Parser<'i, 't>,
137 ) -> Result<Self, ParseError<'i>> {
138 type IntegerOrAuto = NumberOrAuto<Integer>;
139
140 let total_word_length = IntegerOrAuto::parse(context, input)?;
141 let pre_hyphen_length = input
142 .try_parse(|i| IntegerOrAuto::parse(context, i))
143 .unwrap_or(IntegerOrAuto::Auto);
144 let post_hyphen_length = input
145 .try_parse(|i| IntegerOrAuto::parse(context, i))
146 .unwrap_or(pre_hyphen_length);
147 Ok(Self {
148 total_word_length,
149 pre_hyphen_length,
150 post_hyphen_length,
151 })
152 }
153}
154
155impl Parse for InitialLetter {
156 fn parse<'i, 't>(
157 context: &ParserContext,
158 input: &mut Parser<'i, 't>,
159 ) -> Result<Self, ParseError<'i>> {
160 if input
161 .try_parse(|i| i.expect_ident_matching("normal"))
162 .is_ok()
163 {
164 return Ok(Self::normal());
165 }
166 let size = Number::parse_at_least_one(context, input)?;
167 let sink = input
168 .try_parse(|i| Integer::parse_positive(context, i))
169 .unwrap_or_else(|_| crate::Zero::zero());
170 Ok(Self { size, sink })
171 }
172}
173
174#[derive(
176 Clone,
177 Debug,
178 Eq,
179 MallocSizeOf,
180 PartialEq,
181 Parse,
182 SpecifiedValueInfo,
183 ToComputedValue,
184 ToCss,
185 ToResolvedValue,
186 ToShmem,
187)]
188#[repr(C, u8)]
189pub enum TextOverflowSide {
190 Clip,
192 Ellipsis,
194 String(crate::values::AtomString),
196}
197
198#[derive(
199 Clone,
200 Debug,
201 Eq,
202 MallocSizeOf,
203 PartialEq,
204 SpecifiedValueInfo,
205 ToComputedValue,
206 ToResolvedValue,
207 ToShmem,
208 ToTyped,
209)]
210#[repr(C)]
211pub struct TextOverflow {
220 pub first: TextOverflowSide,
222 pub second: TextOverflowSide,
224 pub sides_are_logical: bool,
226}
227
228impl Parse for TextOverflow {
229 fn parse<'i, 't>(
230 context: &ParserContext,
231 input: &mut Parser<'i, 't>,
232 ) -> Result<TextOverflow, ParseError<'i>> {
233 let first = TextOverflowSide::parse(context, input)?;
234 Ok(
235 if let Ok(second) = input.try_parse(|input| TextOverflowSide::parse(context, input)) {
236 Self {
237 first,
238 second,
239 sides_are_logical: false,
240 }
241 } else {
242 Self {
243 first: TextOverflowSide::Clip,
244 second: first,
245 sides_are_logical: true,
246 }
247 },
248 )
249 }
250}
251
252impl TextOverflow {
253 pub fn get_initial_value() -> TextOverflow {
255 TextOverflow {
256 first: TextOverflowSide::Clip,
257 second: TextOverflowSide::Clip,
258 sides_are_logical: true,
259 }
260 }
261}
262
263impl ToCss for TextOverflow {
264 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
265 where
266 W: Write,
267 {
268 if self.sides_are_logical {
269 debug_assert_eq!(self.first, TextOverflowSide::Clip);
270 self.second.to_css(dest)?;
271 } else {
272 self.first.to_css(dest)?;
273 dest.write_char(' ')?;
274 self.second.to_css(dest)?;
275 }
276 Ok(())
277 }
278}
279
280#[derive(
281 Clone,
282 Copy,
283 Debug,
284 Eq,
285 MallocSizeOf,
286 PartialEq,
287 Parse,
288 Serialize,
289 SpecifiedValueInfo,
290 ToCss,
291 ToComputedValue,
292 ToResolvedValue,
293 ToShmem,
294 ToTyped,
295)]
296#[cfg_attr(
297 feature = "gecko",
298 css(bitflags(
299 single = "none,spelling-error,grammar-error",
300 mixed = "underline,overline,line-through,blink",
301 ))
302)]
303#[cfg_attr(
304 not(feature = "gecko"),
305 css(bitflags(single = "none", mixed = "underline,overline,line-through,blink",))
306)]
307#[repr(C)]
308pub struct TextDecorationLine(u8);
310bitflags! {
311 impl TextDecorationLine: u8 {
312 const NONE = 0;
314 const UNDERLINE = 1 << 0;
316 const OVERLINE = 1 << 1;
318 const LINE_THROUGH = 1 << 2;
320 const BLINK = 1 << 3;
322 const SPELLING_ERROR = 1 << 4;
324 const GRAMMAR_ERROR = 1 << 5;
326 #[cfg(feature = "gecko")]
334 const COLOR_OVERRIDE = 1 << 7;
335 }
336}
337
338impl Default for TextDecorationLine {
339 fn default() -> Self {
340 TextDecorationLine::NONE
341 }
342}
343
344impl TextDecorationLine {
345 #[inline]
346 pub fn none() -> Self {
348 TextDecorationLine::NONE
349 }
350}
351
352#[derive(
353 Clone,
354 Copy,
355 Debug,
356 Eq,
357 MallocSizeOf,
358 PartialEq,
359 SpecifiedValueInfo,
360 ToComputedValue,
361 ToCss,
362 ToResolvedValue,
363 ToShmem,
364)]
365#[repr(C)]
366pub enum TextTransformCase {
368 None,
370 Uppercase,
372 Lowercase,
374 Capitalize,
376 #[cfg(feature = "gecko")]
378 MathAuto,
379}
380
381#[derive(
382 Clone,
383 Copy,
384 Debug,
385 Eq,
386 MallocSizeOf,
387 PartialEq,
388 Parse,
389 Serialize,
390 SpecifiedValueInfo,
391 ToCss,
392 ToComputedValue,
393 ToResolvedValue,
394 ToShmem,
395 ToTyped,
396)]
397#[cfg_attr(
398 feature = "gecko",
399 css(bitflags(
400 single = "none,math-auto",
401 mixed = "uppercase,lowercase,capitalize,full-width,full-size-kana",
402 validate_mixed = "Self::validate_mixed_flags",
403 ))
404)]
405#[cfg_attr(
406 not(feature = "gecko"),
407 css(bitflags(
408 single = "none",
409 mixed = "uppercase,lowercase,capitalize,full-width,full-size-kana",
410 validate_mixed = "Self::validate_mixed_flags",
411 ))
412)]
413#[repr(C)]
414pub struct TextTransform(u8);
419bitflags! {
420 impl TextTransform: u8 {
421 const NONE = 0;
423 const UPPERCASE = 1 << 0;
425 const LOWERCASE = 1 << 1;
427 const CAPITALIZE = 1 << 2;
429 #[cfg(feature = "gecko")]
431 const MATH_AUTO = 1 << 3;
432
433 #[cfg(feature = "gecko")]
435 const CASE_TRANSFORMS = Self::UPPERCASE.0 | Self::LOWERCASE.0 | Self::CAPITALIZE.0 | Self::MATH_AUTO.0;
436 #[cfg(feature = "servo")]
438 const CASE_TRANSFORMS = Self::UPPERCASE.0 | Self::LOWERCASE.0 | Self::CAPITALIZE.0;
439
440 const FULL_WIDTH = 1 << 4;
442 const FULL_SIZE_KANA = 1 << 5;
444 }
445}
446
447impl TextTransform {
448 #[inline]
450 pub fn none() -> Self {
451 Self::NONE
452 }
453
454 #[inline]
456 pub fn is_none(self) -> bool {
457 self == Self::NONE
458 }
459
460 fn validate_mixed_flags(&self) -> bool {
461 let case = self.intersection(Self::CASE_TRANSFORMS);
462 case.is_empty() || case.bits().is_power_of_two()
464 }
465
466 pub fn case(&self) -> TextTransformCase {
468 match *self & Self::CASE_TRANSFORMS {
469 Self::NONE => TextTransformCase::None,
470 Self::UPPERCASE => TextTransformCase::Uppercase,
471 Self::LOWERCASE => TextTransformCase::Lowercase,
472 Self::CAPITALIZE => TextTransformCase::Capitalize,
473 #[cfg(feature = "gecko")]
474 Self::MATH_AUTO => TextTransformCase::MathAuto,
475 _ => unreachable!("Case bits are exclusive with each other"),
476 }
477 }
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 TextAlignLast {
501 Auto,
502 Start,
503 End,
504 Left,
505 Right,
506 Center,
507 Justify,
508}
509
510#[derive(
512 Clone,
513 Copy,
514 Debug,
515 Eq,
516 FromPrimitive,
517 Hash,
518 MallocSizeOf,
519 Parse,
520 PartialEq,
521 SpecifiedValueInfo,
522 ToComputedValue,
523 ToCss,
524 ToResolvedValue,
525 ToShmem,
526 ToTyped,
527)]
528#[allow(missing_docs)]
529#[repr(u8)]
530pub enum TextAlignKeyword {
531 Start,
532 Left,
533 Right,
534 Center,
535 Justify,
536 End,
537 #[parse(aliases = "-webkit-center")]
538 MozCenter,
539 #[parse(aliases = "-webkit-left")]
540 MozLeft,
541 #[parse(aliases = "-webkit-right")]
542 MozRight,
543}
544
545#[derive(
547 Clone,
548 Copy,
549 Debug,
550 Eq,
551 Hash,
552 MallocSizeOf,
553 Parse,
554 PartialEq,
555 SpecifiedValueInfo,
556 ToCss,
557 ToShmem,
558 ToTyped,
559)]
560pub enum TextAlign {
561 Keyword(TextAlignKeyword),
563 MatchParent,
566 #[parse(condition = "ParserContext::chrome_rules_enabled")]
579 MozCenterOrInherit,
580}
581
582impl ToComputedValue for TextAlign {
583 type ComputedValue = TextAlignKeyword;
584
585 #[inline]
586 fn to_computed_value(&self, _context: &Context) -> Self::ComputedValue {
587 match *self {
588 TextAlign::Keyword(key) => key,
589 TextAlign::MatchParent => {
590 if _context.builder.is_root_element {
597 return TextAlignKeyword::Start;
598 }
599 let parent = _context
600 .builder
601 .get_parent_inherited_text()
602 .clone_text_align();
603 let ltr = _context.builder.inherited_writing_mode().is_bidi_ltr();
604 match (parent, ltr) {
605 (TextAlignKeyword::Start, true) => TextAlignKeyword::Left,
606 (TextAlignKeyword::Start, false) => TextAlignKeyword::Right,
607 (TextAlignKeyword::End, true) => TextAlignKeyword::Right,
608 (TextAlignKeyword::End, false) => TextAlignKeyword::Left,
609 _ => parent,
610 }
611 },
612 TextAlign::MozCenterOrInherit => {
613 let parent = _context
614 .builder
615 .get_parent_inherited_text()
616 .clone_text_align();
617 if parent == TextAlignKeyword::Start {
618 TextAlignKeyword::Center
619 } else {
620 parent
621 }
622 },
623 }
624 }
625
626 #[inline]
627 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
628 TextAlign::Keyword(*computed)
629 }
630}
631
632fn fill_mode_is_default_and_shape_exists(
633 fill: &TextEmphasisFillMode,
634 shape: &Option<TextEmphasisShapeKeyword>,
635) -> bool {
636 shape.is_some() && fill.is_filled()
637}
638
639#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
643#[allow(missing_docs)]
644pub enum TextEmphasisStyle {
645 Keyword {
647 #[css(contextual_skip_if = "fill_mode_is_default_and_shape_exists")]
648 fill: TextEmphasisFillMode,
649 shape: Option<TextEmphasisShapeKeyword>,
650 },
651 None,
653 String(crate::OwnedStr),
655}
656
657#[derive(
659 Clone,
660 Copy,
661 Debug,
662 MallocSizeOf,
663 Parse,
664 PartialEq,
665 SpecifiedValueInfo,
666 ToCss,
667 ToComputedValue,
668 ToResolvedValue,
669 ToShmem,
670)]
671#[repr(u8)]
672pub enum TextEmphasisFillMode {
673 Filled,
675 Open,
677}
678
679impl TextEmphasisFillMode {
680 #[inline]
682 pub fn is_filled(&self) -> bool {
683 matches!(*self, TextEmphasisFillMode::Filled)
684 }
685}
686
687#[derive(
689 Clone,
690 Copy,
691 Debug,
692 Eq,
693 MallocSizeOf,
694 Parse,
695 PartialEq,
696 SpecifiedValueInfo,
697 ToCss,
698 ToComputedValue,
699 ToResolvedValue,
700 ToShmem,
701)]
702#[repr(u8)]
703pub enum TextEmphasisShapeKeyword {
704 Dot,
706 Circle,
708 DoubleCircle,
710 Triangle,
712 Sesame,
714}
715
716impl ToComputedValue for TextEmphasisStyle {
717 type ComputedValue = ComputedTextEmphasisStyle;
718
719 #[inline]
720 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
721 match *self {
722 TextEmphasisStyle::Keyword { fill, shape } => {
723 let shape = shape.unwrap_or_else(|| {
724 if context.style().get_inherited_box().clone_writing_mode()
730 == SpecifiedWritingMode::HorizontalTb
731 {
732 TextEmphasisShapeKeyword::Circle
733 } else {
734 TextEmphasisShapeKeyword::Sesame
735 }
736 });
737 ComputedTextEmphasisStyle::Keyword { fill, shape }
738 },
739 TextEmphasisStyle::None => ComputedTextEmphasisStyle::None,
740 TextEmphasisStyle::String(ref s) => {
741 let first_grapheme_end = GraphemeClusterSegmenter::new()
747 .segment_str(s)
748 .nth(1)
749 .unwrap_or(0);
750 ComputedTextEmphasisStyle::String(s[0..first_grapheme_end].to_string().into())
751 },
752 }
753 }
754
755 #[inline]
756 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
757 match *computed {
758 ComputedTextEmphasisStyle::Keyword { fill, shape } => TextEmphasisStyle::Keyword {
759 fill,
760 shape: Some(shape),
761 },
762 ComputedTextEmphasisStyle::None => TextEmphasisStyle::None,
763 ComputedTextEmphasisStyle::String(ref string) => {
764 TextEmphasisStyle::String(string.clone())
765 },
766 }
767 }
768}
769
770impl Parse for TextEmphasisStyle {
771 fn parse<'i, 't>(
772 _context: &ParserContext,
773 input: &mut Parser<'i, 't>,
774 ) -> Result<Self, ParseError<'i>> {
775 if input
776 .try_parse(|input| input.expect_ident_matching("none"))
777 .is_ok()
778 {
779 return Ok(TextEmphasisStyle::None);
780 }
781
782 if let Ok(s) = input.try_parse(|i| i.expect_string().map(|s| s.as_ref().to_owned())) {
783 return Ok(TextEmphasisStyle::String(s.into()));
785 }
786
787 let mut shape = input.try_parse(TextEmphasisShapeKeyword::parse).ok();
789 let fill = input.try_parse(TextEmphasisFillMode::parse).ok();
790 if shape.is_none() {
791 shape = input.try_parse(TextEmphasisShapeKeyword::parse).ok();
792 }
793
794 if shape.is_none() && fill.is_none() {
795 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
796 }
797
798 let fill = fill.unwrap_or(TextEmphasisFillMode::Filled);
801
802 Ok(TextEmphasisStyle::Keyword { fill, shape })
805 }
806}
807
808#[derive(
809 Clone,
810 Copy,
811 Debug,
812 Eq,
813 MallocSizeOf,
814 PartialEq,
815 Parse,
816 Serialize,
817 SpecifiedValueInfo,
818 ToCss,
819 ToComputedValue,
820 ToResolvedValue,
821 ToShmem,
822 ToTyped,
823)]
824#[repr(C)]
825#[css(bitflags(
826 single = "auto",
827 mixed = "over,under,left,right",
828 validate_mixed = "Self::validate_and_simplify"
829))]
830pub struct TextEmphasisPosition(u8);
833bitflags! {
834 impl TextEmphasisPosition: u8 {
835 const AUTO = 1 << 0;
837 const OVER = 1 << 1;
839 const UNDER = 1 << 2;
841 const LEFT = 1 << 3;
843 const RIGHT = 1 << 4;
845 }
846}
847
848impl TextEmphasisPosition {
849 fn validate_and_simplify(&mut self) -> bool {
850 if self.intersects(Self::OVER) == self.intersects(Self::UNDER) {
852 return false;
853 }
854
855 if self.intersects(Self::LEFT) {
857 return !self.intersects(Self::RIGHT);
858 }
859
860 self.remove(Self::RIGHT); true
862 }
863}
864
865#[repr(u8)]
867#[derive(
868 Clone,
869 Copy,
870 Debug,
871 Eq,
872 MallocSizeOf,
873 Parse,
874 PartialEq,
875 SpecifiedValueInfo,
876 ToComputedValue,
877 ToCss,
878 ToResolvedValue,
879 ToShmem,
880 ToTyped,
881)]
882#[allow(missing_docs)]
883pub enum WordBreak {
884 Normal,
885 BreakAll,
886 KeepAll,
887 #[cfg(feature = "gecko")]
892 BreakWord,
893}
894
895#[repr(u8)]
897#[derive(
898 Clone,
899 Copy,
900 Debug,
901 Eq,
902 MallocSizeOf,
903 Parse,
904 PartialEq,
905 SpecifiedValueInfo,
906 ToComputedValue,
907 ToCss,
908 ToResolvedValue,
909 ToShmem,
910 ToTyped,
911)]
912#[allow(missing_docs)]
913pub enum TextJustify {
914 Auto,
915 None,
916 InterWord,
917 #[parse(aliases = "distribute")]
920 InterCharacter,
921}
922
923#[repr(u8)]
925#[derive(
926 Clone,
927 Copy,
928 Debug,
929 Eq,
930 MallocSizeOf,
931 Parse,
932 PartialEq,
933 SpecifiedValueInfo,
934 ToComputedValue,
935 ToCss,
936 ToResolvedValue,
937 ToShmem,
938 ToTyped,
939)]
940#[allow(missing_docs)]
941pub enum MozControlCharacterVisibility {
942 Hidden,
943 Visible,
944}
945
946#[cfg(feature = "gecko")]
947impl Default for MozControlCharacterVisibility {
948 fn default() -> Self {
949 if static_prefs::pref!("layout.css.control-characters.visible") {
950 Self::Visible
951 } else {
952 Self::Hidden
953 }
954 }
955}
956
957#[repr(u8)]
959#[derive(
960 Clone,
961 Copy,
962 Debug,
963 Eq,
964 MallocSizeOf,
965 Parse,
966 PartialEq,
967 SpecifiedValueInfo,
968 ToComputedValue,
969 ToCss,
970 ToResolvedValue,
971 ToShmem,
972 ToTyped,
973)]
974#[allow(missing_docs)]
975pub enum LineBreak {
976 Auto,
977 Loose,
978 Normal,
979 Strict,
980 Anywhere,
981}
982
983#[repr(u8)]
985#[derive(
986 Clone,
987 Copy,
988 Debug,
989 Eq,
990 MallocSizeOf,
991 Parse,
992 PartialEq,
993 SpecifiedValueInfo,
994 ToComputedValue,
995 ToCss,
996 ToResolvedValue,
997 ToShmem,
998 ToTyped,
999)]
1000#[allow(missing_docs)]
1001pub enum OverflowWrap {
1002 Normal,
1003 BreakWord,
1004 Anywhere,
1005}
1006
1007pub type TextIndent = GenericTextIndent<LengthPercentage>;
1012
1013impl Parse for TextIndent {
1014 fn parse<'i, 't>(
1015 context: &ParserContext,
1016 input: &mut Parser<'i, 't>,
1017 ) -> Result<Self, ParseError<'i>> {
1018 let mut length = None;
1019 let mut hanging = false;
1020 let mut each_line = false;
1021
1022 while !input.is_exhausted() {
1024 if length.is_none() {
1026 if let Ok(len) = input
1027 .try_parse(|i| LengthPercentage::parse_quirky(context, i, AllowQuirks::Yes))
1028 {
1029 length = Some(len);
1030 continue;
1031 }
1032 }
1033
1034 if cfg!(feature = "servo") {
1036 break;
1037 }
1038
1039 try_match_ident_ignore_ascii_case! { input,
1041 "hanging" if !hanging => hanging = true,
1042 "each-line" if !each_line => each_line = true,
1043 }
1044 }
1045
1046 if let Some(length) = length {
1048 Ok(Self {
1049 length,
1050 hanging,
1051 each_line,
1052 })
1053 } else {
1054 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1055 }
1056 }
1057}
1058
1059#[repr(u8)]
1063#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
1064#[derive(
1065 Clone,
1066 Copy,
1067 Debug,
1068 Eq,
1069 MallocSizeOf,
1070 Parse,
1071 PartialEq,
1072 SpecifiedValueInfo,
1073 ToComputedValue,
1074 ToCss,
1075 ToResolvedValue,
1076 ToShmem,
1077 ToTyped,
1078)]
1079#[allow(missing_docs)]
1080pub enum TextDecorationSkipInk {
1081 Auto,
1082 None,
1083 All,
1084}
1085
1086pub type TextDecorationLength = GenericTextDecorationLength<LengthPercentage>;
1088
1089impl TextDecorationLength {
1090 #[inline]
1092 pub fn auto() -> Self {
1093 GenericTextDecorationLength::Auto
1094 }
1095
1096 #[inline]
1098 pub fn is_auto(&self) -> bool {
1099 matches!(*self, GenericTextDecorationLength::Auto)
1100 }
1101}
1102
1103pub type TextDecorationInset = GenericTextDecorationInset<Length>;
1105
1106impl TextDecorationInset {
1107 #[inline]
1109 pub fn auto() -> Self {
1110 GenericTextDecorationInset::Auto
1111 }
1112
1113 #[inline]
1115 pub fn is_auto(&self) -> bool {
1116 matches!(*self, GenericTextDecorationInset::Auto)
1117 }
1118}
1119
1120impl Parse for TextDecorationInset {
1121 fn parse<'i, 't>(
1122 ctx: &ParserContext,
1123 input: &mut Parser<'i, 't>,
1124 ) -> Result<Self, ParseError<'i>> {
1125 if let Ok(start) = input.try_parse(|i| Length::parse(ctx, i)) {
1126 let end = input.try_parse(|i| Length::parse(ctx, i));
1127 let end = end.unwrap_or_else(|_| start.clone());
1128 return Ok(TextDecorationInset::Length { start, end });
1129 }
1130 input.expect_ident_matching("auto")?;
1131 Ok(TextDecorationInset::Auto)
1132 }
1133}
1134
1135#[derive(
1136 Clone,
1137 Copy,
1138 Debug,
1139 Eq,
1140 MallocSizeOf,
1141 Parse,
1142 PartialEq,
1143 SpecifiedValueInfo,
1144 ToComputedValue,
1145 ToResolvedValue,
1146 ToShmem,
1147 ToTyped,
1148)]
1149#[css(bitflags(
1150 single = "auto",
1151 mixed = "from-font,under,left,right",
1152 validate_mixed = "Self::validate_mixed_flags",
1153))]
1154#[repr(C)]
1155pub struct TextUnderlinePosition(u8);
1160bitflags! {
1161 impl TextUnderlinePosition: u8 {
1162 const AUTO = 0;
1164 const FROM_FONT = 1 << 0;
1166 const UNDER = 1 << 1;
1168 const LEFT = 1 << 2;
1170 const RIGHT = 1 << 3;
1172 }
1173}
1174
1175impl TextUnderlinePosition {
1176 fn validate_mixed_flags(&self) -> bool {
1177 if self.contains(Self::LEFT | Self::RIGHT) {
1178 return false;
1180 }
1181 if self.contains(Self::FROM_FONT | Self::UNDER) {
1182 return false;
1184 }
1185 true
1186 }
1187}
1188
1189impl ToCss for TextUnderlinePosition {
1190 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1191 where
1192 W: Write,
1193 {
1194 if self.is_empty() {
1195 return dest.write_str("auto");
1196 }
1197
1198 let mut writer = SequenceWriter::new(dest, " ");
1199 let mut any = false;
1200
1201 macro_rules! maybe_write {
1202 ($ident:ident => $str:expr) => {
1203 if self.contains(TextUnderlinePosition::$ident) {
1204 any = true;
1205 writer.raw_item($str)?;
1206 }
1207 };
1208 }
1209
1210 maybe_write!(FROM_FONT => "from-font");
1211 maybe_write!(UNDER => "under");
1212 maybe_write!(LEFT => "left");
1213 maybe_write!(RIGHT => "right");
1214
1215 debug_assert!(any);
1216
1217 Ok(())
1218 }
1219}
1220
1221#[repr(u8)]
1223#[derive(
1224 Clone,
1225 Copy,
1226 Debug,
1227 Eq,
1228 MallocSizeOf,
1229 PartialEq,
1230 ToComputedValue,
1231 ToResolvedValue,
1232 ToShmem,
1233 ToTyped,
1234)]
1235#[allow(missing_docs)]
1236pub enum RubyPosition {
1237 AlternateOver,
1238 AlternateUnder,
1239 Over,
1240 Under,
1241}
1242
1243impl Parse for RubyPosition {
1244 fn parse<'i, 't>(
1245 _context: &ParserContext,
1246 input: &mut Parser<'i, 't>,
1247 ) -> Result<RubyPosition, ParseError<'i>> {
1248 let alternate = input
1250 .try_parse(|i| i.expect_ident_matching("alternate"))
1251 .is_ok();
1252 if alternate && input.is_exhausted() {
1253 return Ok(RubyPosition::AlternateOver);
1254 }
1255 let over = try_match_ident_ignore_ascii_case! { input,
1257 "over" => true,
1258 "under" => false,
1259 };
1260 let alternate = alternate
1262 || input
1263 .try_parse(|i| i.expect_ident_matching("alternate"))
1264 .is_ok();
1265
1266 Ok(match (over, alternate) {
1267 (true, true) => RubyPosition::AlternateOver,
1268 (false, true) => RubyPosition::AlternateUnder,
1269 (true, false) => RubyPosition::Over,
1270 (false, false) => RubyPosition::Under,
1271 })
1272 }
1273}
1274
1275impl ToCss for RubyPosition {
1276 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1277 where
1278 W: Write,
1279 {
1280 dest.write_str(match self {
1281 RubyPosition::AlternateOver => "alternate",
1282 RubyPosition::AlternateUnder => "alternate under",
1283 RubyPosition::Over => "over",
1284 RubyPosition::Under => "under",
1285 })
1286 }
1287}
1288
1289impl SpecifiedValueInfo for RubyPosition {
1290 fn collect_completion_keywords(f: KeywordsCollectFn) {
1291 f(&["alternate", "over", "under"])
1292 }
1293}
1294
1295#[derive(
1307 Clone,
1308 Copy,
1309 Debug,
1310 Eq,
1311 MallocSizeOf,
1312 Parse,
1313 PartialEq,
1314 Serialize,
1315 SpecifiedValueInfo,
1316 ToCss,
1317 ToComputedValue,
1318 ToResolvedValue,
1319 ToShmem,
1320 ToTyped,
1321)]
1322#[css(bitflags(
1323 single = "normal,auto,no-autospace",
1324 mixed = "ideograph-alpha,ideograph-numeric,insert",
1327 ))]
1330#[repr(C)]
1331pub struct TextAutospace(u8);
1332bitflags! {
1333 impl TextAutospace: u8 {
1334 const NO_AUTOSPACE = 0;
1336
1337 const AUTO = 1 << 0;
1339
1340 const NORMAL = 1 << 1;
1342
1343 const IDEOGRAPH_ALPHA = 1 << 2;
1345
1346 const IDEOGRAPH_NUMERIC = 1 << 3;
1348
1349 const INSERT = 1 << 5;
1356
1357 }
1362}
1363
1364#[derive(
1373 Clone,
1374 Copy,
1375 Debug,
1376 Eq,
1377 FromPrimitive,
1378 Hash,
1379 MallocSizeOf,
1380 Parse,
1381 PartialEq,
1382 SpecifiedValueInfo,
1383 ToComputedValue,
1384 ToCss,
1385 ToResolvedValue,
1386 ToShmem,
1387 ToTyped,
1388)]
1389#[repr(u8)]
1390pub enum TextEdgeKeyword {
1394 Text,
1396 Ideographic,
1398 IdeographicInk,
1400 Cap,
1402 Ex,
1404 Alphabetic,
1406}
1407
1408impl TextEdgeKeyword {
1409 fn is_valid_for_over(&self) -> bool {
1410 match self {
1411 TextEdgeKeyword::Text
1412 | TextEdgeKeyword::Ideographic
1413 | TextEdgeKeyword::IdeographicInk
1414 | TextEdgeKeyword::Cap
1415 | TextEdgeKeyword::Ex => true,
1416 _ => false,
1417 }
1418 }
1419
1420 fn is_valid_for_under(&self) -> bool {
1421 match self {
1422 TextEdgeKeyword::Text
1423 | TextEdgeKeyword::Ideographic
1424 | TextEdgeKeyword::IdeographicInk
1425 | TextEdgeKeyword::Alphabetic => true,
1426 _ => false,
1427 }
1428 }
1429}
1430
1431#[derive(
1432 Clone,
1433 Copy,
1434 Debug,
1435 Eq,
1436 Hash,
1437 MallocSizeOf,
1438 PartialEq,
1439 SpecifiedValueInfo,
1440 ToComputedValue,
1441 ToResolvedValue,
1442 ToShmem,
1443 ToTyped,
1444)]
1445#[repr(C)]
1446pub struct TextEdge {
1456 pub over: TextEdgeKeyword,
1458 pub under: TextEdgeKeyword,
1460}
1461
1462impl Parse for TextEdge {
1463 fn parse<'i, 't>(
1464 _context: &ParserContext,
1465 input: &mut Parser<'i, 't>,
1466 ) -> Result<TextEdge, ParseError<'i>> {
1467 let first = TextEdgeKeyword::parse(input)?;
1468
1469 if let Ok(second) = input.try_parse(TextEdgeKeyword::parse) {
1470 if !first.is_valid_for_over() || !second.is_valid_for_under() {
1471 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1472 }
1473
1474 return Ok(TextEdge {
1475 over: first,
1476 under: second,
1477 });
1478 }
1479
1480 match (first.is_valid_for_over(), first.is_valid_for_under()) {
1484 (true, true) => Ok(TextEdge {
1485 over: first,
1486 under: first,
1487 }),
1488 (true, false) => Ok(TextEdge {
1489 over: first,
1490 under: TextEdgeKeyword::Text,
1491 }),
1492 (false, true) => Ok(TextEdge {
1493 over: TextEdgeKeyword::Text,
1494 under: first,
1495 }),
1496 _ => unreachable!("Parsed keyword will be valid for at least one edge"),
1497 }
1498 }
1499}
1500
1501impl ToCss for TextEdge {
1502 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1503 where
1504 W: Write,
1505 {
1506 match (self.over, self.under) {
1507 (over, TextEdgeKeyword::Text) if !over.is_valid_for_under() => over.to_css(dest),
1508 (TextEdgeKeyword::Text, under) if !under.is_valid_for_over() => under.to_css(dest),
1509 (over, under) => {
1510 over.to_css(dest)?;
1511
1512 if over != under {
1513 dest.write_char(' ')?;
1514 self.under.to_css(dest)?;
1515 }
1516
1517 Ok(())
1518 },
1519 }
1520 }
1521}
1522
1523#[derive(
1524 Clone,
1525 Copy,
1526 Debug,
1527 Eq,
1528 Hash,
1529 MallocSizeOf,
1530 Parse,
1531 PartialEq,
1532 SpecifiedValueInfo,
1533 ToComputedValue,
1534 ToCss,
1535 ToResolvedValue,
1536 ToShmem,
1537 ToTyped,
1538)]
1539#[repr(C, u8)]
1540pub enum TextBoxEdge {
1544 Auto,
1546 TextEdge(TextEdge),
1548}
1549
1550#[derive(
1551 Clone,
1552 Copy,
1553 Debug,
1554 Eq,
1555 MallocSizeOf,
1556 PartialEq,
1557 Parse,
1558 Serialize,
1559 SpecifiedValueInfo,
1560 ToCss,
1561 ToComputedValue,
1562 ToResolvedValue,
1563 ToShmem,
1564 ToTyped,
1565)]
1566#[css(bitflags(single = "none,trim-start,trim-end,trim-both"))]
1567#[repr(C)]
1568pub struct TextBoxTrim(u8);
1572bitflags! {
1573 impl TextBoxTrim: u8 {
1574 const NONE = 0;
1576 const TRIM_START = 1 << 0;
1578 const TRIM_END = 1 << 1;
1580 const TRIM_BOTH = Self::TRIM_START.0 | Self::TRIM_END.0;
1582 }
1583}
1584
1585impl TextBoxTrim {
1586 #[inline]
1588 pub fn none() -> Self {
1589 TextBoxTrim::NONE
1590 }
1591}