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}
376
377#[derive(
378 Clone,
379 Copy,
380 Debug,
381 Eq,
382 MallocSizeOf,
383 PartialEq,
384 Parse,
385 Serialize,
386 SpecifiedValueInfo,
387 ToCss,
388 ToComputedValue,
389 ToResolvedValue,
390 ToShmem,
391 ToTyped,
392)]
393#[css(bitflags(
394 single = "none,math-auto",
395 mixed = "uppercase,lowercase,capitalize,full-width,full-size-kana",
396 validate_mixed = "Self::validate_mixed_flags",
397))]
398#[repr(C)]
399pub struct TextTransform(u8);
404bitflags! {
405 impl TextTransform: u8 {
406 const NONE = 0;
408 const UPPERCASE = 1 << 0;
410 const LOWERCASE = 1 << 1;
412 const CAPITALIZE = 1 << 2;
414
415 const CASE_TRANSFORMS = Self::UPPERCASE.0 | Self::LOWERCASE.0 | Self::CAPITALIZE.0;
418
419 const MATH_AUTO = 1 << 3;
421 const FULL_WIDTH = 1 << 4;
423 const FULL_SIZE_KANA = 1 << 5;
425 }
426}
427
428impl TextTransform {
429 #[inline]
431 pub fn none() -> Self {
432 Self::NONE
433 }
434
435 #[inline]
437 pub fn is_none(self) -> bool {
438 self == Self::NONE
439 }
440
441 fn validate_mixed_flags(&self) -> bool {
442 let case = self.intersection(Self::CASE_TRANSFORMS);
443 case.is_empty() || case.bits().is_power_of_two()
445 }
446
447 pub fn case(&self) -> TextTransformCase {
449 match *self & Self::CASE_TRANSFORMS {
450 Self::NONE => TextTransformCase::None,
451 Self::UPPERCASE => TextTransformCase::Uppercase,
452 Self::LOWERCASE => TextTransformCase::Lowercase,
453 Self::CAPITALIZE => TextTransformCase::Capitalize,
454 _ => unreachable!("Case bits are exclusive with each other"),
455 }
456 }
457}
458
459#[derive(
461 Clone,
462 Copy,
463 Debug,
464 Eq,
465 FromPrimitive,
466 Hash,
467 MallocSizeOf,
468 Parse,
469 PartialEq,
470 SpecifiedValueInfo,
471 ToComputedValue,
472 ToCss,
473 ToResolvedValue,
474 ToShmem,
475 ToTyped,
476)]
477#[allow(missing_docs)]
478#[repr(u8)]
479pub enum TextAlignLast {
480 Auto,
481 Start,
482 End,
483 Left,
484 Right,
485 Center,
486 Justify,
487}
488
489#[derive(
491 Clone,
492 Copy,
493 Debug,
494 Eq,
495 FromPrimitive,
496 Hash,
497 MallocSizeOf,
498 Parse,
499 PartialEq,
500 SpecifiedValueInfo,
501 ToComputedValue,
502 ToCss,
503 ToResolvedValue,
504 ToShmem,
505 ToTyped,
506)]
507#[allow(missing_docs)]
508#[repr(u8)]
509pub enum TextAlignKeyword {
510 Start,
511 Left,
512 Right,
513 Center,
514 Justify,
515 End,
516 #[parse(aliases = "-webkit-center")]
517 MozCenter,
518 #[parse(aliases = "-webkit-left")]
519 MozLeft,
520 #[parse(aliases = "-webkit-right")]
521 MozRight,
522}
523
524#[derive(
526 Clone,
527 Copy,
528 Debug,
529 Eq,
530 Hash,
531 MallocSizeOf,
532 Parse,
533 PartialEq,
534 SpecifiedValueInfo,
535 ToCss,
536 ToShmem,
537 ToTyped,
538)]
539pub enum TextAlign {
540 Keyword(TextAlignKeyword),
542 MatchParent,
545 #[parse(condition = "ParserContext::chrome_rules_enabled")]
558 MozCenterOrInherit,
559}
560
561impl ToComputedValue for TextAlign {
562 type ComputedValue = TextAlignKeyword;
563
564 #[inline]
565 fn to_computed_value(&self, _context: &Context) -> Self::ComputedValue {
566 match *self {
567 TextAlign::Keyword(key) => key,
568 TextAlign::MatchParent => {
569 if _context.builder.is_root_element {
576 return TextAlignKeyword::Start;
577 }
578 let parent = _context
579 .builder
580 .get_parent_inherited_text()
581 .clone_text_align();
582 let ltr = _context.builder.inherited_writing_mode().is_bidi_ltr();
583 match (parent, ltr) {
584 (TextAlignKeyword::Start, true) => TextAlignKeyword::Left,
585 (TextAlignKeyword::Start, false) => TextAlignKeyword::Right,
586 (TextAlignKeyword::End, true) => TextAlignKeyword::Right,
587 (TextAlignKeyword::End, false) => TextAlignKeyword::Left,
588 _ => parent,
589 }
590 },
591 TextAlign::MozCenterOrInherit => {
592 let parent = _context
593 .builder
594 .get_parent_inherited_text()
595 .clone_text_align();
596 if parent == TextAlignKeyword::Start {
597 TextAlignKeyword::Center
598 } else {
599 parent
600 }
601 },
602 }
603 }
604
605 #[inline]
606 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
607 TextAlign::Keyword(*computed)
608 }
609}
610
611fn fill_mode_is_default_and_shape_exists(
612 fill: &TextEmphasisFillMode,
613 shape: &Option<TextEmphasisShapeKeyword>,
614) -> bool {
615 shape.is_some() && fill.is_filled()
616}
617
618#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
622#[allow(missing_docs)]
623#[typed(todo_derive_fields)]
624pub enum TextEmphasisStyle {
625 Keyword {
627 #[css(contextual_skip_if = "fill_mode_is_default_and_shape_exists")]
628 fill: TextEmphasisFillMode,
629 shape: Option<TextEmphasisShapeKeyword>,
630 },
631 None,
633 String(crate::OwnedStr),
635}
636
637#[derive(
639 Clone,
640 Copy,
641 Debug,
642 MallocSizeOf,
643 Parse,
644 PartialEq,
645 SpecifiedValueInfo,
646 ToCss,
647 ToComputedValue,
648 ToResolvedValue,
649 ToShmem,
650)]
651#[repr(u8)]
652pub enum TextEmphasisFillMode {
653 Filled,
655 Open,
657}
658
659impl TextEmphasisFillMode {
660 #[inline]
662 pub fn is_filled(&self) -> bool {
663 matches!(*self, TextEmphasisFillMode::Filled)
664 }
665}
666
667#[derive(
669 Clone,
670 Copy,
671 Debug,
672 Eq,
673 MallocSizeOf,
674 Parse,
675 PartialEq,
676 SpecifiedValueInfo,
677 ToCss,
678 ToComputedValue,
679 ToResolvedValue,
680 ToShmem,
681)]
682#[repr(u8)]
683pub enum TextEmphasisShapeKeyword {
684 Dot,
686 Circle,
688 DoubleCircle,
690 Triangle,
692 Sesame,
694}
695
696impl ToComputedValue for TextEmphasisStyle {
697 type ComputedValue = ComputedTextEmphasisStyle;
698
699 #[inline]
700 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
701 match *self {
702 TextEmphasisStyle::Keyword { fill, shape } => {
703 let shape = shape.unwrap_or_else(|| {
704 if context.style().get_inherited_box().clone_writing_mode()
710 == SpecifiedWritingMode::HorizontalTb
711 {
712 TextEmphasisShapeKeyword::Circle
713 } else {
714 TextEmphasisShapeKeyword::Sesame
715 }
716 });
717 ComputedTextEmphasisStyle::Keyword { fill, shape }
718 },
719 TextEmphasisStyle::None => ComputedTextEmphasisStyle::None,
720 TextEmphasisStyle::String(ref s) => {
721 let first_grapheme_end = GraphemeClusterSegmenter::new()
727 .segment_str(s)
728 .nth(1)
729 .unwrap_or(0);
730 ComputedTextEmphasisStyle::String(s[0..first_grapheme_end].to_string().into())
731 },
732 }
733 }
734
735 #[inline]
736 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
737 match *computed {
738 ComputedTextEmphasisStyle::Keyword { fill, shape } => TextEmphasisStyle::Keyword {
739 fill,
740 shape: Some(shape),
741 },
742 ComputedTextEmphasisStyle::None => TextEmphasisStyle::None,
743 ComputedTextEmphasisStyle::String(ref string) => {
744 TextEmphasisStyle::String(string.clone())
745 },
746 }
747 }
748}
749
750impl Parse for TextEmphasisStyle {
751 fn parse<'i, 't>(
752 _context: &ParserContext,
753 input: &mut Parser<'i, 't>,
754 ) -> Result<Self, ParseError<'i>> {
755 if input
756 .try_parse(|input| input.expect_ident_matching("none"))
757 .is_ok()
758 {
759 return Ok(TextEmphasisStyle::None);
760 }
761
762 if let Ok(s) = input.try_parse(|i| i.expect_string().map(|s| s.as_ref().to_owned())) {
763 return Ok(TextEmphasisStyle::String(s.into()));
765 }
766
767 let mut shape = input.try_parse(TextEmphasisShapeKeyword::parse).ok();
769 let fill = input.try_parse(TextEmphasisFillMode::parse).ok();
770 if shape.is_none() {
771 shape = input.try_parse(TextEmphasisShapeKeyword::parse).ok();
772 }
773
774 if shape.is_none() && fill.is_none() {
775 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
776 }
777
778 let fill = fill.unwrap_or(TextEmphasisFillMode::Filled);
781
782 Ok(TextEmphasisStyle::Keyword { fill, shape })
785 }
786}
787
788#[derive(
789 Clone,
790 Copy,
791 Debug,
792 Eq,
793 MallocSizeOf,
794 PartialEq,
795 Parse,
796 Serialize,
797 SpecifiedValueInfo,
798 ToCss,
799 ToComputedValue,
800 ToResolvedValue,
801 ToShmem,
802 ToTyped,
803)]
804#[repr(C)]
805#[css(bitflags(
806 single = "auto",
807 mixed = "over,under,left,right",
808 validate_mixed = "Self::validate_and_simplify"
809))]
810pub struct TextEmphasisPosition(u8);
813bitflags! {
814 impl TextEmphasisPosition: u8 {
815 const AUTO = 1 << 0;
817 const OVER = 1 << 1;
819 const UNDER = 1 << 2;
821 const LEFT = 1 << 3;
823 const RIGHT = 1 << 4;
825 }
826}
827
828impl TextEmphasisPosition {
829 fn validate_and_simplify(&mut self) -> bool {
830 if self.intersects(Self::OVER) == self.intersects(Self::UNDER) {
832 return false;
833 }
834
835 if self.intersects(Self::LEFT) {
837 return !self.intersects(Self::RIGHT);
838 }
839
840 self.remove(Self::RIGHT); true
842 }
843}
844
845#[repr(u8)]
847#[derive(
848 Clone,
849 Copy,
850 Debug,
851 Eq,
852 MallocSizeOf,
853 Parse,
854 PartialEq,
855 SpecifiedValueInfo,
856 ToComputedValue,
857 ToCss,
858 ToResolvedValue,
859 ToShmem,
860 ToTyped,
861)]
862#[allow(missing_docs)]
863pub enum WordBreak {
864 Normal,
865 BreakAll,
866 KeepAll,
867 #[cfg(feature = "gecko")]
872 BreakWord,
873}
874
875#[repr(u8)]
877#[derive(
878 Clone,
879 Copy,
880 Debug,
881 Eq,
882 MallocSizeOf,
883 Parse,
884 PartialEq,
885 SpecifiedValueInfo,
886 ToComputedValue,
887 ToCss,
888 ToResolvedValue,
889 ToShmem,
890 ToTyped,
891)]
892#[allow(missing_docs)]
893pub enum TextJustify {
894 Auto,
895 None,
896 InterWord,
897 #[parse(aliases = "distribute")]
900 InterCharacter,
901}
902
903#[repr(u8)]
905#[derive(
906 Clone,
907 Copy,
908 Debug,
909 Eq,
910 MallocSizeOf,
911 Parse,
912 PartialEq,
913 SpecifiedValueInfo,
914 ToComputedValue,
915 ToCss,
916 ToResolvedValue,
917 ToShmem,
918 ToTyped,
919)]
920#[allow(missing_docs)]
921pub enum MozControlCharacterVisibility {
922 Hidden,
923 Visible,
924}
925
926#[cfg(feature = "gecko")]
927impl Default for MozControlCharacterVisibility {
928 fn default() -> Self {
929 if static_prefs::pref!("layout.css.control-characters.visible") {
930 Self::Visible
931 } else {
932 Self::Hidden
933 }
934 }
935}
936
937#[repr(u8)]
939#[derive(
940 Clone,
941 Copy,
942 Debug,
943 Eq,
944 MallocSizeOf,
945 Parse,
946 PartialEq,
947 SpecifiedValueInfo,
948 ToComputedValue,
949 ToCss,
950 ToResolvedValue,
951 ToShmem,
952 ToTyped,
953)]
954#[allow(missing_docs)]
955pub enum LineBreak {
956 Auto,
957 Loose,
958 Normal,
959 Strict,
960 Anywhere,
961}
962
963#[repr(u8)]
965#[derive(
966 Clone,
967 Copy,
968 Debug,
969 Eq,
970 MallocSizeOf,
971 Parse,
972 PartialEq,
973 SpecifiedValueInfo,
974 ToComputedValue,
975 ToCss,
976 ToResolvedValue,
977 ToShmem,
978 ToTyped,
979)]
980#[allow(missing_docs)]
981pub enum OverflowWrap {
982 Normal,
983 BreakWord,
984 Anywhere,
985}
986
987pub type TextIndent = GenericTextIndent<LengthPercentage>;
992
993impl Parse for TextIndent {
994 fn parse<'i, 't>(
995 context: &ParserContext,
996 input: &mut Parser<'i, 't>,
997 ) -> Result<Self, ParseError<'i>> {
998 let mut length = None;
999 let mut hanging = false;
1000 let mut each_line = false;
1001
1002 while !input.is_exhausted() {
1004 if length.is_none() {
1006 if let Ok(len) = input
1007 .try_parse(|i| LengthPercentage::parse_quirky(context, i, AllowQuirks::Yes))
1008 {
1009 length = Some(len);
1010 continue;
1011 }
1012 }
1013
1014 if cfg!(feature = "servo") {
1016 break;
1017 }
1018
1019 try_match_ident_ignore_ascii_case! { input,
1021 "hanging" if !hanging => hanging = true,
1022 "each-line" if !each_line => each_line = true,
1023 }
1024 }
1025
1026 if let Some(length) = length {
1028 Ok(Self {
1029 length,
1030 hanging,
1031 each_line,
1032 })
1033 } else {
1034 Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
1035 }
1036 }
1037}
1038
1039#[repr(u8)]
1043#[derive(
1044 Clone,
1045 Copy,
1046 Debug,
1047 Deserialize,
1048 Eq,
1049 MallocSizeOf,
1050 Parse,
1051 PartialEq,
1052 Serialize,
1053 SpecifiedValueInfo,
1054 ToComputedValue,
1055 ToCss,
1056 ToResolvedValue,
1057 ToShmem,
1058 ToTyped,
1059)]
1060#[allow(missing_docs)]
1061pub enum TextDecorationSkipInk {
1062 Auto,
1063 None,
1064 All,
1065}
1066
1067pub type TextDecorationLength = GenericTextDecorationLength<LengthPercentage>;
1069
1070impl TextDecorationLength {
1071 #[inline]
1073 pub fn auto() -> Self {
1074 GenericTextDecorationLength::Auto
1075 }
1076
1077 #[inline]
1079 pub fn is_auto(&self) -> bool {
1080 matches!(*self, GenericTextDecorationLength::Auto)
1081 }
1082}
1083
1084pub type TextDecorationInset = GenericTextDecorationInset<LengthPercentage>;
1086
1087impl TextDecorationInset {
1088 #[inline]
1090 pub fn auto() -> Self {
1091 GenericTextDecorationInset::Auto
1092 }
1093
1094 #[inline]
1096 pub fn is_auto(&self) -> bool {
1097 matches!(*self, GenericTextDecorationInset::Auto)
1098 }
1099}
1100
1101fn parse_inset_endpoint<'i, 't>(
1102 ctx: &ParserContext,
1103 input: &mut Parser<'i, 't>,
1104) -> Result<LengthPercentage, ParseError<'i>> {
1105 if !static_prefs::pref!("layout.css.text-decoration-inset-percentage.enabled") {
1106 Length::parse(ctx, input).map(|l| l.into())
1107 } else {
1108 LengthPercentage::parse(ctx, input)
1109 }
1110}
1111
1112impl Parse for TextDecorationInset {
1113 fn parse<'i, 't>(
1114 ctx: &ParserContext,
1115 input: &mut Parser<'i, 't>,
1116 ) -> Result<Self, ParseError<'i>> {
1117 if input.try_parse(|i| i.expect_ident_matching("auto")).is_ok() {
1118 return Ok(TextDecorationInset::Auto);
1119 }
1120
1121 let start = parse_inset_endpoint(ctx, input)?;
1122 let end = input
1123 .try_parse(|i| parse_inset_endpoint(ctx, i))
1124 .unwrap_or_else(|_| start.clone());
1125 Ok(TextDecorationInset::LengthPercentage { start, end })
1126 }
1127}
1128
1129#[derive(
1130 Clone,
1131 Copy,
1132 Debug,
1133 Eq,
1134 MallocSizeOf,
1135 Parse,
1136 PartialEq,
1137 SpecifiedValueInfo,
1138 ToComputedValue,
1139 ToResolvedValue,
1140 ToShmem,
1141 ToTyped,
1142)]
1143#[css(bitflags(
1144 single = "auto",
1145 mixed = "from-font,under,left,right",
1146 validate_mixed = "Self::validate_mixed_flags",
1147))]
1148#[repr(C)]
1149pub struct TextUnderlinePosition(u8);
1154bitflags! {
1155 impl TextUnderlinePosition: u8 {
1156 const AUTO = 0;
1158 const FROM_FONT = 1 << 0;
1160 const UNDER = 1 << 1;
1162 const LEFT = 1 << 2;
1164 const RIGHT = 1 << 3;
1166 }
1167}
1168
1169impl TextUnderlinePosition {
1170 fn validate_mixed_flags(&self) -> bool {
1171 if self.contains(Self::LEFT | Self::RIGHT) {
1172 return false;
1174 }
1175 if self.contains(Self::FROM_FONT | Self::UNDER) {
1176 return false;
1178 }
1179 true
1180 }
1181}
1182
1183impl ToCss for TextUnderlinePosition {
1184 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1185 where
1186 W: Write,
1187 {
1188 if self.is_empty() {
1189 return dest.write_str("auto");
1190 }
1191
1192 let mut writer = SequenceWriter::new(dest, " ");
1193 let mut any = false;
1194
1195 macro_rules! maybe_write {
1196 ($ident:ident => $str:expr) => {
1197 if self.contains(TextUnderlinePosition::$ident) {
1198 any = true;
1199 writer.raw_item($str)?;
1200 }
1201 };
1202 }
1203
1204 maybe_write!(FROM_FONT => "from-font");
1205 maybe_write!(UNDER => "under");
1206 maybe_write!(LEFT => "left");
1207 maybe_write!(RIGHT => "right");
1208
1209 debug_assert!(any);
1210
1211 Ok(())
1212 }
1213}
1214
1215#[repr(u8)]
1217#[derive(
1218 Clone,
1219 Copy,
1220 Debug,
1221 Eq,
1222 MallocSizeOf,
1223 PartialEq,
1224 ToComputedValue,
1225 ToResolvedValue,
1226 ToShmem,
1227 ToTyped,
1228)]
1229#[allow(missing_docs)]
1230pub enum RubyPosition {
1231 AlternateOver,
1232 AlternateUnder,
1233 Over,
1234 Under,
1235}
1236
1237impl Parse for RubyPosition {
1238 fn parse<'i, 't>(
1239 _context: &ParserContext,
1240 input: &mut Parser<'i, 't>,
1241 ) -> Result<RubyPosition, ParseError<'i>> {
1242 let alternate = input
1244 .try_parse(|i| i.expect_ident_matching("alternate"))
1245 .is_ok();
1246 if alternate && input.is_exhausted() {
1247 return Ok(RubyPosition::AlternateOver);
1248 }
1249 let over = try_match_ident_ignore_ascii_case! { input,
1251 "over" => true,
1252 "under" => false,
1253 };
1254 let alternate = alternate
1256 || input
1257 .try_parse(|i| i.expect_ident_matching("alternate"))
1258 .is_ok();
1259
1260 Ok(match (over, alternate) {
1261 (true, true) => RubyPosition::AlternateOver,
1262 (false, true) => RubyPosition::AlternateUnder,
1263 (true, false) => RubyPosition::Over,
1264 (false, false) => RubyPosition::Under,
1265 })
1266 }
1267}
1268
1269impl ToCss for RubyPosition {
1270 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1271 where
1272 W: Write,
1273 {
1274 dest.write_str(match self {
1275 RubyPosition::AlternateOver => "alternate",
1276 RubyPosition::AlternateUnder => "alternate under",
1277 RubyPosition::Over => "over",
1278 RubyPosition::Under => "under",
1279 })
1280 }
1281}
1282
1283impl SpecifiedValueInfo for RubyPosition {
1284 fn collect_completion_keywords(f: KeywordsCollectFn) {
1285 f(&["alternate", "over", "under"])
1286 }
1287}
1288
1289#[derive(
1301 Clone,
1302 Copy,
1303 Debug,
1304 Eq,
1305 MallocSizeOf,
1306 Parse,
1307 PartialEq,
1308 Serialize,
1309 SpecifiedValueInfo,
1310 ToCss,
1311 ToComputedValue,
1312 ToResolvedValue,
1313 ToShmem,
1314 ToTyped,
1315)]
1316#[css(bitflags(
1317 single = "normal,auto,no-autospace",
1318 mixed = "ideograph-alpha,ideograph-numeric,insert",
1321 ))]
1324#[repr(C)]
1325pub struct TextAutospace(u8);
1326bitflags! {
1327 impl TextAutospace: u8 {
1328 const NO_AUTOSPACE = 0;
1330
1331 const AUTO = 1 << 0;
1333
1334 const NORMAL = 1 << 1;
1336
1337 const IDEOGRAPH_ALPHA = 1 << 2;
1339
1340 const IDEOGRAPH_NUMERIC = 1 << 3;
1342
1343 const INSERT = 1 << 5;
1350
1351 }
1356}
1357
1358#[derive(
1367 Clone,
1368 Copy,
1369 Debug,
1370 Eq,
1371 FromPrimitive,
1372 Hash,
1373 MallocSizeOf,
1374 Parse,
1375 PartialEq,
1376 SpecifiedValueInfo,
1377 ToComputedValue,
1378 ToCss,
1379 ToResolvedValue,
1380 ToShmem,
1381 ToTyped,
1382)]
1383#[repr(u8)]
1384pub enum TextEdgeKeyword {
1388 Text,
1390 Ideographic,
1392 IdeographicInk,
1394 Cap,
1396 Ex,
1398 Alphabetic,
1400}
1401
1402impl TextEdgeKeyword {
1403 fn is_valid_for_over(&self) -> bool {
1404 match self {
1405 TextEdgeKeyword::Text
1406 | TextEdgeKeyword::Ideographic
1407 | TextEdgeKeyword::IdeographicInk
1408 | TextEdgeKeyword::Cap
1409 | TextEdgeKeyword::Ex => true,
1410 _ => false,
1411 }
1412 }
1413
1414 fn is_valid_for_under(&self) -> bool {
1415 match self {
1416 TextEdgeKeyword::Text
1417 | TextEdgeKeyword::Ideographic
1418 | TextEdgeKeyword::IdeographicInk
1419 | TextEdgeKeyword::Alphabetic => true,
1420 _ => false,
1421 }
1422 }
1423}
1424
1425#[derive(
1426 Clone,
1427 Copy,
1428 Debug,
1429 Eq,
1430 Hash,
1431 MallocSizeOf,
1432 PartialEq,
1433 SpecifiedValueInfo,
1434 ToComputedValue,
1435 ToResolvedValue,
1436 ToShmem,
1437 ToTyped,
1438)]
1439#[repr(C)]
1440pub struct TextEdge {
1450 pub over: TextEdgeKeyword,
1452 pub under: TextEdgeKeyword,
1454}
1455
1456impl Parse for TextEdge {
1457 fn parse<'i, 't>(
1458 _context: &ParserContext,
1459 input: &mut Parser<'i, 't>,
1460 ) -> Result<TextEdge, ParseError<'i>> {
1461 let first = TextEdgeKeyword::parse(input)?;
1462
1463 if let Ok(second) = input.try_parse(TextEdgeKeyword::parse) {
1464 if !first.is_valid_for_over() || !second.is_valid_for_under() {
1465 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1466 }
1467
1468 return Ok(TextEdge {
1469 over: first,
1470 under: second,
1471 });
1472 }
1473
1474 match (first.is_valid_for_over(), first.is_valid_for_under()) {
1478 (true, true) => Ok(TextEdge {
1479 over: first,
1480 under: first,
1481 }),
1482 (true, false) => Ok(TextEdge {
1483 over: first,
1484 under: TextEdgeKeyword::Text,
1485 }),
1486 (false, true) => Ok(TextEdge {
1487 over: TextEdgeKeyword::Text,
1488 under: first,
1489 }),
1490 _ => unreachable!("Parsed keyword will be valid for at least one edge"),
1491 }
1492 }
1493}
1494
1495impl ToCss for TextEdge {
1496 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1497 where
1498 W: Write,
1499 {
1500 match (self.over, self.under) {
1501 (over, TextEdgeKeyword::Text) if !over.is_valid_for_under() => over.to_css(dest),
1502 (TextEdgeKeyword::Text, under) if !under.is_valid_for_over() => under.to_css(dest),
1503 (over, under) => {
1504 over.to_css(dest)?;
1505
1506 if over != under {
1507 dest.write_char(' ')?;
1508 self.under.to_css(dest)?;
1509 }
1510
1511 Ok(())
1512 },
1513 }
1514 }
1515}
1516
1517#[derive(
1518 Clone,
1519 Copy,
1520 Debug,
1521 Eq,
1522 Hash,
1523 MallocSizeOf,
1524 Parse,
1525 PartialEq,
1526 SpecifiedValueInfo,
1527 ToComputedValue,
1528 ToCss,
1529 ToResolvedValue,
1530 ToShmem,
1531 ToTyped,
1532)]
1533#[repr(C, u8)]
1534pub enum TextBoxEdge {
1538 Auto,
1540 TextEdge(TextEdge),
1542}
1543
1544#[derive(
1545 Clone,
1546 Copy,
1547 Debug,
1548 Eq,
1549 MallocSizeOf,
1550 PartialEq,
1551 Parse,
1552 Serialize,
1553 SpecifiedValueInfo,
1554 ToCss,
1555 ToComputedValue,
1556 ToResolvedValue,
1557 ToShmem,
1558 ToTyped,
1559)]
1560#[css(bitflags(single = "none,trim-start,trim-end,trim-both"))]
1561#[repr(C)]
1562pub struct TextBoxTrim(u8);
1566bitflags! {
1567 impl TextBoxTrim: u8 {
1568 const NONE = 0;
1570 const TRIM_START = 1 << 0;
1572 const TRIM_END = 1 << 1;
1574 const TRIM_BOTH = Self::TRIM_START.0 | Self::TRIM_END.0;
1576 }
1577}
1578
1579impl TextBoxTrim {
1580 #[inline]
1582 pub fn none() -> Self {
1583 TextBoxTrim::NONE
1584 }
1585}