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(Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
56pub struct LetterSpacing(pub Spacing);
57
58impl ToComputedValue for LetterSpacing {
59 type ComputedValue = computed::LetterSpacing;
60
61 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
62 use computed::text::GenericLetterSpacing;
63 match self.0 {
64 Spacing::Normal => GenericLetterSpacing(computed::LengthPercentage::zero()),
65 Spacing::Value(ref v) => GenericLetterSpacing(v.to_computed_value(context)),
66 }
67 }
68
69 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
70 if computed.0.is_zero() {
71 return LetterSpacing(Spacing::Normal);
72 }
73 LetterSpacing(Spacing::Value(ToComputedValue::from_computed_value(
74 &computed.0,
75 )))
76 }
77}
78
79#[derive(Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
81pub struct WordSpacing(pub Spacing);
82
83impl ToComputedValue for WordSpacing {
84 type ComputedValue = computed::WordSpacing;
85
86 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
87 match self.0 {
88 Spacing::Normal => computed::LengthPercentage::zero(),
89 Spacing::Value(ref v) => v.to_computed_value(context),
90 }
91 }
92
93 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
94 WordSpacing(Spacing::Value(ToComputedValue::from_computed_value(
95 computed,
96 )))
97 }
98}
99
100#[derive(
102 Clone,
103 Debug,
104 MallocSizeOf,
105 Parse,
106 PartialEq,
107 SpecifiedValueInfo,
108 ToComputedValue,
109 ToCss,
110 ToResolvedValue,
111 ToShmem,
112)]
113#[repr(C, u8)]
114pub enum HyphenateCharacter {
115 Auto,
117 String(crate::OwnedStr),
119}
120
121pub type HyphenateLimitChars = GenericHyphenateLimitChars<Integer>;
123
124impl Parse for HyphenateLimitChars {
125 fn parse<'i, 't>(
126 context: &ParserContext,
127 input: &mut Parser<'i, 't>,
128 ) -> Result<Self, ParseError<'i>> {
129 type IntegerOrAuto = NumberOrAuto<Integer>;
130
131 let total_word_length = IntegerOrAuto::parse(context, input)?;
132 let pre_hyphen_length = input
133 .try_parse(|i| IntegerOrAuto::parse(context, i))
134 .unwrap_or(IntegerOrAuto::Auto);
135 let post_hyphen_length = input
136 .try_parse(|i| IntegerOrAuto::parse(context, i))
137 .unwrap_or(pre_hyphen_length);
138 Ok(Self {
139 total_word_length,
140 pre_hyphen_length,
141 post_hyphen_length,
142 })
143 }
144}
145
146impl Parse for InitialLetter {
147 fn parse<'i, 't>(
148 context: &ParserContext,
149 input: &mut Parser<'i, 't>,
150 ) -> Result<Self, ParseError<'i>> {
151 if input
152 .try_parse(|i| i.expect_ident_matching("normal"))
153 .is_ok()
154 {
155 return Ok(Self::normal());
156 }
157 let size = Number::parse_at_least_one(context, input)?;
158 let sink = input
159 .try_parse(|i| Integer::parse_positive(context, i))
160 .unwrap_or_else(|_| crate::Zero::zero());
161 Ok(Self { size, sink })
162 }
163}
164
165#[derive(
167 Clone,
168 Debug,
169 Eq,
170 MallocSizeOf,
171 PartialEq,
172 Parse,
173 SpecifiedValueInfo,
174 ToComputedValue,
175 ToCss,
176 ToResolvedValue,
177 ToShmem,
178)]
179#[repr(C, u8)]
180pub enum TextOverflowSide {
181 Clip,
183 Ellipsis,
185 String(crate::values::AtomString),
187}
188
189#[derive(
190 Clone,
191 Debug,
192 Eq,
193 MallocSizeOf,
194 PartialEq,
195 SpecifiedValueInfo,
196 ToComputedValue,
197 ToResolvedValue,
198 ToShmem,
199)]
200#[repr(C)]
201pub struct TextOverflow {
210 pub first: TextOverflowSide,
212 pub second: TextOverflowSide,
214 pub sides_are_logical: bool,
216}
217
218impl Parse for TextOverflow {
219 fn parse<'i, 't>(
220 context: &ParserContext,
221 input: &mut Parser<'i, 't>,
222 ) -> Result<TextOverflow, ParseError<'i>> {
223 let first = TextOverflowSide::parse(context, input)?;
224 Ok(
225 if let Ok(second) = input.try_parse(|input| TextOverflowSide::parse(context, input)) {
226 Self {
227 first,
228 second,
229 sides_are_logical: false,
230 }
231 } else {
232 Self {
233 first: TextOverflowSide::Clip,
234 second: first,
235 sides_are_logical: true,
236 }
237 },
238 )
239 }
240}
241
242impl TextOverflow {
243 pub fn get_initial_value() -> TextOverflow {
245 TextOverflow {
246 first: TextOverflowSide::Clip,
247 second: TextOverflowSide::Clip,
248 sides_are_logical: true,
249 }
250 }
251}
252
253impl ToCss for TextOverflow {
254 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
255 where
256 W: Write,
257 {
258 if self.sides_are_logical {
259 debug_assert_eq!(self.first, TextOverflowSide::Clip);
260 self.second.to_css(dest)?;
261 } else {
262 self.first.to_css(dest)?;
263 dest.write_char(' ')?;
264 self.second.to_css(dest)?;
265 }
266 Ok(())
267 }
268}
269
270#[derive(
271 Clone,
272 Copy,
273 Debug,
274 Eq,
275 MallocSizeOf,
276 PartialEq,
277 Parse,
278 Serialize,
279 SpecifiedValueInfo,
280 ToCss,
281 ToComputedValue,
282 ToResolvedValue,
283 ToShmem,
284)]
285#[cfg_attr(
286 feature = "gecko",
287 css(bitflags(
288 single = "none,spelling-error,grammar-error",
289 mixed = "underline,overline,line-through,blink",
290 ))
291)]
292#[cfg_attr(
293 not(feature = "gecko"),
294 css(bitflags(single = "none", mixed = "underline,overline,line-through,blink",))
295)]
296#[repr(C)]
297pub struct TextDecorationLine(u8);
299bitflags! {
300 impl TextDecorationLine: u8 {
301 const NONE = 0;
303 const UNDERLINE = 1 << 0;
305 const OVERLINE = 1 << 1;
307 const LINE_THROUGH = 1 << 2;
309 const BLINK = 1 << 3;
311 const SPELLING_ERROR = 1 << 4;
313 const GRAMMAR_ERROR = 1 << 5;
315 #[cfg(feature = "gecko")]
323 const COLOR_OVERRIDE = 1 << 7;
324 }
325}
326
327impl Default for TextDecorationLine {
328 fn default() -> Self {
329 TextDecorationLine::NONE
330 }
331}
332
333impl TextDecorationLine {
334 #[inline]
335 pub fn none() -> Self {
337 TextDecorationLine::NONE
338 }
339}
340
341#[derive(
342 Clone,
343 Copy,
344 Debug,
345 Eq,
346 MallocSizeOf,
347 PartialEq,
348 SpecifiedValueInfo,
349 ToComputedValue,
350 ToCss,
351 ToResolvedValue,
352 ToShmem,
353)]
354#[repr(C)]
355pub enum TextTransformCase {
357 None,
359 Uppercase,
361 Lowercase,
363 Capitalize,
365 #[cfg(feature = "gecko")]
367 MathAuto,
368}
369
370#[derive(
371 Clone,
372 Copy,
373 Debug,
374 Eq,
375 MallocSizeOf,
376 PartialEq,
377 Parse,
378 Serialize,
379 SpecifiedValueInfo,
380 ToCss,
381 ToComputedValue,
382 ToResolvedValue,
383 ToShmem,
384)]
385#[cfg_attr(
386 feature = "gecko",
387 css(bitflags(
388 single = "none,math-auto",
389 mixed = "uppercase,lowercase,capitalize,full-width,full-size-kana",
390 validate_mixed = "Self::validate_mixed_flags",
391 ))
392)]
393#[cfg_attr(
394 not(feature = "gecko"),
395 css(bitflags(
396 single = "none",
397 mixed = "uppercase,lowercase,capitalize,full-width,full-size-kana",
398 validate_mixed = "Self::validate_mixed_flags",
399 ))
400)]
401#[repr(C)]
402pub struct TextTransform(u8);
407bitflags! {
408 impl TextTransform: u8 {
409 const NONE = 0;
411 const UPPERCASE = 1 << 0;
413 const LOWERCASE = 1 << 1;
415 const CAPITALIZE = 1 << 2;
417 #[cfg(feature = "gecko")]
419 const MATH_AUTO = 1 << 3;
420
421 #[cfg(feature = "gecko")]
423 const CASE_TRANSFORMS = Self::UPPERCASE.0 | Self::LOWERCASE.0 | Self::CAPITALIZE.0 | Self::MATH_AUTO.0;
424 #[cfg(feature = "servo")]
426 const CASE_TRANSFORMS = Self::UPPERCASE.0 | Self::LOWERCASE.0 | Self::CAPITALIZE.0;
427
428 const FULL_WIDTH = 1 << 4;
430 const FULL_SIZE_KANA = 1 << 5;
432 }
433}
434
435impl TextTransform {
436 #[inline]
438 pub fn none() -> Self {
439 Self::NONE
440 }
441
442 #[inline]
444 pub fn is_none(self) -> bool {
445 self == Self::NONE
446 }
447
448 fn validate_mixed_flags(&self) -> bool {
449 let case = self.intersection(Self::CASE_TRANSFORMS);
450 case.is_empty() || case.bits().is_power_of_two()
452 }
453
454 pub fn case(&self) -> TextTransformCase {
456 match *self & Self::CASE_TRANSFORMS {
457 Self::NONE => TextTransformCase::None,
458 Self::UPPERCASE => TextTransformCase::Uppercase,
459 Self::LOWERCASE => TextTransformCase::Lowercase,
460 Self::CAPITALIZE => TextTransformCase::Capitalize,
461 #[cfg(feature = "gecko")]
462 Self::MATH_AUTO => TextTransformCase::MathAuto,
463 _ => unreachable!("Case bits are exclusive with each other"),
464 }
465 }
466}
467
468#[derive(
470 Clone,
471 Copy,
472 Debug,
473 Eq,
474 FromPrimitive,
475 Hash,
476 MallocSizeOf,
477 Parse,
478 PartialEq,
479 SpecifiedValueInfo,
480 ToComputedValue,
481 ToCss,
482 ToResolvedValue,
483 ToShmem,
484)]
485#[allow(missing_docs)]
486#[repr(u8)]
487pub enum TextAlignLast {
488 Auto,
489 Start,
490 End,
491 Left,
492 Right,
493 Center,
494 Justify,
495}
496
497#[derive(
499 Clone,
500 Copy,
501 Debug,
502 Eq,
503 FromPrimitive,
504 Hash,
505 MallocSizeOf,
506 Parse,
507 PartialEq,
508 SpecifiedValueInfo,
509 ToComputedValue,
510 ToCss,
511 ToResolvedValue,
512 ToShmem,
513)]
514#[allow(missing_docs)]
515#[repr(u8)]
516pub enum TextAlignKeyword {
517 Start,
518 Left,
519 Right,
520 Center,
521 Justify,
522 End,
523 #[parse(aliases = "-webkit-center")]
524 MozCenter,
525 #[parse(aliases = "-webkit-left")]
526 MozLeft,
527 #[parse(aliases = "-webkit-right")]
528 MozRight,
529}
530
531#[derive(
533 Clone, Copy, Debug, Eq, Hash, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem,
534)]
535pub enum TextAlign {
536 Keyword(TextAlignKeyword),
538 #[cfg(feature = "gecko")]
541 MatchParent,
542 #[parse(condition = "ParserContext::chrome_rules_enabled")]
555 MozCenterOrInherit,
556}
557
558impl ToComputedValue for TextAlign {
559 type ComputedValue = TextAlignKeyword;
560
561 #[inline]
562 fn to_computed_value(&self, _context: &Context) -> Self::ComputedValue {
563 match *self {
564 TextAlign::Keyword(key) => key,
565 #[cfg(feature = "gecko")]
566 TextAlign::MatchParent => {
567 if _context.builder.is_root_element {
574 return TextAlignKeyword::Start;
575 }
576 let parent = _context
577 .builder
578 .get_parent_inherited_text()
579 .clone_text_align();
580 let ltr = _context.builder.inherited_writing_mode().is_bidi_ltr();
581 match (parent, ltr) {
582 (TextAlignKeyword::Start, true) => TextAlignKeyword::Left,
583 (TextAlignKeyword::Start, false) => TextAlignKeyword::Right,
584 (TextAlignKeyword::End, true) => TextAlignKeyword::Right,
585 (TextAlignKeyword::End, false) => TextAlignKeyword::Left,
586 _ => parent,
587 }
588 },
589 TextAlign::MozCenterOrInherit => {
590 let parent = _context
591 .builder
592 .get_parent_inherited_text()
593 .clone_text_align();
594 if parent == TextAlignKeyword::Start {
595 TextAlignKeyword::Center
596 } else {
597 parent
598 }
599 },
600 }
601 }
602
603 #[inline]
604 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
605 TextAlign::Keyword(*computed)
606 }
607}
608
609fn fill_mode_is_default_and_shape_exists(
610 fill: &TextEmphasisFillMode,
611 shape: &Option<TextEmphasisShapeKeyword>,
612) -> bool {
613 shape.is_some() && fill.is_filled()
614}
615
616#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
620#[allow(missing_docs)]
621pub enum TextEmphasisStyle {
622 Keyword {
624 #[css(contextual_skip_if = "fill_mode_is_default_and_shape_exists")]
625 fill: TextEmphasisFillMode,
626 shape: Option<TextEmphasisShapeKeyword>,
627 },
628 None,
630 String(crate::OwnedStr),
632}
633
634#[derive(
636 Clone,
637 Copy,
638 Debug,
639 MallocSizeOf,
640 Parse,
641 PartialEq,
642 SpecifiedValueInfo,
643 ToCss,
644 ToComputedValue,
645 ToResolvedValue,
646 ToShmem,
647)]
648#[repr(u8)]
649pub enum TextEmphasisFillMode {
650 Filled,
652 Open,
654}
655
656impl TextEmphasisFillMode {
657 #[inline]
659 pub fn is_filled(&self) -> bool {
660 matches!(*self, TextEmphasisFillMode::Filled)
661 }
662}
663
664#[derive(
666 Clone,
667 Copy,
668 Debug,
669 Eq,
670 MallocSizeOf,
671 Parse,
672 PartialEq,
673 SpecifiedValueInfo,
674 ToCss,
675 ToComputedValue,
676 ToResolvedValue,
677 ToShmem,
678)]
679#[repr(u8)]
680pub enum TextEmphasisShapeKeyword {
681 Dot,
683 Circle,
685 DoubleCircle,
687 Triangle,
689 Sesame,
691}
692
693impl ToComputedValue for TextEmphasisStyle {
694 type ComputedValue = ComputedTextEmphasisStyle;
695
696 #[inline]
697 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
698 match *self {
699 TextEmphasisStyle::Keyword { fill, shape } => {
700 let shape = shape.unwrap_or_else(|| {
701 if context.style().get_inherited_box().clone_writing_mode()
707 == SpecifiedWritingMode::HorizontalTb
708 {
709 TextEmphasisShapeKeyword::Circle
710 } else {
711 TextEmphasisShapeKeyword::Sesame
712 }
713 });
714 ComputedTextEmphasisStyle::Keyword { fill, shape }
715 },
716 TextEmphasisStyle::None => ComputedTextEmphasisStyle::None,
717 TextEmphasisStyle::String(ref s) => {
718 let first_grapheme_end = GraphemeClusterSegmenter::new()
724 .segment_str(s)
725 .nth(1)
726 .unwrap_or(0);
727 ComputedTextEmphasisStyle::String(s[0..first_grapheme_end].to_string().into())
728 },
729 }
730 }
731
732 #[inline]
733 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
734 match *computed {
735 ComputedTextEmphasisStyle::Keyword { fill, shape } => TextEmphasisStyle::Keyword {
736 fill,
737 shape: Some(shape),
738 },
739 ComputedTextEmphasisStyle::None => TextEmphasisStyle::None,
740 ComputedTextEmphasisStyle::String(ref string) => {
741 TextEmphasisStyle::String(string.clone())
742 },
743 }
744 }
745}
746
747impl Parse for TextEmphasisStyle {
748 fn parse<'i, 't>(
749 _context: &ParserContext,
750 input: &mut Parser<'i, 't>,
751 ) -> Result<Self, ParseError<'i>> {
752 if input
753 .try_parse(|input| input.expect_ident_matching("none"))
754 .is_ok()
755 {
756 return Ok(TextEmphasisStyle::None);
757 }
758
759 if let Ok(s) = input.try_parse(|i| i.expect_string().map(|s| s.as_ref().to_owned())) {
760 return Ok(TextEmphasisStyle::String(s.into()));
762 }
763
764 let mut shape = input.try_parse(TextEmphasisShapeKeyword::parse).ok();
766 let fill = input.try_parse(TextEmphasisFillMode::parse).ok();
767 if shape.is_none() {
768 shape = input.try_parse(TextEmphasisShapeKeyword::parse).ok();
769 }
770
771 if shape.is_none() && fill.is_none() {
772 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
773 }
774
775 let fill = fill.unwrap_or(TextEmphasisFillMode::Filled);
778
779 Ok(TextEmphasisStyle::Keyword { fill, shape })
782 }
783}
784
785#[derive(
786 Clone,
787 Copy,
788 Debug,
789 Eq,
790 MallocSizeOf,
791 PartialEq,
792 Parse,
793 Serialize,
794 SpecifiedValueInfo,
795 ToCss,
796 ToComputedValue,
797 ToResolvedValue,
798 ToShmem,
799)]
800#[repr(C)]
801#[css(bitflags(
802 single = "auto",
803 mixed = "over,under,left,right",
804 validate_mixed = "Self::validate_and_simplify"
805))]
806pub struct TextEmphasisPosition(u8);
809bitflags! {
810 impl TextEmphasisPosition: u8 {
811 const AUTO = 1 << 0;
813 const OVER = 1 << 1;
815 const UNDER = 1 << 2;
817 const LEFT = 1 << 3;
819 const RIGHT = 1 << 4;
821 }
822}
823
824impl TextEmphasisPosition {
825 fn validate_and_simplify(&mut self) -> bool {
826 if self.intersects(Self::OVER) == self.intersects(Self::UNDER) {
828 return false;
829 }
830
831 if self.intersects(Self::LEFT) {
833 return !self.intersects(Self::RIGHT);
834 }
835
836 self.remove(Self::RIGHT); true
838 }
839}
840
841#[repr(u8)]
843#[derive(
844 Clone,
845 Copy,
846 Debug,
847 Eq,
848 MallocSizeOf,
849 Parse,
850 PartialEq,
851 SpecifiedValueInfo,
852 ToComputedValue,
853 ToCss,
854 ToResolvedValue,
855 ToShmem,
856)]
857#[allow(missing_docs)]
858pub enum WordBreak {
859 Normal,
860 BreakAll,
861 KeepAll,
862 #[cfg(feature = "gecko")]
867 BreakWord,
868}
869
870#[repr(u8)]
872#[derive(
873 Clone,
874 Copy,
875 Debug,
876 Eq,
877 MallocSizeOf,
878 Parse,
879 PartialEq,
880 SpecifiedValueInfo,
881 ToComputedValue,
882 ToCss,
883 ToResolvedValue,
884 ToShmem,
885)]
886#[allow(missing_docs)]
887pub enum TextJustify {
888 Auto,
889 None,
890 InterWord,
891 #[parse(aliases = "distribute")]
894 InterCharacter,
895}
896
897#[repr(u8)]
899#[derive(
900 Clone,
901 Copy,
902 Debug,
903 Eq,
904 MallocSizeOf,
905 Parse,
906 PartialEq,
907 SpecifiedValueInfo,
908 ToComputedValue,
909 ToCss,
910 ToResolvedValue,
911 ToShmem,
912)]
913#[allow(missing_docs)]
914pub enum MozControlCharacterVisibility {
915 Hidden,
916 Visible,
917}
918
919#[cfg(feature = "gecko")]
920impl Default for MozControlCharacterVisibility {
921 fn default() -> Self {
922 if static_prefs::pref!("layout.css.control-characters.visible") {
923 Self::Visible
924 } else {
925 Self::Hidden
926 }
927 }
928}
929
930#[repr(u8)]
932#[derive(
933 Clone,
934 Copy,
935 Debug,
936 Eq,
937 MallocSizeOf,
938 Parse,
939 PartialEq,
940 SpecifiedValueInfo,
941 ToComputedValue,
942 ToCss,
943 ToResolvedValue,
944 ToShmem,
945)]
946#[allow(missing_docs)]
947pub enum LineBreak {
948 Auto,
949 Loose,
950 Normal,
951 Strict,
952 Anywhere,
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)]
971#[allow(missing_docs)]
972pub enum OverflowWrap {
973 Normal,
974 BreakWord,
975 Anywhere,
976}
977
978pub type TextIndent = GenericTextIndent<LengthPercentage>;
983
984impl Parse for TextIndent {
985 fn parse<'i, 't>(
986 context: &ParserContext,
987 input: &mut Parser<'i, 't>,
988 ) -> Result<Self, ParseError<'i>> {
989 let mut length = None;
990 let mut hanging = false;
991 let mut each_line = false;
992
993 while !input.is_exhausted() {
995 if length.is_none() {
997 if let Ok(len) = input
998 .try_parse(|i| LengthPercentage::parse_quirky(context, i, AllowQuirks::Yes))
999 {
1000 length = Some(len);
1001 continue;
1002 }
1003 }
1004
1005 if cfg!(feature = "servo") {
1007 break;
1008 }
1009
1010 try_match_ident_ignore_ascii_case! { input,
1012 "hanging" if !hanging => hanging = true,
1013 "each-line" if !each_line => each_line = true,
1014 }
1015 }
1016
1017 if let Some(length) = length {
1019 Ok(Self {
1020 length,
1021 hanging,
1022 each_line,
1023 })
1024 } else {
1025 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1026 }
1027 }
1028}
1029
1030#[repr(u8)]
1034#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
1035#[derive(
1036 Clone,
1037 Copy,
1038 Debug,
1039 Eq,
1040 MallocSizeOf,
1041 Parse,
1042 PartialEq,
1043 SpecifiedValueInfo,
1044 ToComputedValue,
1045 ToCss,
1046 ToResolvedValue,
1047 ToShmem,
1048)]
1049#[allow(missing_docs)]
1050pub enum TextDecorationSkipInk {
1051 Auto,
1052 None,
1053 All,
1054}
1055
1056pub type TextDecorationLength = GenericTextDecorationLength<LengthPercentage>;
1058
1059impl TextDecorationLength {
1060 #[inline]
1062 pub fn auto() -> Self {
1063 GenericTextDecorationLength::Auto
1064 }
1065
1066 #[inline]
1068 pub fn is_auto(&self) -> bool {
1069 matches!(*self, GenericTextDecorationLength::Auto)
1070 }
1071}
1072
1073pub type TextDecorationTrim = GenericTextDecorationTrim<Length>;
1075
1076impl TextDecorationTrim {
1077 #[inline]
1079 pub fn auto() -> Self {
1080 GenericTextDecorationTrim::Auto
1081 }
1082
1083 #[inline]
1085 pub fn is_auto(&self) -> bool {
1086 matches!(*self, GenericTextDecorationTrim::Auto)
1087 }
1088}
1089
1090impl Parse for TextDecorationTrim {
1091 fn parse<'i, 't>(
1092 ctx: &ParserContext,
1093 input: &mut Parser<'i, 't>,
1094 ) -> Result<Self, ParseError<'i>> {
1095 if let Ok(start) = input.try_parse(|i| Length::parse(ctx, i)) {
1096 let end = input.try_parse(|i| Length::parse(ctx, i));
1097 let end = end.unwrap_or_else(|_| start.clone());
1098 return Ok(TextDecorationTrim::Length { start, end });
1099 }
1100 input.expect_ident_matching("auto")?;
1101 Ok(TextDecorationTrim::Auto)
1102 }
1103}
1104
1105#[derive(
1106 Clone,
1107 Copy,
1108 Debug,
1109 Eq,
1110 MallocSizeOf,
1111 Parse,
1112 PartialEq,
1113 SpecifiedValueInfo,
1114 ToComputedValue,
1115 ToResolvedValue,
1116 ToShmem,
1117)]
1118#[css(bitflags(
1119 single = "auto",
1120 mixed = "from-font,under,left,right",
1121 validate_mixed = "Self::validate_mixed_flags",
1122))]
1123#[repr(C)]
1124pub struct TextUnderlinePosition(u8);
1129bitflags! {
1130 impl TextUnderlinePosition: u8 {
1131 const AUTO = 0;
1133 const FROM_FONT = 1 << 0;
1135 const UNDER = 1 << 1;
1137 const LEFT = 1 << 2;
1139 const RIGHT = 1 << 3;
1141 }
1142}
1143
1144impl TextUnderlinePosition {
1145 fn validate_mixed_flags(&self) -> bool {
1146 if self.contains(Self::LEFT | Self::RIGHT) {
1147 return false;
1149 }
1150 if self.contains(Self::FROM_FONT | Self::UNDER) {
1151 return false;
1153 }
1154 true
1155 }
1156}
1157
1158impl ToCss for TextUnderlinePosition {
1159 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1160 where
1161 W: Write,
1162 {
1163 if self.is_empty() {
1164 return dest.write_str("auto");
1165 }
1166
1167 let mut writer = SequenceWriter::new(dest, " ");
1168 let mut any = false;
1169
1170 macro_rules! maybe_write {
1171 ($ident:ident => $str:expr) => {
1172 if self.contains(TextUnderlinePosition::$ident) {
1173 any = true;
1174 writer.raw_item($str)?;
1175 }
1176 };
1177 }
1178
1179 maybe_write!(FROM_FONT => "from-font");
1180 maybe_write!(UNDER => "under");
1181 maybe_write!(LEFT => "left");
1182 maybe_write!(RIGHT => "right");
1183
1184 debug_assert!(any);
1185
1186 Ok(())
1187 }
1188}
1189
1190#[repr(u8)]
1192#[derive(
1193 Clone, Copy, Debug, Eq, MallocSizeOf, PartialEq, ToComputedValue, ToResolvedValue, ToShmem,
1194)]
1195#[allow(missing_docs)]
1196pub enum RubyPosition {
1197 AlternateOver,
1198 AlternateUnder,
1199 Over,
1200 Under,
1201}
1202
1203impl Parse for RubyPosition {
1204 fn parse<'i, 't>(
1205 _context: &ParserContext,
1206 input: &mut Parser<'i, 't>,
1207 ) -> Result<RubyPosition, ParseError<'i>> {
1208 let alternate = input
1210 .try_parse(|i| i.expect_ident_matching("alternate"))
1211 .is_ok();
1212 if alternate && input.is_exhausted() {
1213 return Ok(RubyPosition::AlternateOver);
1214 }
1215 let over = try_match_ident_ignore_ascii_case! { input,
1217 "over" => true,
1218 "under" => false,
1219 };
1220 let alternate = alternate
1222 || input
1223 .try_parse(|i| i.expect_ident_matching("alternate"))
1224 .is_ok();
1225
1226 Ok(match (over, alternate) {
1227 (true, true) => RubyPosition::AlternateOver,
1228 (false, true) => RubyPosition::AlternateUnder,
1229 (true, false) => RubyPosition::Over,
1230 (false, false) => RubyPosition::Under,
1231 })
1232 }
1233}
1234
1235impl ToCss for RubyPosition {
1236 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1237 where
1238 W: Write,
1239 {
1240 dest.write_str(match self {
1241 RubyPosition::AlternateOver => "alternate",
1242 RubyPosition::AlternateUnder => "alternate under",
1243 RubyPosition::Over => "over",
1244 RubyPosition::Under => "under",
1245 })
1246 }
1247}
1248
1249impl SpecifiedValueInfo for RubyPosition {
1250 fn collect_completion_keywords(f: KeywordsCollectFn) {
1251 f(&["alternate", "over", "under"])
1252 }
1253}
1254
1255#[derive(
1267 Clone,
1268 Copy,
1269 Debug,
1270 Eq,
1271 MallocSizeOf,
1272 Parse,
1273 PartialEq,
1274 Serialize,
1275 SpecifiedValueInfo,
1276 ToCss,
1277 ToComputedValue,
1278 ToResolvedValue,
1279 ToShmem,
1280)]
1281#[css(bitflags(
1282 single = "normal,auto,no-autospace",
1283 mixed = "ideograph-alpha,ideograph-numeric,punctuation,insert",
1285 ))]
1288#[repr(C)]
1289pub struct TextAutospace(u8);
1290bitflags! {
1291 impl TextAutospace: u8 {
1292 const NORMAL = 0;
1294
1295 const AUTO = 1 << 0;
1297
1298 const NO_AUTOSPACE = 1 << 1;
1300
1301 const IDEOGRAPH_ALPHA = 1 << 2;
1303
1304 const IDEOGRAPH_NUMERIC = 1 << 3;
1306
1307 const PUNCTUATION = 1 << 4;
1309
1310 const INSERT = 1 << 5;
1312
1313 }
1318}
1319
1320