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::{should_compile, Browsers, Targets};
14use crate::traits::{FallbackValues, IsCompatible, 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::percentage::Percentage;
19use crate::values::string::CSSString;
20use crate::vendor_prefix::VendorPrefix;
21#[cfg(feature = "visitor")]
22use crate::visitor::Visit;
23use bitflags::bitflags;
24use cssparser::*;
25use smallvec::SmallVec;
26
27enum_property! {
28 pub enum TextTransformCase {
31 None,
33 Uppercase,
35 Lowercase,
37 Capitalize,
39 }
40}
41
42impl Default for TextTransformCase {
43 fn default() -> TextTransformCase {
44 TextTransformCase::None
45 }
46}
47
48bitflags! {
49 #[cfg_attr(feature = "visitor", derive(Visit))]
54 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(from = "SerializedTextTransformOther", into = "SerializedTextTransformOther"))]
55 #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
56 pub struct TextTransformOther: u8 {
57 const FullWidth = 0b00000001;
59 const FullSizeKana = 0b00000010;
61 }
62}
63
64impl<'i> Parse<'i> for TextTransformOther {
65 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
66 let location = input.current_source_location();
67 let ident = input.expect_ident()?;
68 match_ignore_ascii_case! { &ident,
69 "full-width" => Ok(TextTransformOther::FullWidth),
70 "full-size-kana" => Ok(TextTransformOther::FullSizeKana),
71 _ => Err(location.new_unexpected_token_error(
72 cssparser::Token::Ident(ident.clone())
73 ))
74 }
75 }
76}
77
78impl ToCss for TextTransformOther {
79 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
80 where
81 W: std::fmt::Write,
82 {
83 let mut needs_space = false;
84 if self.contains(TextTransformOther::FullWidth) {
85 dest.write_str("full-width")?;
86 needs_space = true;
87 }
88
89 if self.contains(TextTransformOther::FullSizeKana) {
90 if needs_space {
91 dest.write_char(' ')?;
92 }
93 dest.write_str("full-size-kana")?;
94 }
95
96 Ok(())
97 }
98}
99
100#[cfg_attr(
101 feature = "serde",
102 derive(serde::Serialize, serde::Deserialize),
103 serde(rename_all = "camelCase")
104)]
105#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
106#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
107struct SerializedTextTransformOther {
108 full_width: bool,
110 full_size_kana: bool,
112}
113
114impl From<TextTransformOther> for SerializedTextTransformOther {
115 fn from(t: TextTransformOther) -> Self {
116 Self {
117 full_width: t.contains(TextTransformOther::FullWidth),
118 full_size_kana: t.contains(TextTransformOther::FullSizeKana),
119 }
120 }
121}
122
123impl From<SerializedTextTransformOther> for TextTransformOther {
124 fn from(t: SerializedTextTransformOther) -> Self {
125 let mut res = TextTransformOther::empty();
126 if t.full_width {
127 res |= TextTransformOther::FullWidth;
128 }
129 if t.full_size_kana {
130 res |= TextTransformOther::FullSizeKana;
131 }
132 res
133 }
134}
135
136#[cfg(feature = "jsonschema")]
137#[cfg_attr(docsrs, doc(cfg(feature = "jsonschema")))]
138impl<'a> schemars::JsonSchema for TextTransformOther {
139 fn is_referenceable() -> bool {
140 true
141 }
142
143 fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
144 SerializedTextTransformOther::json_schema(gen)
145 }
146
147 fn schema_name() -> String {
148 "TextTransformOther".into()
149 }
150}
151
152#[derive(Debug, Clone, PartialEq)]
154#[cfg_attr(feature = "visitor", derive(Visit))]
155#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
156#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
157#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
158pub struct TextTransform {
159 pub case: TextTransformCase,
161 #[cfg_attr(feature = "serde", serde(flatten))]
163 pub other: TextTransformOther,
164}
165
166impl<'i> Parse<'i> for TextTransform {
167 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
168 let mut case = None;
169 let mut other = TextTransformOther::empty();
170
171 loop {
172 if case.is_none() {
173 if let Ok(c) = input.try_parse(TextTransformCase::parse) {
174 case = Some(c);
175 if c == TextTransformCase::None {
176 other = TextTransformOther::empty();
177 break;
178 }
179 continue;
180 }
181 }
182
183 if let Ok(o) = input.try_parse(TextTransformOther::parse) {
184 other |= o;
185 continue;
186 }
187
188 break;
189 }
190
191 Ok(TextTransform {
192 case: case.unwrap_or_default(),
193 other,
194 })
195 }
196}
197
198impl ToCss for TextTransform {
199 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
200 where
201 W: std::fmt::Write,
202 {
203 let mut needs_space = false;
204 if self.case != TextTransformCase::None || self.other.is_empty() {
205 self.case.to_css(dest)?;
206 needs_space = true;
207 }
208
209 if !self.other.is_empty() {
210 if needs_space {
211 dest.write_char(' ')?;
212 }
213 self.other.to_css(dest)?;
214 }
215 Ok(())
216 }
217}
218
219enum_property! {
220 pub enum WhiteSpace {
222 "normal": Normal,
224 "pre": Pre,
226 "nowrap": NoWrap,
228 "pre-wrap": PreWrap,
230 "break-spaces": BreakSpaces,
232 "pre-line": PreLine,
234 }
235}
236
237enum_property! {
238 pub enum WordBreak {
240 Normal,
242 KeepAll,
244 BreakAll,
246 BreakWord,
248 }
249}
250
251enum_property! {
252 pub enum LineBreak {
254 Auto,
256 Loose,
258 Normal,
260 Strict,
262 Anywhere,
264 }
265}
266enum_property! {
267 pub enum Hyphens {
269 None,
271 Manual,
273 Auto,
275 }
276}
277
278enum_property! {
279 pub enum OverflowWrap {
281 Normal,
283 Anywhere,
285 BreakWord,
288 }
289}
290
291enum_property! {
292 pub enum TextAlign {
294 Start,
296 End,
298 Left,
300 Right,
302 Center,
304 Justify,
306 MatchParent,
308 JustifyAll,
310 }
311}
312
313enum_property! {
314 pub enum TextAlignLast {
316 Auto,
318 Start,
320 End,
322 Left,
324 Right,
326 Center,
328 Justify,
330 MatchParent,
332 }
333}
334
335enum_property! {
336 pub enum TextJustify {
338 Auto,
340 None,
342 InterWord,
344 InterCharacter,
346 }
347}
348
349#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
352#[cfg_attr(feature = "visitor", derive(Visit))]
353#[cfg_attr(
354 feature = "serde",
355 derive(serde::Serialize, serde::Deserialize),
356 serde(tag = "type", content = "value", rename_all = "kebab-case")
357)]
358#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
359#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
360pub enum Spacing {
361 Normal,
363 Length(Length),
365}
366
367#[derive(Debug, Clone, PartialEq)]
369#[cfg_attr(feature = "visitor", derive(Visit))]
370#[cfg_attr(
371 feature = "serde",
372 derive(serde::Serialize, serde::Deserialize),
373 serde(rename_all = "camelCase")
374)]
375#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
376#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
377pub struct TextIndent {
378 pub value: LengthPercentage,
380 pub hanging: bool,
382 pub each_line: bool,
384}
385
386impl<'i> Parse<'i> for TextIndent {
387 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
388 let mut value = None;
389 let mut hanging = false;
390 let mut each_line = false;
391
392 loop {
393 if value.is_none() {
394 if let Ok(val) = input.try_parse(LengthPercentage::parse) {
395 value = Some(val);
396 continue;
397 }
398 }
399
400 if !hanging {
401 if input.try_parse(|input| input.expect_ident_matching("hanging")).is_ok() {
402 hanging = true;
403 continue;
404 }
405 }
406
407 if !each_line {
408 if input.try_parse(|input| input.expect_ident_matching("each-line")).is_ok() {
409 each_line = true;
410 continue;
411 }
412 }
413
414 break;
415 }
416
417 if let Some(value) = value {
418 Ok(TextIndent {
419 value,
420 hanging,
421 each_line,
422 })
423 } else {
424 Err(input.new_custom_error(ParserError::InvalidDeclaration))
425 }
426 }
427}
428
429impl ToCss for TextIndent {
430 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
431 where
432 W: std::fmt::Write,
433 {
434 self.value.to_css(dest)?;
435 if self.hanging {
436 dest.write_str(" hanging")?;
437 }
438 if self.each_line {
439 dest.write_str(" each-line")?;
440 }
441 Ok(())
442 }
443}
444
445#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
447#[cfg_attr(feature = "visitor", derive(Visit))]
448#[cfg_attr(
449 feature = "serde",
450 derive(serde::Serialize, serde::Deserialize),
451 serde(tag = "type", content = "value", rename_all = "kebab-case")
452)]
453#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
454#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
455pub enum TextSizeAdjust {
456 Auto,
458 None,
460 Percentage(Percentage),
462}
463
464bitflags! {
465 #[cfg_attr(feature = "visitor", derive(Visit))]
469 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(from = "SerializedTextDecorationLine", into = "SerializedTextDecorationLine"))]
470 #[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
471 #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
472 pub struct TextDecorationLine: u8 {
473 const Underline = 0b00000001;
475 const Overline = 0b00000010;
477 const LineThrough = 0b00000100;
479 const Blink = 0b00001000;
481 const SpellingError = 0b00010000;
483 const GrammarError = 0b00100000;
485 }
486}
487
488impl Default for TextDecorationLine {
489 fn default() -> TextDecorationLine {
490 TextDecorationLine::empty()
491 }
492}
493
494impl<'i> Parse<'i> for TextDecorationLine {
495 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
496 let mut value = TextDecorationLine::empty();
497 let mut any = false;
498
499 loop {
500 let flag: Result<_, ParseError<'i, ParserError<'i>>> = input.try_parse(|input| {
501 let location = input.current_source_location();
502 let ident = input.expect_ident()?;
503 Ok(match_ignore_ascii_case! { &ident,
504 "none" if value.is_empty() => TextDecorationLine::empty(),
505 "underline" => TextDecorationLine::Underline,
506 "overline" => TextDecorationLine::Overline,
507 "line-through" => TextDecorationLine::LineThrough,
508 "blink" =>TextDecorationLine::Blink,
509 "spelling-error" if value.is_empty() => TextDecorationLine::SpellingError,
510 "grammar-error" if value.is_empty() => TextDecorationLine::GrammarError,
511 _ => return Err(location.new_unexpected_token_error(
512 cssparser::Token::Ident(ident.clone())
513 ))
514 })
515 });
516
517 if let Ok(flag) = flag {
518 value |= flag;
519 any = true;
520 } else {
521 break;
522 }
523 }
524
525 if !any {
526 return Err(input.new_custom_error(ParserError::InvalidDeclaration));
527 }
528
529 Ok(value)
530 }
531}
532
533impl ToCss for TextDecorationLine {
534 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
535 where
536 W: std::fmt::Write,
537 {
538 if self.is_empty() {
539 return dest.write_str("none");
540 }
541
542 if self.contains(TextDecorationLine::SpellingError) {
543 return dest.write_str("spelling-error");
544 }
545
546 if self.contains(TextDecorationLine::GrammarError) {
547 return dest.write_str("grammar-error");
548 }
549
550 let mut needs_space = false;
551 macro_rules! val {
552 ($val: ident, $str: expr) => {
553 #[allow(unused_assignments)]
554 if self.contains(TextDecorationLine::$val) {
555 if needs_space {
556 dest.write_char(' ')?;
557 }
558 dest.write_str($str)?;
559 needs_space = true;
560 }
561 };
562 }
563
564 val!(Underline, "underline");
565 val!(Overline, "overline");
566 val!(LineThrough, "line-through");
567 val!(Blink, "blink");
568 Ok(())
569 }
570}
571
572#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(untagged))]
573#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
574enum SerializedTextDecorationLine {
575 Exclusive(ExclusiveTextDecorationLine),
576 Other(Vec<OtherTextDecorationLine>),
577}
578
579#[cfg_attr(
580 feature = "serde",
581 derive(serde::Serialize, serde::Deserialize),
582 serde(rename_all = "kebab-case")
583)]
584#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
585enum ExclusiveTextDecorationLine {
586 None,
587 SpellingError,
588 GrammarError,
589}
590
591#[cfg_attr(
592 feature = "serde",
593 derive(serde::Serialize, serde::Deserialize),
594 serde(rename_all = "kebab-case")
595)]
596#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
597enum OtherTextDecorationLine {
598 Underline,
599 Overline,
600 LineThrough,
601 Blink,
602}
603
604impl From<TextDecorationLine> for SerializedTextDecorationLine {
605 fn from(l: TextDecorationLine) -> Self {
606 if l.is_empty() {
607 return Self::Exclusive(ExclusiveTextDecorationLine::None);
608 }
609
610 macro_rules! exclusive {
611 ($t: ident) => {
612 if l.contains(TextDecorationLine::$t) {
613 return Self::Exclusive(ExclusiveTextDecorationLine::$t);
614 }
615 };
616 }
617
618 exclusive!(SpellingError);
619 exclusive!(GrammarError);
620
621 let mut v = Vec::new();
622 macro_rules! other {
623 ($t: ident) => {
624 if l.contains(TextDecorationLine::$t) {
625 v.push(OtherTextDecorationLine::$t)
626 }
627 };
628 }
629
630 other!(Underline);
631 other!(Overline);
632 other!(LineThrough);
633 other!(Blink);
634 Self::Other(v)
635 }
636}
637
638impl From<SerializedTextDecorationLine> for TextDecorationLine {
639 fn from(l: SerializedTextDecorationLine) -> Self {
640 match l {
641 SerializedTextDecorationLine::Exclusive(v) => match v {
642 ExclusiveTextDecorationLine::None => TextDecorationLine::empty(),
643 ExclusiveTextDecorationLine::SpellingError => TextDecorationLine::SpellingError,
644 ExclusiveTextDecorationLine::GrammarError => TextDecorationLine::GrammarError,
645 },
646 SerializedTextDecorationLine::Other(v) => {
647 let mut res = TextDecorationLine::empty();
648 for val in v {
649 res |= match val {
650 OtherTextDecorationLine::Underline => TextDecorationLine::Underline,
651 OtherTextDecorationLine::Overline => TextDecorationLine::Overline,
652 OtherTextDecorationLine::LineThrough => TextDecorationLine::LineThrough,
653 OtherTextDecorationLine::Blink => TextDecorationLine::Blink,
654 }
655 }
656 res
657 }
658 }
659 }
660}
661
662#[cfg(feature = "jsonschema")]
663#[cfg_attr(docsrs, doc(cfg(feature = "jsonschema")))]
664impl<'a> schemars::JsonSchema for TextDecorationLine {
665 fn is_referenceable() -> bool {
666 true
667 }
668
669 fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
670 SerializedTextDecorationLine::json_schema(gen)
671 }
672
673 fn schema_name() -> String {
674 "TextDecorationLine".into()
675 }
676}
677
678enum_property! {
679 pub enum TextDecorationStyle {
681 Solid,
683 Double,
685 Dotted,
687 Dashed,
689 Wavy,
691 }
692}
693
694impl Default for TextDecorationStyle {
695 fn default() -> TextDecorationStyle {
696 TextDecorationStyle::Solid
697 }
698}
699
700#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
702#[cfg_attr(feature = "visitor", derive(Visit))]
703#[cfg_attr(
704 feature = "serde",
705 derive(serde::Serialize, serde::Deserialize),
706 serde(tag = "type", content = "value", rename_all = "kebab-case")
707)]
708#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
709#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
710pub enum TextDecorationThickness {
711 Auto,
713 FromFont,
715 LengthPercentage(LengthPercentage),
717}
718
719impl Default for TextDecorationThickness {
720 fn default() -> TextDecorationThickness {
721 TextDecorationThickness::Auto
722 }
723}
724
725define_shorthand! {
726 pub struct TextDecoration(VendorPrefix) {
728 line: TextDecorationLine(TextDecorationLine, VendorPrefix),
730 thickness: TextDecorationThickness(TextDecorationThickness),
732 style: TextDecorationStyle(TextDecorationStyle, VendorPrefix),
734 color: TextDecorationColor(CssColor, VendorPrefix),
736 }
737}
738
739impl<'i> Parse<'i> for TextDecoration {
740 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
741 let mut line = None;
742 let mut thickness = None;
743 let mut style = None;
744 let mut color = None;
745
746 loop {
747 macro_rules! prop {
748 ($key: ident, $type: ident) => {
749 if $key.is_none() {
750 if let Ok(val) = input.try_parse($type::parse) {
751 $key = Some(val);
752 continue;
753 }
754 }
755 };
756 }
757
758 prop!(line, TextDecorationLine);
759 prop!(thickness, TextDecorationThickness);
760 prop!(style, TextDecorationStyle);
761 prop!(color, CssColor);
762 break;
763 }
764
765 Ok(TextDecoration {
766 line: line.unwrap_or_default(),
767 thickness: thickness.unwrap_or_default(),
768 style: style.unwrap_or_default(),
769 color: color.unwrap_or(CssColor::current_color()),
770 })
771 }
772}
773
774impl ToCss for TextDecoration {
775 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
776 where
777 W: std::fmt::Write,
778 {
779 self.line.to_css(dest)?;
780 if self.line.is_empty() {
781 return Ok(());
782 }
783
784 let mut needs_space = true;
785 if self.thickness != TextDecorationThickness::default() {
786 dest.write_char(' ')?;
787 self.thickness.to_css(dest)?;
788 needs_space = true;
789 }
790
791 if self.style != TextDecorationStyle::default() {
792 if needs_space {
793 dest.write_char(' ')?;
794 }
795 self.style.to_css(dest)?;
796 needs_space = true;
797 }
798
799 if self.color != CssColor::current_color() {
800 if needs_space {
801 dest.write_char(' ')?;
802 }
803 self.color.to_css(dest)?;
804 }
805
806 Ok(())
807 }
808}
809
810impl FallbackValues for TextDecoration {
811 fn get_fallbacks(&mut self, targets: Targets) -> Vec<Self> {
812 self
813 .color
814 .get_fallbacks(targets)
815 .into_iter()
816 .map(|color| TextDecoration { color, ..self.clone() })
817 .collect()
818 }
819}
820
821enum_property! {
822 pub enum TextDecorationSkipInk {
824 Auto,
826 None,
828 All,
830 }
831}
832
833enum_property! {
834 pub enum TextEmphasisFillMode {
838 Filled,
840 Open,
842 }
843}
844
845enum_property! {
846 pub enum TextEmphasisShape {
850 Dot,
852 Circle,
854 DoubleCircle,
856 Triangle,
858 Sesame,
860 }
861}
862
863#[derive(Debug, Clone, PartialEq)]
865#[cfg_attr(feature = "visitor", derive(Visit))]
866#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
867#[cfg_attr(
868 feature = "serde",
869 derive(serde::Serialize, serde::Deserialize),
870 serde(tag = "type", rename_all = "kebab-case")
871)]
872#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
873pub enum TextEmphasisStyle<'i> {
874 None,
876 Keyword {
878 fill: TextEmphasisFillMode,
880 shape: Option<TextEmphasisShape>,
882 },
883 #[cfg_attr(
885 feature = "serde",
886 serde(borrow, with = "crate::serialization::ValueWrapper::<CSSString>")
887 )]
888 String(CSSString<'i>),
889}
890
891impl<'i> Default for TextEmphasisStyle<'i> {
892 fn default() -> TextEmphasisStyle<'i> {
893 TextEmphasisStyle::None
894 }
895}
896
897impl<'i> Parse<'i> for TextEmphasisStyle<'i> {
898 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
899 if input.try_parse(|input| input.expect_ident_matching("none")).is_ok() {
900 return Ok(TextEmphasisStyle::None);
901 }
902
903 if let Ok(s) = input.try_parse(CSSString::parse) {
904 return Ok(TextEmphasisStyle::String(s));
905 }
906
907 let mut shape = input.try_parse(TextEmphasisShape::parse).ok();
908 let fill = input.try_parse(TextEmphasisFillMode::parse).ok();
909 if shape.is_none() {
910 shape = input.try_parse(TextEmphasisShape::parse).ok();
911 }
912
913 if shape.is_none() && fill.is_none() {
914 return Err(input.new_custom_error(ParserError::InvalidDeclaration));
915 }
916
917 let fill = fill.unwrap_or(TextEmphasisFillMode::Filled);
918 Ok(TextEmphasisStyle::Keyword { fill, shape })
919 }
920}
921
922impl<'i> ToCss for TextEmphasisStyle<'i> {
923 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
924 where
925 W: std::fmt::Write,
926 {
927 match self {
928 TextEmphasisStyle::None => dest.write_str("none"),
929 TextEmphasisStyle::String(s) => s.to_css(dest),
930 TextEmphasisStyle::Keyword { fill, shape } => {
931 let mut needs_space = false;
932 if *fill != TextEmphasisFillMode::Filled || shape.is_none() {
933 fill.to_css(dest)?;
934 needs_space = true;
935 }
936
937 if let Some(shape) = shape {
938 if needs_space {
939 dest.write_char(' ')?;
940 }
941 shape.to_css(dest)?;
942 }
943 Ok(())
944 }
945 }
946 }
947}
948
949define_shorthand! {
950 pub struct TextEmphasis<'i>(VendorPrefix) {
952 #[cfg_attr(feature = "serde", serde(borrow))]
954 style: TextEmphasisStyle(TextEmphasisStyle<'i>, VendorPrefix),
955 color: TextEmphasisColor(CssColor, VendorPrefix),
957 }
958}
959
960impl<'i> Parse<'i> for TextEmphasis<'i> {
961 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
962 let mut style = None;
963 let mut color = None;
964
965 loop {
966 if style.is_none() {
967 if let Ok(s) = input.try_parse(TextEmphasisStyle::parse) {
968 style = Some(s);
969 continue;
970 }
971 }
972
973 if color.is_none() {
974 if let Ok(c) = input.try_parse(CssColor::parse) {
975 color = Some(c);
976 continue;
977 }
978 }
979
980 break;
981 }
982
983 Ok(TextEmphasis {
984 style: style.unwrap_or_default(),
985 color: color.unwrap_or(CssColor::current_color()),
986 })
987 }
988}
989
990impl<'i> ToCss for TextEmphasis<'i> {
991 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
992 where
993 W: std::fmt::Write,
994 {
995 self.style.to_css(dest)?;
996
997 if self.style != TextEmphasisStyle::None && self.color != CssColor::current_color() {
998 dest.write_char(' ')?;
999 self.color.to_css(dest)?;
1000 }
1001
1002 Ok(())
1003 }
1004}
1005
1006impl<'i> FallbackValues for TextEmphasis<'i> {
1007 fn get_fallbacks(&mut self, targets: Targets) -> Vec<Self> {
1008 self
1009 .color
1010 .get_fallbacks(targets)
1011 .into_iter()
1012 .map(|color| TextEmphasis { color, ..self.clone() })
1013 .collect()
1014 }
1015}
1016
1017enum_property! {
1018 pub enum TextEmphasisPositionVertical {
1022 Over,
1024 Under,
1026 }
1027}
1028
1029enum_property! {
1030 pub enum TextEmphasisPositionHorizontal {
1034 Left,
1036 Right,
1038 }
1039}
1040
1041#[derive(Debug, Clone, PartialEq)]
1043#[cfg_attr(feature = "visitor", derive(Visit))]
1044#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1045#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1046#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1047pub struct TextEmphasisPosition {
1048 pub vertical: TextEmphasisPositionVertical,
1050 pub horizontal: TextEmphasisPositionHorizontal,
1052}
1053
1054impl<'i> Parse<'i> for TextEmphasisPosition {
1055 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1056 if let Ok(horizontal) = input.try_parse(TextEmphasisPositionHorizontal::parse) {
1057 let vertical = TextEmphasisPositionVertical::parse(input)?;
1058 Ok(TextEmphasisPosition { horizontal, vertical })
1059 } else {
1060 let vertical = TextEmphasisPositionVertical::parse(input)?;
1061 let horizontal = input
1062 .try_parse(TextEmphasisPositionHorizontal::parse)
1063 .unwrap_or(TextEmphasisPositionHorizontal::Right);
1064 Ok(TextEmphasisPosition { horizontal, vertical })
1065 }
1066 }
1067}
1068
1069enum_property! {
1070 pub enum BoxDecorationBreak {
1072 Slice,
1074 Clone,
1076 }
1077}
1078
1079impl Default for BoxDecorationBreak {
1080 fn default() -> Self {
1081 BoxDecorationBreak::Slice
1082 }
1083}
1084
1085impl ToCss for TextEmphasisPosition {
1086 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1087 where
1088 W: std::fmt::Write,
1089 {
1090 self.vertical.to_css(dest)?;
1091 if self.horizontal != TextEmphasisPositionHorizontal::Right {
1092 dest.write_char(' ')?;
1093 self.horizontal.to_css(dest)?;
1094 }
1095 Ok(())
1096 }
1097}
1098
1099#[derive(Default)]
1100pub(crate) struct TextDecorationHandler<'i> {
1101 line: Option<(TextDecorationLine, VendorPrefix)>,
1102 thickness: Option<TextDecorationThickness>,
1103 style: Option<(TextDecorationStyle, VendorPrefix)>,
1104 color: Option<(CssColor, VendorPrefix)>,
1105 emphasis_style: Option<(TextEmphasisStyle<'i>, VendorPrefix)>,
1106 emphasis_color: Option<(CssColor, VendorPrefix)>,
1107 emphasis_position: Option<(TextEmphasisPosition, VendorPrefix)>,
1108 has_any: bool,
1109}
1110
1111impl<'i> PropertyHandler<'i> for TextDecorationHandler<'i> {
1112 fn handle_property(
1113 &mut self,
1114 property: &Property<'i>,
1115 dest: &mut DeclarationList<'i>,
1116 context: &mut PropertyHandlerContext<'i, '_>,
1117 ) -> bool {
1118 use Property::*;
1119
1120 macro_rules! maybe_flush {
1121 ($prop: ident, $val: expr, $vp: expr) => {{
1122 if let Some((val, prefixes)) = &self.$prop {
1125 if val != $val && !prefixes.contains(*$vp) {
1126 self.finalize(dest, context);
1127 }
1128 }
1129 }};
1130 }
1131
1132 macro_rules! property {
1133 ($prop: ident, $val: expr, $vp: expr) => {{
1134 maybe_flush!($prop, $val, $vp);
1135
1136 if let Some((val, prefixes)) = &mut self.$prop {
1138 *val = $val.clone();
1139 *prefixes |= *$vp;
1140 } else {
1141 self.$prop = Some(($val.clone(), *$vp));
1142 self.has_any = true;
1143 }
1144 }};
1145 }
1146
1147 match property {
1148 TextDecorationLine(val, vp) => property!(line, val, vp),
1149 TextDecorationThickness(val) => {
1150 self.thickness = Some(val.clone());
1151 self.has_any = true;
1152 }
1153 TextDecorationStyle(val, vp) => property!(style, val, vp),
1154 TextDecorationColor(val, vp) => property!(color, val, vp),
1155 TextDecoration(val, vp) => {
1156 maybe_flush!(line, &val.line, vp);
1157 maybe_flush!(style, &val.style, vp);
1158 maybe_flush!(color, &val.color, vp);
1159 property!(line, &val.line, vp);
1160 self.thickness = Some(val.thickness.clone());
1161 property!(style, &val.style, vp);
1162 property!(color, &val.color, vp);
1163 }
1164 TextEmphasisStyle(val, vp) => property!(emphasis_style, val, vp),
1165 TextEmphasisColor(val, vp) => property!(emphasis_color, val, vp),
1166 TextEmphasis(val, vp) => {
1167 maybe_flush!(emphasis_style, &val.style, vp);
1168 maybe_flush!(emphasis_color, &val.color, vp);
1169 property!(emphasis_style, &val.style, vp);
1170 property!(emphasis_color, &val.color, vp);
1171 }
1172 TextEmphasisPosition(val, vp) => property!(emphasis_position, val, vp),
1173 TextAlign(align) => {
1174 use super::text::*;
1175 macro_rules! logical {
1176 ($ltr: ident, $rtl: ident) => {{
1177 let logical_supported = !context.should_compile_logical(compat::Feature::LogicalTextAlign);
1178 if logical_supported {
1179 dest.push(property.clone());
1180 } else {
1181 context.add_logical_rule(
1182 Property::TextAlign(TextAlign::$ltr),
1183 Property::TextAlign(TextAlign::$rtl),
1184 );
1185 }
1186 }};
1187 }
1188
1189 match align {
1190 TextAlign::Start => logical!(Left, Right),
1191 TextAlign::End => logical!(Right, Left),
1192 _ => dest.push(property.clone()),
1193 }
1194 }
1195 Unparsed(val) if is_text_decoration_property(&val.property_id) => {
1196 self.finalize(dest, context);
1197 let mut unparsed = val.get_prefixed(context.targets, Feature::TextDecoration);
1198 context.add_unparsed_fallbacks(&mut unparsed);
1199 dest.push(Property::Unparsed(unparsed))
1200 }
1201 Unparsed(val) if is_text_emphasis_property(&val.property_id) => {
1202 self.finalize(dest, context);
1203 let mut unparsed = val.get_prefixed(context.targets, Feature::TextEmphasis);
1204 context.add_unparsed_fallbacks(&mut unparsed);
1205 dest.push(Property::Unparsed(unparsed))
1206 }
1207 _ => return false,
1208 }
1209
1210 true
1211 }
1212
1213 fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
1214 if !self.has_any {
1215 return;
1216 }
1217
1218 self.has_any = false;
1219
1220 let mut line = std::mem::take(&mut self.line);
1221 let mut thickness = std::mem::take(&mut self.thickness);
1222 let mut style = std::mem::take(&mut self.style);
1223 let mut color = std::mem::take(&mut self.color);
1224 let mut emphasis_style = std::mem::take(&mut self.emphasis_style);
1225 let mut emphasis_color = std::mem::take(&mut self.emphasis_color);
1226 let emphasis_position = std::mem::take(&mut self.emphasis_position);
1227
1228 if let (Some((line, line_vp)), Some(thickness_val), Some((style, style_vp)), Some((color, color_vp))) =
1229 (&mut line, &mut thickness, &mut style, &mut color)
1230 {
1231 let intersection = *line_vp | *style_vp | *color_vp;
1232 if !intersection.is_empty() {
1233 let mut prefix = intersection;
1234
1235 let supports_thickness = context.targets.is_compatible(compat::Feature::TextDecorationThicknessShorthand);
1237 let mut decoration = TextDecoration {
1238 line: line.clone(),
1239 thickness: if supports_thickness {
1240 thickness_val.clone()
1241 } else {
1242 TextDecorationThickness::default()
1243 },
1244 style: style.clone(),
1245 color: color.clone(),
1246 };
1247
1248 if prefix.contains(VendorPrefix::None)
1250 && (*style != TextDecorationStyle::default() || *color != CssColor::current_color())
1251 {
1252 prefix = context.targets.prefixes(VendorPrefix::None, Feature::TextDecoration);
1253
1254 let fallbacks = decoration.get_fallbacks(context.targets);
1255 for fallback in fallbacks {
1256 dest.push(Property::TextDecoration(fallback, prefix))
1257 }
1258 }
1259
1260 dest.push(Property::TextDecoration(decoration, prefix));
1261 line_vp.remove(intersection);
1262 style_vp.remove(intersection);
1263 color_vp.remove(intersection);
1264 if supports_thickness || *thickness_val == TextDecorationThickness::default() {
1265 thickness = None;
1266 }
1267 }
1268 }
1269
1270 macro_rules! color {
1271 ($key: ident, $prop: ident) => {
1272 if let Some((mut val, vp)) = $key {
1273 if !vp.is_empty() {
1274 let prefix = context.targets.prefixes(vp, Feature::$prop);
1275 if prefix.contains(VendorPrefix::None) {
1276 let fallbacks = val.get_fallbacks(context.targets);
1277 for fallback in fallbacks {
1278 dest.push(Property::$prop(fallback, prefix))
1279 }
1280 }
1281 dest.push(Property::$prop(val, prefix))
1282 }
1283 }
1284 };
1285 }
1286
1287 macro_rules! single_property {
1288 ($key: ident, $prop: ident) => {
1289 if let Some((val, vp)) = $key {
1290 if !vp.is_empty() {
1291 let prefix = context.targets.prefixes(vp, Feature::$prop);
1292 dest.push(Property::$prop(val, prefix))
1293 }
1294 }
1295 };
1296 }
1297
1298 single_property!(line, TextDecorationLine);
1299 single_property!(style, TextDecorationStyle);
1300 color!(color, TextDecorationColor);
1301
1302 if let Some(thickness) = thickness {
1303 match thickness {
1306 TextDecorationThickness::LengthPercentage(LengthPercentage::Percentage(p))
1307 if should_compile!(context.targets, TextDecorationThicknessPercent) =>
1308 {
1309 let calc = Calc::Function(Box::new(MathFunction::Calc(Calc::Product(
1310 p.0,
1311 Box::new(Calc::Value(Box::new(LengthPercentage::Dimension(LengthValue::Em(1.0))))),
1312 ))));
1313 let thickness = TextDecorationThickness::LengthPercentage(LengthPercentage::Calc(Box::new(calc)));
1314 dest.push(Property::TextDecorationThickness(thickness));
1315 }
1316 thickness => dest.push(Property::TextDecorationThickness(thickness)),
1317 }
1318 }
1319
1320 if let (Some((style, style_vp)), Some((color, color_vp))) = (&mut emphasis_style, &mut emphasis_color) {
1321 let intersection = *style_vp | *color_vp;
1322 if !intersection.is_empty() {
1323 let prefix = context.targets.prefixes(intersection, Feature::TextEmphasis);
1324 let mut emphasis = TextEmphasis {
1325 style: style.clone(),
1326 color: color.clone(),
1327 };
1328
1329 if prefix.contains(VendorPrefix::None) {
1330 let fallbacks = emphasis.get_fallbacks(context.targets);
1331 for fallback in fallbacks {
1332 dest.push(Property::TextEmphasis(fallback, prefix))
1333 }
1334 }
1335
1336 dest.push(Property::TextEmphasis(emphasis, prefix));
1337 style_vp.remove(intersection);
1338 color_vp.remove(intersection);
1339 }
1340 }
1341
1342 single_property!(emphasis_style, TextEmphasisStyle);
1343 color!(emphasis_color, TextEmphasisColor);
1344
1345 if let Some((pos, vp)) = emphasis_position {
1346 if !vp.is_empty() {
1347 let mut prefix = context.targets.prefixes(vp, Feature::TextEmphasisPosition);
1348 if pos.horizontal != TextEmphasisPositionHorizontal::Right {
1350 prefix = VendorPrefix::None;
1351 }
1352 dest.push(Property::TextEmphasisPosition(pos, prefix))
1353 }
1354 }
1355 }
1356}
1357
1358#[derive(Debug, Clone, PartialEq)]
1360#[cfg_attr(feature = "visitor", derive(Visit))]
1361#[cfg_attr(
1362 feature = "serde",
1363 derive(serde::Serialize, serde::Deserialize),
1364 serde(rename_all = "camelCase")
1365)]
1366#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
1367#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
1368pub struct TextShadow {
1369 pub color: CssColor,
1371 pub x_offset: Length,
1373 pub y_offset: Length,
1375 pub blur: Length,
1377 pub spread: Length, }
1380
1381impl<'i> Parse<'i> for TextShadow {
1382 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
1383 let mut color = None;
1384 let mut lengths = None;
1385
1386 loop {
1387 if lengths.is_none() {
1388 let value = input.try_parse::<_, _, ParseError<ParserError<'i>>>(|input| {
1389 let horizontal = Length::parse(input)?;
1390 let vertical = Length::parse(input)?;
1391 let blur = input.try_parse(Length::parse).unwrap_or(Length::zero());
1392 let spread = input.try_parse(Length::parse).unwrap_or(Length::zero());
1393 Ok((horizontal, vertical, blur, spread))
1394 });
1395
1396 if let Ok(value) = value {
1397 lengths = Some(value);
1398 continue;
1399 }
1400 }
1401
1402 if color.is_none() {
1403 if let Ok(value) = input.try_parse(CssColor::parse) {
1404 color = Some(value);
1405 continue;
1406 }
1407 }
1408
1409 break;
1410 }
1411
1412 let lengths = lengths.ok_or(input.new_error(BasicParseErrorKind::QualifiedRuleInvalid))?;
1413 Ok(TextShadow {
1414 color: color.unwrap_or(CssColor::current_color()),
1415 x_offset: lengths.0,
1416 y_offset: lengths.1,
1417 blur: lengths.2,
1418 spread: lengths.3,
1419 })
1420 }
1421}
1422
1423impl ToCss for TextShadow {
1424 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
1425 where
1426 W: std::fmt::Write,
1427 {
1428 self.x_offset.to_css(dest)?;
1429 dest.write_char(' ')?;
1430 self.y_offset.to_css(dest)?;
1431
1432 if self.blur != Length::zero() || self.spread != Length::zero() {
1433 dest.write_char(' ')?;
1434 self.blur.to_css(dest)?;
1435
1436 if self.spread != Length::zero() {
1437 dest.write_char(' ')?;
1438 self.spread.to_css(dest)?;
1439 }
1440 }
1441
1442 if self.color != CssColor::current_color() {
1443 dest.write_char(' ')?;
1444 self.color.to_css(dest)?;
1445 }
1446
1447 Ok(())
1448 }
1449}
1450
1451impl IsCompatible for TextShadow {
1452 fn is_compatible(&self, browsers: Browsers) -> bool {
1453 self.color.is_compatible(browsers)
1454 && self.x_offset.is_compatible(browsers)
1455 && self.y_offset.is_compatible(browsers)
1456 && self.blur.is_compatible(browsers)
1457 && self.spread.is_compatible(browsers)
1458 }
1459}
1460
1461#[inline]
1462fn is_text_decoration_property(property_id: &PropertyId) -> bool {
1463 match property_id {
1464 PropertyId::TextDecorationLine(_)
1465 | PropertyId::TextDecorationThickness
1466 | PropertyId::TextDecorationStyle(_)
1467 | PropertyId::TextDecorationColor(_)
1468 | PropertyId::TextDecoration(_) => true,
1469 _ => false,
1470 }
1471}
1472
1473#[inline]
1474fn is_text_emphasis_property(property_id: &PropertyId) -> bool {
1475 match property_id {
1476 PropertyId::TextEmphasisStyle(_)
1477 | PropertyId::TextEmphasisColor(_)
1478 | PropertyId::TextEmphasis(_)
1479 | PropertyId::TextEmphasisPosition(_) => true,
1480 _ => false,
1481 }
1482}
1483
1484impl FallbackValues for SmallVec<[TextShadow; 1]> {
1485 fn get_fallbacks(&mut self, targets: Targets) -> Vec<Self> {
1486 let mut fallbacks = ColorFallbackKind::empty();
1487 for shadow in self.iter() {
1488 fallbacks |= shadow.color.get_necessary_fallbacks(targets);
1489 }
1490
1491 let mut res = Vec::new();
1492 if fallbacks.contains(ColorFallbackKind::RGB) {
1493 let rgb = self
1494 .iter()
1495 .map(|shadow| TextShadow {
1496 color: shadow.color.to_rgb().unwrap(),
1497 ..shadow.clone()
1498 })
1499 .collect();
1500 res.push(rgb);
1501 }
1502
1503 if fallbacks.contains(ColorFallbackKind::P3) {
1504 let p3 = self
1505 .iter()
1506 .map(|shadow| TextShadow {
1507 color: shadow.color.to_p3().unwrap(),
1508 ..shadow.clone()
1509 })
1510 .collect();
1511 res.push(p3);
1512 }
1513
1514 if fallbacks.contains(ColorFallbackKind::LAB) {
1515 for shadow in self.iter_mut() {
1516 shadow.color = shadow.color.to_lab().unwrap();
1517 }
1518 }
1519
1520 res
1521 }
1522}
1523
1524enum_property! {
1525 pub enum Direction {
1527 Ltr,
1529 Rtl,
1531 }
1532}
1533
1534enum_property! {
1535 pub enum UnicodeBidi {
1537 Normal,
1539 Embed,
1541 Isolate,
1543 BidiOverride,
1545 IsolateOverride,
1547 Plaintext,
1549 }
1550}