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::NumberOrAuto;
13use crate::values::generics::text::{
14 GenericHyphenateLimitChars, GenericInitialLetter, GenericTextDecorationLength, GenericTextIndent,
15};
16use crate::values::specified::length::LengthPercentage;
17use crate::values::specified::{AllowQuirks, Integer, Number};
18use crate::Zero;
19use cssparser::Parser;
20use icu_segmenter::GraphemeClusterSegmenter;
21use std::fmt::{self, Write};
22use style_traits::values::SequenceWriter;
23use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
24use style_traits::{KeywordsCollectFn, SpecifiedValueInfo};
25
26pub type InitialLetter = GenericInitialLetter<Number, Integer>;
28
29#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
31pub enum Spacing {
32 Normal,
34 Value(LengthPercentage),
36}
37
38impl Parse for Spacing {
39 fn parse<'i, 't>(
40 context: &ParserContext,
41 input: &mut Parser<'i, 't>,
42 ) -> Result<Self, ParseError<'i>> {
43 if input
44 .try_parse(|i| i.expect_ident_matching("normal"))
45 .is_ok()
46 {
47 return Ok(Spacing::Normal);
48 }
49 LengthPercentage::parse_quirky(context, input, AllowQuirks::Yes).map(Spacing::Value)
50 }
51}
52
53#[derive(Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
55pub struct LetterSpacing(pub Spacing);
56
57impl ToComputedValue for LetterSpacing {
58 type ComputedValue = computed::LetterSpacing;
59
60 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
61 use computed::text::GenericLetterSpacing;
62 match self.0 {
63 Spacing::Normal => GenericLetterSpacing(computed::LengthPercentage::zero()),
64 Spacing::Value(ref v) => GenericLetterSpacing(v.to_computed_value(context)),
65 }
66 }
67
68 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
69 if computed.0.is_zero() {
70 return LetterSpacing(Spacing::Normal);
71 }
72 LetterSpacing(Spacing::Value(ToComputedValue::from_computed_value(&computed.0)))
73 }
74}
75
76
77#[derive(Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
79pub struct WordSpacing(pub Spacing);
80
81impl ToComputedValue for WordSpacing {
82 type ComputedValue = computed::WordSpacing;
83
84 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
85 match self.0 {
86 Spacing::Normal => computed::LengthPercentage::zero(),
87 Spacing::Value(ref v) => v.to_computed_value(context),
88 }
89 }
90
91 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
92 WordSpacing(Spacing::Value(ToComputedValue::from_computed_value(computed)))
93 }
94}
95
96#[derive(
98 Clone,
99 Debug,
100 MallocSizeOf,
101 Parse,
102 PartialEq,
103 SpecifiedValueInfo,
104 ToComputedValue,
105 ToCss,
106 ToResolvedValue,
107 ToShmem,
108)]
109#[repr(C, u8)]
110pub enum HyphenateCharacter {
111 Auto,
113 String(crate::OwnedStr),
115}
116
117pub type HyphenateLimitChars = GenericHyphenateLimitChars<Integer>;
119
120impl Parse for HyphenateLimitChars {
121 fn parse<'i, 't>(
122 context: &ParserContext,
123 input: &mut Parser<'i, 't>,
124 ) -> Result<Self, ParseError<'i>> {
125 type IntegerOrAuto = NumberOrAuto<Integer>;
126
127 let total_word_length = IntegerOrAuto::parse(context, input)?;
128 let pre_hyphen_length = input.try_parse(|i| IntegerOrAuto::parse(context, i)).unwrap_or(IntegerOrAuto::Auto);
129 let post_hyphen_length = input.try_parse(|i| IntegerOrAuto::parse(context, i)).unwrap_or(pre_hyphen_length);
130 Ok(Self {
131 total_word_length,
132 pre_hyphen_length,
133 post_hyphen_length,
134 })
135 }
136}
137
138impl Parse for InitialLetter {
139 fn parse<'i, 't>(
140 context: &ParserContext,
141 input: &mut Parser<'i, 't>,
142 ) -> Result<Self, ParseError<'i>> {
143 if input
144 .try_parse(|i| i.expect_ident_matching("normal"))
145 .is_ok()
146 {
147 return Ok(Self::normal());
148 }
149 let size = Number::parse_at_least_one(context, input)?;
150 let sink = input
151 .try_parse(|i| Integer::parse_positive(context, i))
152 .unwrap_or_else(|_| crate::Zero::zero());
153 Ok(Self { size, sink })
154 }
155}
156
157#[derive(
159 Clone,
160 Debug,
161 Eq,
162 MallocSizeOf,
163 PartialEq,
164 Parse,
165 SpecifiedValueInfo,
166 ToComputedValue,
167 ToCss,
168 ToResolvedValue,
169 ToShmem,
170)]
171#[repr(C, u8)]
172pub enum TextOverflowSide {
173 Clip,
175 Ellipsis,
177 String(crate::values::AtomString),
179}
180
181#[derive(
182 Clone,
183 Debug,
184 Eq,
185 MallocSizeOf,
186 PartialEq,
187 SpecifiedValueInfo,
188 ToComputedValue,
189 ToResolvedValue,
190 ToShmem,
191)]
192#[repr(C)]
193pub struct TextOverflow {
202 pub first: TextOverflowSide,
204 pub second: TextOverflowSide,
206 pub sides_are_logical: bool,
208}
209
210impl Parse for TextOverflow {
211 fn parse<'i, 't>(
212 context: &ParserContext,
213 input: &mut Parser<'i, 't>,
214 ) -> Result<TextOverflow, ParseError<'i>> {
215 let first = TextOverflowSide::parse(context, input)?;
216 Ok(
217 if let Ok(second) = input.try_parse(|input| TextOverflowSide::parse(context, input)) {
218 Self {
219 first,
220 second,
221 sides_are_logical: false,
222 }
223 } else {
224 Self {
225 first: TextOverflowSide::Clip,
226 second: first,
227 sides_are_logical: true,
228 }
229 },
230 )
231 }
232}
233
234impl TextOverflow {
235 pub fn get_initial_value() -> TextOverflow {
237 TextOverflow {
238 first: TextOverflowSide::Clip,
239 second: TextOverflowSide::Clip,
240 sides_are_logical: true,
241 }
242 }
243}
244
245impl ToCss for TextOverflow {
246 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
247 where
248 W: Write,
249 {
250 if self.sides_are_logical {
251 debug_assert_eq!(self.first, TextOverflowSide::Clip);
252 self.second.to_css(dest)?;
253 } else {
254 self.first.to_css(dest)?;
255 dest.write_char(' ')?;
256 self.second.to_css(dest)?;
257 }
258 Ok(())
259 }
260}
261
262#[derive(
263 Clone,
264 Copy,
265 Debug,
266 Eq,
267 MallocSizeOf,
268 PartialEq,
269 Parse,
270 Serialize,
271 SpecifiedValueInfo,
272 ToCss,
273 ToComputedValue,
274 ToResolvedValue,
275 ToShmem,
276)]
277#[cfg_attr(feature = "gecko", css(bitflags(
278 single = "none,spelling-error,grammar-error",
279 mixed = "underline,overline,line-through,blink",
280)))]
281#[cfg_attr(not(feature = "gecko"), css(bitflags(
282 single = "none",
283 mixed = "underline,overline,line-through,blink",
284)))]
285#[repr(C)]
286pub struct TextDecorationLine(u8);
288bitflags! {
289 impl TextDecorationLine: u8 {
290 const NONE = 0;
292 const UNDERLINE = 1 << 0;
294 const OVERLINE = 1 << 1;
296 const LINE_THROUGH = 1 << 2;
298 const BLINK = 1 << 3;
300 const SPELLING_ERROR = 1 << 4;
302 const GRAMMAR_ERROR = 1 << 5;
304 #[cfg(feature = "gecko")]
312 const COLOR_OVERRIDE = 1 << 7;
313 }
314}
315
316impl Default for TextDecorationLine {
317 fn default() -> Self {
318 TextDecorationLine::NONE
319 }
320}
321
322impl TextDecorationLine {
323 #[inline]
324 pub fn none() -> Self {
326 TextDecorationLine::NONE
327 }
328}
329
330#[derive(
331 Clone,
332 Copy,
333 Debug,
334 Eq,
335 MallocSizeOf,
336 PartialEq,
337 SpecifiedValueInfo,
338 ToComputedValue,
339 ToCss,
340 ToResolvedValue,
341 ToShmem,
342)]
343#[repr(C)]
344pub enum TextTransformCase {
346 None,
348 Uppercase,
350 Lowercase,
352 Capitalize,
354 #[cfg(feature = "gecko")]
356 MathAuto,
357}
358
359#[derive(
360 Clone,
361 Copy,
362 Debug,
363 Eq,
364 MallocSizeOf,
365 PartialEq,
366 Parse,
367 Serialize,
368 SpecifiedValueInfo,
369 ToCss,
370 ToComputedValue,
371 ToResolvedValue,
372 ToShmem,
373)]
374#[cfg_attr(feature = "gecko", css(bitflags(
375 single = "none,math-auto",
376 mixed = "uppercase,lowercase,capitalize,full-width,full-size-kana",
377 validate_mixed = "Self::validate_mixed_flags",
378)))]
379#[cfg_attr(not(feature = "gecko"), css(bitflags(
380 single = "none",
381 mixed = "uppercase,lowercase,capitalize,full-width,full-size-kana",
382 validate_mixed = "Self::validate_mixed_flags",
383)))]
384#[repr(C)]
385pub struct TextTransform(u8);
390bitflags! {
391 impl TextTransform: u8 {
392 const NONE = 0;
394 const UPPERCASE = 1 << 0;
396 const LOWERCASE = 1 << 1;
398 const CAPITALIZE = 1 << 2;
400 #[cfg(feature = "gecko")]
402 const MATH_AUTO = 1 << 3;
403
404 #[cfg(feature = "gecko")]
406 const CASE_TRANSFORMS = Self::UPPERCASE.0 | Self::LOWERCASE.0 | Self::CAPITALIZE.0 | Self::MATH_AUTO.0;
407 #[cfg(feature = "servo")]
409 const CASE_TRANSFORMS = Self::UPPERCASE.0 | Self::LOWERCASE.0 | Self::CAPITALIZE.0;
410
411 const FULL_WIDTH = 1 << 4;
413 const FULL_SIZE_KANA = 1 << 5;
415 }
416}
417
418impl TextTransform {
419 #[inline]
421 pub fn none() -> Self {
422 Self::NONE
423 }
424
425 #[inline]
427 pub fn is_none(self) -> bool {
428 self == Self::NONE
429 }
430
431 fn validate_mixed_flags(&self) -> bool {
432 let case = self.intersection(Self::CASE_TRANSFORMS);
433 case.is_empty() || case.bits().is_power_of_two()
435 }
436
437 pub fn case(&self) -> TextTransformCase {
439 match *self & Self::CASE_TRANSFORMS {
440 Self::NONE => TextTransformCase::None,
441 Self::UPPERCASE => TextTransformCase::Uppercase,
442 Self::LOWERCASE => TextTransformCase::Lowercase,
443 Self::CAPITALIZE => TextTransformCase::Capitalize,
444 #[cfg(feature = "gecko")]
445 Self::MATH_AUTO => TextTransformCase::MathAuto,
446 _ => unreachable!("Case bits are exclusive with each other"),
447 }
448 }
449}
450
451#[derive(
453 Clone,
454 Copy,
455 Debug,
456 Eq,
457 FromPrimitive,
458 Hash,
459 MallocSizeOf,
460 Parse,
461 PartialEq,
462 SpecifiedValueInfo,
463 ToComputedValue,
464 ToCss,
465 ToResolvedValue,
466 ToShmem,
467)]
468#[allow(missing_docs)]
469#[repr(u8)]
470pub enum TextAlignLast {
471 Auto,
472 Start,
473 End,
474 Left,
475 Right,
476 Center,
477 Justify,
478}
479
480#[derive(
482 Clone,
483 Copy,
484 Debug,
485 Eq,
486 FromPrimitive,
487 Hash,
488 MallocSizeOf,
489 Parse,
490 PartialEq,
491 SpecifiedValueInfo,
492 ToComputedValue,
493 ToCss,
494 ToResolvedValue,
495 ToShmem,
496)]
497#[allow(missing_docs)]
498#[repr(u8)]
499pub enum TextAlignKeyword {
500 Start,
501 Left,
502 Right,
503 Center,
504 Justify,
505 End,
506 #[parse(aliases = "-webkit-center")]
507 MozCenter,
508 #[parse(aliases = "-webkit-left")]
509 MozLeft,
510 #[parse(aliases = "-webkit-right")]
511 MozRight,
512}
513
514#[derive(
516 Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem,
517)]
518pub enum TextAlign {
519 Keyword(TextAlignKeyword),
521 #[cfg(feature = "gecko")]
524 MatchParent,
525 #[parse(condition = "ParserContext::chrome_rules_enabled")]
538 MozCenterOrInherit,
539}
540
541impl ToComputedValue for TextAlign {
542 type ComputedValue = TextAlignKeyword;
543
544 #[inline]
545 fn to_computed_value(&self, _context: &Context) -> Self::ComputedValue {
546 match *self {
547 TextAlign::Keyword(key) => key,
548 #[cfg(feature = "gecko")]
549 TextAlign::MatchParent => {
550 if _context.builder.is_root_element {
557 return TextAlignKeyword::Start;
558 }
559 let parent = _context
560 .builder
561 .get_parent_inherited_text()
562 .clone_text_align();
563 let ltr = _context.builder.inherited_writing_mode().is_bidi_ltr();
564 match (parent, ltr) {
565 (TextAlignKeyword::Start, true) => TextAlignKeyword::Left,
566 (TextAlignKeyword::Start, false) => TextAlignKeyword::Right,
567 (TextAlignKeyword::End, true) => TextAlignKeyword::Right,
568 (TextAlignKeyword::End, false) => TextAlignKeyword::Left,
569 _ => parent,
570 }
571 },
572 TextAlign::MozCenterOrInherit => {
573 let parent = _context
574 .builder
575 .get_parent_inherited_text()
576 .clone_text_align();
577 if parent == TextAlignKeyword::Start {
578 TextAlignKeyword::Center
579 } else {
580 parent
581 }
582 },
583 }
584 }
585
586 #[inline]
587 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
588 TextAlign::Keyword(*computed)
589 }
590}
591
592fn fill_mode_is_default_and_shape_exists(
593 fill: &TextEmphasisFillMode,
594 shape: &Option<TextEmphasisShapeKeyword>,
595) -> bool {
596 shape.is_some() && fill.is_filled()
597}
598
599#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
603#[allow(missing_docs)]
604pub enum TextEmphasisStyle {
605 Keyword {
607 #[css(contextual_skip_if = "fill_mode_is_default_and_shape_exists")]
608 fill: TextEmphasisFillMode,
609 shape: Option<TextEmphasisShapeKeyword>,
610 },
611 None,
613 String(crate::OwnedStr),
615}
616
617#[derive(
619 Clone,
620 Copy,
621 Debug,
622 MallocSizeOf,
623 Parse,
624 PartialEq,
625 SpecifiedValueInfo,
626 ToCss,
627 ToComputedValue,
628 ToResolvedValue,
629 ToShmem,
630)]
631#[repr(u8)]
632pub enum TextEmphasisFillMode {
633 Filled,
635 Open,
637}
638
639impl TextEmphasisFillMode {
640 #[inline]
642 pub fn is_filled(&self) -> bool {
643 matches!(*self, TextEmphasisFillMode::Filled)
644 }
645}
646
647#[derive(
649 Clone,
650 Copy,
651 Debug,
652 Eq,
653 MallocSizeOf,
654 Parse,
655 PartialEq,
656 SpecifiedValueInfo,
657 ToCss,
658 ToComputedValue,
659 ToResolvedValue,
660 ToShmem,
661)]
662#[repr(u8)]
663pub enum TextEmphasisShapeKeyword {
664 Dot,
666 Circle,
668 DoubleCircle,
670 Triangle,
672 Sesame,
674}
675
676impl ToComputedValue for TextEmphasisStyle {
677 type ComputedValue = ComputedTextEmphasisStyle;
678
679 #[inline]
680 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
681 match *self {
682 TextEmphasisStyle::Keyword { fill, shape } => {
683 let shape = shape.unwrap_or_else(|| {
684 if context.style().get_inherited_box().clone_writing_mode() ==
690 SpecifiedWritingMode::HorizontalTb
691 {
692 TextEmphasisShapeKeyword::Circle
693 } else {
694 TextEmphasisShapeKeyword::Sesame
695 }
696 });
697 ComputedTextEmphasisStyle::Keyword { fill, shape }
698 },
699 TextEmphasisStyle::None => ComputedTextEmphasisStyle::None,
700 TextEmphasisStyle::String(ref s) => {
701 let first_grapheme_end = GraphemeClusterSegmenter::new()
707 .segment_str(s)
708 .nth(1)
709 .unwrap_or(0);
710 ComputedTextEmphasisStyle::String(s[0..first_grapheme_end].to_string().into())
711 },
712 }
713 }
714
715 #[inline]
716 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
717 match *computed {
718 ComputedTextEmphasisStyle::Keyword { fill, shape } => TextEmphasisStyle::Keyword {
719 fill,
720 shape: Some(shape),
721 },
722 ComputedTextEmphasisStyle::None => TextEmphasisStyle::None,
723 ComputedTextEmphasisStyle::String(ref string) => {
724 TextEmphasisStyle::String(string.clone())
725 },
726 }
727 }
728}
729
730impl Parse for TextEmphasisStyle {
731 fn parse<'i, 't>(
732 _context: &ParserContext,
733 input: &mut Parser<'i, 't>,
734 ) -> Result<Self, ParseError<'i>> {
735 if input
736 .try_parse(|input| input.expect_ident_matching("none"))
737 .is_ok()
738 {
739 return Ok(TextEmphasisStyle::None);
740 }
741
742 if let Ok(s) = input.try_parse(|i| i.expect_string().map(|s| s.as_ref().to_owned())) {
743 return Ok(TextEmphasisStyle::String(s.into()));
745 }
746
747 let mut shape = input.try_parse(TextEmphasisShapeKeyword::parse).ok();
749 let fill = input.try_parse(TextEmphasisFillMode::parse).ok();
750 if shape.is_none() {
751 shape = input.try_parse(TextEmphasisShapeKeyword::parse).ok();
752 }
753
754 if shape.is_none() && fill.is_none() {
755 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
756 }
757
758 let fill = fill.unwrap_or(TextEmphasisFillMode::Filled);
761
762 Ok(TextEmphasisStyle::Keyword { fill, shape })
765 }
766}
767
768#[derive(
769 Clone,
770 Copy,
771 Debug,
772 Eq,
773 MallocSizeOf,
774 PartialEq,
775 Parse,
776 Serialize,
777 SpecifiedValueInfo,
778 ToCss,
779 ToComputedValue,
780 ToResolvedValue,
781 ToShmem,
782)]
783#[repr(C)]
784#[css(bitflags(
785 single = "auto",
786 mixed = "over,under,left,right",
787 validate_mixed = "Self::validate_and_simplify"
788))]
789pub struct TextEmphasisPosition(u8);
792bitflags! {
793 impl TextEmphasisPosition: u8 {
794 const AUTO = 1 << 0;
796 const OVER = 1 << 1;
798 const UNDER = 1 << 2;
800 const LEFT = 1 << 3;
802 const RIGHT = 1 << 4;
804 }
805}
806
807impl TextEmphasisPosition {
808 fn validate_and_simplify(&mut self) -> bool {
809 if self.intersects(Self::OVER) == self.intersects(Self::UNDER) {
811 return false;
812 }
813
814 if self.intersects(Self::LEFT) {
816 return !self.intersects(Self::RIGHT);
817 }
818
819 self.remove(Self::RIGHT); true
821 }
822}
823
824#[repr(u8)]
826#[derive(
827 Clone,
828 Copy,
829 Debug,
830 Eq,
831 MallocSizeOf,
832 Parse,
833 PartialEq,
834 SpecifiedValueInfo,
835 ToComputedValue,
836 ToCss,
837 ToResolvedValue,
838 ToShmem,
839)]
840#[allow(missing_docs)]
841pub enum WordBreak {
842 Normal,
843 BreakAll,
844 KeepAll,
845 #[cfg(feature = "gecko")]
850 BreakWord,
851}
852
853#[repr(u8)]
855#[derive(
856 Clone,
857 Copy,
858 Debug,
859 Eq,
860 MallocSizeOf,
861 Parse,
862 PartialEq,
863 SpecifiedValueInfo,
864 ToComputedValue,
865 ToCss,
866 ToResolvedValue,
867 ToShmem,
868)]
869#[allow(missing_docs)]
870pub enum TextJustify {
871 Auto,
872 None,
873 InterWord,
874 #[parse(aliases = "distribute")]
877 InterCharacter,
878}
879
880#[repr(u8)]
882#[derive(
883 Clone,
884 Copy,
885 Debug,
886 Eq,
887 MallocSizeOf,
888 Parse,
889 PartialEq,
890 SpecifiedValueInfo,
891 ToComputedValue,
892 ToCss,
893 ToResolvedValue,
894 ToShmem,
895)]
896#[allow(missing_docs)]
897pub enum MozControlCharacterVisibility {
898 Hidden,
899 Visible,
900}
901
902#[cfg(feature = "gecko")]
903impl Default for MozControlCharacterVisibility {
904 fn default() -> Self {
905 if static_prefs::pref!("layout.css.control-characters.visible") {
906 Self::Visible
907 } else {
908 Self::Hidden
909 }
910 }
911}
912
913#[repr(u8)]
915#[derive(
916 Clone,
917 Copy,
918 Debug,
919 Eq,
920 MallocSizeOf,
921 Parse,
922 PartialEq,
923 SpecifiedValueInfo,
924 ToComputedValue,
925 ToCss,
926 ToResolvedValue,
927 ToShmem,
928)]
929#[allow(missing_docs)]
930pub enum LineBreak {
931 Auto,
932 Loose,
933 Normal,
934 Strict,
935 Anywhere,
936}
937
938#[repr(u8)]
940#[derive(
941 Clone,
942 Copy,
943 Debug,
944 Eq,
945 MallocSizeOf,
946 Parse,
947 PartialEq,
948 SpecifiedValueInfo,
949 ToComputedValue,
950 ToCss,
951 ToResolvedValue,
952 ToShmem,
953)]
954#[allow(missing_docs)]
955pub enum OverflowWrap {
956 Normal,
957 BreakWord,
958 Anywhere,
959}
960
961pub type TextIndent = GenericTextIndent<LengthPercentage>;
966
967impl Parse for TextIndent {
968 fn parse<'i, 't>(
969 context: &ParserContext,
970 input: &mut Parser<'i, 't>,
971 ) -> Result<Self, ParseError<'i>> {
972 let mut length = None;
973 let mut hanging = false;
974 let mut each_line = false;
975
976 while !input.is_exhausted() {
978 if length.is_none() {
980 if let Ok(len) = input
981 .try_parse(|i| LengthPercentage::parse_quirky(context, i, AllowQuirks::Yes))
982 {
983 length = Some(len);
984 continue;
985 }
986 }
987
988 if cfg!(feature = "servo") {
990 break;
991 }
992
993 try_match_ident_ignore_ascii_case! { input,
995 "hanging" if !hanging => hanging = true,
996 "each-line" if !each_line => each_line = true,
997 }
998 }
999
1000 if let Some(length) = length {
1002 Ok(Self {
1003 length,
1004 hanging,
1005 each_line,
1006 })
1007 } else {
1008 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1009 }
1010 }
1011}
1012
1013#[repr(u8)]
1017#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
1018#[derive(
1019 Clone,
1020 Copy,
1021 Debug,
1022 Eq,
1023 MallocSizeOf,
1024 Parse,
1025 PartialEq,
1026 SpecifiedValueInfo,
1027 ToComputedValue,
1028 ToCss,
1029 ToResolvedValue,
1030 ToShmem,
1031)]
1032#[allow(missing_docs)]
1033pub enum TextDecorationSkipInk {
1034 Auto,
1035 None,
1036 All,
1037}
1038
1039pub type TextDecorationLength = GenericTextDecorationLength<LengthPercentage>;
1041
1042impl TextDecorationLength {
1043 #[inline]
1045 pub fn auto() -> Self {
1046 GenericTextDecorationLength::Auto
1047 }
1048
1049 #[inline]
1051 pub fn is_auto(&self) -> bool {
1052 matches!(*self, GenericTextDecorationLength::Auto)
1053 }
1054}
1055
1056#[derive(
1057 Clone,
1058 Copy,
1059 Debug,
1060 Eq,
1061 MallocSizeOf,
1062 Parse,
1063 PartialEq,
1064 SpecifiedValueInfo,
1065 ToComputedValue,
1066 ToResolvedValue,
1067 ToShmem,
1068)]
1069#[css(bitflags(
1070 single = "auto",
1071 mixed = "from-font,under,left,right",
1072 validate_mixed = "Self::validate_mixed_flags",
1073))]
1074#[repr(C)]
1075pub struct TextUnderlinePosition(u8);
1080bitflags! {
1081 impl TextUnderlinePosition: u8 {
1082 const AUTO = 0;
1084 const FROM_FONT = 1 << 0;
1086 const UNDER = 1 << 1;
1088 const LEFT = 1 << 2;
1090 const RIGHT = 1 << 3;
1092 }
1093}
1094
1095impl TextUnderlinePosition {
1096 fn validate_mixed_flags(&self) -> bool {
1097 if self.contains(Self::LEFT | Self::RIGHT) {
1098 return false;
1100 }
1101 if self.contains(Self::FROM_FONT | Self::UNDER) {
1102 return false;
1104 }
1105 true
1106 }
1107}
1108
1109impl ToCss for TextUnderlinePosition {
1110 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1111 where
1112 W: Write,
1113 {
1114 if self.is_empty() {
1115 return dest.write_str("auto");
1116 }
1117
1118 let mut writer = SequenceWriter::new(dest, " ");
1119 let mut any = false;
1120
1121 macro_rules! maybe_write {
1122 ($ident:ident => $str:expr) => {
1123 if self.contains(TextUnderlinePosition::$ident) {
1124 any = true;
1125 writer.raw_item($str)?;
1126 }
1127 };
1128 }
1129
1130 maybe_write!(FROM_FONT => "from-font");
1131 maybe_write!(UNDER => "under");
1132 maybe_write!(LEFT => "left");
1133 maybe_write!(RIGHT => "right");
1134
1135 debug_assert!(any);
1136
1137 Ok(())
1138 }
1139}
1140
1141#[repr(u8)]
1143#[derive(
1144 Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem,
1145)]
1146#[allow(missing_docs)]
1147pub enum RubyPosition {
1148 AlternateOver,
1149 AlternateUnder,
1150 Over,
1151 Under,
1152}
1153
1154impl Parse for RubyPosition {
1155 fn parse<'i, 't>(
1156 _context: &ParserContext,
1157 input: &mut Parser<'i, 't>,
1158 ) -> Result<RubyPosition, ParseError<'i>> {
1159 let alternate = input
1161 .try_parse(|i| i.expect_ident_matching("alternate"))
1162 .is_ok();
1163 if alternate && input.is_exhausted() {
1164 return Ok(RubyPosition::AlternateOver);
1165 }
1166 let over = try_match_ident_ignore_ascii_case! { input,
1168 "over" => true,
1169 "under" => false,
1170 };
1171 let alternate = alternate ||
1173 input
1174 .try_parse(|i| i.expect_ident_matching("alternate"))
1175 .is_ok();
1176
1177 Ok(match (over, alternate) {
1178 (true, true) => RubyPosition::AlternateOver,
1179 (false, true) => RubyPosition::AlternateUnder,
1180 (true, false) => RubyPosition::Over,
1181 (false, false) => RubyPosition::Under,
1182 })
1183 }
1184}
1185
1186impl ToCss for RubyPosition {
1187 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1188 where
1189 W: Write,
1190 {
1191 dest.write_str(match self {
1192 RubyPosition::AlternateOver => "alternate",
1193 RubyPosition::AlternateUnder => "alternate under",
1194 RubyPosition::Over => "over",
1195 RubyPosition::Under => "under",
1196 })
1197 }
1198}
1199
1200impl SpecifiedValueInfo for RubyPosition {
1201 fn collect_completion_keywords(f: KeywordsCollectFn) {
1202 f(&["alternate", "over", "under"])
1203 }
1204}