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, GenericTextDecorationInset,
14 GenericTextDecorationLength, 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, ToTyped)]
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)]
58#[typed_value(derive_fields)]
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)]
120pub enum HyphenateCharacter {
121 Auto,
123 String(crate::OwnedStr),
125}
126
127pub type HyphenateLimitChars = GenericHyphenateLimitChars<Integer>;
129
130impl Parse for HyphenateLimitChars {
131 fn parse<'i, 't>(
132 context: &ParserContext,
133 input: &mut Parser<'i, 't>,
134 ) -> Result<Self, ParseError<'i>> {
135 type IntegerOrAuto = NumberOrAuto<Integer>;
136
137 let total_word_length = IntegerOrAuto::parse(context, input)?;
138 let pre_hyphen_length = input
139 .try_parse(|i| IntegerOrAuto::parse(context, i))
140 .unwrap_or(IntegerOrAuto::Auto);
141 let post_hyphen_length = input
142 .try_parse(|i| IntegerOrAuto::parse(context, i))
143 .unwrap_or(pre_hyphen_length);
144 Ok(Self {
145 total_word_length,
146 pre_hyphen_length,
147 post_hyphen_length,
148 })
149 }
150}
151
152impl Parse for InitialLetter {
153 fn parse<'i, 't>(
154 context: &ParserContext,
155 input: &mut Parser<'i, 't>,
156 ) -> Result<Self, ParseError<'i>> {
157 if input
158 .try_parse(|i| i.expect_ident_matching("normal"))
159 .is_ok()
160 {
161 return Ok(Self::normal());
162 }
163 let size = Number::parse_at_least_one(context, input)?;
164 let sink = input
165 .try_parse(|i| Integer::parse_positive(context, i))
166 .unwrap_or_else(|_| crate::Zero::zero());
167 Ok(Self { size, sink })
168 }
169}
170
171#[derive(
173 Clone,
174 Debug,
175 Eq,
176 MallocSizeOf,
177 PartialEq,
178 Parse,
179 SpecifiedValueInfo,
180 ToComputedValue,
181 ToCss,
182 ToResolvedValue,
183 ToShmem,
184)]
185#[repr(C, u8)]
186pub enum TextOverflowSide {
187 Clip,
189 Ellipsis,
191 String(crate::values::AtomString),
193}
194
195#[derive(
196 Clone,
197 Debug,
198 Eq,
199 MallocSizeOf,
200 PartialEq,
201 SpecifiedValueInfo,
202 ToComputedValue,
203 ToResolvedValue,
204 ToShmem,
205 ToTyped,
206)]
207#[repr(C)]
208pub struct TextOverflow {
217 pub first: TextOverflowSide,
219 pub second: TextOverflowSide,
221 pub sides_are_logical: bool,
223}
224
225impl Parse for TextOverflow {
226 fn parse<'i, 't>(
227 context: &ParserContext,
228 input: &mut Parser<'i, 't>,
229 ) -> Result<TextOverflow, ParseError<'i>> {
230 let first = TextOverflowSide::parse(context, input)?;
231 Ok(
232 if let Ok(second) = input.try_parse(|input| TextOverflowSide::parse(context, input)) {
233 Self {
234 first,
235 second,
236 sides_are_logical: false,
237 }
238 } else {
239 Self {
240 first: TextOverflowSide::Clip,
241 second: first,
242 sides_are_logical: true,
243 }
244 },
245 )
246 }
247}
248
249impl TextOverflow {
250 pub fn get_initial_value() -> TextOverflow {
252 TextOverflow {
253 first: TextOverflowSide::Clip,
254 second: TextOverflowSide::Clip,
255 sides_are_logical: true,
256 }
257 }
258}
259
260impl ToCss for TextOverflow {
261 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
262 where
263 W: Write,
264 {
265 if self.sides_are_logical {
266 debug_assert_eq!(self.first, TextOverflowSide::Clip);
267 self.second.to_css(dest)?;
268 } else {
269 self.first.to_css(dest)?;
270 dest.write_char(' ')?;
271 self.second.to_css(dest)?;
272 }
273 Ok(())
274 }
275}
276
277#[derive(
278 Clone,
279 Copy,
280 Debug,
281 Eq,
282 MallocSizeOf,
283 PartialEq,
284 Parse,
285 Serialize,
286 SpecifiedValueInfo,
287 ToCss,
288 ToComputedValue,
289 ToResolvedValue,
290 ToShmem,
291 ToTyped,
292)]
293#[cfg_attr(
294 feature = "gecko",
295 css(bitflags(
296 single = "none,spelling-error,grammar-error",
297 mixed = "underline,overline,line-through,blink",
298 ))
299)]
300#[cfg_attr(
301 not(feature = "gecko"),
302 css(bitflags(single = "none", mixed = "underline,overline,line-through,blink",))
303)]
304#[repr(C)]
305pub struct TextDecorationLine(u8);
307bitflags! {
308 impl TextDecorationLine: u8 {
309 const NONE = 0;
311 const UNDERLINE = 1 << 0;
313 const OVERLINE = 1 << 1;
315 const LINE_THROUGH = 1 << 2;
317 const BLINK = 1 << 3;
319 const SPELLING_ERROR = 1 << 4;
321 const GRAMMAR_ERROR = 1 << 5;
323 #[cfg(feature = "gecko")]
331 const COLOR_OVERRIDE = 1 << 7;
332 }
333}
334
335impl Default for TextDecorationLine {
336 fn default() -> Self {
337 TextDecorationLine::NONE
338 }
339}
340
341impl TextDecorationLine {
342 #[inline]
343 pub fn none() -> Self {
345 TextDecorationLine::NONE
346 }
347}
348
349#[derive(
350 Clone,
351 Copy,
352 Debug,
353 Eq,
354 MallocSizeOf,
355 PartialEq,
356 SpecifiedValueInfo,
357 ToComputedValue,
358 ToCss,
359 ToResolvedValue,
360 ToShmem,
361)]
362#[repr(C)]
363pub enum TextTransformCase {
365 None,
367 Uppercase,
369 Lowercase,
371 Capitalize,
373 #[cfg(feature = "gecko")]
375 MathAuto,
376}
377
378#[derive(
379 Clone,
380 Copy,
381 Debug,
382 Eq,
383 MallocSizeOf,
384 PartialEq,
385 Parse,
386 Serialize,
387 SpecifiedValueInfo,
388 ToCss,
389 ToComputedValue,
390 ToResolvedValue,
391 ToShmem,
392 ToTyped,
393)]
394#[cfg_attr(
395 feature = "gecko",
396 css(bitflags(
397 single = "none,math-auto",
398 mixed = "uppercase,lowercase,capitalize,full-width,full-size-kana",
399 validate_mixed = "Self::validate_mixed_flags",
400 ))
401)]
402#[cfg_attr(
403 not(feature = "gecko"),
404 css(bitflags(
405 single = "none",
406 mixed = "uppercase,lowercase,capitalize,full-width,full-size-kana",
407 validate_mixed = "Self::validate_mixed_flags",
408 ))
409)]
410#[repr(C)]
411pub struct TextTransform(u8);
416bitflags! {
417 impl TextTransform: u8 {
418 const NONE = 0;
420 const UPPERCASE = 1 << 0;
422 const LOWERCASE = 1 << 1;
424 const CAPITALIZE = 1 << 2;
426 #[cfg(feature = "gecko")]
428 const MATH_AUTO = 1 << 3;
429
430 #[cfg(feature = "gecko")]
432 const CASE_TRANSFORMS = Self::UPPERCASE.0 | Self::LOWERCASE.0 | Self::CAPITALIZE.0 | Self::MATH_AUTO.0;
433 #[cfg(feature = "servo")]
435 const CASE_TRANSFORMS = Self::UPPERCASE.0 | Self::LOWERCASE.0 | Self::CAPITALIZE.0;
436
437 const FULL_WIDTH = 1 << 4;
439 const FULL_SIZE_KANA = 1 << 5;
441 }
442}
443
444impl TextTransform {
445 #[inline]
447 pub fn none() -> Self {
448 Self::NONE
449 }
450
451 #[inline]
453 pub fn is_none(self) -> bool {
454 self == Self::NONE
455 }
456
457 fn validate_mixed_flags(&self) -> bool {
458 let case = self.intersection(Self::CASE_TRANSFORMS);
459 case.is_empty() || case.bits().is_power_of_two()
461 }
462
463 pub fn case(&self) -> TextTransformCase {
465 match *self & Self::CASE_TRANSFORMS {
466 Self::NONE => TextTransformCase::None,
467 Self::UPPERCASE => TextTransformCase::Uppercase,
468 Self::LOWERCASE => TextTransformCase::Lowercase,
469 Self::CAPITALIZE => TextTransformCase::Capitalize,
470 #[cfg(feature = "gecko")]
471 Self::MATH_AUTO => TextTransformCase::MathAuto,
472 _ => unreachable!("Case bits are exclusive with each other"),
473 }
474 }
475}
476
477#[derive(
479 Clone,
480 Copy,
481 Debug,
482 Eq,
483 FromPrimitive,
484 Hash,
485 MallocSizeOf,
486 Parse,
487 PartialEq,
488 SpecifiedValueInfo,
489 ToComputedValue,
490 ToCss,
491 ToResolvedValue,
492 ToShmem,
493 ToTyped,
494)]
495#[allow(missing_docs)]
496#[repr(u8)]
497pub enum TextAlignLast {
498 Auto,
499 Start,
500 End,
501 Left,
502 Right,
503 Center,
504 Justify,
505}
506
507#[derive(
509 Clone,
510 Copy,
511 Debug,
512 Eq,
513 FromPrimitive,
514 Hash,
515 MallocSizeOf,
516 Parse,
517 PartialEq,
518 SpecifiedValueInfo,
519 ToComputedValue,
520 ToCss,
521 ToResolvedValue,
522 ToShmem,
523 ToTyped,
524)]
525#[allow(missing_docs)]
526#[repr(u8)]
527pub enum TextAlignKeyword {
528 Start,
529 Left,
530 Right,
531 Center,
532 Justify,
533 End,
534 #[parse(aliases = "-webkit-center")]
535 MozCenter,
536 #[parse(aliases = "-webkit-left")]
537 MozLeft,
538 #[parse(aliases = "-webkit-right")]
539 MozRight,
540}
541
542#[derive(
544 Clone,
545 Copy,
546 Debug,
547 Eq,
548 Hash,
549 MallocSizeOf,
550 Parse,
551 PartialEq,
552 SpecifiedValueInfo,
553 ToCss,
554 ToShmem,
555 ToTyped,
556)]
557pub enum TextAlign {
558 Keyword(TextAlignKeyword),
560 #[cfg(feature = "gecko")]
563 MatchParent,
564 #[parse(condition = "ParserContext::chrome_rules_enabled")]
577 MozCenterOrInherit,
578}
579
580impl ToComputedValue for TextAlign {
581 type ComputedValue = TextAlignKeyword;
582
583 #[inline]
584 fn to_computed_value(&self, _context: &Context) -> Self::ComputedValue {
585 match *self {
586 TextAlign::Keyword(key) => key,
587 #[cfg(feature = "gecko")]
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)]
643pub enum TextEmphasisStyle {
644 Keyword {
646 #[css(contextual_skip_if = "fill_mode_is_default_and_shape_exists")]
647 fill: TextEmphasisFillMode,
648 shape: Option<TextEmphasisShapeKeyword>,
649 },
650 None,
652 String(crate::OwnedStr),
654}
655
656#[derive(
658 Clone,
659 Copy,
660 Debug,
661 MallocSizeOf,
662 Parse,
663 PartialEq,
664 SpecifiedValueInfo,
665 ToCss,
666 ToComputedValue,
667 ToResolvedValue,
668 ToShmem,
669)]
670#[repr(u8)]
671pub enum TextEmphasisFillMode {
672 Filled,
674 Open,
676}
677
678impl TextEmphasisFillMode {
679 #[inline]
681 pub fn is_filled(&self) -> bool {
682 matches!(*self, TextEmphasisFillMode::Filled)
683 }
684}
685
686#[derive(
688 Clone,
689 Copy,
690 Debug,
691 Eq,
692 MallocSizeOf,
693 Parse,
694 PartialEq,
695 SpecifiedValueInfo,
696 ToCss,
697 ToComputedValue,
698 ToResolvedValue,
699 ToShmem,
700)]
701#[repr(u8)]
702pub enum TextEmphasisShapeKeyword {
703 Dot,
705 Circle,
707 DoubleCircle,
709 Triangle,
711 Sesame,
713}
714
715impl ToComputedValue for TextEmphasisStyle {
716 type ComputedValue = ComputedTextEmphasisStyle;
717
718 #[inline]
719 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
720 match *self {
721 TextEmphasisStyle::Keyword { fill, shape } => {
722 let shape = shape.unwrap_or_else(|| {
723 if context.style().get_inherited_box().clone_writing_mode()
729 == SpecifiedWritingMode::HorizontalTb
730 {
731 TextEmphasisShapeKeyword::Circle
732 } else {
733 TextEmphasisShapeKeyword::Sesame
734 }
735 });
736 ComputedTextEmphasisStyle::Keyword { fill, shape }
737 },
738 TextEmphasisStyle::None => ComputedTextEmphasisStyle::None,
739 TextEmphasisStyle::String(ref s) => {
740 let first_grapheme_end = GraphemeClusterSegmenter::new()
746 .segment_str(s)
747 .nth(1)
748 .unwrap_or(0);
749 ComputedTextEmphasisStyle::String(s[0..first_grapheme_end].to_string().into())
750 },
751 }
752 }
753
754 #[inline]
755 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
756 match *computed {
757 ComputedTextEmphasisStyle::Keyword { fill, shape } => TextEmphasisStyle::Keyword {
758 fill,
759 shape: Some(shape),
760 },
761 ComputedTextEmphasisStyle::None => TextEmphasisStyle::None,
762 ComputedTextEmphasisStyle::String(ref string) => {
763 TextEmphasisStyle::String(string.clone())
764 },
765 }
766 }
767}
768
769impl Parse for TextEmphasisStyle {
770 fn parse<'i, 't>(
771 _context: &ParserContext,
772 input: &mut Parser<'i, 't>,
773 ) -> Result<Self, ParseError<'i>> {
774 if input
775 .try_parse(|input| input.expect_ident_matching("none"))
776 .is_ok()
777 {
778 return Ok(TextEmphasisStyle::None);
779 }
780
781 if let Ok(s) = input.try_parse(|i| i.expect_string().map(|s| s.as_ref().to_owned())) {
782 return Ok(TextEmphasisStyle::String(s.into()));
784 }
785
786 let mut shape = input.try_parse(TextEmphasisShapeKeyword::parse).ok();
788 let fill = input.try_parse(TextEmphasisFillMode::parse).ok();
789 if shape.is_none() {
790 shape = input.try_parse(TextEmphasisShapeKeyword::parse).ok();
791 }
792
793 if shape.is_none() && fill.is_none() {
794 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
795 }
796
797 let fill = fill.unwrap_or(TextEmphasisFillMode::Filled);
800
801 Ok(TextEmphasisStyle::Keyword { fill, shape })
804 }
805}
806
807#[derive(
808 Clone,
809 Copy,
810 Debug,
811 Eq,
812 MallocSizeOf,
813 PartialEq,
814 Parse,
815 Serialize,
816 SpecifiedValueInfo,
817 ToCss,
818 ToComputedValue,
819 ToResolvedValue,
820 ToShmem,
821 ToTyped,
822)]
823#[repr(C)]
824#[css(bitflags(
825 single = "auto",
826 mixed = "over,under,left,right",
827 validate_mixed = "Self::validate_and_simplify"
828))]
829pub struct TextEmphasisPosition(u8);
832bitflags! {
833 impl TextEmphasisPosition: u8 {
834 const AUTO = 1 << 0;
836 const OVER = 1 << 1;
838 const UNDER = 1 << 2;
840 const LEFT = 1 << 3;
842 const RIGHT = 1 << 4;
844 }
845}
846
847impl TextEmphasisPosition {
848 fn validate_and_simplify(&mut self) -> bool {
849 if self.intersects(Self::OVER) == self.intersects(Self::UNDER) {
851 return false;
852 }
853
854 if self.intersects(Self::LEFT) {
856 return !self.intersects(Self::RIGHT);
857 }
858
859 self.remove(Self::RIGHT); true
861 }
862}
863
864#[repr(u8)]
866#[derive(
867 Clone,
868 Copy,
869 Debug,
870 Eq,
871 MallocSizeOf,
872 Parse,
873 PartialEq,
874 SpecifiedValueInfo,
875 ToComputedValue,
876 ToCss,
877 ToResolvedValue,
878 ToShmem,
879 ToTyped,
880)]
881#[allow(missing_docs)]
882pub enum WordBreak {
883 Normal,
884 BreakAll,
885 KeepAll,
886 #[cfg(feature = "gecko")]
891 BreakWord,
892}
893
894#[repr(u8)]
896#[derive(
897 Clone,
898 Copy,
899 Debug,
900 Eq,
901 MallocSizeOf,
902 Parse,
903 PartialEq,
904 SpecifiedValueInfo,
905 ToComputedValue,
906 ToCss,
907 ToResolvedValue,
908 ToShmem,
909 ToTyped,
910)]
911#[allow(missing_docs)]
912pub enum TextJustify {
913 Auto,
914 None,
915 InterWord,
916 #[parse(aliases = "distribute")]
919 InterCharacter,
920}
921
922#[repr(u8)]
924#[derive(
925 Clone,
926 Copy,
927 Debug,
928 Eq,
929 MallocSizeOf,
930 Parse,
931 PartialEq,
932 SpecifiedValueInfo,
933 ToComputedValue,
934 ToCss,
935 ToResolvedValue,
936 ToShmem,
937 ToTyped,
938)]
939#[allow(missing_docs)]
940pub enum MozControlCharacterVisibility {
941 Hidden,
942 Visible,
943}
944
945#[cfg(feature = "gecko")]
946impl Default for MozControlCharacterVisibility {
947 fn default() -> Self {
948 if static_prefs::pref!("layout.css.control-characters.visible") {
949 Self::Visible
950 } else {
951 Self::Hidden
952 }
953 }
954}
955
956#[repr(u8)]
958#[derive(
959 Clone,
960 Copy,
961 Debug,
962 Eq,
963 MallocSizeOf,
964 Parse,
965 PartialEq,
966 SpecifiedValueInfo,
967 ToComputedValue,
968 ToCss,
969 ToResolvedValue,
970 ToShmem,
971 ToTyped,
972)]
973#[allow(missing_docs)]
974pub enum LineBreak {
975 Auto,
976 Loose,
977 Normal,
978 Strict,
979 Anywhere,
980}
981
982#[repr(u8)]
984#[derive(
985 Clone,
986 Copy,
987 Debug,
988 Eq,
989 MallocSizeOf,
990 Parse,
991 PartialEq,
992 SpecifiedValueInfo,
993 ToComputedValue,
994 ToCss,
995 ToResolvedValue,
996 ToShmem,
997 ToTyped,
998)]
999#[allow(missing_docs)]
1000pub enum OverflowWrap {
1001 Normal,
1002 BreakWord,
1003 Anywhere,
1004}
1005
1006pub type TextIndent = GenericTextIndent<LengthPercentage>;
1011
1012impl Parse for TextIndent {
1013 fn parse<'i, 't>(
1014 context: &ParserContext,
1015 input: &mut Parser<'i, 't>,
1016 ) -> Result<Self, ParseError<'i>> {
1017 let mut length = None;
1018 let mut hanging = false;
1019 let mut each_line = false;
1020
1021 while !input.is_exhausted() {
1023 if length.is_none() {
1025 if let Ok(len) = input
1026 .try_parse(|i| LengthPercentage::parse_quirky(context, i, AllowQuirks::Yes))
1027 {
1028 length = Some(len);
1029 continue;
1030 }
1031 }
1032
1033 if cfg!(feature = "servo") {
1035 break;
1036 }
1037
1038 try_match_ident_ignore_ascii_case! { input,
1040 "hanging" if !hanging => hanging = true,
1041 "each-line" if !each_line => each_line = true,
1042 }
1043 }
1044
1045 if let Some(length) = length {
1047 Ok(Self {
1048 length,
1049 hanging,
1050 each_line,
1051 })
1052 } else {
1053 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1054 }
1055 }
1056}
1057
1058#[repr(u8)]
1062#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
1063#[derive(
1064 Clone,
1065 Copy,
1066 Debug,
1067 Eq,
1068 MallocSizeOf,
1069 Parse,
1070 PartialEq,
1071 SpecifiedValueInfo,
1072 ToComputedValue,
1073 ToCss,
1074 ToResolvedValue,
1075 ToShmem,
1076 ToTyped,
1077)]
1078#[allow(missing_docs)]
1079pub enum TextDecorationSkipInk {
1080 Auto,
1081 None,
1082 All,
1083}
1084
1085pub type TextDecorationLength = GenericTextDecorationLength<LengthPercentage>;
1087
1088impl TextDecorationLength {
1089 #[inline]
1091 pub fn auto() -> Self {
1092 GenericTextDecorationLength::Auto
1093 }
1094
1095 #[inline]
1097 pub fn is_auto(&self) -> bool {
1098 matches!(*self, GenericTextDecorationLength::Auto)
1099 }
1100}
1101
1102pub type TextDecorationInset = GenericTextDecorationInset<Length>;
1104
1105impl TextDecorationInset {
1106 #[inline]
1108 pub fn auto() -> Self {
1109 GenericTextDecorationInset::Auto
1110 }
1111
1112 #[inline]
1114 pub fn is_auto(&self) -> bool {
1115 matches!(*self, GenericTextDecorationInset::Auto)
1116 }
1117}
1118
1119impl Parse for TextDecorationInset {
1120 fn parse<'i, 't>(
1121 ctx: &ParserContext,
1122 input: &mut Parser<'i, 't>,
1123 ) -> Result<Self, ParseError<'i>> {
1124 if let Ok(start) = input.try_parse(|i| Length::parse(ctx, i)) {
1125 let end = input.try_parse(|i| Length::parse(ctx, i));
1126 let end = end.unwrap_or_else(|_| start.clone());
1127 return Ok(TextDecorationInset::Length { start, end });
1128 }
1129 input.expect_ident_matching("auto")?;
1130 Ok(TextDecorationInset::Auto)
1131 }
1132}
1133
1134#[derive(
1135 Clone,
1136 Copy,
1137 Debug,
1138 Eq,
1139 MallocSizeOf,
1140 Parse,
1141 PartialEq,
1142 SpecifiedValueInfo,
1143 ToComputedValue,
1144 ToResolvedValue,
1145 ToShmem,
1146 ToTyped,
1147)]
1148#[css(bitflags(
1149 single = "auto",
1150 mixed = "from-font,under,left,right",
1151 validate_mixed = "Self::validate_mixed_flags",
1152))]
1153#[repr(C)]
1154pub struct TextUnderlinePosition(u8);
1159bitflags! {
1160 impl TextUnderlinePosition: u8 {
1161 const AUTO = 0;
1163 const FROM_FONT = 1 << 0;
1165 const UNDER = 1 << 1;
1167 const LEFT = 1 << 2;
1169 const RIGHT = 1 << 3;
1171 }
1172}
1173
1174impl TextUnderlinePosition {
1175 fn validate_mixed_flags(&self) -> bool {
1176 if self.contains(Self::LEFT | Self::RIGHT) {
1177 return false;
1179 }
1180 if self.contains(Self::FROM_FONT | Self::UNDER) {
1181 return false;
1183 }
1184 true
1185 }
1186}
1187
1188impl ToCss for TextUnderlinePosition {
1189 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1190 where
1191 W: Write,
1192 {
1193 if self.is_empty() {
1194 return dest.write_str("auto");
1195 }
1196
1197 let mut writer = SequenceWriter::new(dest, " ");
1198 let mut any = false;
1199
1200 macro_rules! maybe_write {
1201 ($ident:ident => $str:expr) => {
1202 if self.contains(TextUnderlinePosition::$ident) {
1203 any = true;
1204 writer.raw_item($str)?;
1205 }
1206 };
1207 }
1208
1209 maybe_write!(FROM_FONT => "from-font");
1210 maybe_write!(UNDER => "under");
1211 maybe_write!(LEFT => "left");
1212 maybe_write!(RIGHT => "right");
1213
1214 debug_assert!(any);
1215
1216 Ok(())
1217 }
1218}
1219
1220#[repr(u8)]
1222#[derive(
1223 Clone,
1224 Copy,
1225 Debug,
1226 Eq,
1227 MallocSizeOf,
1228 PartialEq,
1229 ToComputedValue,
1230 ToResolvedValue,
1231 ToShmem,
1232 ToTyped,
1233)]
1234#[allow(missing_docs)]
1235pub enum RubyPosition {
1236 AlternateOver,
1237 AlternateUnder,
1238 Over,
1239 Under,
1240}
1241
1242impl Parse for RubyPosition {
1243 fn parse<'i, 't>(
1244 _context: &ParserContext,
1245 input: &mut Parser<'i, 't>,
1246 ) -> Result<RubyPosition, ParseError<'i>> {
1247 let alternate = input
1249 .try_parse(|i| i.expect_ident_matching("alternate"))
1250 .is_ok();
1251 if alternate && input.is_exhausted() {
1252 return Ok(RubyPosition::AlternateOver);
1253 }
1254 let over = try_match_ident_ignore_ascii_case! { input,
1256 "over" => true,
1257 "under" => false,
1258 };
1259 let alternate = alternate
1261 || input
1262 .try_parse(|i| i.expect_ident_matching("alternate"))
1263 .is_ok();
1264
1265 Ok(match (over, alternate) {
1266 (true, true) => RubyPosition::AlternateOver,
1267 (false, true) => RubyPosition::AlternateUnder,
1268 (true, false) => RubyPosition::Over,
1269 (false, false) => RubyPosition::Under,
1270 })
1271 }
1272}
1273
1274impl ToCss for RubyPosition {
1275 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1276 where
1277 W: Write,
1278 {
1279 dest.write_str(match self {
1280 RubyPosition::AlternateOver => "alternate",
1281 RubyPosition::AlternateUnder => "alternate under",
1282 RubyPosition::Over => "over",
1283 RubyPosition::Under => "under",
1284 })
1285 }
1286}
1287
1288impl SpecifiedValueInfo for RubyPosition {
1289 fn collect_completion_keywords(f: KeywordsCollectFn) {
1290 f(&["alternate", "over", "under"])
1291 }
1292}
1293
1294#[derive(
1306 Clone,
1307 Copy,
1308 Debug,
1309 Eq,
1310 MallocSizeOf,
1311 Parse,
1312 PartialEq,
1313 Serialize,
1314 SpecifiedValueInfo,
1315 ToCss,
1316 ToComputedValue,
1317 ToResolvedValue,
1318 ToShmem,
1319 ToTyped,
1320)]
1321#[css(bitflags(
1322 single = "normal,auto,no-autospace",
1323 mixed = "ideograph-alpha,ideograph-numeric,insert",
1326 ))]
1329#[repr(C)]
1330pub struct TextAutospace(u8);
1331bitflags! {
1332 impl TextAutospace: u8 {
1333 const NO_AUTOSPACE = 0;
1335
1336 const AUTO = 1 << 0;
1338
1339 const NORMAL = 1 << 1;
1341
1342 const IDEOGRAPH_ALPHA = 1 << 2;
1344
1345 const IDEOGRAPH_NUMERIC = 1 << 3;
1347
1348 const INSERT = 1 << 5;
1355
1356 }
1361}
1362
1363