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