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_else(|_| pre_hyphen_length.clone());
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)]
559pub enum TextAlign {
560 Keyword(TextAlignKeyword),
562 MatchParent,
565 #[parse(condition = "ParserContext::chrome_rules_enabled")]
578 MozCenterOrInherit,
579}
580
581impl ToComputedValue for TextAlign {
582 type ComputedValue = TextAlignKeyword;
583
584 #[inline]
585 fn to_computed_value(&self, _context: &Context) -> Self::ComputedValue {
586 match *self {
587 TextAlign::Keyword(key) => key,
588 TextAlign::MatchParent => {
589 if _context.builder.is_root_element {
596 return TextAlignKeyword::Start;
597 }
598 let parent = _context
599 .builder
600 .get_parent_inherited_text()
601 .clone_text_align();
602 let ltr = _context.builder.inherited_writing_mode().is_bidi_ltr();
603 match (parent, ltr) {
604 (TextAlignKeyword::Start, true) => TextAlignKeyword::Left,
605 (TextAlignKeyword::Start, false) => TextAlignKeyword::Right,
606 (TextAlignKeyword::End, true) => TextAlignKeyword::Right,
607 (TextAlignKeyword::End, false) => TextAlignKeyword::Left,
608 _ => parent,
609 }
610 },
611 TextAlign::MozCenterOrInherit => {
612 let parent = _context
613 .builder
614 .get_parent_inherited_text()
615 .clone_text_align();
616 if parent == TextAlignKeyword::Start {
617 TextAlignKeyword::Center
618 } else {
619 parent
620 }
621 },
622 }
623 }
624
625 #[inline]
626 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
627 TextAlign::Keyword(*computed)
628 }
629}
630
631fn fill_mode_is_default_and_shape_exists(
632 fill: &TextEmphasisFillMode,
633 shape: &Option<TextEmphasisShapeKeyword>,
634) -> bool {
635 shape.is_some() && fill.is_filled()
636}
637
638#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
642#[allow(missing_docs)]
643#[typed(todo_derive_fields)]
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}