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)]
33#[typed_value(derive_fields)]
34pub enum Spacing {
35 Normal,
37 Value(LengthPercentage),
39}
40
41impl Parse for Spacing {
42 fn parse<'i, 't>(
43 context: &ParserContext,
44 input: &mut Parser<'i, 't>,
45 ) -> Result<Self, ParseError<'i>> {
46 if input
47 .try_parse(|i| i.expect_ident_matching("normal"))
48 .is_ok()
49 {
50 return Ok(Spacing::Normal);
51 }
52 LengthPercentage::parse_quirky(context, input, AllowQuirks::Yes).map(Spacing::Value)
53 }
54}
55
56#[derive(
58 Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
59)]
60#[typed_value(derive_fields)]
61pub struct LetterSpacing(pub Spacing);
62
63impl ToComputedValue for LetterSpacing {
64 type ComputedValue = computed::LetterSpacing;
65
66 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
67 use computed::text::GenericLetterSpacing;
68 match self.0 {
69 Spacing::Normal => GenericLetterSpacing(computed::LengthPercentage::zero()),
70 Spacing::Value(ref v) => GenericLetterSpacing(v.to_computed_value(context)),
71 }
72 }
73
74 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
75 if computed.0.is_zero() {
76 return LetterSpacing(Spacing::Normal);
77 }
78 LetterSpacing(Spacing::Value(ToComputedValue::from_computed_value(
79 &computed.0,
80 )))
81 }
82}
83
84#[derive(
86 Clone, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
87)]
88#[typed_value(derive_fields)]
89pub struct WordSpacing(pub Spacing);
90
91impl ToComputedValue for WordSpacing {
92 type ComputedValue = computed::WordSpacing;
93
94 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
95 match self.0 {
96 Spacing::Normal => computed::LengthPercentage::zero(),
97 Spacing::Value(ref v) => v.to_computed_value(context),
98 }
99 }
100
101 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
102 WordSpacing(Spacing::Value(ToComputedValue::from_computed_value(
103 computed,
104 )))
105 }
106}
107
108#[derive(
110 Clone,
111 Debug,
112 MallocSizeOf,
113 Parse,
114 PartialEq,
115 SpecifiedValueInfo,
116 ToComputedValue,
117 ToCss,
118 ToResolvedValue,
119 ToShmem,
120 ToTyped,
121)]
122#[repr(C, u8)]
123pub enum HyphenateCharacter {
124 Auto,
126 String(crate::OwnedStr),
128}
129
130pub type HyphenateLimitChars = GenericHyphenateLimitChars<Integer>;
132
133impl Parse for HyphenateLimitChars {
134 fn parse<'i, 't>(
135 context: &ParserContext,
136 input: &mut Parser<'i, 't>,
137 ) -> Result<Self, ParseError<'i>> {
138 type IntegerOrAuto = NumberOrAuto<Integer>;
139
140 let total_word_length = IntegerOrAuto::parse(context, input)?;
141 let pre_hyphen_length = input
142 .try_parse(|i| IntegerOrAuto::parse(context, i))
143 .unwrap_or(IntegerOrAuto::Auto);
144 let post_hyphen_length = input
145 .try_parse(|i| IntegerOrAuto::parse(context, i))
146 .unwrap_or(pre_hyphen_length);
147 Ok(Self {
148 total_word_length,
149 pre_hyphen_length,
150 post_hyphen_length,
151 })
152 }
153}
154
155impl Parse for InitialLetter {
156 fn parse<'i, 't>(
157 context: &ParserContext,
158 input: &mut Parser<'i, 't>,
159 ) -> Result<Self, ParseError<'i>> {
160 if input
161 .try_parse(|i| i.expect_ident_matching("normal"))
162 .is_ok()
163 {
164 return Ok(Self::normal());
165 }
166 let size = Number::parse_at_least_one(context, input)?;
167 let sink = input
168 .try_parse(|i| Integer::parse_positive(context, i))
169 .unwrap_or_else(|_| crate::Zero::zero());
170 Ok(Self { size, sink })
171 }
172}
173
174#[derive(
176 Clone,
177 Debug,
178 Eq,
179 MallocSizeOf,
180 PartialEq,
181 Parse,
182 SpecifiedValueInfo,
183 ToComputedValue,
184 ToCss,
185 ToResolvedValue,
186 ToShmem,
187)]
188#[repr(C, u8)]
189pub enum TextOverflowSide {
190 Clip,
192 Ellipsis,
194 String(crate::values::AtomString),
196}
197
198#[derive(
199 Clone,
200 Debug,
201 Eq,
202 MallocSizeOf,
203 PartialEq,
204 SpecifiedValueInfo,
205 ToComputedValue,
206 ToResolvedValue,
207 ToShmem,
208 ToTyped,
209)]
210#[repr(C)]
211pub struct TextOverflow {
220 pub first: TextOverflowSide,
222 pub second: TextOverflowSide,
224 pub sides_are_logical: bool,
226}
227
228impl Parse for TextOverflow {
229 fn parse<'i, 't>(
230 context: &ParserContext,
231 input: &mut Parser<'i, 't>,
232 ) -> Result<TextOverflow, ParseError<'i>> {
233 let first = TextOverflowSide::parse(context, input)?;
234 Ok(
235 if let Ok(second) = input.try_parse(|input| TextOverflowSide::parse(context, input)) {
236 Self {
237 first,
238 second,
239 sides_are_logical: false,
240 }
241 } else {
242 Self {
243 first: TextOverflowSide::Clip,
244 second: first,
245 sides_are_logical: true,
246 }
247 },
248 )
249 }
250}
251
252impl TextOverflow {
253 pub fn get_initial_value() -> TextOverflow {
255 TextOverflow {
256 first: TextOverflowSide::Clip,
257 second: TextOverflowSide::Clip,
258 sides_are_logical: true,
259 }
260 }
261}
262
263impl ToCss for TextOverflow {
264 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
265 where
266 W: Write,
267 {
268 if self.sides_are_logical {
269 debug_assert_eq!(self.first, TextOverflowSide::Clip);
270 self.second.to_css(dest)?;
271 } else {
272 self.first.to_css(dest)?;
273 dest.write_char(' ')?;
274 self.second.to_css(dest)?;
275 }
276 Ok(())
277 }
278}
279
280#[derive(
281 Clone,
282 Copy,
283 Debug,
284 Eq,
285 MallocSizeOf,
286 PartialEq,
287 Parse,
288 Serialize,
289 SpecifiedValueInfo,
290 ToCss,
291 ToComputedValue,
292 ToResolvedValue,
293 ToShmem,
294 ToTyped,
295)]
296#[cfg_attr(
297 feature = "gecko",
298 css(bitflags(
299 single = "none,spelling-error,grammar-error",
300 mixed = "underline,overline,line-through,blink",
301 ))
302)]
303#[cfg_attr(
304 not(feature = "gecko"),
305 css(bitflags(single = "none", mixed = "underline,overline,line-through,blink",))
306)]
307#[repr(C)]
308pub struct TextDecorationLine(u8);
310bitflags! {
311 impl TextDecorationLine: u8 {
312 const NONE = 0;
314 const UNDERLINE = 1 << 0;
316 const OVERLINE = 1 << 1;
318 const LINE_THROUGH = 1 << 2;
320 const BLINK = 1 << 3;
322 const SPELLING_ERROR = 1 << 4;
324 const GRAMMAR_ERROR = 1 << 5;
326 #[cfg(feature = "gecko")]
334 const COLOR_OVERRIDE = 1 << 7;
335 }
336}
337
338impl Default for TextDecorationLine {
339 fn default() -> Self {
340 TextDecorationLine::NONE
341 }
342}
343
344impl TextDecorationLine {
345 #[inline]
346 pub fn none() -> Self {
348 TextDecorationLine::NONE
349 }
350}
351
352#[derive(
353 Clone,
354 Copy,
355 Debug,
356 Eq,
357 MallocSizeOf,
358 PartialEq,
359 SpecifiedValueInfo,
360 ToComputedValue,
361 ToCss,
362 ToResolvedValue,
363 ToShmem,
364)]
365#[repr(C)]
366pub enum TextTransformCase {
368 None,
370 Uppercase,
372 Lowercase,
374 Capitalize,
376 #[cfg(feature = "gecko")]
378 MathAuto,
379}
380
381#[derive(
382 Clone,
383 Copy,
384 Debug,
385 Eq,
386 MallocSizeOf,
387 PartialEq,
388 Parse,
389 Serialize,
390 SpecifiedValueInfo,
391 ToCss,
392 ToComputedValue,
393 ToResolvedValue,
394 ToShmem,
395 ToTyped,
396)]
397#[cfg_attr(
398 feature = "gecko",
399 css(bitflags(
400 single = "none,math-auto",
401 mixed = "uppercase,lowercase,capitalize,full-width,full-size-kana",
402 validate_mixed = "Self::validate_mixed_flags",
403 ))
404)]
405#[cfg_attr(
406 not(feature = "gecko"),
407 css(bitflags(
408 single = "none",
409 mixed = "uppercase,lowercase,capitalize,full-width,full-size-kana",
410 validate_mixed = "Self::validate_mixed_flags",
411 ))
412)]
413#[repr(C)]
414pub struct TextTransform(u8);
419bitflags! {
420 impl TextTransform: u8 {
421 const NONE = 0;
423 const UPPERCASE = 1 << 0;
425 const LOWERCASE = 1 << 1;
427 const CAPITALIZE = 1 << 2;
429 #[cfg(feature = "gecko")]
431 const MATH_AUTO = 1 << 3;
432
433 #[cfg(feature = "gecko")]
435 const CASE_TRANSFORMS = Self::UPPERCASE.0 | Self::LOWERCASE.0 | Self::CAPITALIZE.0 | Self::MATH_AUTO.0;
436 #[cfg(feature = "servo")]
438 const CASE_TRANSFORMS = Self::UPPERCASE.0 | Self::LOWERCASE.0 | Self::CAPITALIZE.0;
439
440 const FULL_WIDTH = 1 << 4;
442 const FULL_SIZE_KANA = 1 << 5;
444 }
445}
446
447impl TextTransform {
448 #[inline]
450 pub fn none() -> Self {
451 Self::NONE
452 }
453
454 #[inline]
456 pub fn is_none(self) -> bool {
457 self == Self::NONE
458 }
459
460 fn validate_mixed_flags(&self) -> bool {
461 let case = self.intersection(Self::CASE_TRANSFORMS);
462 case.is_empty() || case.bits().is_power_of_two()
464 }
465
466 pub fn case(&self) -> TextTransformCase {
468 match *self & Self::CASE_TRANSFORMS {
469 Self::NONE => TextTransformCase::None,
470 Self::UPPERCASE => TextTransformCase::Uppercase,
471 Self::LOWERCASE => TextTransformCase::Lowercase,
472 Self::CAPITALIZE => TextTransformCase::Capitalize,
473 #[cfg(feature = "gecko")]
474 Self::MATH_AUTO => TextTransformCase::MathAuto,
475 _ => unreachable!("Case bits are exclusive with each other"),
476 }
477 }
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 ToTyped,
497)]
498#[allow(missing_docs)]
499#[repr(u8)]
500pub enum TextAlignLast {
501 Auto,
502 Start,
503 End,
504 Left,
505 Right,
506 Center,
507 Justify,
508}
509
510#[derive(
512 Clone,
513 Copy,
514 Debug,
515 Eq,
516 FromPrimitive,
517 Hash,
518 MallocSizeOf,
519 Parse,
520 PartialEq,
521 SpecifiedValueInfo,
522 ToComputedValue,
523 ToCss,
524 ToResolvedValue,
525 ToShmem,
526 ToTyped,
527)]
528#[allow(missing_docs)]
529#[repr(u8)]
530pub enum TextAlignKeyword {
531 Start,
532 Left,
533 Right,
534 Center,
535 Justify,
536 End,
537 #[parse(aliases = "-webkit-center")]
538 MozCenter,
539 #[parse(aliases = "-webkit-left")]
540 MozLeft,
541 #[parse(aliases = "-webkit-right")]
542 MozRight,
543}
544
545#[derive(
547 Clone,
548 Copy,
549 Debug,
550 Eq,
551 Hash,
552 MallocSizeOf,
553 Parse,
554 PartialEq,
555 SpecifiedValueInfo,
556 ToCss,
557 ToShmem,
558 ToTyped,
559)]
560pub enum TextAlign {
561 Keyword(TextAlignKeyword),
563 #[cfg(feature = "gecko")]
566 MatchParent,
567 #[parse(condition = "ParserContext::chrome_rules_enabled")]
580 MozCenterOrInherit,
581}
582
583impl ToComputedValue for TextAlign {
584 type ComputedValue = TextAlignKeyword;
585
586 #[inline]
587 fn to_computed_value(&self, _context: &Context) -> Self::ComputedValue {
588 match *self {
589 TextAlign::Keyword(key) => key,
590 #[cfg(feature = "gecko")]
591 TextAlign::MatchParent => {
592 if _context.builder.is_root_element {
599 return TextAlignKeyword::Start;
600 }
601 let parent = _context
602 .builder
603 .get_parent_inherited_text()
604 .clone_text_align();
605 let ltr = _context.builder.inherited_writing_mode().is_bidi_ltr();
606 match (parent, ltr) {
607 (TextAlignKeyword::Start, true) => TextAlignKeyword::Left,
608 (TextAlignKeyword::Start, false) => TextAlignKeyword::Right,
609 (TextAlignKeyword::End, true) => TextAlignKeyword::Right,
610 (TextAlignKeyword::End, false) => TextAlignKeyword::Left,
611 _ => parent,
612 }
613 },
614 TextAlign::MozCenterOrInherit => {
615 let parent = _context
616 .builder
617 .get_parent_inherited_text()
618 .clone_text_align();
619 if parent == TextAlignKeyword::Start {
620 TextAlignKeyword::Center
621 } else {
622 parent
623 }
624 },
625 }
626 }
627
628 #[inline]
629 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
630 TextAlign::Keyword(*computed)
631 }
632}
633
634fn fill_mode_is_default_and_shape_exists(
635 fill: &TextEmphasisFillMode,
636 shape: &Option<TextEmphasisShapeKeyword>,
637) -> bool {
638 shape.is_some() && fill.is_filled()
639}
640
641#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
645#[allow(missing_docs)]
646pub enum TextEmphasisStyle {
647 Keyword {
649 #[css(contextual_skip_if = "fill_mode_is_default_and_shape_exists")]
650 fill: TextEmphasisFillMode,
651 shape: Option<TextEmphasisShapeKeyword>,
652 },
653 None,
655 String(crate::OwnedStr),
657}
658
659#[derive(
661 Clone,
662 Copy,
663 Debug,
664 MallocSizeOf,
665 Parse,
666 PartialEq,
667 SpecifiedValueInfo,
668 ToCss,
669 ToComputedValue,
670 ToResolvedValue,
671 ToShmem,
672)]
673#[repr(u8)]
674pub enum TextEmphasisFillMode {
675 Filled,
677 Open,
679}
680
681impl TextEmphasisFillMode {
682 #[inline]
684 pub fn is_filled(&self) -> bool {
685 matches!(*self, TextEmphasisFillMode::Filled)
686 }
687}
688
689#[derive(
691 Clone,
692 Copy,
693 Debug,
694 Eq,
695 MallocSizeOf,
696 Parse,
697 PartialEq,
698 SpecifiedValueInfo,
699 ToCss,
700 ToComputedValue,
701 ToResolvedValue,
702 ToShmem,
703)]
704#[repr(u8)]
705pub enum TextEmphasisShapeKeyword {
706 Dot,
708 Circle,
710 DoubleCircle,
712 Triangle,
714 Sesame,
716}
717
718impl ToComputedValue for TextEmphasisStyle {
719 type ComputedValue = ComputedTextEmphasisStyle;
720
721 #[inline]
722 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
723 match *self {
724 TextEmphasisStyle::Keyword { fill, shape } => {
725 let shape = shape.unwrap_or_else(|| {
726 if context.style().get_inherited_box().clone_writing_mode()
732 == SpecifiedWritingMode::HorizontalTb
733 {
734 TextEmphasisShapeKeyword::Circle
735 } else {
736 TextEmphasisShapeKeyword::Sesame
737 }
738 });
739 ComputedTextEmphasisStyle::Keyword { fill, shape }
740 },
741 TextEmphasisStyle::None => ComputedTextEmphasisStyle::None,
742 TextEmphasisStyle::String(ref s) => {
743 let first_grapheme_end = GraphemeClusterSegmenter::new()
749 .segment_str(s)
750 .nth(1)
751 .unwrap_or(0);
752 ComputedTextEmphasisStyle::String(s[0..first_grapheme_end].to_string().into())
753 },
754 }
755 }
756
757 #[inline]
758 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
759 match *computed {
760 ComputedTextEmphasisStyle::Keyword { fill, shape } => TextEmphasisStyle::Keyword {
761 fill,
762 shape: Some(shape),
763 },
764 ComputedTextEmphasisStyle::None => TextEmphasisStyle::None,
765 ComputedTextEmphasisStyle::String(ref string) => {
766 TextEmphasisStyle::String(string.clone())
767 },
768 }
769 }
770}
771
772impl Parse for TextEmphasisStyle {
773 fn parse<'i, 't>(
774 _context: &ParserContext,
775 input: &mut Parser<'i, 't>,
776 ) -> Result<Self, ParseError<'i>> {
777 if input
778 .try_parse(|input| input.expect_ident_matching("none"))
779 .is_ok()
780 {
781 return Ok(TextEmphasisStyle::None);
782 }
783
784 if let Ok(s) = input.try_parse(|i| i.expect_string().map(|s| s.as_ref().to_owned())) {
785 return Ok(TextEmphasisStyle::String(s.into()));
787 }
788
789 let mut shape = input.try_parse(TextEmphasisShapeKeyword::parse).ok();
791 let fill = input.try_parse(TextEmphasisFillMode::parse).ok();
792 if shape.is_none() {
793 shape = input.try_parse(TextEmphasisShapeKeyword::parse).ok();
794 }
795
796 if shape.is_none() && fill.is_none() {
797 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
798 }
799
800 let fill = fill.unwrap_or(TextEmphasisFillMode::Filled);
803
804 Ok(TextEmphasisStyle::Keyword { fill, shape })
807 }
808}
809
810#[derive(
811 Clone,
812 Copy,
813 Debug,
814 Eq,
815 MallocSizeOf,
816 PartialEq,
817 Parse,
818 Serialize,
819 SpecifiedValueInfo,
820 ToCss,
821 ToComputedValue,
822 ToResolvedValue,
823 ToShmem,
824 ToTyped,
825)]
826#[repr(C)]
827#[css(bitflags(
828 single = "auto",
829 mixed = "over,under,left,right",
830 validate_mixed = "Self::validate_and_simplify"
831))]
832pub struct TextEmphasisPosition(u8);
835bitflags! {
836 impl TextEmphasisPosition: u8 {
837 const AUTO = 1 << 0;
839 const OVER = 1 << 1;
841 const UNDER = 1 << 2;
843 const LEFT = 1 << 3;
845 const RIGHT = 1 << 4;
847 }
848}
849
850impl TextEmphasisPosition {
851 fn validate_and_simplify(&mut self) -> bool {
852 if self.intersects(Self::OVER) == self.intersects(Self::UNDER) {
854 return false;
855 }
856
857 if self.intersects(Self::LEFT) {
859 return !self.intersects(Self::RIGHT);
860 }
861
862 self.remove(Self::RIGHT); true
864 }
865}
866
867#[repr(u8)]
869#[derive(
870 Clone,
871 Copy,
872 Debug,
873 Eq,
874 MallocSizeOf,
875 Parse,
876 PartialEq,
877 SpecifiedValueInfo,
878 ToComputedValue,
879 ToCss,
880 ToResolvedValue,
881 ToShmem,
882 ToTyped,
883)]
884#[allow(missing_docs)]
885pub enum WordBreak {
886 Normal,
887 BreakAll,
888 KeepAll,
889 #[cfg(feature = "gecko")]
894 BreakWord,
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 ToTyped,
913)]
914#[allow(missing_docs)]
915pub enum TextJustify {
916 Auto,
917 None,
918 InterWord,
919 #[parse(aliases = "distribute")]
922 InterCharacter,
923}
924
925#[repr(u8)]
927#[derive(
928 Clone,
929 Copy,
930 Debug,
931 Eq,
932 MallocSizeOf,
933 Parse,
934 PartialEq,
935 SpecifiedValueInfo,
936 ToComputedValue,
937 ToCss,
938 ToResolvedValue,
939 ToShmem,
940 ToTyped,
941)]
942#[allow(missing_docs)]
943pub enum MozControlCharacterVisibility {
944 Hidden,
945 Visible,
946}
947
948#[cfg(feature = "gecko")]
949impl Default for MozControlCharacterVisibility {
950 fn default() -> Self {
951 if static_prefs::pref!("layout.css.control-characters.visible") {
952 Self::Visible
953 } else {
954 Self::Hidden
955 }
956 }
957}
958
959#[repr(u8)]
961#[derive(
962 Clone,
963 Copy,
964 Debug,
965 Eq,
966 MallocSizeOf,
967 Parse,
968 PartialEq,
969 SpecifiedValueInfo,
970 ToComputedValue,
971 ToCss,
972 ToResolvedValue,
973 ToShmem,
974 ToTyped,
975)]
976#[allow(missing_docs)]
977pub enum LineBreak {
978 Auto,
979 Loose,
980 Normal,
981 Strict,
982 Anywhere,
983}
984
985#[repr(u8)]
987#[derive(
988 Clone,
989 Copy,
990 Debug,
991 Eq,
992 MallocSizeOf,
993 Parse,
994 PartialEq,
995 SpecifiedValueInfo,
996 ToComputedValue,
997 ToCss,
998 ToResolvedValue,
999 ToShmem,
1000 ToTyped,
1001)]
1002#[allow(missing_docs)]
1003pub enum OverflowWrap {
1004 Normal,
1005 BreakWord,
1006 Anywhere,
1007}
1008
1009pub type TextIndent = GenericTextIndent<LengthPercentage>;
1014
1015impl Parse for TextIndent {
1016 fn parse<'i, 't>(
1017 context: &ParserContext,
1018 input: &mut Parser<'i, 't>,
1019 ) -> Result<Self, ParseError<'i>> {
1020 let mut length = None;
1021 let mut hanging = false;
1022 let mut each_line = false;
1023
1024 while !input.is_exhausted() {
1026 if length.is_none() {
1028 if let Ok(len) = input
1029 .try_parse(|i| LengthPercentage::parse_quirky(context, i, AllowQuirks::Yes))
1030 {
1031 length = Some(len);
1032 continue;
1033 }
1034 }
1035
1036 if cfg!(feature = "servo") {
1038 break;
1039 }
1040
1041 try_match_ident_ignore_ascii_case! { input,
1043 "hanging" if !hanging => hanging = true,
1044 "each-line" if !each_line => each_line = true,
1045 }
1046 }
1047
1048 if let Some(length) = length {
1050 Ok(Self {
1051 length,
1052 hanging,
1053 each_line,
1054 })
1055 } else {
1056 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1057 }
1058 }
1059}
1060
1061#[repr(u8)]
1065#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
1066#[derive(
1067 Clone,
1068 Copy,
1069 Debug,
1070 Eq,
1071 MallocSizeOf,
1072 Parse,
1073 PartialEq,
1074 SpecifiedValueInfo,
1075 ToComputedValue,
1076 ToCss,
1077 ToResolvedValue,
1078 ToShmem,
1079 ToTyped,
1080)]
1081#[allow(missing_docs)]
1082pub enum TextDecorationSkipInk {
1083 Auto,
1084 None,
1085 All,
1086}
1087
1088pub type TextDecorationLength = GenericTextDecorationLength<LengthPercentage>;
1090
1091impl TextDecorationLength {
1092 #[inline]
1094 pub fn auto() -> Self {
1095 GenericTextDecorationLength::Auto
1096 }
1097
1098 #[inline]
1100 pub fn is_auto(&self) -> bool {
1101 matches!(*self, GenericTextDecorationLength::Auto)
1102 }
1103}
1104
1105pub type TextDecorationInset = GenericTextDecorationInset<Length>;
1107
1108impl TextDecorationInset {
1109 #[inline]
1111 pub fn auto() -> Self {
1112 GenericTextDecorationInset::Auto
1113 }
1114
1115 #[inline]
1117 pub fn is_auto(&self) -> bool {
1118 matches!(*self, GenericTextDecorationInset::Auto)
1119 }
1120}
1121
1122impl Parse for TextDecorationInset {
1123 fn parse<'i, 't>(
1124 ctx: &ParserContext,
1125 input: &mut Parser<'i, 't>,
1126 ) -> Result<Self, ParseError<'i>> {
1127 if let Ok(start) = input.try_parse(|i| Length::parse(ctx, i)) {
1128 let end = input.try_parse(|i| Length::parse(ctx, i));
1129 let end = end.unwrap_or_else(|_| start.clone());
1130 return Ok(TextDecorationInset::Length { start, end });
1131 }
1132 input.expect_ident_matching("auto")?;
1133 Ok(TextDecorationInset::Auto)
1134 }
1135}
1136
1137#[derive(
1138 Clone,
1139 Copy,
1140 Debug,
1141 Eq,
1142 MallocSizeOf,
1143 Parse,
1144 PartialEq,
1145 SpecifiedValueInfo,
1146 ToComputedValue,
1147 ToResolvedValue,
1148 ToShmem,
1149 ToTyped,
1150)]
1151#[css(bitflags(
1152 single = "auto",
1153 mixed = "from-font,under,left,right",
1154 validate_mixed = "Self::validate_mixed_flags",
1155))]
1156#[repr(C)]
1157pub struct TextUnderlinePosition(u8);
1162bitflags! {
1163 impl TextUnderlinePosition: u8 {
1164 const AUTO = 0;
1166 const FROM_FONT = 1 << 0;
1168 const UNDER = 1 << 1;
1170 const LEFT = 1 << 2;
1172 const RIGHT = 1 << 3;
1174 }
1175}
1176
1177impl TextUnderlinePosition {
1178 fn validate_mixed_flags(&self) -> bool {
1179 if self.contains(Self::LEFT | Self::RIGHT) {
1180 return false;
1182 }
1183 if self.contains(Self::FROM_FONT | Self::UNDER) {
1184 return false;
1186 }
1187 true
1188 }
1189}
1190
1191impl ToCss for TextUnderlinePosition {
1192 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1193 where
1194 W: Write,
1195 {
1196 if self.is_empty() {
1197 return dest.write_str("auto");
1198 }
1199
1200 let mut writer = SequenceWriter::new(dest, " ");
1201 let mut any = false;
1202
1203 macro_rules! maybe_write {
1204 ($ident:ident => $str:expr) => {
1205 if self.contains(TextUnderlinePosition::$ident) {
1206 any = true;
1207 writer.raw_item($str)?;
1208 }
1209 };
1210 }
1211
1212 maybe_write!(FROM_FONT => "from-font");
1213 maybe_write!(UNDER => "under");
1214 maybe_write!(LEFT => "left");
1215 maybe_write!(RIGHT => "right");
1216
1217 debug_assert!(any);
1218
1219 Ok(())
1220 }
1221}
1222
1223#[repr(u8)]
1225#[derive(
1226 Clone,
1227 Copy,
1228 Debug,
1229 Eq,
1230 MallocSizeOf,
1231 PartialEq,
1232 ToComputedValue,
1233 ToResolvedValue,
1234 ToShmem,
1235 ToTyped,
1236)]
1237#[allow(missing_docs)]
1238pub enum RubyPosition {
1239 AlternateOver,
1240 AlternateUnder,
1241 Over,
1242 Under,
1243}
1244
1245impl Parse for RubyPosition {
1246 fn parse<'i, 't>(
1247 _context: &ParserContext,
1248 input: &mut Parser<'i, 't>,
1249 ) -> Result<RubyPosition, ParseError<'i>> {
1250 let alternate = input
1252 .try_parse(|i| i.expect_ident_matching("alternate"))
1253 .is_ok();
1254 if alternate && input.is_exhausted() {
1255 return Ok(RubyPosition::AlternateOver);
1256 }
1257 let over = try_match_ident_ignore_ascii_case! { input,
1259 "over" => true,
1260 "under" => false,
1261 };
1262 let alternate = alternate
1264 || input
1265 .try_parse(|i| i.expect_ident_matching("alternate"))
1266 .is_ok();
1267
1268 Ok(match (over, alternate) {
1269 (true, true) => RubyPosition::AlternateOver,
1270 (false, true) => RubyPosition::AlternateUnder,
1271 (true, false) => RubyPosition::Over,
1272 (false, false) => RubyPosition::Under,
1273 })
1274 }
1275}
1276
1277impl ToCss for RubyPosition {
1278 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1279 where
1280 W: Write,
1281 {
1282 dest.write_str(match self {
1283 RubyPosition::AlternateOver => "alternate",
1284 RubyPosition::AlternateUnder => "alternate under",
1285 RubyPosition::Over => "over",
1286 RubyPosition::Under => "under",
1287 })
1288 }
1289}
1290
1291impl SpecifiedValueInfo for RubyPosition {
1292 fn collect_completion_keywords(f: KeywordsCollectFn) {
1293 f(&["alternate", "over", "under"])
1294 }
1295}
1296
1297#[derive(
1309 Clone,
1310 Copy,
1311 Debug,
1312 Eq,
1313 MallocSizeOf,
1314 Parse,
1315 PartialEq,
1316 Serialize,
1317 SpecifiedValueInfo,
1318 ToCss,
1319 ToComputedValue,
1320 ToResolvedValue,
1321 ToShmem,
1322 ToTyped,
1323)]
1324#[css(bitflags(
1325 single = "normal,auto,no-autospace",
1326 mixed = "ideograph-alpha,ideograph-numeric,insert",
1329 ))]
1332#[repr(C)]
1333pub struct TextAutospace(u8);
1334bitflags! {
1335 impl TextAutospace: u8 {
1336 const NO_AUTOSPACE = 0;
1338
1339 const AUTO = 1 << 0;
1341
1342 const NORMAL = 1 << 1;
1344
1345 const IDEOGRAPH_ALPHA = 1 << 2;
1347
1348 const IDEOGRAPH_NUMERIC = 1 << 3;
1350
1351 const INSERT = 1 << 5;
1358
1359 }
1364}
1365
1366#[derive(
1375 Clone,
1376 Copy,
1377 Debug,
1378 Eq,
1379 FromPrimitive,
1380 Hash,
1381 MallocSizeOf,
1382 Parse,
1383 PartialEq,
1384 SpecifiedValueInfo,
1385 ToComputedValue,
1386 ToCss,
1387 ToResolvedValue,
1388 ToShmem,
1389 ToTyped,
1390)]
1391#[repr(u8)]
1392pub enum TextEdgeKeyword {
1396 Text,
1398 Ideographic,
1400 IdeographicInk,
1402 Cap,
1404 Ex,
1406 Alphabetic,
1408}
1409
1410impl TextEdgeKeyword {
1411 fn is_valid_for_over(&self) -> bool {
1412 match self {
1413 TextEdgeKeyword::Text
1414 | TextEdgeKeyword::Ideographic
1415 | TextEdgeKeyword::IdeographicInk
1416 | TextEdgeKeyword::Cap
1417 | TextEdgeKeyword::Ex => true,
1418 _ => false,
1419 }
1420 }
1421
1422 fn is_valid_for_under(&self) -> bool {
1423 match self {
1424 TextEdgeKeyword::Text
1425 | TextEdgeKeyword::Ideographic
1426 | TextEdgeKeyword::IdeographicInk
1427 | TextEdgeKeyword::Alphabetic => true,
1428 _ => false,
1429 }
1430 }
1431}
1432
1433#[derive(
1434 Clone,
1435 Copy,
1436 Debug,
1437 Eq,
1438 Hash,
1439 MallocSizeOf,
1440 PartialEq,
1441 SpecifiedValueInfo,
1442 ToComputedValue,
1443 ToResolvedValue,
1444 ToShmem,
1445 ToTyped,
1446)]
1447#[repr(C)]
1448pub struct TextEdge {
1458 pub over: TextEdgeKeyword,
1460 pub under: TextEdgeKeyword,
1462}
1463
1464impl Parse for TextEdge {
1465 fn parse<'i, 't>(
1466 _context: &ParserContext,
1467 input: &mut Parser<'i, 't>,
1468 ) -> Result<TextEdge, ParseError<'i>> {
1469 let first = TextEdgeKeyword::parse(input)?;
1470
1471 if let Ok(second) = input.try_parse(TextEdgeKeyword::parse) {
1472 if !first.is_valid_for_over() || !second.is_valid_for_under() {
1473 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1474 }
1475
1476 return Ok(TextEdge {
1477 over: first,
1478 under: second,
1479 });
1480 }
1481
1482 match (first.is_valid_for_over(), first.is_valid_for_under()) {
1486 (true, true) => Ok(TextEdge {
1487 over: first,
1488 under: first,
1489 }),
1490 (true, false) => Ok(TextEdge {
1491 over: first,
1492 under: TextEdgeKeyword::Text,
1493 }),
1494 (false, true) => Ok(TextEdge {
1495 over: TextEdgeKeyword::Text,
1496 under: first,
1497 }),
1498 _ => unreachable!("Parsed keyword will be valid for at least one edge"),
1499 }
1500 }
1501}
1502
1503impl ToCss for TextEdge {
1504 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1505 where
1506 W: Write,
1507 {
1508 match (self.over, self.under) {
1509 (over, TextEdgeKeyword::Text) if !over.is_valid_for_under() => over.to_css(dest),
1510 (TextEdgeKeyword::Text, under) if !under.is_valid_for_over() => under.to_css(dest),
1511 (over, under) => {
1512 over.to_css(dest)?;
1513
1514 if over != under {
1515 dest.write_char(' ')?;
1516 self.under.to_css(dest)?;
1517 }
1518
1519 Ok(())
1520 },
1521 }
1522 }
1523}
1524
1525#[derive(
1526 Clone,
1527 Copy,
1528 Debug,
1529 Eq,
1530 Hash,
1531 MallocSizeOf,
1532 Parse,
1533 PartialEq,
1534 SpecifiedValueInfo,
1535 ToComputedValue,
1536 ToCss,
1537 ToResolvedValue,
1538 ToShmem,
1539 ToTyped,
1540)]
1541#[repr(C, u8)]
1542pub enum TextBoxEdge {
1546 Auto,
1548 TextEdge(TextEdge),
1550}
1551
1552#[derive(
1553 Clone,
1554 Copy,
1555 Debug,
1556 Eq,
1557 MallocSizeOf,
1558 PartialEq,
1559 Parse,
1560 Serialize,
1561 SpecifiedValueInfo,
1562 ToCss,
1563 ToComputedValue,
1564 ToResolvedValue,
1565 ToShmem,
1566 ToTyped,
1567)]
1568#[css(bitflags(single = "none,trim-start,trim-end,trim-both"))]
1569#[repr(C)]
1570pub struct TextBoxTrim(u8);
1574bitflags! {
1575 impl TextBoxTrim: u8 {
1576 const NONE = 0;
1578 const TRIM_START = 1 << 0;
1580 const TRIM_END = 1 << 1;
1582 const TRIM_BOTH = Self::TRIM_START.0 | Self::TRIM_END.0;
1584 }
1585}
1586
1587impl TextBoxTrim {
1588 #[inline]
1590 pub fn none() -> Self {
1591 TextBoxTrim::NONE
1592 }
1593}