1use crate::parser::{Parse, ParserContext};
8use crate::properties::longhands::writing_mode::computed_value::T as SpecifiedWritingMode;
9use crate::values::computed;
10use crate::values::computed::text::TextEmphasisStyle as ComputedTextEmphasisStyle;
11use crate::values::computed::{Context, ToComputedValue};
12use crate::values::generics::text::{
13 GenericHyphenateLimitChars, GenericInitialLetter, GenericTextDecorationLength,
14 GenericTextDecorationTrim, GenericTextIndent,
15};
16use crate::values::generics::NumberOrAuto;
17use crate::values::specified::length::{Length, LengthPercentage};
18use crate::values::specified::{AllowQuirks, Integer, Number};
19use crate::Zero;
20use cssparser::Parser;
21use icu_segmenter::GraphemeClusterSegmenter;
22use std::fmt::{self, Write};
23use style_traits::values::SequenceWriter;
24use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
25use style_traits::{KeywordsCollectFn, SpecifiedValueInfo};
26
27pub type InitialLetter = GenericInitialLetter<Number, Integer>;
29
30#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
32pub enum Spacing {
33 Normal,
35 Value(LengthPercentage),
37}
38
39impl Parse for Spacing {
40 fn parse<'i, 't>(
41 context: &ParserContext,
42 input: &mut Parser<'i, 't>,
43 ) -> Result<Self, ParseError<'i>> {
44 if input
45 .try_parse(|i| i.expect_ident_matching("normal"))
46 .is_ok()
47 {
48 return Ok(Spacing::Normal);
49 }
50 LengthPercentage::parse_quirky(context, input, AllowQuirks::Yes).map(Spacing::Value)
51 }
52}
53
54#[derive(
56 Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
57)]
58pub struct LetterSpacing(pub Spacing);
59
60impl ToComputedValue for LetterSpacing {
61 type ComputedValue = computed::LetterSpacing;
62
63 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
64 use computed::text::GenericLetterSpacing;
65 match self.0 {
66 Spacing::Normal => GenericLetterSpacing(computed::LengthPercentage::zero()),
67 Spacing::Value(ref v) => GenericLetterSpacing(v.to_computed_value(context)),
68 }
69 }
70
71 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
72 if computed.0.is_zero() {
73 return LetterSpacing(Spacing::Normal);
74 }
75 LetterSpacing(Spacing::Value(ToComputedValue::from_computed_value(
76 &computed.0,
77 )))
78 }
79}
80
81#[derive(
83 Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
84)]
85pub struct WordSpacing(pub Spacing);
86
87impl ToComputedValue for WordSpacing {
88 type ComputedValue = computed::WordSpacing;
89
90 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
91 match self.0 {
92 Spacing::Normal => computed::LengthPercentage::zero(),
93 Spacing::Value(ref v) => v.to_computed_value(context),
94 }
95 }
96
97 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
98 WordSpacing(Spacing::Value(ToComputedValue::from_computed_value(
99 computed,
100 )))
101 }
102}
103
104#[derive(
106 Clone,
107 Debug,
108 MallocSizeOf,
109 Parse,
110 PartialEq,
111 SpecifiedValueInfo,
112 ToComputedValue,
113 ToCss,
114 ToResolvedValue,
115 ToShmem,
116 ToTyped,
117)]
118#[repr(C, u8)]
119pub enum HyphenateCharacter {
120 Auto,
122 String(crate::OwnedStr),
124}
125
126pub type HyphenateLimitChars = GenericHyphenateLimitChars<Integer>;
128
129impl Parse for HyphenateLimitChars {
130 fn parse<'i, 't>(
131 context: &ParserContext,
132 input: &mut Parser<'i, 't>,
133 ) -> Result<Self, ParseError<'i>> {
134 type IntegerOrAuto = NumberOrAuto<Integer>;
135
136 let total_word_length = IntegerOrAuto::parse(context, input)?;
137 let pre_hyphen_length = input
138 .try_parse(|i| IntegerOrAuto::parse(context, i))
139 .unwrap_or(IntegerOrAuto::Auto);
140 let post_hyphen_length = input
141 .try_parse(|i| IntegerOrAuto::parse(context, i))
142 .unwrap_or(pre_hyphen_length);
143 Ok(Self {
144 total_word_length,
145 pre_hyphen_length,
146 post_hyphen_length,
147 })
148 }
149}
150
151impl Parse for InitialLetter {
152 fn parse<'i, 't>(
153 context: &ParserContext,
154 input: &mut Parser<'i, 't>,
155 ) -> Result<Self, ParseError<'i>> {
156 if input
157 .try_parse(|i| i.expect_ident_matching("normal"))
158 .is_ok()
159 {
160 return Ok(Self::normal());
161 }
162 let size = Number::parse_at_least_one(context, input)?;
163 let sink = input
164 .try_parse(|i| Integer::parse_positive(context, i))
165 .unwrap_or_else(|_| crate::Zero::zero());
166 Ok(Self { size, sink })
167 }
168}
169
170#[derive(
172 Clone,
173 Debug,
174 Eq,
175 MallocSizeOf,
176 PartialEq,
177 Parse,
178 SpecifiedValueInfo,
179 ToComputedValue,
180 ToCss,
181 ToResolvedValue,
182 ToShmem,
183)]
184#[repr(C, u8)]
185pub enum TextOverflowSide {
186 Clip,
188 Ellipsis,
190 String(crate::values::AtomString),
192}
193
194#[derive(
195 Clone,
196 Debug,
197 Eq,
198 MallocSizeOf,
199 PartialEq,
200 SpecifiedValueInfo,
201 ToComputedValue,
202 ToResolvedValue,
203 ToShmem,
204 ToTyped,
205)]
206#[repr(C)]
207pub struct TextOverflow {
216 pub first: TextOverflowSide,
218 pub second: TextOverflowSide,
220 pub sides_are_logical: bool,
222}
223
224impl Parse for TextOverflow {
225 fn parse<'i, 't>(
226 context: &ParserContext,
227 input: &mut Parser<'i, 't>,
228 ) -> Result<TextOverflow, ParseError<'i>> {
229 let first = TextOverflowSide::parse(context, input)?;
230 Ok(
231 if let Ok(second) = input.try_parse(|input| TextOverflowSide::parse(context, input)) {
232 Self {
233 first,
234 second,
235 sides_are_logical: false,
236 }
237 } else {
238 Self {
239 first: TextOverflowSide::Clip,
240 second: first,
241 sides_are_logical: true,
242 }
243 },
244 )
245 }
246}
247
248impl TextOverflow {
249 pub fn get_initial_value() -> TextOverflow {
251 TextOverflow {
252 first: TextOverflowSide::Clip,
253 second: TextOverflowSide::Clip,
254 sides_are_logical: true,
255 }
256 }
257}
258
259impl ToCss for TextOverflow {
260 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
261 where
262 W: Write,
263 {
264 if self.sides_are_logical {
265 debug_assert_eq!(self.first, TextOverflowSide::Clip);
266 self.second.to_css(dest)?;
267 } else {
268 self.first.to_css(dest)?;
269 dest.write_char(' ')?;
270 self.second.to_css(dest)?;
271 }
272 Ok(())
273 }
274}
275
276#[derive(
277 Clone,
278 Copy,
279 Debug,
280 Eq,
281 MallocSizeOf,
282 PartialEq,
283 Parse,
284 Serialize,
285 SpecifiedValueInfo,
286 ToCss,
287 ToComputedValue,
288 ToResolvedValue,
289 ToShmem,
290 ToTyped,
291)]
292#[cfg_attr(
293 feature = "gecko",
294 css(bitflags(
295 single = "none,spelling-error,grammar-error",
296 mixed = "underline,overline,line-through,blink",
297 ))
298)]
299#[cfg_attr(
300 not(feature = "gecko"),
301 css(bitflags(single = "none", mixed = "underline,overline,line-through,blink",))
302)]
303#[repr(C)]
304pub struct TextDecorationLine(u8);
306bitflags! {
307 impl TextDecorationLine: u8 {
308 const NONE = 0;
310 const UNDERLINE = 1 << 0;
312 const OVERLINE = 1 << 1;
314 const LINE_THROUGH = 1 << 2;
316 const BLINK = 1 << 3;
318 const SPELLING_ERROR = 1 << 4;
320 const GRAMMAR_ERROR = 1 << 5;
322 #[cfg(feature = "gecko")]
330 const COLOR_OVERRIDE = 1 << 7;
331 }
332}
333
334impl Default for TextDecorationLine {
335 fn default() -> Self {
336 TextDecorationLine::NONE
337 }
338}
339
340impl TextDecorationLine {
341 #[inline]
342 pub fn none() -> Self {
344 TextDecorationLine::NONE
345 }
346}
347
348#[derive(
349 Clone,
350 Copy,
351 Debug,
352 Eq,
353 MallocSizeOf,
354 PartialEq,
355 SpecifiedValueInfo,
356 ToComputedValue,
357 ToCss,
358 ToResolvedValue,
359 ToShmem,
360)]
361#[repr(C)]
362pub enum TextTransformCase {
364 None,
366 Uppercase,
368 Lowercase,
370 Capitalize,
372 #[cfg(feature = "gecko")]
374 MathAuto,
375}
376
377#[derive(
378 Clone,
379 Copy,
380 Debug,
381 Eq,
382 MallocSizeOf,
383 PartialEq,
384 Parse,
385 Serialize,
386 SpecifiedValueInfo,
387 ToCss,
388 ToComputedValue,
389 ToResolvedValue,
390 ToShmem,
391 ToTyped,
392)]
393#[cfg_attr(
394 feature = "gecko",
395 css(bitflags(
396 single = "none,math-auto",
397 mixed = "uppercase,lowercase,capitalize,full-width,full-size-kana",
398 validate_mixed = "Self::validate_mixed_flags",
399 ))
400)]
401#[cfg_attr(
402 not(feature = "gecko"),
403 css(bitflags(
404 single = "none",
405 mixed = "uppercase,lowercase,capitalize,full-width,full-size-kana",
406 validate_mixed = "Self::validate_mixed_flags",
407 ))
408)]
409#[repr(C)]
410pub struct TextTransform(u8);
415bitflags! {
416 impl TextTransform: u8 {
417 const NONE = 0;
419 const UPPERCASE = 1 << 0;
421 const LOWERCASE = 1 << 1;
423 const CAPITALIZE = 1 << 2;
425 #[cfg(feature = "gecko")]
427 const MATH_AUTO = 1 << 3;
428
429 #[cfg(feature = "gecko")]
431 const CASE_TRANSFORMS = Self::UPPERCASE.0 | Self::LOWERCASE.0 | Self::CAPITALIZE.0 | Self::MATH_AUTO.0;
432 #[cfg(feature = "servo")]
434 const CASE_TRANSFORMS = Self::UPPERCASE.0 | Self::LOWERCASE.0 | Self::CAPITALIZE.0;
435
436 const FULL_WIDTH = 1 << 4;
438 const FULL_SIZE_KANA = 1 << 5;
440 }
441}
442
443impl TextTransform {
444 #[inline]
446 pub fn none() -> Self {
447 Self::NONE
448 }
449
450 #[inline]
452 pub fn is_none(self) -> bool {
453 self == Self::NONE
454 }
455
456 fn validate_mixed_flags(&self) -> bool {
457 let case = self.intersection(Self::CASE_TRANSFORMS);
458 case.is_empty() || case.bits().is_power_of_two()
460 }
461
462 pub fn case(&self) -> TextTransformCase {
464 match *self & Self::CASE_TRANSFORMS {
465 Self::NONE => TextTransformCase::None,
466 Self::UPPERCASE => TextTransformCase::Uppercase,
467 Self::LOWERCASE => TextTransformCase::Lowercase,
468 Self::CAPITALIZE => TextTransformCase::Capitalize,
469 #[cfg(feature = "gecko")]
470 Self::MATH_AUTO => TextTransformCase::MathAuto,
471 _ => unreachable!("Case bits are exclusive with each other"),
472 }
473 }
474}
475
476#[derive(
478 Clone,
479 Copy,
480 Debug,
481 Eq,
482 FromPrimitive,
483 Hash,
484 MallocSizeOf,
485 Parse,
486 PartialEq,
487 SpecifiedValueInfo,
488 ToComputedValue,
489 ToCss,
490 ToResolvedValue,
491 ToShmem,
492 ToTyped,
493)]
494#[allow(missing_docs)]
495#[repr(u8)]
496pub enum TextAlignLast {
497 Auto,
498 Start,
499 End,
500 Left,
501 Right,
502 Center,
503 Justify,
504}
505
506#[derive(
508 Clone,
509 Copy,
510 Debug,
511 Eq,
512 FromPrimitive,
513 Hash,
514 MallocSizeOf,
515 Parse,
516 PartialEq,
517 SpecifiedValueInfo,
518 ToComputedValue,
519 ToCss,
520 ToResolvedValue,
521 ToShmem,
522 ToTyped,
523)]
524#[allow(missing_docs)]
525#[repr(u8)]
526pub enum TextAlignKeyword {
527 Start,
528 Left,
529 Right,
530 Center,
531 Justify,
532 End,
533 #[parse(aliases = "-webkit-center")]
534 MozCenter,
535 #[parse(aliases = "-webkit-left")]
536 MozLeft,
537 #[parse(aliases = "-webkit-right")]
538 MozRight,
539}
540
541#[derive(
543 Clone,
544 Copy,
545 Debug,
546 Eq,
547 Hash,
548 MallocSizeOf,
549 Parse,
550 PartialEq,
551 SpecifiedValueInfo,
552 ToCss,
553 ToShmem,
554 ToTyped,
555)]
556pub enum TextAlign {
557 Keyword(TextAlignKeyword),
559 #[cfg(feature = "gecko")]
562 MatchParent,
563 #[parse(condition = "ParserContext::chrome_rules_enabled")]
576 MozCenterOrInherit,
577}
578
579impl ToComputedValue for TextAlign {
580 type ComputedValue = TextAlignKeyword;
581
582 #[inline]
583 fn to_computed_value(&self, _context: &Context) -> Self::ComputedValue {
584 match *self {
585 TextAlign::Keyword(key) => key,
586 #[cfg(feature = "gecko")]
587 TextAlign::MatchParent => {
588 if _context.builder.is_root_element {
595 return TextAlignKeyword::Start;
596 }
597 let parent = _context
598 .builder
599 .get_parent_inherited_text()
600 .clone_text_align();
601 let ltr = _context.builder.inherited_writing_mode().is_bidi_ltr();
602 match (parent, ltr) {
603 (TextAlignKeyword::Start, true) => TextAlignKeyword::Left,
604 (TextAlignKeyword::Start, false) => TextAlignKeyword::Right,
605 (TextAlignKeyword::End, true) => TextAlignKeyword::Right,
606 (TextAlignKeyword::End, false) => TextAlignKeyword::Left,
607 _ => parent,
608 }
609 },
610 TextAlign::MozCenterOrInherit => {
611 let parent = _context
612 .builder
613 .get_parent_inherited_text()
614 .clone_text_align();
615 if parent == TextAlignKeyword::Start {
616 TextAlignKeyword::Center
617 } else {
618 parent
619 }
620 },
621 }
622 }
623
624 #[inline]
625 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
626 TextAlign::Keyword(*computed)
627 }
628}
629
630fn fill_mode_is_default_and_shape_exists(
631 fill: &TextEmphasisFillMode,
632 shape: &Option<TextEmphasisShapeKeyword>,
633) -> bool {
634 shape.is_some() && fill.is_filled()
635}
636
637#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
641#[allow(missing_docs)]
642pub enum TextEmphasisStyle {
643 Keyword {
645 #[css(contextual_skip_if = "fill_mode_is_default_and_shape_exists")]
646 fill: TextEmphasisFillMode,
647 shape: Option<TextEmphasisShapeKeyword>,
648 },
649 None,
651 String(crate::OwnedStr),
653}
654
655#[derive(
657 Clone,
658 Copy,
659 Debug,
660 MallocSizeOf,
661 Parse,
662 PartialEq,
663 SpecifiedValueInfo,
664 ToCss,
665 ToComputedValue,
666 ToResolvedValue,
667 ToShmem,
668)]
669#[repr(u8)]
670pub enum TextEmphasisFillMode {
671 Filled,
673 Open,
675}
676
677impl TextEmphasisFillMode {
678 #[inline]
680 pub fn is_filled(&self) -> bool {
681 matches!(*self, TextEmphasisFillMode::Filled)
682 }
683}
684
685#[derive(
687 Clone,
688 Copy,
689 Debug,
690 Eq,
691 MallocSizeOf,
692 Parse,
693 PartialEq,
694 SpecifiedValueInfo,
695 ToCss,
696 ToComputedValue,
697 ToResolvedValue,
698 ToShmem,
699)]
700#[repr(u8)]
701pub enum TextEmphasisShapeKeyword {
702 Dot,
704 Circle,
706 DoubleCircle,
708 Triangle,
710 Sesame,
712}
713
714impl ToComputedValue for TextEmphasisStyle {
715 type ComputedValue = ComputedTextEmphasisStyle;
716
717 #[inline]
718 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
719 match *self {
720 TextEmphasisStyle::Keyword { fill, shape } => {
721 let shape = shape.unwrap_or_else(|| {
722 if context.style().get_inherited_box().clone_writing_mode()
728 == SpecifiedWritingMode::HorizontalTb
729 {
730 TextEmphasisShapeKeyword::Circle
731 } else {
732 TextEmphasisShapeKeyword::Sesame
733 }
734 });
735 ComputedTextEmphasisStyle::Keyword { fill, shape }
736 },
737 TextEmphasisStyle::None => ComputedTextEmphasisStyle::None,
738 TextEmphasisStyle::String(ref s) => {
739 let first_grapheme_end = GraphemeClusterSegmenter::new()
745 .segment_str(s)
746 .nth(1)
747 .unwrap_or(0);
748 ComputedTextEmphasisStyle::String(s[0..first_grapheme_end].to_string().into())
749 },
750 }
751 }
752
753 #[inline]
754 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
755 match *computed {
756 ComputedTextEmphasisStyle::Keyword { fill, shape } => TextEmphasisStyle::Keyword {
757 fill,
758 shape: Some(shape),
759 },
760 ComputedTextEmphasisStyle::None => TextEmphasisStyle::None,
761 ComputedTextEmphasisStyle::String(ref string) => {
762 TextEmphasisStyle::String(string.clone())
763 },
764 }
765 }
766}
767
768impl Parse for TextEmphasisStyle {
769 fn parse<'i, 't>(
770 _context: &ParserContext,
771 input: &mut Parser<'i, 't>,
772 ) -> Result<Self, ParseError<'i>> {
773 if input
774 .try_parse(|input| input.expect_ident_matching("none"))
775 .is_ok()
776 {
777 return Ok(TextEmphasisStyle::None);
778 }
779
780 if let Ok(s) = input.try_parse(|i| i.expect_string().map(|s| s.as_ref().to_owned())) {
781 return Ok(TextEmphasisStyle::String(s.into()));
783 }
784
785 let mut shape = input.try_parse(TextEmphasisShapeKeyword::parse).ok();
787 let fill = input.try_parse(TextEmphasisFillMode::parse).ok();
788 if shape.is_none() {
789 shape = input.try_parse(TextEmphasisShapeKeyword::parse).ok();
790 }
791
792 if shape.is_none() && fill.is_none() {
793 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
794 }
795
796 let fill = fill.unwrap_or(TextEmphasisFillMode::Filled);
799
800 Ok(TextEmphasisStyle::Keyword { fill, shape })
803 }
804}
805
806#[derive(
807 Clone,
808 Copy,
809 Debug,
810 Eq,
811 MallocSizeOf,
812 PartialEq,
813 Parse,
814 Serialize,
815 SpecifiedValueInfo,
816 ToCss,
817 ToComputedValue,
818 ToResolvedValue,
819 ToShmem,
820 ToTyped,
821)]
822#[repr(C)]
823#[css(bitflags(
824 single = "auto",
825 mixed = "over,under,left,right",
826 validate_mixed = "Self::validate_and_simplify"
827))]
828pub struct TextEmphasisPosition(u8);
831bitflags! {
832 impl TextEmphasisPosition: u8 {
833 const AUTO = 1 << 0;
835 const OVER = 1 << 1;
837 const UNDER = 1 << 2;
839 const LEFT = 1 << 3;
841 const RIGHT = 1 << 4;
843 }
844}
845
846impl TextEmphasisPosition {
847 fn validate_and_simplify(&mut self) -> bool {
848 if self.intersects(Self::OVER) == self.intersects(Self::UNDER) {
850 return false;
851 }
852
853 if self.intersects(Self::LEFT) {
855 return !self.intersects(Self::RIGHT);
856 }
857
858 self.remove(Self::RIGHT); true
860 }
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 WordBreak {
882 Normal,
883 BreakAll,
884 KeepAll,
885 #[cfg(feature = "gecko")]
890 BreakWord,
891}
892
893#[repr(u8)]
895#[derive(
896 Clone,
897 Copy,
898 Debug,
899 Eq,
900 MallocSizeOf,
901 Parse,
902 PartialEq,
903 SpecifiedValueInfo,
904 ToComputedValue,
905 ToCss,
906 ToResolvedValue,
907 ToShmem,
908 ToTyped,
909)]
910#[allow(missing_docs)]
911pub enum TextJustify {
912 Auto,
913 None,
914 InterWord,
915 #[parse(aliases = "distribute")]
918 InterCharacter,
919}
920
921#[repr(u8)]
923#[derive(
924 Clone,
925 Copy,
926 Debug,
927 Eq,
928 MallocSizeOf,
929 Parse,
930 PartialEq,
931 SpecifiedValueInfo,
932 ToComputedValue,
933 ToCss,
934 ToResolvedValue,
935 ToShmem,
936 ToTyped,
937)]
938#[allow(missing_docs)]
939pub enum MozControlCharacterVisibility {
940 Hidden,
941 Visible,
942}
943
944#[cfg(feature = "gecko")]
945impl Default for MozControlCharacterVisibility {
946 fn default() -> Self {
947 if static_prefs::pref!("layout.css.control-characters.visible") {
948 Self::Visible
949 } else {
950 Self::Hidden
951 }
952 }
953}
954
955#[repr(u8)]
957#[derive(
958 Clone,
959 Copy,
960 Debug,
961 Eq,
962 MallocSizeOf,
963 Parse,
964 PartialEq,
965 SpecifiedValueInfo,
966 ToComputedValue,
967 ToCss,
968 ToResolvedValue,
969 ToShmem,
970 ToTyped,
971)]
972#[allow(missing_docs)]
973pub enum LineBreak {
974 Auto,
975 Loose,
976 Normal,
977 Strict,
978 Anywhere,
979}
980
981#[repr(u8)]
983#[derive(
984 Clone,
985 Copy,
986 Debug,
987 Eq,
988 MallocSizeOf,
989 Parse,
990 PartialEq,
991 SpecifiedValueInfo,
992 ToComputedValue,
993 ToCss,
994 ToResolvedValue,
995 ToShmem,
996 ToTyped,
997)]
998#[allow(missing_docs)]
999pub enum OverflowWrap {
1000 Normal,
1001 BreakWord,
1002 Anywhere,
1003}
1004
1005pub type TextIndent = GenericTextIndent<LengthPercentage>;
1010
1011impl Parse for TextIndent {
1012 fn parse<'i, 't>(
1013 context: &ParserContext,
1014 input: &mut Parser<'i, 't>,
1015 ) -> Result<Self, ParseError<'i>> {
1016 let mut length = None;
1017 let mut hanging = false;
1018 let mut each_line = false;
1019
1020 while !input.is_exhausted() {
1022 if length.is_none() {
1024 if let Ok(len) = input
1025 .try_parse(|i| LengthPercentage::parse_quirky(context, i, AllowQuirks::Yes))
1026 {
1027 length = Some(len);
1028 continue;
1029 }
1030 }
1031
1032 if cfg!(feature = "servo") {
1034 break;
1035 }
1036
1037 try_match_ident_ignore_ascii_case! { input,
1039 "hanging" if !hanging => hanging = true,
1040 "each-line" if !each_line => each_line = true,
1041 }
1042 }
1043
1044 if let Some(length) = length {
1046 Ok(Self {
1047 length,
1048 hanging,
1049 each_line,
1050 })
1051 } else {
1052 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1053 }
1054 }
1055}
1056
1057#[repr(u8)]
1061#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
1062#[derive(
1063 Clone,
1064 Copy,
1065 Debug,
1066 Eq,
1067 MallocSizeOf,
1068 Parse,
1069 PartialEq,
1070 SpecifiedValueInfo,
1071 ToComputedValue,
1072 ToCss,
1073 ToResolvedValue,
1074 ToShmem,
1075 ToTyped,
1076)]
1077#[allow(missing_docs)]
1078pub enum TextDecorationSkipInk {
1079 Auto,
1080 None,
1081 All,
1082}
1083
1084pub type TextDecorationLength = GenericTextDecorationLength<LengthPercentage>;
1086
1087impl TextDecorationLength {
1088 #[inline]
1090 pub fn auto() -> Self {
1091 GenericTextDecorationLength::Auto
1092 }
1093
1094 #[inline]
1096 pub fn is_auto(&self) -> bool {
1097 matches!(*self, GenericTextDecorationLength::Auto)
1098 }
1099}
1100
1101pub type TextDecorationTrim = GenericTextDecorationTrim<Length>;
1103
1104impl TextDecorationTrim {
1105 #[inline]
1107 pub fn auto() -> Self {
1108 GenericTextDecorationTrim::Auto
1109 }
1110
1111 #[inline]
1113 pub fn is_auto(&self) -> bool {
1114 matches!(*self, GenericTextDecorationTrim::Auto)
1115 }
1116}
1117
1118impl Parse for TextDecorationTrim {
1119 fn parse<'i, 't>(
1120 ctx: &ParserContext,
1121 input: &mut Parser<'i, 't>,
1122 ) -> Result<Self, ParseError<'i>> {
1123 if let Ok(start) = input.try_parse(|i| Length::parse(ctx, i)) {
1124 let end = input.try_parse(|i| Length::parse(ctx, i));
1125 let end = end.unwrap_or_else(|_| start.clone());
1126 return Ok(TextDecorationTrim::Length { start, end });
1127 }
1128 input.expect_ident_matching("auto")?;
1129 Ok(TextDecorationTrim::Auto)
1130 }
1131}
1132
1133#[derive(
1134 Clone,
1135 Copy,
1136 Debug,
1137 Eq,
1138 MallocSizeOf,
1139 Parse,
1140 PartialEq,
1141 SpecifiedValueInfo,
1142 ToComputedValue,
1143 ToResolvedValue,
1144 ToShmem,
1145 ToTyped,
1146)]
1147#[css(bitflags(
1148 single = "auto",
1149 mixed = "from-font,under,left,right",
1150 validate_mixed = "Self::validate_mixed_flags",
1151))]
1152#[repr(C)]
1153pub struct TextUnderlinePosition(u8);
1158bitflags! {
1159 impl TextUnderlinePosition: u8 {
1160 const AUTO = 0;
1162 const FROM_FONT = 1 << 0;
1164 const UNDER = 1 << 1;
1166 const LEFT = 1 << 2;
1168 const RIGHT = 1 << 3;
1170 }
1171}
1172
1173impl TextUnderlinePosition {
1174 fn validate_mixed_flags(&self) -> bool {
1175 if self.contains(Self::LEFT | Self::RIGHT) {
1176 return false;
1178 }
1179 if self.contains(Self::FROM_FONT | Self::UNDER) {
1180 return false;
1182 }
1183 true
1184 }
1185}
1186
1187impl ToCss for TextUnderlinePosition {
1188 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1189 where
1190 W: Write,
1191 {
1192 if self.is_empty() {
1193 return dest.write_str("auto");
1194 }
1195
1196 let mut writer = SequenceWriter::new(dest, " ");
1197 let mut any = false;
1198
1199 macro_rules! maybe_write {
1200 ($ident:ident => $str:expr) => {
1201 if self.contains(TextUnderlinePosition::$ident) {
1202 any = true;
1203 writer.raw_item($str)?;
1204 }
1205 };
1206 }
1207
1208 maybe_write!(FROM_FONT => "from-font");
1209 maybe_write!(UNDER => "under");
1210 maybe_write!(LEFT => "left");
1211 maybe_write!(RIGHT => "right");
1212
1213 debug_assert!(any);
1214
1215 Ok(())
1216 }
1217}
1218
1219#[repr(u8)]
1221#[derive(
1222 Clone,
1223 Copy,
1224 Debug,
1225 Eq,
1226 MallocSizeOf,
1227 PartialEq,
1228 ToComputedValue,
1229 ToResolvedValue,
1230 ToShmem,
1231 ToTyped,
1232)]
1233#[allow(missing_docs)]
1234pub enum RubyPosition {
1235 AlternateOver,
1236 AlternateUnder,
1237 Over,
1238 Under,
1239}
1240
1241impl Parse for RubyPosition {
1242 fn parse<'i, 't>(
1243 _context: &ParserContext,
1244 input: &mut Parser<'i, 't>,
1245 ) -> Result<RubyPosition, ParseError<'i>> {
1246 let alternate = input
1248 .try_parse(|i| i.expect_ident_matching("alternate"))
1249 .is_ok();
1250 if alternate && input.is_exhausted() {
1251 return Ok(RubyPosition::AlternateOver);
1252 }
1253 let over = try_match_ident_ignore_ascii_case! { input,
1255 "over" => true,
1256 "under" => false,
1257 };
1258 let alternate = alternate
1260 || input
1261 .try_parse(|i| i.expect_ident_matching("alternate"))
1262 .is_ok();
1263
1264 Ok(match (over, alternate) {
1265 (true, true) => RubyPosition::AlternateOver,
1266 (false, true) => RubyPosition::AlternateUnder,
1267 (true, false) => RubyPosition::Over,
1268 (false, false) => RubyPosition::Under,
1269 })
1270 }
1271}
1272
1273impl ToCss for RubyPosition {
1274 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1275 where
1276 W: Write,
1277 {
1278 dest.write_str(match self {
1279 RubyPosition::AlternateOver => "alternate",
1280 RubyPosition::AlternateUnder => "alternate under",
1281 RubyPosition::Over => "over",
1282 RubyPosition::Under => "under",
1283 })
1284 }
1285}
1286
1287impl SpecifiedValueInfo for RubyPosition {
1288 fn collect_completion_keywords(f: KeywordsCollectFn) {
1289 f(&["alternate", "over", "under"])
1290 }
1291}
1292
1293#[derive(
1305 Clone,
1306 Copy,
1307 Debug,
1308 Eq,
1309 MallocSizeOf,
1310 Parse,
1311 PartialEq,
1312 Serialize,
1313 SpecifiedValueInfo,
1314 ToCss,
1315 ToComputedValue,
1316 ToResolvedValue,
1317 ToShmem,
1318 ToTyped,
1319)]
1320#[css(bitflags(
1321 single = "normal,auto,no-autospace",
1322 mixed = "ideograph-alpha,ideograph-numeric,insert",
1325 ))]
1328#[repr(C)]
1329pub struct TextAutospace(u8);
1330bitflags! {
1331 impl TextAutospace: u8 {
1332 const NO_AUTOSPACE = 0;
1334
1335 const AUTO = 1 << 0;
1337
1338 const NORMAL = 1 << 1;
1340
1341 const IDEOGRAPH_ALPHA = 1 << 2;
1343
1344 const IDEOGRAPH_NUMERIC = 1 << 3;
1346
1347 const INSERT = 1 << 5;
1354
1355 }
1360}
1361
1362