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<'i, 't>(
42 context: &ParserContext,
43 input: &mut Parser<'i, 't>,
44 ) -> Result<Self, ParseError<'i>> {
45 if input
46 .try_parse(|i| i.expect_ident_matching("normal"))
47 .is_ok()
48 {
49 return Ok(Spacing::Normal);
50 }
51 LengthPercentage::parse_quirky(context, input, AllowQuirks::Yes).map(Spacing::Value)
52 }
53}
54
55#[derive(
57 Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
58)]
59pub struct LetterSpacing(pub Spacing);
60
61impl ToComputedValue for LetterSpacing {
62 type ComputedValue = computed::LetterSpacing;
63
64 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
65 use computed::text::GenericLetterSpacing;
66 match self.0 {
67 Spacing::Normal => GenericLetterSpacing(computed::LengthPercentage::zero()),
68 Spacing::Value(ref v) => GenericLetterSpacing(v.to_computed_value(context)),
69 }
70 }
71
72 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
73 if computed.0.is_zero() {
74 return LetterSpacing(Spacing::Normal);
75 }
76 LetterSpacing(Spacing::Value(ToComputedValue::from_computed_value(
77 &computed.0,
78 )))
79 }
80}
81
82#[derive(
84 Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
85)]
86pub struct WordSpacing(pub Spacing);
87
88impl ToComputedValue for WordSpacing {
89 type ComputedValue = computed::WordSpacing;
90
91 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
92 match self.0 {
93 Spacing::Normal => computed::LengthPercentage::zero(),
94 Spacing::Value(ref v) => v.to_computed_value(context),
95 }
96 }
97
98 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
99 WordSpacing(Spacing::Value(ToComputedValue::from_computed_value(
100 computed,
101 )))
102 }
103}
104
105#[derive(
107 Clone,
108 Debug,
109 MallocSizeOf,
110 Parse,
111 PartialEq,
112 SpecifiedValueInfo,
113 ToComputedValue,
114 ToCss,
115 ToResolvedValue,
116 ToShmem,
117 ToTyped,
118)]
119#[repr(C, u8)]
120#[typed(todo_derive_fields)]
121pub enum HyphenateCharacter {
122 Auto,
124 String(crate::OwnedStr),
126}
127
128pub type HyphenateLimitChars = GenericHyphenateLimitChars<Integer>;
130
131impl Parse for HyphenateLimitChars {
132 fn parse<'i, 't>(
133 context: &ParserContext,
134 input: &mut Parser<'i, 't>,
135 ) -> Result<Self, ParseError<'i>> {
136 type IntegerOrAuto = NumberOrAuto<Integer>;
137
138 let total_word_length = IntegerOrAuto::parse(context, input)?;
139 let pre_hyphen_length = input
140 .try_parse(|i| IntegerOrAuto::parse(context, i))
141 .unwrap_or(IntegerOrAuto::Auto);
142 let post_hyphen_length = input
143 .try_parse(|i| IntegerOrAuto::parse(context, i))
144 .unwrap_or(pre_hyphen_length);
145 Ok(Self {
146 total_word_length,
147 pre_hyphen_length,
148 post_hyphen_length,
149 })
150 }
151}
152
153impl Parse for InitialLetter {
154 fn parse<'i, 't>(
155 context: &ParserContext,
156 input: &mut Parser<'i, 't>,
157 ) -> Result<Self, ParseError<'i>> {
158 if input
159 .try_parse(|i| i.expect_ident_matching("normal"))
160 .is_ok()
161 {
162 return Ok(Self::normal());
163 }
164 let size = Number::parse_at_least_one(context, input)?;
165 let sink = input
166 .try_parse(|i| Integer::parse_positive(context, i))
167 .unwrap_or_else(|_| crate::Zero::zero());
168 Ok(Self { size, sink })
169 }
170}
171
172#[derive(
174 Clone,
175 Debug,
176 Eq,
177 MallocSizeOf,
178 PartialEq,
179 Parse,
180 SpecifiedValueInfo,
181 ToComputedValue,
182 ToCss,
183 ToResolvedValue,
184 ToShmem,
185)]
186#[repr(C, u8)]
187pub enum TextOverflowSide {
188 Clip,
190 Ellipsis,
192 String(crate::values::AtomString),
194}
195
196#[derive(
197 Clone,
198 Debug,
199 Eq,
200 MallocSizeOf,
201 PartialEq,
202 SpecifiedValueInfo,
203 ToComputedValue,
204 ToResolvedValue,
205 ToShmem,
206 ToTyped,
207)]
208#[repr(C)]
209#[typed(todo_derive_fields)]
210pub struct TextOverflow {
219 pub first: TextOverflowSide,
221 pub second: TextOverflowSide,
223 pub sides_are_logical: bool,
225}
226
227impl Parse for TextOverflow {
228 fn parse<'i, 't>(
229 context: &ParserContext,
230 input: &mut Parser<'i, 't>,
231 ) -> Result<TextOverflow, ParseError<'i>> {
232 let first = TextOverflowSide::parse(context, input)?;
233 Ok(
234 if let Ok(second) = input.try_parse(|input| TextOverflowSide::parse(context, input)) {
235 Self {
236 first,
237 second,
238 sides_are_logical: false,
239 }
240 } else {
241 Self {
242 first: TextOverflowSide::Clip,
243 second: first,
244 sides_are_logical: true,
245 }
246 },
247 )
248 }
249}
250
251impl TextOverflow {
252 pub fn get_initial_value() -> TextOverflow {
254 TextOverflow {
255 first: TextOverflowSide::Clip,
256 second: TextOverflowSide::Clip,
257 sides_are_logical: true,
258 }
259 }
260}
261
262impl ToCss for TextOverflow {
263 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
264 where
265 W: Write,
266 {
267 if self.sides_are_logical {
268 debug_assert_eq!(self.first, TextOverflowSide::Clip);
269 self.second.to_css(dest)?;
270 } else {
271 self.first.to_css(dest)?;
272 dest.write_char(' ')?;
273 self.second.to_css(dest)?;
274 }
275 Ok(())
276 }
277}
278
279#[derive(
280 Clone,
281 Copy,
282 Debug,
283 Eq,
284 MallocSizeOf,
285 PartialEq,
286 Parse,
287 Serialize,
288 SpecifiedValueInfo,
289 ToCss,
290 ToComputedValue,
291 ToResolvedValue,
292 ToShmem,
293 ToTyped,
294)]
295#[cfg_attr(
296 feature = "gecko",
297 css(bitflags(
298 single = "none,spelling-error,grammar-error",
299 mixed = "underline,overline,line-through,blink",
300 ))
301)]
302#[cfg_attr(
303 not(feature = "gecko"),
304 css(bitflags(single = "none", mixed = "underline,overline,line-through,blink",))
305)]
306#[repr(C)]
307pub struct TextDecorationLine(u8);
309bitflags! {
310 impl TextDecorationLine: u8 {
311 const NONE = 0;
313 const UNDERLINE = 1 << 0;
315 const OVERLINE = 1 << 1;
317 const LINE_THROUGH = 1 << 2;
319 const BLINK = 1 << 3;
321 const SPELLING_ERROR = 1 << 4;
323 const GRAMMAR_ERROR = 1 << 5;
325 #[cfg(feature = "gecko")]
333 const COLOR_OVERRIDE = 1 << 7;
334 }
335}
336
337impl Default for TextDecorationLine {
338 fn default() -> Self {
339 TextDecorationLine::NONE
340 }
341}
342
343impl TextDecorationLine {
344 #[inline]
345 pub fn none() -> Self {
347 TextDecorationLine::NONE
348 }
349}
350
351#[derive(
352 Clone,
353 Copy,
354 Debug,
355 Eq,
356 MallocSizeOf,
357 PartialEq,
358 SpecifiedValueInfo,
359 ToComputedValue,
360 ToCss,
361 ToResolvedValue,
362 ToShmem,
363)]
364#[repr(C)]
365pub enum TextTransformCase {
367 None,
369 Uppercase,
371 Lowercase,
373 Capitalize,
375 #[cfg(feature = "gecko")]
377 MathAuto,
378}
379
380#[derive(
381 Clone,
382 Copy,
383 Debug,
384 Eq,
385 MallocSizeOf,
386 PartialEq,
387 Parse,
388 Serialize,
389 SpecifiedValueInfo,
390 ToCss,
391 ToComputedValue,
392 ToResolvedValue,
393 ToShmem,
394 ToTyped,
395)]
396#[cfg_attr(
397 feature = "gecko",
398 css(bitflags(
399 single = "none,math-auto",
400 mixed = "uppercase,lowercase,capitalize,full-width,full-size-kana",
401 validate_mixed = "Self::validate_mixed_flags",
402 ))
403)]
404#[cfg_attr(
405 not(feature = "gecko"),
406 css(bitflags(
407 single = "none",
408 mixed = "uppercase,lowercase,capitalize,full-width,full-size-kana",
409 validate_mixed = "Self::validate_mixed_flags",
410 ))
411)]
412#[repr(C)]
413pub struct TextTransform(u8);
418bitflags! {
419 impl TextTransform: u8 {
420 const NONE = 0;
422 const UPPERCASE = 1 << 0;
424 const LOWERCASE = 1 << 1;
426 const CAPITALIZE = 1 << 2;
428 #[cfg(feature = "gecko")]
430 const MATH_AUTO = 1 << 3;
431
432 #[cfg(feature = "gecko")]
434 const CASE_TRANSFORMS = Self::UPPERCASE.0 | Self::LOWERCASE.0 | Self::CAPITALIZE.0 | Self::MATH_AUTO.0;
435 #[cfg(feature = "servo")]
437 const CASE_TRANSFORMS = Self::UPPERCASE.0 | Self::LOWERCASE.0 | Self::CAPITALIZE.0;
438
439 const FULL_WIDTH = 1 << 4;
441 const FULL_SIZE_KANA = 1 << 5;
443 }
444}
445
446impl TextTransform {
447 #[inline]
449 pub fn none() -> Self {
450 Self::NONE
451 }
452
453 #[inline]
455 pub fn is_none(self) -> bool {
456 self == Self::NONE
457 }
458
459 fn validate_mixed_flags(&self) -> bool {
460 let case = self.intersection(Self::CASE_TRANSFORMS);
461 case.is_empty() || case.bits().is_power_of_two()
463 }
464
465 pub fn case(&self) -> TextTransformCase {
467 match *self & Self::CASE_TRANSFORMS {
468 Self::NONE => TextTransformCase::None,
469 Self::UPPERCASE => TextTransformCase::Uppercase,
470 Self::LOWERCASE => TextTransformCase::Lowercase,
471 Self::CAPITALIZE => TextTransformCase::Capitalize,
472 #[cfg(feature = "gecko")]
473 Self::MATH_AUTO => TextTransformCase::MathAuto,
474 _ => unreachable!("Case bits are exclusive with each other"),
475 }
476 }
477}
478
479#[derive(
481 Clone,
482 Copy,
483 Debug,
484 Eq,
485 FromPrimitive,
486 Hash,
487 MallocSizeOf,
488 Parse,
489 PartialEq,
490 SpecifiedValueInfo,
491 ToComputedValue,
492 ToCss,
493 ToResolvedValue,
494 ToShmem,
495 ToTyped,
496)]
497#[allow(missing_docs)]
498#[repr(u8)]
499pub enum TextAlignLast {
500 Auto,
501 Start,
502 End,
503 Left,
504 Right,
505 Center,
506 Justify,
507}
508
509#[derive(
511 Clone,
512 Copy,
513 Debug,
514 Eq,
515 FromPrimitive,
516 Hash,
517 MallocSizeOf,
518 Parse,
519 PartialEq,
520 SpecifiedValueInfo,
521 ToComputedValue,
522 ToCss,
523 ToResolvedValue,
524 ToShmem,
525 ToTyped,
526)]
527#[allow(missing_docs)]
528#[repr(u8)]
529pub enum TextAlignKeyword {
530 Start,
531 Left,
532 Right,
533 Center,
534 Justify,
535 End,
536 #[parse(aliases = "-webkit-center")]
537 MozCenter,
538 #[parse(aliases = "-webkit-left")]
539 MozLeft,
540 #[parse(aliases = "-webkit-right")]
541 MozRight,
542}
543
544#[derive(
546 Clone,
547 Copy,
548 Debug,
549 Eq,
550 Hash,
551 MallocSizeOf,
552 Parse,
553 PartialEq,
554 SpecifiedValueInfo,
555 ToCss,
556 ToShmem,
557 ToTyped,
558)]
559#[typed(todo_derive_fields)]
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)]
644#[typed(todo_derive_fields)]
645pub enum TextEmphasisStyle {
646 Keyword {
648 #[css(contextual_skip_if = "fill_mode_is_default_and_shape_exists")]
649 fill: TextEmphasisFillMode,
650 shape: Option<TextEmphasisShapeKeyword>,
651 },
652 None,
654 String(crate::OwnedStr),
656}
657
658#[derive(
660 Clone,
661 Copy,
662 Debug,
663 MallocSizeOf,
664 Parse,
665 PartialEq,
666 SpecifiedValueInfo,
667 ToCss,
668 ToComputedValue,
669 ToResolvedValue,
670 ToShmem,
671)]
672#[repr(u8)]
673pub enum TextEmphasisFillMode {
674 Filled,
676 Open,
678}
679
680impl TextEmphasisFillMode {
681 #[inline]
683 pub fn is_filled(&self) -> bool {
684 matches!(*self, TextEmphasisFillMode::Filled)
685 }
686}
687
688#[derive(
690 Clone,
691 Copy,
692 Debug,
693 Eq,
694 MallocSizeOf,
695 Parse,
696 PartialEq,
697 SpecifiedValueInfo,
698 ToCss,
699 ToComputedValue,
700 ToResolvedValue,
701 ToShmem,
702)]
703#[repr(u8)]
704pub enum TextEmphasisShapeKeyword {
705 Dot,
707 Circle,
709 DoubleCircle,
711 Triangle,
713 Sesame,
715}
716
717impl ToComputedValue for TextEmphasisStyle {
718 type ComputedValue = ComputedTextEmphasisStyle;
719
720 #[inline]
721 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
722 match *self {
723 TextEmphasisStyle::Keyword { fill, shape } => {
724 let shape = shape.unwrap_or_else(|| {
725 if context.style().get_inherited_box().clone_writing_mode()
731 == SpecifiedWritingMode::HorizontalTb
732 {
733 TextEmphasisShapeKeyword::Circle
734 } else {
735 TextEmphasisShapeKeyword::Sesame
736 }
737 });
738 ComputedTextEmphasisStyle::Keyword { fill, shape }
739 },
740 TextEmphasisStyle::None => ComputedTextEmphasisStyle::None,
741 TextEmphasisStyle::String(ref s) => {
742 let first_grapheme_end = GraphemeClusterSegmenter::new()
748 .segment_str(s)
749 .nth(1)
750 .unwrap_or(0);
751 ComputedTextEmphasisStyle::String(s[0..first_grapheme_end].to_string().into())
752 },
753 }
754 }
755
756 #[inline]
757 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
758 match *computed {
759 ComputedTextEmphasisStyle::Keyword { fill, shape } => TextEmphasisStyle::Keyword {
760 fill,
761 shape: Some(shape),
762 },
763 ComputedTextEmphasisStyle::None => TextEmphasisStyle::None,
764 ComputedTextEmphasisStyle::String(ref string) => {
765 TextEmphasisStyle::String(string.clone())
766 },
767 }
768 }
769}
770
771impl Parse for TextEmphasisStyle {
772 fn parse<'i, 't>(
773 _context: &ParserContext,
774 input: &mut Parser<'i, 't>,
775 ) -> Result<Self, ParseError<'i>> {
776 if input
777 .try_parse(|input| input.expect_ident_matching("none"))
778 .is_ok()
779 {
780 return Ok(TextEmphasisStyle::None);
781 }
782
783 if let Ok(s) = input.try_parse(|i| i.expect_string().map(|s| s.as_ref().to_owned())) {
784 return Ok(TextEmphasisStyle::String(s.into()));
786 }
787
788 let mut shape = input.try_parse(TextEmphasisShapeKeyword::parse).ok();
790 let fill = input.try_parse(TextEmphasisFillMode::parse).ok();
791 if shape.is_none() {
792 shape = input.try_parse(TextEmphasisShapeKeyword::parse).ok();
793 }
794
795 if shape.is_none() && fill.is_none() {
796 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
797 }
798
799 let fill = fill.unwrap_or(TextEmphasisFillMode::Filled);
802
803 Ok(TextEmphasisStyle::Keyword { fill, shape })
806 }
807}
808
809#[derive(
810 Clone,
811 Copy,
812 Debug,
813 Eq,
814 MallocSizeOf,
815 PartialEq,
816 Parse,
817 Serialize,
818 SpecifiedValueInfo,
819 ToCss,
820 ToComputedValue,
821 ToResolvedValue,
822 ToShmem,
823 ToTyped,
824)]
825#[repr(C)]
826#[css(bitflags(
827 single = "auto",
828 mixed = "over,under,left,right",
829 validate_mixed = "Self::validate_and_simplify"
830))]
831pub struct TextEmphasisPosition(u8);
834bitflags! {
835 impl TextEmphasisPosition: u8 {
836 const AUTO = 1 << 0;
838 const OVER = 1 << 1;
840 const UNDER = 1 << 2;
842 const LEFT = 1 << 3;
844 const RIGHT = 1 << 4;
846 }
847}
848
849impl TextEmphasisPosition {
850 fn validate_and_simplify(&mut self) -> bool {
851 if self.intersects(Self::OVER) == self.intersects(Self::UNDER) {
853 return false;
854 }
855
856 if self.intersects(Self::LEFT) {
858 return !self.intersects(Self::RIGHT);
859 }
860
861 self.remove(Self::RIGHT); true
863 }
864}
865
866#[repr(u8)]
868#[derive(
869 Clone,
870 Copy,
871 Debug,
872 Eq,
873 MallocSizeOf,
874 Parse,
875 PartialEq,
876 SpecifiedValueInfo,
877 ToComputedValue,
878 ToCss,
879 ToResolvedValue,
880 ToShmem,
881 ToTyped,
882)]
883#[allow(missing_docs)]
884pub enum WordBreak {
885 Normal,
886 BreakAll,
887 KeepAll,
888 #[cfg(feature = "gecko")]
893 BreakWord,
894}
895
896#[repr(u8)]
898#[derive(
899 Clone,
900 Copy,
901 Debug,
902 Eq,
903 MallocSizeOf,
904 Parse,
905 PartialEq,
906 SpecifiedValueInfo,
907 ToComputedValue,
908 ToCss,
909 ToResolvedValue,
910 ToShmem,
911 ToTyped,
912)]
913#[allow(missing_docs)]
914pub enum TextJustify {
915 Auto,
916 None,
917 InterWord,
918 #[parse(aliases = "distribute")]
921 InterCharacter,
922}
923
924#[repr(u8)]
926#[derive(
927 Clone,
928 Copy,
929 Debug,
930 Eq,
931 MallocSizeOf,
932 Parse,
933 PartialEq,
934 SpecifiedValueInfo,
935 ToComputedValue,
936 ToCss,
937 ToResolvedValue,
938 ToShmem,
939 ToTyped,
940)]
941#[allow(missing_docs)]
942pub enum MozControlCharacterVisibility {
943 Hidden,
944 Visible,
945}
946
947#[cfg(feature = "gecko")]
948impl Default for MozControlCharacterVisibility {
949 fn default() -> Self {
950 if static_prefs::pref!("layout.css.control-characters.visible") {
951 Self::Visible
952 } else {
953 Self::Hidden
954 }
955 }
956}
957
958#[repr(u8)]
960#[derive(
961 Clone,
962 Copy,
963 Debug,
964 Eq,
965 MallocSizeOf,
966 Parse,
967 PartialEq,
968 SpecifiedValueInfo,
969 ToComputedValue,
970 ToCss,
971 ToResolvedValue,
972 ToShmem,
973 ToTyped,
974)]
975#[allow(missing_docs)]
976pub enum LineBreak {
977 Auto,
978 Loose,
979 Normal,
980 Strict,
981 Anywhere,
982}
983
984#[repr(u8)]
986#[derive(
987 Clone,
988 Copy,
989 Debug,
990 Eq,
991 MallocSizeOf,
992 Parse,
993 PartialEq,
994 SpecifiedValueInfo,
995 ToComputedValue,
996 ToCss,
997 ToResolvedValue,
998 ToShmem,
999 ToTyped,
1000)]
1001#[allow(missing_docs)]
1002pub enum OverflowWrap {
1003 Normal,
1004 BreakWord,
1005 Anywhere,
1006}
1007
1008pub type TextIndent = GenericTextIndent<LengthPercentage>;
1013
1014impl Parse for TextIndent {
1015 fn parse<'i, 't>(
1016 context: &ParserContext,
1017 input: &mut Parser<'i, 't>,
1018 ) -> Result<Self, ParseError<'i>> {
1019 let mut length = None;
1020 let mut hanging = false;
1021 let mut each_line = false;
1022
1023 while !input.is_exhausted() {
1025 if length.is_none() {
1027 if let Ok(len) = input
1028 .try_parse(|i| LengthPercentage::parse_quirky(context, i, AllowQuirks::Yes))
1029 {
1030 length = Some(len);
1031 continue;
1032 }
1033 }
1034
1035 if cfg!(feature = "servo") {
1037 break;
1038 }
1039
1040 try_match_ident_ignore_ascii_case! { input,
1042 "hanging" if !hanging => hanging = true,
1043 "each-line" if !each_line => each_line = true,
1044 }
1045 }
1046
1047 if let Some(length) = length {
1049 Ok(Self {
1050 length,
1051 hanging,
1052 each_line,
1053 })
1054 } else {
1055 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1056 }
1057 }
1058}
1059
1060#[repr(u8)]
1064#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
1065#[derive(
1066 Clone,
1067 Copy,
1068 Debug,
1069 Eq,
1070 MallocSizeOf,
1071 Parse,
1072 PartialEq,
1073 SpecifiedValueInfo,
1074 ToComputedValue,
1075 ToCss,
1076 ToResolvedValue,
1077 ToShmem,
1078 ToTyped,
1079)]
1080#[allow(missing_docs)]
1081pub enum TextDecorationSkipInk {
1082 Auto,
1083 None,
1084 All,
1085}
1086
1087pub type TextDecorationLength = GenericTextDecorationLength<LengthPercentage>;
1089
1090impl TextDecorationLength {
1091 #[inline]
1093 pub fn auto() -> Self {
1094 GenericTextDecorationLength::Auto
1095 }
1096
1097 #[inline]
1099 pub fn is_auto(&self) -> bool {
1100 matches!(*self, GenericTextDecorationLength::Auto)
1101 }
1102}
1103
1104pub type TextDecorationInset = GenericTextDecorationInset<Length>;
1106
1107impl TextDecorationInset {
1108 #[inline]
1110 pub fn auto() -> Self {
1111 GenericTextDecorationInset::Auto
1112 }
1113
1114 #[inline]
1116 pub fn is_auto(&self) -> bool {
1117 matches!(*self, GenericTextDecorationInset::Auto)
1118 }
1119}
1120
1121impl Parse for TextDecorationInset {
1122 fn parse<'i, 't>(
1123 ctx: &ParserContext,
1124 input: &mut Parser<'i, 't>,
1125 ) -> Result<Self, ParseError<'i>> {
1126 if let Ok(start) = input.try_parse(|i| Length::parse(ctx, i)) {
1127 let end = input.try_parse(|i| Length::parse(ctx, i));
1128 let end = end.unwrap_or_else(|_| start.clone());
1129 return Ok(TextDecorationInset::Length { start, end });
1130 }
1131 input.expect_ident_matching("auto")?;
1132 Ok(TextDecorationInset::Auto)
1133 }
1134}
1135
1136#[derive(
1137 Clone,
1138 Copy,
1139 Debug,
1140 Eq,
1141 MallocSizeOf,
1142 Parse,
1143 PartialEq,
1144 SpecifiedValueInfo,
1145 ToComputedValue,
1146 ToResolvedValue,
1147 ToShmem,
1148 ToTyped,
1149)]
1150#[css(bitflags(
1151 single = "auto",
1152 mixed = "from-font,under,left,right",
1153 validate_mixed = "Self::validate_mixed_flags",
1154))]
1155#[repr(C)]
1156pub struct TextUnderlinePosition(u8);
1161bitflags! {
1162 impl TextUnderlinePosition: u8 {
1163 const AUTO = 0;
1165 const FROM_FONT = 1 << 0;
1167 const UNDER = 1 << 1;
1169 const LEFT = 1 << 2;
1171 const RIGHT = 1 << 3;
1173 }
1174}
1175
1176impl TextUnderlinePosition {
1177 fn validate_mixed_flags(&self) -> bool {
1178 if self.contains(Self::LEFT | Self::RIGHT) {
1179 return false;
1181 }
1182 if self.contains(Self::FROM_FONT | Self::UNDER) {
1183 return false;
1185 }
1186 true
1187 }
1188}
1189
1190impl ToCss for TextUnderlinePosition {
1191 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1192 where
1193 W: Write,
1194 {
1195 if self.is_empty() {
1196 return dest.write_str("auto");
1197 }
1198
1199 let mut writer = SequenceWriter::new(dest, " ");
1200 let mut any = false;
1201
1202 macro_rules! maybe_write {
1203 ($ident:ident => $str:expr) => {
1204 if self.contains(TextUnderlinePosition::$ident) {
1205 any = true;
1206 writer.raw_item($str)?;
1207 }
1208 };
1209 }
1210
1211 maybe_write!(FROM_FONT => "from-font");
1212 maybe_write!(UNDER => "under");
1213 maybe_write!(LEFT => "left");
1214 maybe_write!(RIGHT => "right");
1215
1216 debug_assert!(any);
1217
1218 Ok(())
1219 }
1220}
1221
1222#[repr(u8)]
1224#[derive(
1225 Clone,
1226 Copy,
1227 Debug,
1228 Eq,
1229 MallocSizeOf,
1230 PartialEq,
1231 ToComputedValue,
1232 ToResolvedValue,
1233 ToShmem,
1234 ToTyped,
1235)]
1236#[allow(missing_docs)]
1237pub enum RubyPosition {
1238 AlternateOver,
1239 AlternateUnder,
1240 Over,
1241 Under,
1242}
1243
1244impl Parse for RubyPosition {
1245 fn parse<'i, 't>(
1246 _context: &ParserContext,
1247 input: &mut Parser<'i, 't>,
1248 ) -> Result<RubyPosition, ParseError<'i>> {
1249 let alternate = input
1251 .try_parse(|i| i.expect_ident_matching("alternate"))
1252 .is_ok();
1253 if alternate && input.is_exhausted() {
1254 return Ok(RubyPosition::AlternateOver);
1255 }
1256 let over = try_match_ident_ignore_ascii_case! { input,
1258 "over" => true,
1259 "under" => false,
1260 };
1261 let alternate = alternate
1263 || input
1264 .try_parse(|i| i.expect_ident_matching("alternate"))
1265 .is_ok();
1266
1267 Ok(match (over, alternate) {
1268 (true, true) => RubyPosition::AlternateOver,
1269 (false, true) => RubyPosition::AlternateUnder,
1270 (true, false) => RubyPosition::Over,
1271 (false, false) => RubyPosition::Under,
1272 })
1273 }
1274}
1275
1276impl ToCss for RubyPosition {
1277 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1278 where
1279 W: Write,
1280 {
1281 dest.write_str(match self {
1282 RubyPosition::AlternateOver => "alternate",
1283 RubyPosition::AlternateUnder => "alternate under",
1284 RubyPosition::Over => "over",
1285 RubyPosition::Under => "under",
1286 })
1287 }
1288}
1289
1290impl SpecifiedValueInfo for RubyPosition {
1291 fn collect_completion_keywords(f: KeywordsCollectFn) {
1292 f(&["alternate", "over", "under"])
1293 }
1294}
1295
1296#[derive(
1308 Clone,
1309 Copy,
1310 Debug,
1311 Eq,
1312 MallocSizeOf,
1313 Parse,
1314 PartialEq,
1315 Serialize,
1316 SpecifiedValueInfo,
1317 ToCss,
1318 ToComputedValue,
1319 ToResolvedValue,
1320 ToShmem,
1321 ToTyped,
1322)]
1323#[css(bitflags(
1324 single = "normal,auto,no-autospace",
1325 mixed = "ideograph-alpha,ideograph-numeric,insert",
1328 ))]
1331#[repr(C)]
1332pub struct TextAutospace(u8);
1333bitflags! {
1334 impl TextAutospace: u8 {
1335 const NO_AUTOSPACE = 0;
1337
1338 const AUTO = 1 << 0;
1340
1341 const NORMAL = 1 << 1;
1343
1344 const IDEOGRAPH_ALPHA = 1 << 2;
1346
1347 const IDEOGRAPH_NUMERIC = 1 << 3;
1349
1350 const INSERT = 1 << 5;
1357
1358 }
1363}
1364
1365#[derive(
1374 Clone,
1375 Copy,
1376 Debug,
1377 Eq,
1378 FromPrimitive,
1379 Hash,
1380 MallocSizeOf,
1381 Parse,
1382 PartialEq,
1383 SpecifiedValueInfo,
1384 ToComputedValue,
1385 ToCss,
1386 ToResolvedValue,
1387 ToShmem,
1388 ToTyped,
1389)]
1390#[repr(u8)]
1391pub enum TextEdgeKeyword {
1395 Text,
1397 Ideographic,
1399 IdeographicInk,
1401 Cap,
1403 Ex,
1405 Alphabetic,
1407}
1408
1409impl TextEdgeKeyword {
1410 fn is_valid_for_over(&self) -> bool {
1411 match self {
1412 TextEdgeKeyword::Text
1413 | TextEdgeKeyword::Ideographic
1414 | TextEdgeKeyword::IdeographicInk
1415 | TextEdgeKeyword::Cap
1416 | TextEdgeKeyword::Ex => true,
1417 _ => false,
1418 }
1419 }
1420
1421 fn is_valid_for_under(&self) -> bool {
1422 match self {
1423 TextEdgeKeyword::Text
1424 | TextEdgeKeyword::Ideographic
1425 | TextEdgeKeyword::IdeographicInk
1426 | TextEdgeKeyword::Alphabetic => true,
1427 _ => false,
1428 }
1429 }
1430}
1431
1432#[derive(
1433 Clone,
1434 Copy,
1435 Debug,
1436 Eq,
1437 Hash,
1438 MallocSizeOf,
1439 PartialEq,
1440 SpecifiedValueInfo,
1441 ToComputedValue,
1442 ToResolvedValue,
1443 ToShmem,
1444 ToTyped,
1445)]
1446#[repr(C)]
1447pub struct TextEdge {
1457 pub over: TextEdgeKeyword,
1459 pub under: TextEdgeKeyword,
1461}
1462
1463impl Parse for TextEdge {
1464 fn parse<'i, 't>(
1465 _context: &ParserContext,
1466 input: &mut Parser<'i, 't>,
1467 ) -> Result<TextEdge, ParseError<'i>> {
1468 let first = TextEdgeKeyword::parse(input)?;
1469
1470 if let Ok(second) = input.try_parse(TextEdgeKeyword::parse) {
1471 if !first.is_valid_for_over() || !second.is_valid_for_under() {
1472 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1473 }
1474
1475 return Ok(TextEdge {
1476 over: first,
1477 under: second,
1478 });
1479 }
1480
1481 match (first.is_valid_for_over(), first.is_valid_for_under()) {
1485 (true, true) => Ok(TextEdge {
1486 over: first,
1487 under: first,
1488 }),
1489 (true, false) => Ok(TextEdge {
1490 over: first,
1491 under: TextEdgeKeyword::Text,
1492 }),
1493 (false, true) => Ok(TextEdge {
1494 over: TextEdgeKeyword::Text,
1495 under: first,
1496 }),
1497 _ => unreachable!("Parsed keyword will be valid for at least one edge"),
1498 }
1499 }
1500}
1501
1502impl ToCss for TextEdge {
1503 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1504 where
1505 W: Write,
1506 {
1507 match (self.over, self.under) {
1508 (over, TextEdgeKeyword::Text) if !over.is_valid_for_under() => over.to_css(dest),
1509 (TextEdgeKeyword::Text, under) if !under.is_valid_for_over() => under.to_css(dest),
1510 (over, under) => {
1511 over.to_css(dest)?;
1512
1513 if over != under {
1514 dest.write_char(' ')?;
1515 self.under.to_css(dest)?;
1516 }
1517
1518 Ok(())
1519 },
1520 }
1521 }
1522}
1523
1524#[derive(
1525 Clone,
1526 Copy,
1527 Debug,
1528 Eq,
1529 Hash,
1530 MallocSizeOf,
1531 Parse,
1532 PartialEq,
1533 SpecifiedValueInfo,
1534 ToComputedValue,
1535 ToCss,
1536 ToResolvedValue,
1537 ToShmem,
1538 ToTyped,
1539)]
1540#[repr(C, u8)]
1541pub enum TextBoxEdge {
1545 Auto,
1547 TextEdge(TextEdge),
1549}
1550
1551#[derive(
1552 Clone,
1553 Copy,
1554 Debug,
1555 Eq,
1556 MallocSizeOf,
1557 PartialEq,
1558 Parse,
1559 Serialize,
1560 SpecifiedValueInfo,
1561 ToCss,
1562 ToComputedValue,
1563 ToResolvedValue,
1564 ToShmem,
1565 ToTyped,
1566)]
1567#[css(bitflags(single = "none,trim-start,trim-end,trim-both"))]
1568#[repr(C)]
1569pub struct TextBoxTrim(u8);
1573bitflags! {
1574 impl TextBoxTrim: u8 {
1575 const NONE = 0;
1577 const TRIM_START = 1 << 0;
1579 const TRIM_END = 1 << 1;
1581 const TRIM_BOTH = Self::TRIM_START.0 | Self::TRIM_END.0;
1583 }
1584}
1585
1586impl TextBoxTrim {
1587 #[inline]
1589 pub fn none() -> Self {
1590 TextBoxTrim::NONE
1591 }
1592}