1#![allow(non_upper_case_globals)]
4
5use super::{Property, PropertyId};
6use crate::compat;
7use crate::context::PropertyHandlerContext;
8use crate::declaration::{DeclarationBlock, DeclarationList};
9use crate::error::{ParserError, PrinterError};
10use crate::macros::{define_shorthand, enum_property};
11use crate::prefixes::Feature;
12use crate::printer::Printer;
13use crate::targets::Browsers;
14use crate::traits::{FallbackValues, Parse, PropertyHandler, Shorthand, ToCss, Zero};
15use crate::values::calc::{Calc, MathFunction};
16use crate::values::color::{ColorFallbackKind, CssColor};
17use crate::values::length::{Length, LengthPercentage, LengthValue};
18use crate::values::string::CowArcStr;
19use crate::vendor_prefix::VendorPrefix;
20use bitflags::bitflags;
21use cssparser::*;
22use smallvec::SmallVec;
23
24enum_property! {
25 pub enum TextTransformCase {
28 None,
30 Uppercase,
32 Lowercase,
34 Capitalize,
36 }
37}
38
39impl Default for TextTransformCase {
40 fn default() -> TextTransformCase {
41 TextTransformCase::None
42 }
43}
44
45bitflags! {
46 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
51 pub struct TextTransformOther: u8 {
52 const FullWidth = 0b00000001;
54 const FullSizeKana = 0b00000010;
56 }
57}
58
59impl<'i> Parse<'i> for TextTransformOther {
60 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
61 let location = input.current_source_location();
62 let ident = input.expect_ident()?;
63 match_ignore_ascii_case! { &ident,
64 "full-width" => Ok(TextTransformOther::FullWidth),
65 "full-size-kana" => Ok(TextTransformOther::FullSizeKana),
66 _ => Err(location.new_unexpected_token_error(
67 cssparser::Token::Ident(ident.clone())
68 ))
69 }
70 }
71}
72
73impl ToCss for TextTransformOther {
74 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
75 where
76 W: std::fmt::Write,
77 {
78 let mut needs_space = false;
79 if self.contains(TextTransformOther::FullWidth) {
80 dest.write_str("full-width")?;
81 needs_space = true;
82 }
83
84 if self.contains(TextTransformOther::FullSizeKana) {
85 if needs_space {
86 dest.write_char(' ')?;
87 }
88 dest.write_str("full-size-kana")?;
89 }
90
91 Ok(())
92 }
93}
94
95#[derive(Debug, Clone, PartialEq)]
97#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
98pub struct TextTransform {
99 pub case: TextTransformCase,
101 pub other: TextTransformOther,
103}
104
105impl<'i> Parse<'i> for TextTransform {
106 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
107 let mut case = None;
108 let mut other = TextTransformOther::empty();
109
110 loop {
111 if case.is_none() {
112 if let Ok(c) = input.try_parse(TextTransformCase::parse) {
113 case = Some(c);
114 if c == TextTransformCase::None {
115 other = TextTransformOther::empty();
116 break;
117 }
118 continue;
119 }
120 }
121
122 if let Ok(o) = input.try_parse(TextTransformOther::parse) {
123 other |= o;
124 continue;
125 }
126
127 break;
128 }
129
130 Ok(TextTransform {
131 case: case.unwrap_or_default(),
132 other,
133 })
134 }
135}
136
137impl ToCss for TextTransform {
138 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
139 where
140 W: std::fmt::Write,
141 {
142 let mut needs_space = false;
143 if self.case != TextTransformCase::None || self.other.is_empty() {
144 self.case.to_css(dest)?;
145 needs_space = true;
146 }
147
148 if !self.other.is_empty() {
149 if needs_space {
150 dest.write_char(' ')?;
151 }
152 self.other.to_css(dest)?;
153 }
154 Ok(())
155 }
156}
157
158enum_property! {
159 pub enum WhiteSpace {
161 "normal": Normal,
163 "pre": Pre,
165 "nowrap": NoWrap,
167 "pre-wrap": PreWrap,
169 "break-spaces": BreakSpaces,
171 "pre-line": PreLine,
173 }
174}
175
176enum_property! {
177 pub enum WordBreak {
179 "normal": Normal,
181 "keep-all": KeepAll,
183 "break-all": BreakAll,
185 "break-word": BreakWord,
187 }
188}
189
190enum_property! {
191 pub enum LineBreak {
193 Auto,
195 Loose,
197 Normal,
199 Strict,
201 Anywhere,
203 }
204}
205enum_property! {
206 pub enum Hyphens {
208 None,
210 Manual,
212 Auto,
214 }
215}
216
217enum_property! {
218 pub enum OverflowWrap {
220 "normal": Normal,
222 "anywhere": Anywhere,
224 "break-word": BreakWord,
227 }
228}
229
230enum_property! {
231 pub enum TextAlign {
233 "start": Start,
235 "end": End,
237 "left": Left,
239 "right": Right,
241 "center": Center,
243 "justify": Justify,
245 "match-parent": MatchParent,
247 "justify-all": JustifyAll,
249 }
250}
251
252enum_property! {
253 pub enum TextAlignLast {
255 "auto": Auto,
257 "start": Start,
259 "end": End,
261 "left": Left,
263 "right": Right,
265 "center": Center,
267 "justify": Justify,
269 "match-parent": MatchParent,
271 }
272}
273
274enum_property! {
275 pub enum TextJustify {
277 "auto": Auto,
279 "none": None,
281 "inter-word": InterWord,
283 "inter-character": InterCharacter,
285 }
286}
287
288#[derive(Debug, Clone, PartialEq)]
291#[cfg_attr(
292 feature = "serde",
293 derive(serde::Serialize, serde::Deserialize),
294 serde(tag = "type", content = "value", rename_all = "kebab-case")
295)]
296pub enum Spacing {
297 Normal,
299 Length(Length),
301}
302
303impl<'i> Parse<'i> for Spacing {
304 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
305 if input.try_parse(|input| input.expect_ident_matching("normal")).is_ok() {
306 return Ok(Spacing::Normal);
307 }
308
309 let length = Length::parse(input)?;
310 Ok(Spacing::Length(length))
311 }
312}
313
314impl ToCss for Spacing {
315 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
316 where
317 W: std::fmt::Write,
318 {
319 match self {
320 Spacing::Normal => dest.write_str("normal"),
321 Spacing::Length(len) => len.to_css(dest),
322 }
323 }
324}
325
326#[derive(Debug, Clone, PartialEq)]
328#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
329pub struct TextIndent {
330 pub value: LengthPercentage,
332 pub hanging: bool,
334 pub each_line: bool,
336}
337
338impl<'i> Parse<'i> for TextIndent {
339 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
340 let mut value = None;
341 let mut hanging = false;
342 let mut each_line = false;
343
344 loop {
345 if value.is_none() {
346 if let Ok(val) = input.try_parse(LengthPercentage::parse) {
347 value = Some(val);
348 continue;
349 }
350 }
351
352 if !hanging {
353 if input.try_parse(|input| input.expect_ident_matching("hanging")).is_ok() {
354 hanging = true;
355 continue;
356 }
357 }
358
359 if !each_line {
360 if input.try_parse(|input| input.expect_ident_matching("each-line")).is_ok() {
361 each_line = true;
362 continue;
363 }
364 }
365
366 break;
367 }
368
369 if let Some(value) = value {
370 Ok(TextIndent {
371 value,
372 hanging,
373 each_line,
374 })
375 } else {
376 Err(input.new_custom_error(ParserError::InvalidDeclaration))
377 }
378 }
379}
380
381impl ToCss for TextIndent {
382 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
383 where
384 W: std::fmt::Write,
385 {
386 self.value.to_css(dest)?;
387 if self.hanging {
388 dest.write_str(" hanging")?;
389 }
390 if self.each_line {
391 dest.write_str(" each-line")?;
392 }
393 Ok(())
394 }
395}
396
397bitflags! {
398 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
402 pub struct TextDecorationLine: u8 {
403 const Underline = 0b00000001;
405 const Overline = 0b00000010;
407 const LineThrough = 0b00000100;
409 const Blink = 0b00001000;
411 const SpellingError = 0b00010000;
413 const GrammarError = 0b00100000;
415 }
416}
417
418impl Default for TextDecorationLine {
419 fn default() -> TextDecorationLine {
420 TextDecorationLine::empty()
421 }
422}
423
424impl<'i> Parse<'i> for TextDecorationLine {
425 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
426 let mut value = TextDecorationLine::empty();
427 let mut any = false;
428
429 loop {
430 let flag: Result<_, ParseError<'i, ParserError<'i>>> = input.try_parse(|input| {
431 let location = input.current_source_location();
432 let ident = input.expect_ident()?;
433 Ok(match_ignore_ascii_case! { &ident,
434 "none" if value.is_empty() => TextDecorationLine::empty(),
435 "underline" => TextDecorationLine::Underline,
436 "overline" => TextDecorationLine::Overline,
437 "line-through" => TextDecorationLine::LineThrough,
438 "blink" =>TextDecorationLine::Blink,
439 "spelling-error" if value.is_empty() => TextDecorationLine::SpellingError,
440 "grammar-error" if value.is_empty() => TextDecorationLine::GrammarError,
441 _ => return Err(location.new_unexpected_token_error(
442 cssparser::Token::Ident(ident.clone())
443 ))
444 })
445 });
446
447 if let Ok(flag) = flag {
448 value |= flag;
449 any = true;
450 } else {
451 break;
452 }
453 }
454
455 if !any {
456 return Err(input.new_custom_error(ParserError::InvalidDeclaration));
457 }
458
459 Ok(value)
460 }
461}
462
463impl ToCss for TextDecorationLine {
464 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
465 where
466 W: std::fmt::Write,
467 {
468 if self.is_empty() {
469 return dest.write_str("none");
470 }
471
472 if self.contains(TextDecorationLine::SpellingError) {
473 return dest.write_str("spelling-error");
474 }
475
476 if self.contains(TextDecorationLine::GrammarError) {
477 return dest.write_str("grammar-error");
478 }
479
480 let mut needs_space = false;
481 macro_rules! val {
482 ($val: ident, $str: expr) => {
483 #[allow(unused_assignments)]
484 if self.contains(TextDecorationLine::$val) {
485 if needs_space {
486 dest.write_char(' ')?;
487 }
488 dest.write_str($str)?;
489 needs_space = true;
490 }
491 };
492 }
493
494 val!(Underline, "underline");
495 val!(Overline, "overline");
496 val!(LineThrough, "line-through");
497 val!(Blink, "blink");
498 Ok(())
499 }
500}
501
502enum_property! {
503 pub enum TextDecorationStyle {
505 Solid,
507 Double,
509 Dotted,
511 Dashed,
513 Wavy,
515 }
516}
517
518impl Default for TextDecorationStyle {
519 fn default() -> TextDecorationStyle {
520 TextDecorationStyle::Solid
521 }
522}
523
524#[derive(Debug, Clone, PartialEq)]
526#[cfg_attr(
527 feature = "serde",
528 derive(serde::Serialize, serde::Deserialize),
529 serde(tag = "type", content = "value", rename_all = "kebab-case")
530)]
531pub enum TextDecorationThickness {
532 Auto,
534 FromFont,
536 LengthPercentage(LengthPercentage),
538}
539
540impl Default for TextDecorationThickness {
541 fn default() -> TextDecorationThickness {
542 TextDecorationThickness::Auto
543 }
544}
545
546impl<'i> Parse<'i> for TextDecorationThickness {
547 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
548 if input.try_parse(|input| input.expect_ident_matching("auto")).is_ok() {
549 return Ok(TextDecorationThickness::Auto);
550 }
551
552 if input.try_parse(|input| input.expect_ident_matching("from-font")).is_ok() {
553 return Ok(TextDecorationThickness::FromFont);
554 }
555
556 let lp = LengthPercentage::parse(input)?;
557 Ok(TextDecorationThickness::LengthPercentage(lp))
558 }
559}
560
561impl ToCss for TextDecorationThickness {
562 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
563 where
564 W: std::fmt::Write,
565 {
566 match self {
567 TextDecorationThickness::Auto => dest.write_str("auto"),
568 TextDecorationThickness::FromFont => dest.write_str("from-font"),
569 TextDecorationThickness::LengthPercentage(lp) => lp.to_css(dest),
570 }
571 }
572}
573
574define_shorthand! {
575 pub struct TextDecoration(VendorPrefix) {
577 line: TextDecorationLine(TextDecorationLine, VendorPrefix),
579 thickness: TextDecorationThickness(TextDecorationThickness),
581 style: TextDecorationStyle(TextDecorationStyle, VendorPrefix),
583 color: TextDecorationColor(CssColor, VendorPrefix),
585 }
586}
587
588impl<'i> Parse<'i> for TextDecoration {
589 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
590 let mut line = None;
591 let mut thickness = None;
592 let mut style = None;
593 let mut color = None;
594
595 loop {
596 macro_rules! prop {
597 ($key: ident, $type: ident) => {
598 if $key.is_none() {
599 if let Ok(val) = input.try_parse($type::parse) {
600 $key = Some(val);
601 continue;
602 }
603 }
604 };
605 }
606
607 prop!(line, TextDecorationLine);
608 prop!(thickness, TextDecorationThickness);
609 prop!(style, TextDecorationStyle);
610 prop!(color, CssColor);
611 break;
612 }
613
614 Ok(TextDecoration {
615 line: line.unwrap_or_default(),
616 thickness: thickness.unwrap_or_default(),
617 style: style.unwrap_or_default(),
618 color: color.unwrap_or(CssColor::current_color()),
619 })
620 }
621}
622
623impl ToCss for TextDecoration {
624 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
625 where
626 W: std::fmt::Write,
627 {
628 self.line.to_css(dest)?;
629 if self.line.is_empty() {
630 return Ok(());
631 }
632
633 let mut needs_space = true;
634 if self.thickness != TextDecorationThickness::default() {
635 dest.write_char(' ')?;
636 self.thickness.to_css(dest)?;
637 needs_space = true;
638 }
639
640 if self.style != TextDecorationStyle::default() {
641 if needs_space {
642 dest.write_char(' ')?;
643 }
644 self.style.to_css(dest)?;
645 needs_space = true;
646 }
647
648 if self.color != CssColor::current_color() {
649 if needs_space {
650 dest.write_char(' ')?;
651 }
652 self.color.to_css(dest)?;
653 }
654
655 Ok(())
656 }
657}
658
659impl FallbackValues for TextDecoration {
660 fn get_fallbacks(&mut self, targets: Browsers) -> Vec<Self> {
661 self
662 .color
663 .get_fallbacks(targets)
664 .into_iter()
665 .map(|color| TextDecoration { color, ..self.clone() })
666 .collect()
667 }
668}
669
670enum_property! {
671 pub enum TextDecorationSkipInk {
673 Auto,
675 None,
677 All,
679 }
680}
681
682enum_property! {
683 pub enum TextEmphasisFillMode {
687 Filled,
689 Open,
691 }
692}
693
694enum_property! {
695 pub enum TextEmphasisShape {
699 "dot": Dot,
701 "circle": Circle,
703 "double-circle": DoubleCircle,
705 "triangle": Triangle,
707 "sesame": Sesame,
709 }
710}
711
712#[derive(Debug, Clone, PartialEq)]
714#[cfg_attr(
715 feature = "serde",
716 derive(serde::Serialize, serde::Deserialize),
717 serde(tag = "type", content = "value", rename_all = "kebab-case")
718)]
719pub enum TextEmphasisStyle<'i> {
720 None,
722 Keyword {
724 fill: TextEmphasisFillMode,
726 shape: Option<TextEmphasisShape>,
728 },
729 #[cfg_attr(feature = "serde", serde(borrow))]
731 String(CowArcStr<'i>),
732}
733
734impl<'i> Default for TextEmphasisStyle<'i> {
735 fn default() -> TextEmphasisStyle<'i> {
736 TextEmphasisStyle::None
737 }
738}
739
740impl<'i> Parse<'i> for TextEmphasisStyle<'i> {
741 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
742 if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() {
743 return Ok(TextEmphasisStyle::None);
744 }
745
746 if let Ok(s) = input.try_parse(|input| input.expect_string_cloned()) {
747 return Ok(TextEmphasisStyle::String(s.into()));
748 }
749
750 let mut shape = input.try_parse(TextEmphasisShape::parse).ok();
751 let fill = input.try_parse(TextEmphasisFillMode::parse).ok();
752 if shape.is_none() {
753 shape = input.try_parse(TextEmphasisShape::parse).ok();
754 }
755
756 if shape.is_none() && fill.is_none() {
757 return Err(input.new_custom_error(ParserError::InvalidDeclaration));
758 }
759
760 let fill = fill.unwrap_or(TextEmphasisFillMode::Filled);
761 Ok(TextEmphasisStyle::Keyword { fill, shape })
762 }
763}
764
765impl<'i> ToCss for TextEmphasisStyle<'i> {
766 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
767 where
768 W: std::fmt::Write,
769 {
770 match self {
771 TextEmphasisStyle::None => dest.write_str("none"),
772 TextEmphasisStyle::String(s) => {
773 serialize_string(&s, dest)?;
774 Ok(())
775 }
776 TextEmphasisStyle::Keyword { fill, shape } => {
777 let mut needs_space = false;
778 if *fill != TextEmphasisFillMode::Filled || shape.is_none() {
779 fill.to_css(dest)?;
780 needs_space = true;
781 }
782
783 if let Some(shape) = shape {
784 if needs_space {
785 dest.write_char(' ')?;
786 }
787 shape.to_css(dest)?;
788 }
789 Ok(())
790 }
791 }
792 }
793}
794
795define_shorthand! {
796 pub struct TextEmphasis<'i>(VendorPrefix) {
798 #[cfg_attr(feature = "serde", serde(borrow))]
800 style: TextEmphasisStyle(TextEmphasisStyle<'i>, VendorPrefix),
801 color: TextEmphasisColor(CssColor, VendorPrefix),
803 }
804}
805
806impl<'i> Parse<'i> for TextEmphasis<'i> {
807 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
808 let mut style = None;
809 let mut color = None;
810
811 loop {
812 if style.is_none() {
813 if let Ok(s) = input.try_parse(TextEmphasisStyle::parse) {
814 style = Some(s);
815 continue;
816 }
817 }
818
819 if color.is_none() {
820 if let Ok(c) = input.try_parse(CssColor::parse) {
821 color = Some(c);
822 continue;
823 }
824 }
825
826 break;
827 }
828
829 Ok(TextEmphasis {
830 style: style.unwrap_or_default(),
831 color: color.unwrap_or(CssColor::current_color()),
832 })
833 }
834}
835
836impl<'i> ToCss for TextEmphasis<'i> {
837 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
838 where
839 W: std::fmt::Write,
840 {
841 self.style.to_css(dest)?;
842
843 if self.style != TextEmphasisStyle::None && self.color != CssColor::current_color() {
844 dest.write_char(' ')?;
845 self.color.to_css(dest)?;
846 }
847
848 Ok(())
849 }
850}
851
852impl<'i> FallbackValues for TextEmphasis<'i> {
853 fn get_fallbacks(&mut self, targets: Browsers) -> Vec<Self> {
854 self
855 .color
856 .get_fallbacks(targets)
857 .into_iter()
858 .map(|color| TextEmphasis { color, ..self.clone() })
859 .collect()
860 }
861}
862
863enum_property! {
864 pub enum TextEmphasisPositionVertical {
868 Over,
870 Under,
872 }
873}
874
875enum_property! {
876 pub enum TextEmphasisPositionHorizontal {
880 Left,
882 Right,
884 }
885}
886
887#[derive(Debug, Clone, PartialEq)]
889#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
890pub struct TextEmphasisPosition {
891 pub vertical: TextEmphasisPositionVertical,
893 pub horizontal: TextEmphasisPositionHorizontal,
895}
896
897impl<'i> Parse<'i> for TextEmphasisPosition {
898 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
899 if let Ok(horizontal) = input.try_parse(TextEmphasisPositionHorizontal::parse) {
900 let vertical = TextEmphasisPositionVertical::parse(input)?;
901 Ok(TextEmphasisPosition { horizontal, vertical })
902 } else {
903 let vertical = TextEmphasisPositionVertical::parse(input)?;
904 let horizontal = input
905 .try_parse(TextEmphasisPositionHorizontal::parse)
906 .unwrap_or(TextEmphasisPositionHorizontal::Right);
907 Ok(TextEmphasisPosition { horizontal, vertical })
908 }
909 }
910}
911
912enum_property! {
913 pub enum BoxDecorationBreak {
915 Slice,
917 Clone,
919 }
920}
921
922impl Default for BoxDecorationBreak {
923 fn default() -> Self {
924 BoxDecorationBreak::Slice
925 }
926}
927
928impl ToCss for TextEmphasisPosition {
929 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
930 where
931 W: std::fmt::Write,
932 {
933 self.vertical.to_css(dest)?;
934 if self.horizontal != TextEmphasisPositionHorizontal::Right {
935 dest.write_char(' ')?;
936 self.horizontal.to_css(dest)?;
937 }
938 Ok(())
939 }
940}
941
942#[derive(Default)]
943pub(crate) struct TextDecorationHandler<'i> {
944 targets: Option<Browsers>,
945 line: Option<(TextDecorationLine, VendorPrefix)>,
946 thickness: Option<TextDecorationThickness>,
947 style: Option<(TextDecorationStyle, VendorPrefix)>,
948 color: Option<(CssColor, VendorPrefix)>,
949 emphasis_style: Option<(TextEmphasisStyle<'i>, VendorPrefix)>,
950 emphasis_color: Option<(CssColor, VendorPrefix)>,
951 emphasis_position: Option<(TextEmphasisPosition, VendorPrefix)>,
952 has_any: bool,
953}
954
955impl<'i> TextDecorationHandler<'i> {
956 pub fn new(targets: Option<Browsers>) -> TextDecorationHandler<'i> {
957 TextDecorationHandler {
958 targets,
959 ..TextDecorationHandler::default()
960 }
961 }
962}
963
964impl<'i> PropertyHandler<'i> for TextDecorationHandler<'i> {
965 fn handle_property(
966 &mut self,
967 property: &Property<'i>,
968 dest: &mut DeclarationList<'i>,
969 context: &mut PropertyHandlerContext<'i, '_>,
970 ) -> bool {
971 use Property::*;
972
973 macro_rules! maybe_flush {
974 ($prop: ident, $val: expr, $vp: expr) => {{
975 if let Some((val, prefixes)) = &self.$prop {
978 if val != $val && !prefixes.contains(*$vp) {
979 self.finalize(dest, context);
980 }
981 }
982 }};
983 }
984
985 macro_rules! property {
986 ($prop: ident, $val: expr, $vp: expr) => {{
987 maybe_flush!($prop, $val, $vp);
988
989 if let Some((val, prefixes)) = &mut self.$prop {
991 *val = $val.clone();
992 *prefixes |= *$vp;
993 } else {
994 self.$prop = Some(($val.clone(), *$vp));
995 self.has_any = true;
996 }
997 }};
998 }
999
1000 match property {
1001 TextDecorationLine(val, vp) => property!(line, val, vp),
1002 TextDecorationThickness(val) => {
1003 self.thickness = Some(val.clone());
1004 self.has_any = true;
1005 }
1006 TextDecorationStyle(val, vp) => property!(style, val, vp),
1007 TextDecorationColor(val, vp) => property!(color, val, vp),
1008 TextDecoration(val, vp) => {
1009 maybe_flush!(line, &val.line, vp);
1010 maybe_flush!(style, &val.style, vp);
1011 maybe_flush!(color, &val.color, vp);
1012 property!(line, &val.line, vp);
1013 self.thickness = Some(val.thickness.clone());
1014 property!(style, &val.style, vp);
1015 property!(color, &val.color, vp);
1016 }
1017 TextEmphasisStyle(val, vp) => property!(emphasis_style, val, vp),
1018 TextEmphasisColor(val, vp) => property!(emphasis_color, val, vp),
1019 TextEmphasis(val, vp) => {
1020 maybe_flush!(emphasis_style, &val.style, vp);
1021 maybe_flush!(emphasis_color, &val.color, vp);
1022 property!(emphasis_style, &val.style, vp);
1023 property!(emphasis_color, &val.color, vp);
1024 }
1025 TextEmphasisPosition(val, vp) => property!(emphasis_position, val, vp),
1026 TextAlign(align) => {
1027 use super::text::*;
1028 macro_rules! logical {
1029 ($ltr: ident, $rtl: ident) => {{
1030 let logical_supported = context.is_supported(compat::Feature::LogicalTextAlign);
1031 if logical_supported {
1032 dest.push(property.clone());
1033 } else {
1034 context.add_logical_rule(
1035 Property::TextAlign(TextAlign::$ltr),
1036 Property::TextAlign(TextAlign::$rtl),
1037 );
1038 }
1039 }};
1040 }
1041
1042 match align {
1043 TextAlign::Start => logical!(Left, Right),
1044 TextAlign::End => logical!(Right, Left),
1045 _ => dest.push(property.clone()),
1046 }
1047 }
1048 Unparsed(val) if is_text_decoration_property(&val.property_id) => {
1049 self.finalize(dest, context);
1050 let mut unparsed = val.get_prefixed(self.targets, Feature::TextDecoration);
1051 context.add_unparsed_fallbacks(&mut unparsed);
1052 dest.push(Property::Unparsed(unparsed))
1053 }
1054 Unparsed(val) if is_text_emphasis_property(&val.property_id) => {
1055 self.finalize(dest, context);
1056 let mut unparsed = val.get_prefixed(self.targets, Feature::TextEmphasis);
1057 context.add_unparsed_fallbacks(&mut unparsed);
1058 dest.push(Property::Unparsed(unparsed))
1059 }
1060 _ => return false,
1061 }
1062
1063 true
1064 }
1065
1066 fn finalize(&mut self, dest: &mut DeclarationList<'i>, _: &mut PropertyHandlerContext<'i, '_>) {
1067 if !self.has_any {
1068 return;
1069 }
1070
1071 self.has_any = false;
1072
1073 let mut line = std::mem::take(&mut self.line);
1074 let mut thickness = std::mem::take(&mut self.thickness);
1075 let mut style = std::mem::take(&mut self.style);
1076 let mut color = std::mem::take(&mut self.color);
1077 let mut emphasis_style = std::mem::take(&mut self.emphasis_style);
1078 let mut emphasis_color = std::mem::take(&mut self.emphasis_color);
1079 let emphasis_position = std::mem::take(&mut self.emphasis_position);
1080
1081 if let (Some((line, line_vp)), Some(thickness_val), Some((style, style_vp)), Some((color, color_vp))) =
1082 (&mut line, &mut thickness, &mut style, &mut color)
1083 {
1084 let intersection = *line_vp | *style_vp | *color_vp;
1085 if !intersection.is_empty() {
1086 let mut prefix = intersection;
1087
1088 let supports_thickness = if let Some(targets) = self.targets {
1090 compat::Feature::TextDecorationThicknessShorthand.is_compatible(targets)
1091 } else {
1092 true
1093 };
1094
1095 let mut decoration = TextDecoration {
1096 line: line.clone(),
1097 thickness: if supports_thickness {
1098 thickness_val.clone()
1099 } else {
1100 TextDecorationThickness::default()
1101 },
1102 style: style.clone(),
1103 color: color.clone(),
1104 };
1105
1106 if prefix.contains(VendorPrefix::None)
1108 && (*style != TextDecorationStyle::default() || *color != CssColor::current_color())
1109 {
1110 if let Some(targets) = self.targets {
1111 prefix = Feature::TextDecoration.prefixes_for(targets);
1112
1113 let fallbacks = decoration.get_fallbacks(targets);
1114 for fallback in fallbacks {
1115 dest.push(Property::TextDecoration(fallback, prefix))
1116 }
1117 }
1118 }
1119
1120 dest.push(Property::TextDecoration(decoration, prefix));
1121 line_vp.remove(intersection);
1122 style_vp.remove(intersection);
1123 color_vp.remove(intersection);
1124 if supports_thickness || *thickness_val == TextDecorationThickness::default() {
1125 thickness = None;
1126 }
1127 }
1128 }
1129
1130 macro_rules! color {
1131 ($key: ident, $prop: ident) => {
1132 if let Some((mut val, vp)) = $key {
1133 if !vp.is_empty() {
1134 let mut prefix = vp;
1135 if prefix.contains(VendorPrefix::None) {
1136 if let Some(targets) = self.targets {
1137 prefix = Feature::$prop.prefixes_for(targets);
1138
1139 let fallbacks = val.get_fallbacks(targets);
1140 for fallback in fallbacks {
1141 dest.push(Property::$prop(fallback, prefix))
1142 }
1143 }
1144 }
1145 dest.push(Property::$prop(val, prefix))
1146 }
1147 }
1148 };
1149 }
1150
1151 macro_rules! single_property {
1152 ($key: ident, $prop: ident) => {
1153 if let Some((val, vp)) = $key {
1154 if !vp.is_empty() {
1155 let mut prefix = vp;
1156 if prefix.contains(VendorPrefix::None) {
1157 if let Some(targets) = self.targets {
1158 prefix = Feature::$prop.prefixes_for(targets);
1159 }
1160 }
1161 dest.push(Property::$prop(val, prefix))
1162 }
1163 }
1164 };
1165 }
1166
1167 single_property!(line, TextDecorationLine);
1168 single_property!(style, TextDecorationStyle);
1169 color!(color, TextDecorationColor);
1170
1171 if let Some(thickness) = thickness {
1172 match (self.targets, thickness) {
1175 (Some(targets), TextDecorationThickness::LengthPercentage(LengthPercentage::Percentage(p)))
1176 if !compat::Feature::TextDecorationThicknessPercent.is_compatible(targets) =>
1177 {
1178 let calc = Calc::Function(Box::new(MathFunction::Calc(Calc::Product(
1179 p.0,
1180 Box::new(Calc::Value(Box::new(LengthPercentage::Dimension(LengthValue::Em(1.0))))),
1181 ))));
1182 let thickness = TextDecorationThickness::LengthPercentage(LengthPercentage::Calc(Box::new(calc)));
1183 dest.push(Property::TextDecorationThickness(thickness));
1184 }
1185 (_, thickness) => dest.push(Property::TextDecorationThickness(thickness)),
1186 }
1187 }
1188
1189 if let (Some((style, style_vp)), Some((color, color_vp))) = (&mut emphasis_style, &mut emphasis_color) {
1190 let intersection = *style_vp | *color_vp;
1191 if !intersection.is_empty() {
1192 let mut prefix = intersection;
1193 let mut emphasis = TextEmphasis {
1194 style: style.clone(),
1195 color: color.clone(),
1196 };
1197
1198 if prefix.contains(VendorPrefix::None) {
1199 if let Some(targets) = self.targets {
1200 prefix = Feature::TextEmphasis.prefixes_for(targets);
1201
1202 let fallbacks = emphasis.get_fallbacks(targets);
1203 for fallback in fallbacks {
1204 dest.push(Property::TextEmphasis(fallback, prefix))
1205 }
1206 }
1207 }
1208
1209 dest.push(Property::TextEmphasis(emphasis, prefix));
1210 style_vp.remove(intersection);
1211 color_vp.remove(intersection);
1212 }
1213 }
1214
1215 single_property!(emphasis_style, TextEmphasisStyle);
1216 color!(emphasis_color, TextEmphasisColor);
1217
1218 if let Some((pos, vp)) = emphasis_position {
1219 if !vp.is_empty() {
1220 let mut prefix = vp;
1221 if prefix.contains(VendorPrefix::None) {
1222 if let Some(targets) = self.targets {
1223 prefix = Feature::TextEmphasisPosition.prefixes_for(targets);
1224 if pos.horizontal != TextEmphasisPositionHorizontal::Right {
1226 prefix = VendorPrefix::None;
1227 }
1228 }
1229 }
1230 dest.push(Property::TextEmphasisPosition(pos, prefix))
1231 }
1232 }
1233 }
1234}
1235
1236#[derive(Debug, Clone, PartialEq)]
1238#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1239pub struct TextShadow {
1240 pub color: CssColor,
1242 pub x_offset: Length,
1244 pub y_offset: Length,
1246 pub blur: Length,
1248 pub spread: Length, }
1251
1252impl<'i> Parse<'i> for TextShadow {
1253 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1254 let mut color = None;
1255 let mut lengths = None;
1256
1257 loop {
1258 if lengths.is_none() {
1259 let value = input.try_parse::<_, _, ParseError<ParserError<'i>>>(|input| {
1260 let horizontal = Length::parse(input)?;
1261 let vertical = Length::parse(input)?;
1262 let blur = input.try_parse(Length::parse).unwrap_or(Length::zero());
1263 let spread = input.try_parse(Length::parse).unwrap_or(Length::zero());
1264 Ok((horizontal, vertical, blur, spread))
1265 });
1266
1267 if let Ok(value) = value {
1268 lengths = Some(value);
1269 continue;
1270 }
1271 }
1272
1273 if color.is_none() {
1274 if let Ok(value) = input.try_parse(CssColor::parse) {
1275 color = Some(value);
1276 continue;
1277 }
1278 }
1279
1280 break;
1281 }
1282
1283 let lengths = lengths.ok_or(input.new_error(BasicParseErrorKind::QualifiedRuleInvalid))?;
1284 Ok(TextShadow {
1285 color: color.unwrap_or(CssColor::current_color()),
1286 x_offset: lengths.0,
1287 y_offset: lengths.1,
1288 blur: lengths.2,
1289 spread: lengths.3,
1290 })
1291 }
1292}
1293
1294impl ToCss for TextShadow {
1295 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1296 where
1297 W: std::fmt::Write,
1298 {
1299 self.x_offset.to_css(dest)?;
1300 dest.write_char(' ')?;
1301 self.y_offset.to_css(dest)?;
1302
1303 if self.blur != Length::zero() || self.spread != Length::zero() {
1304 dest.write_char(' ')?;
1305 self.blur.to_css(dest)?;
1306
1307 if self.spread != Length::zero() {
1308 dest.write_char(' ')?;
1309 self.spread.to_css(dest)?;
1310 }
1311 }
1312
1313 if self.color != CssColor::current_color() {
1314 dest.write_char(' ')?;
1315 self.color.to_css(dest)?;
1316 }
1317
1318 Ok(())
1319 }
1320}
1321
1322#[inline]
1323fn is_text_decoration_property(property_id: &PropertyId) -> bool {
1324 match property_id {
1325 PropertyId::TextDecorationLine(_)
1326 | PropertyId::TextDecorationThickness
1327 | PropertyId::TextDecorationStyle(_)
1328 | PropertyId::TextDecorationColor(_)
1329 | PropertyId::TextDecoration(_) => true,
1330 _ => false,
1331 }
1332}
1333
1334#[inline]
1335fn is_text_emphasis_property(property_id: &PropertyId) -> bool {
1336 match property_id {
1337 PropertyId::TextEmphasisStyle(_)
1338 | PropertyId::TextEmphasisColor(_)
1339 | PropertyId::TextEmphasis(_)
1340 | PropertyId::TextEmphasisPosition(_) => true,
1341 _ => false,
1342 }
1343}
1344
1345impl FallbackValues for SmallVec<[TextShadow; 1]> {
1346 fn get_fallbacks(&mut self, targets: Browsers) -> Vec<Self> {
1347 let mut fallbacks = ColorFallbackKind::empty();
1348 for shadow in self.iter() {
1349 fallbacks |= shadow.color.get_necessary_fallbacks(targets);
1350 }
1351
1352 let mut res = Vec::new();
1353 if fallbacks.contains(ColorFallbackKind::RGB) {
1354 let rgb = self
1355 .iter()
1356 .map(|shadow| TextShadow {
1357 color: shadow.color.to_rgb(),
1358 ..shadow.clone()
1359 })
1360 .collect();
1361 res.push(rgb);
1362 }
1363
1364 if fallbacks.contains(ColorFallbackKind::P3) {
1365 let p3 = self
1366 .iter()
1367 .map(|shadow| TextShadow {
1368 color: shadow.color.to_p3(),
1369 ..shadow.clone()
1370 })
1371 .collect();
1372 res.push(p3);
1373 }
1374
1375 if fallbacks.contains(ColorFallbackKind::LAB) {
1376 for shadow in self.iter_mut() {
1377 shadow.color = shadow.color.to_lab();
1378 }
1379 }
1380
1381 res
1382 }
1383}