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)]
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)]
120#[typed(todo_derive_fields)]
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_else(|_| pre_hyphen_length.clone());
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)]
209#[typed(todo_derive_fields)]
210pub struct TextOverflow {
219 pub first: TextOverflowSide,
221 pub second: TextOverflowSide,
223 pub sides_are_logical: bool,
225}
226
227impl Parse for TextOverflow {
228 fn parse<'i, 't>(
229 context: &ParserContext,
230 input: &mut Parser<'i, 't>,
231 ) -> Result<TextOverflow, ParseError<'i>> {
232 let first = TextOverflowSide::parse(context, input)?;
233 Ok(
234 if let Ok(second) = input.try_parse(|input| TextOverflowSide::parse(context, input)) {
235 Self {
236 first,
237 second,
238 sides_are_logical: false,
239 }
240 } else {
241 Self {
242 first: TextOverflowSide::Clip,
243 second: first,
244 sides_are_logical: true,
245 }
246 },
247 )
248 }
249}
250
251impl TextOverflow {
252 pub fn get_initial_value() -> TextOverflow {
254 TextOverflow {
255 first: TextOverflowSide::Clip,
256 second: TextOverflowSide::Clip,
257 sides_are_logical: true,
258 }
259 }
260}
261
262impl ToCss for TextOverflow {
263 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
264 where
265 W: Write,
266 {
267 if self.sides_are_logical {
268 debug_assert_eq!(self.first, TextOverflowSide::Clip);
269 self.second.to_css(dest)?;
270 } else {
271 self.first.to_css(dest)?;
272 dest.write_char(' ')?;
273 self.second.to_css(dest)?;
274 }
275 Ok(())
276 }
277}
278
279#[derive(
280 Clone,
281 Copy,
282 Debug,
283 Eq,
284 MallocSizeOf,
285 PartialEq,
286 Parse,
287 Serialize,
288 SpecifiedValueInfo,
289 ToCss,
290 ToComputedValue,
291 ToResolvedValue,
292 ToShmem,
293 ToTyped,
294)]
295#[cfg_attr(
296 feature = "gecko",
297 css(bitflags(
298 single = "none,spelling-error,grammar-error",
299 mixed = "underline,overline,line-through,blink",
300 ))
301)]
302#[cfg_attr(
303 not(feature = "gecko"),
304 css(bitflags(single = "none", mixed = "underline,overline,line-through,blink",))
305)]
306#[repr(C)]
307pub struct TextDecorationLine(u8);
309bitflags! {
310 impl TextDecorationLine: u8 {
311 const NONE = 0;
313 const UNDERLINE = 1 << 0;
315 const OVERLINE = 1 << 1;
317 const LINE_THROUGH = 1 << 2;
319 const BLINK = 1 << 3;
321 const SPELLING_ERROR = 1 << 4;
323 const GRAMMAR_ERROR = 1 << 5;
325 #[cfg(feature = "gecko")]
333 const COLOR_OVERRIDE = 1 << 7;
334 }
335}
336
337impl Default for TextDecorationLine {
338 fn default() -> Self {
339 TextDecorationLine::NONE
340 }
341}
342
343impl TextDecorationLine {
344 #[inline]
345 pub fn none() -> Self {
347 TextDecorationLine::NONE
348 }
349}
350
351#[derive(
352 Clone,
353 Copy,
354 Debug,
355 Eq,
356 MallocSizeOf,
357 PartialEq,
358 SpecifiedValueInfo,
359 ToComputedValue,
360 ToCss,
361 ToResolvedValue,
362 ToShmem,
363)]
364#[repr(C)]
365pub enum TextTransformCase {
367 None,
369 Uppercase,
371 Lowercase,
373 Capitalize,
375 #[cfg(feature = "gecko")]
377 MathAuto,
378}
379
380#[derive(
381 Clone,
382 Copy,
383 Debug,
384 Eq,
385 MallocSizeOf,
386 PartialEq,
387 Parse,
388 Serialize,
389 SpecifiedValueInfo,
390 ToCss,
391 ToComputedValue,
392 ToResolvedValue,
393 ToShmem,
394 ToTyped,
395)]
396#[cfg_attr(
397 feature = "gecko",
398 css(bitflags(
399 single = "none,math-auto",
400 mixed = "uppercase,lowercase,capitalize,full-width,full-size-kana",
401 validate_mixed = "Self::validate_mixed_flags",
402 ))
403)]
404#[cfg_attr(
405 not(feature = "gecko"),
406 css(bitflags(
407 single = "none",
408 mixed = "uppercase,lowercase,capitalize,full-width,full-size-kana",
409 validate_mixed = "Self::validate_mixed_flags",
410 ))
411)]
412#[repr(C)]
413pub struct TextTransform(u8);
418bitflags! {
419 impl TextTransform: u8 {
420 const NONE = 0;
422 const UPPERCASE = 1 << 0;
424 const LOWERCASE = 1 << 1;
426 const CAPITALIZE = 1 << 2;
428 #[cfg(feature = "gecko")]
430 const MATH_AUTO = 1 << 3;
431
432 #[cfg(feature = "gecko")]
434 const CASE_TRANSFORMS = Self::UPPERCASE.0 | Self::LOWERCASE.0 | Self::CAPITALIZE.0 | Self::MATH_AUTO.0;
435 #[cfg(feature = "servo")]
437 const CASE_TRANSFORMS = Self::UPPERCASE.0 | Self::LOWERCASE.0 | Self::CAPITALIZE.0;
438
439 const FULL_WIDTH = 1 << 4;
441 const FULL_SIZE_KANA = 1 << 5;
443 }
444}
445
446impl TextTransform {
447 #[inline]
449 pub fn none() -> Self {
450 Self::NONE
451 }
452
453 #[inline]
455 pub fn is_none(self) -> bool {
456 self == Self::NONE
457 }
458
459 fn validate_mixed_flags(&self) -> bool {
460 let case = self.intersection(Self::CASE_TRANSFORMS);
461 case.is_empty() || case.bits().is_power_of_two()
463 }
464
465 pub fn case(&self) -> TextTransformCase {
467 match *self & Self::CASE_TRANSFORMS {
468 Self::NONE => TextTransformCase::None,
469 Self::UPPERCASE => TextTransformCase::Uppercase,
470 Self::LOWERCASE => TextTransformCase::Lowercase,
471 Self::CAPITALIZE => TextTransformCase::Capitalize,
472 #[cfg(feature = "gecko")]
473 Self::MATH_AUTO => TextTransformCase::MathAuto,
474 _ => unreachable!("Case bits are exclusive with each other"),
475 }
476 }
477}
478
479#[derive(
481 Clone,
482 Copy,
483 Debug,
484 Eq,
485 FromPrimitive,
486 Hash,
487 MallocSizeOf,
488 Parse,
489 PartialEq,
490 SpecifiedValueInfo,
491 ToComputedValue,
492 ToCss,
493 ToResolvedValue,
494 ToShmem,
495 ToTyped,
496)]
497#[allow(missing_docs)]
498#[repr(u8)]
499pub enum TextAlignLast {
500 Auto,
501 Start,
502 End,
503 Left,
504 Right,
505 Center,
506 Justify,
507}
508
509#[derive(
511 Clone,
512 Copy,
513 Debug,
514 Eq,
515 FromPrimitive,
516 Hash,
517 MallocSizeOf,
518 Parse,
519 PartialEq,
520 SpecifiedValueInfo,
521 ToComputedValue,
522 ToCss,
523 ToResolvedValue,
524 ToShmem,
525 ToTyped,
526)]
527#[allow(missing_docs)]
528#[repr(u8)]
529pub enum TextAlignKeyword {
530 Start,
531 Left,
532 Right,
533 Center,
534 Justify,
535 End,
536 #[parse(aliases = "-webkit-center")]
537 MozCenter,
538 #[parse(aliases = "-webkit-left")]
539 MozLeft,
540 #[parse(aliases = "-webkit-right")]
541 MozRight,
542}
543
544#[derive(
546 Clone,
547 Copy,
548 Debug,
549 Eq,
550 Hash,
551 MallocSizeOf,
552 Parse,
553 PartialEq,
554 SpecifiedValueInfo,
555 ToCss,
556 ToShmem,
557 ToTyped,
558)]
559pub enum TextAlign {
560 Keyword(TextAlignKeyword),
562 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 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)]
643#[typed(todo_derive_fields)]
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#[derive(
1064 Clone,
1065 Copy,
1066 Debug,
1067 Deserialize,
1068 Eq,
1069 MallocSizeOf,
1070 Parse,
1071 PartialEq,
1072 Serialize,
1073 SpecifiedValueInfo,
1074 ToComputedValue,
1075 ToCss,
1076 ToResolvedValue,
1077 ToShmem,
1078 ToTyped,
1079)]
1080#[allow(missing_docs)]
1081pub enum TextDecorationSkipInk {
1082 Auto,
1083 None,
1084 All,
1085}
1086
1087pub type TextDecorationLength = GenericTextDecorationLength<LengthPercentage>;
1089
1090impl TextDecorationLength {
1091 #[inline]
1093 pub fn auto() -> Self {
1094 GenericTextDecorationLength::Auto
1095 }
1096
1097 #[inline]
1099 pub fn is_auto(&self) -> bool {
1100 matches!(*self, GenericTextDecorationLength::Auto)
1101 }
1102}
1103
1104pub type TextDecorationInset = GenericTextDecorationInset<LengthPercentage>;
1106
1107impl TextDecorationInset {
1108 #[inline]
1110 pub fn auto() -> Self {
1111 GenericTextDecorationInset::Auto
1112 }
1113
1114 #[inline]
1116 pub fn is_auto(&self) -> bool {
1117 matches!(*self, GenericTextDecorationInset::Auto)
1118 }
1119}
1120
1121fn parse_inset_endpoint<'i, 't>(
1122 ctx: &ParserContext,
1123 input: &mut Parser<'i, 't>,
1124) -> Result<LengthPercentage, ParseError<'i>> {
1125 if !static_prefs::pref!("layout.css.text-decoration-inset-percentage.enabled") {
1126 Length::parse(ctx, input).map(|l| l.into())
1127 } else {
1128 LengthPercentage::parse(ctx, input)
1129 }
1130}
1131
1132impl Parse for TextDecorationInset {
1133 fn parse<'i, 't>(
1134 ctx: &ParserContext,
1135 input: &mut Parser<'i, 't>,
1136 ) -> Result<Self, ParseError<'i>> {
1137 if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
1138 return Ok(TextDecorationInset::Auto);
1139 }
1140
1141 let start = parse_inset_endpoint(ctx, input)?;
1142 let end = input
1143 .try_parse(|i| parse_inset_endpoint(ctx, i))
1144 .unwrap_or_else(|_| start.clone());
1145 Ok(TextDecorationInset::LengthPercentage { start, end })
1146 }
1147}
1148
1149#[derive(
1150 Clone,
1151 Copy,
1152 Debug,
1153 Eq,
1154 MallocSizeOf,
1155 Parse,
1156 PartialEq,
1157 SpecifiedValueInfo,
1158 ToComputedValue,
1159 ToResolvedValue,
1160 ToShmem,
1161 ToTyped,
1162)]
1163#[css(bitflags(
1164 single = "auto",
1165 mixed = "from-font,under,left,right",
1166 validate_mixed = "Self::validate_mixed_flags",
1167))]
1168#[repr(C)]
1169pub struct TextUnderlinePosition(u8);
1174bitflags! {
1175 impl TextUnderlinePosition: u8 {
1176 const AUTO = 0;
1178 const FROM_FONT = 1 << 0;
1180 const UNDER = 1 << 1;
1182 const LEFT = 1 << 2;
1184 const RIGHT = 1 << 3;
1186 }
1187}
1188
1189impl TextUnderlinePosition {
1190 fn validate_mixed_flags(&self) -> bool {
1191 if self.contains(Self::LEFT | Self::RIGHT) {
1192 return false;
1194 }
1195 if self.contains(Self::FROM_FONT | Self::UNDER) {
1196 return false;
1198 }
1199 true
1200 }
1201}
1202
1203impl ToCss for TextUnderlinePosition {
1204 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1205 where
1206 W: Write,
1207 {
1208 if self.is_empty() {
1209 return dest.write_str("auto");
1210 }
1211
1212 let mut writer = SequenceWriter::new(dest, " ");
1213 let mut any = false;
1214
1215 macro_rules! maybe_write {
1216 ($ident:ident => $str:expr) => {
1217 if self.contains(TextUnderlinePosition::$ident) {
1218 any = true;
1219 writer.raw_item($str)?;
1220 }
1221 };
1222 }
1223
1224 maybe_write!(FROM_FONT => "from-font");
1225 maybe_write!(UNDER => "under");
1226 maybe_write!(LEFT => "left");
1227 maybe_write!(RIGHT => "right");
1228
1229 debug_assert!(any);
1230
1231 Ok(())
1232 }
1233}
1234
1235#[repr(u8)]
1237#[derive(
1238 Clone,
1239 Copy,
1240 Debug,
1241 Eq,
1242 MallocSizeOf,
1243 PartialEq,
1244 ToComputedValue,
1245 ToResolvedValue,
1246 ToShmem,
1247 ToTyped,
1248)]
1249#[allow(missing_docs)]
1250pub enum RubyPosition {
1251 AlternateOver,
1252 AlternateUnder,
1253 Over,
1254 Under,
1255}
1256
1257impl Parse for RubyPosition {
1258 fn parse<'i, 't>(
1259 _context: &ParserContext,
1260 input: &mut Parser<'i, 't>,
1261 ) -> Result<RubyPosition, ParseError<'i>> {
1262 let alternate = input
1264 .try_parse(|i| i.expect_ident_matching("alternate"))
1265 .is_ok();
1266 if alternate && input.is_exhausted() {
1267 return Ok(RubyPosition::AlternateOver);
1268 }
1269 let over = try_match_ident_ignore_ascii_case! { input,
1271 "over" => true,
1272 "under" => false,
1273 };
1274 let alternate = alternate
1276 || input
1277 .try_parse(|i| i.expect_ident_matching("alternate"))
1278 .is_ok();
1279
1280 Ok(match (over, alternate) {
1281 (true, true) => RubyPosition::AlternateOver,
1282 (false, true) => RubyPosition::AlternateUnder,
1283 (true, false) => RubyPosition::Over,
1284 (false, false) => RubyPosition::Under,
1285 })
1286 }
1287}
1288
1289impl ToCss for RubyPosition {
1290 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1291 where
1292 W: Write,
1293 {
1294 dest.write_str(match self {
1295 RubyPosition::AlternateOver => "alternate",
1296 RubyPosition::AlternateUnder => "alternate under",
1297 RubyPosition::Over => "over",
1298 RubyPosition::Under => "under",
1299 })
1300 }
1301}
1302
1303impl SpecifiedValueInfo for RubyPosition {
1304 fn collect_completion_keywords(f: KeywordsCollectFn) {
1305 f(&["alternate", "over", "under"])
1306 }
1307}
1308
1309#[derive(
1321 Clone,
1322 Copy,
1323 Debug,
1324 Eq,
1325 MallocSizeOf,
1326 Parse,
1327 PartialEq,
1328 Serialize,
1329 SpecifiedValueInfo,
1330 ToCss,
1331 ToComputedValue,
1332 ToResolvedValue,
1333 ToShmem,
1334 ToTyped,
1335)]
1336#[css(bitflags(
1337 single = "normal,auto,no-autospace",
1338 mixed = "ideograph-alpha,ideograph-numeric,insert",
1341 ))]
1344#[repr(C)]
1345pub struct TextAutospace(u8);
1346bitflags! {
1347 impl TextAutospace: u8 {
1348 const NO_AUTOSPACE = 0;
1350
1351 const AUTO = 1 << 0;
1353
1354 const NORMAL = 1 << 1;
1356
1357 const IDEOGRAPH_ALPHA = 1 << 2;
1359
1360 const IDEOGRAPH_NUMERIC = 1 << 3;
1362
1363 const INSERT = 1 << 5;
1370
1371 }
1376}
1377
1378#[derive(
1387 Clone,
1388 Copy,
1389 Debug,
1390 Eq,
1391 FromPrimitive,
1392 Hash,
1393 MallocSizeOf,
1394 Parse,
1395 PartialEq,
1396 SpecifiedValueInfo,
1397 ToComputedValue,
1398 ToCss,
1399 ToResolvedValue,
1400 ToShmem,
1401 ToTyped,
1402)]
1403#[repr(u8)]
1404pub enum TextEdgeKeyword {
1408 Text,
1410 Ideographic,
1412 IdeographicInk,
1414 Cap,
1416 Ex,
1418 Alphabetic,
1420}
1421
1422impl TextEdgeKeyword {
1423 fn is_valid_for_over(&self) -> bool {
1424 match self {
1425 TextEdgeKeyword::Text
1426 | TextEdgeKeyword::Ideographic
1427 | TextEdgeKeyword::IdeographicInk
1428 | TextEdgeKeyword::Cap
1429 | TextEdgeKeyword::Ex => true,
1430 _ => false,
1431 }
1432 }
1433
1434 fn is_valid_for_under(&self) -> bool {
1435 match self {
1436 TextEdgeKeyword::Text
1437 | TextEdgeKeyword::Ideographic
1438 | TextEdgeKeyword::IdeographicInk
1439 | TextEdgeKeyword::Alphabetic => true,
1440 _ => false,
1441 }
1442 }
1443}
1444
1445#[derive(
1446 Clone,
1447 Copy,
1448 Debug,
1449 Eq,
1450 Hash,
1451 MallocSizeOf,
1452 PartialEq,
1453 SpecifiedValueInfo,
1454 ToComputedValue,
1455 ToResolvedValue,
1456 ToShmem,
1457 ToTyped,
1458)]
1459#[repr(C)]
1460pub struct TextEdge {
1470 pub over: TextEdgeKeyword,
1472 pub under: TextEdgeKeyword,
1474}
1475
1476impl Parse for TextEdge {
1477 fn parse<'i, 't>(
1478 _context: &ParserContext,
1479 input: &mut Parser<'i, 't>,
1480 ) -> Result<TextEdge, ParseError<'i>> {
1481 let first = TextEdgeKeyword::parse(input)?;
1482
1483 if let Ok(second) = input.try_parse(TextEdgeKeyword::parse) {
1484 if !first.is_valid_for_over() || !second.is_valid_for_under() {
1485 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1486 }
1487
1488 return Ok(TextEdge {
1489 over: first,
1490 under: second,
1491 });
1492 }
1493
1494 match (first.is_valid_for_over(), first.is_valid_for_under()) {
1498 (true, true) => Ok(TextEdge {
1499 over: first,
1500 under: first,
1501 }),
1502 (true, false) => Ok(TextEdge {
1503 over: first,
1504 under: TextEdgeKeyword::Text,
1505 }),
1506 (false, true) => Ok(TextEdge {
1507 over: TextEdgeKeyword::Text,
1508 under: first,
1509 }),
1510 _ => unreachable!("Parsed keyword will be valid for at least one edge"),
1511 }
1512 }
1513}
1514
1515impl ToCss for TextEdge {
1516 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1517 where
1518 W: Write,
1519 {
1520 match (self.over, self.under) {
1521 (over, TextEdgeKeyword::Text) if !over.is_valid_for_under() => over.to_css(dest),
1522 (TextEdgeKeyword::Text, under) if !under.is_valid_for_over() => under.to_css(dest),
1523 (over, under) => {
1524 over.to_css(dest)?;
1525
1526 if over != under {
1527 dest.write_char(' ')?;
1528 self.under.to_css(dest)?;
1529 }
1530
1531 Ok(())
1532 },
1533 }
1534 }
1535}
1536
1537#[derive(
1538 Clone,
1539 Copy,
1540 Debug,
1541 Eq,
1542 Hash,
1543 MallocSizeOf,
1544 Parse,
1545 PartialEq,
1546 SpecifiedValueInfo,
1547 ToComputedValue,
1548 ToCss,
1549 ToResolvedValue,
1550 ToShmem,
1551 ToTyped,
1552)]
1553#[repr(C, u8)]
1554pub enum TextBoxEdge {
1558 Auto,
1560 TextEdge(TextEdge),
1562}
1563
1564#[derive(
1565 Clone,
1566 Copy,
1567 Debug,
1568 Eq,
1569 MallocSizeOf,
1570 PartialEq,
1571 Parse,
1572 Serialize,
1573 SpecifiedValueInfo,
1574 ToCss,
1575 ToComputedValue,
1576 ToResolvedValue,
1577 ToShmem,
1578 ToTyped,
1579)]
1580#[css(bitflags(single = "none,trim-start,trim-end,trim-both"))]
1581#[repr(C)]
1582pub struct TextBoxTrim(u8);
1586bitflags! {
1587 impl TextBoxTrim: u8 {
1588 const NONE = 0;
1590 const TRIM_START = 1 << 0;
1592 const TRIM_END = 1 << 1;
1594 const TRIM_BOTH = Self::TRIM_START.0 | Self::TRIM_END.0;
1596 }
1597}
1598
1599impl TextBoxTrim {
1600 #[inline]
1602 pub fn none() -> Self {
1603 TextBoxTrim::NONE
1604 }
1605}