Skip to main content

style/values/specified/
background.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Specified types for CSS values related to backgrounds.
6
7use crate::derives::*;
8use crate::parser::{Parse, ParserContext};
9use crate::typed_om::{KeywordValue, ToTyped, TypedValue};
10use crate::values::generics::background::BackgroundSize as GenericBackgroundSize;
11use crate::values::specified::length::{
12    NonNegativeLengthPercentage, NonNegativeLengthPercentageOrAuto,
13};
14use cssparser::{match_ignore_ascii_case, Parser};
15use selectors::parser::SelectorParseErrorKind;
16use std::fmt::{self, Write};
17use style_traits::{CssString, CssWriter, ParseError, StyleParseErrorKind, ToCss};
18use thin_vec::ThinVec;
19
20/// A specified value for the `background-size` property.
21pub type BackgroundSize = GenericBackgroundSize<NonNegativeLengthPercentage>;
22
23impl Parse for BackgroundSize {
24    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
25        if let Ok(width) = input.try_parse(|i| NonNegativeLengthPercentageOrAuto::parse(context, i))
26        {
27            let height = input
28                .try_parse(|i| NonNegativeLengthPercentageOrAuto::parse(context, i))
29                .unwrap_or(NonNegativeLengthPercentageOrAuto::auto());
30            return Ok(GenericBackgroundSize::ExplicitSize { width, height });
31        }
32        Ok(try_match_ident_ignore_ascii_case! { input,
33            "cover" => GenericBackgroundSize::Cover,
34            "contain" => GenericBackgroundSize::Contain,
35        })
36    }
37}
38
39/// One of the keywords for `background-repeat`.
40#[derive(
41    Clone,
42    Copy,
43    Debug,
44    Eq,
45    MallocSizeOf,
46    Parse,
47    PartialEq,
48    SpecifiedValueInfo,
49    ToComputedValue,
50    ToCss,
51    ToResolvedValue,
52    ToShmem,
53    ToTyped,
54)]
55#[allow(missing_docs)]
56#[value_info(other_values = "repeat-x,repeat-y")]
57pub enum BackgroundRepeatKeyword {
58    Repeat,
59    Space,
60    Round,
61    NoRepeat,
62}
63
64/// The value of the `background-repeat` property, with `repeat-x` / `repeat-y`
65/// represented as the combination of `no-repeat` and `repeat` in the opposite
66/// axes.
67///
68/// https://drafts.csswg.org/css-backgrounds/#the-background-repeat
69#[derive(
70    Clone,
71    Debug,
72    MallocSizeOf,
73    PartialEq,
74    SpecifiedValueInfo,
75    ToComputedValue,
76    ToResolvedValue,
77    ToShmem,
78)]
79pub struct BackgroundRepeat(pub BackgroundRepeatKeyword, pub BackgroundRepeatKeyword);
80
81impl BackgroundRepeat {
82    /// Returns the `repeat repeat` value.
83    pub fn repeat() -> Self {
84        BackgroundRepeat(
85            BackgroundRepeatKeyword::Repeat,
86            BackgroundRepeatKeyword::Repeat,
87        )
88    }
89}
90
91impl ToCss for BackgroundRepeat {
92    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
93    where
94        W: Write,
95    {
96        match (self.0, self.1) {
97            (BackgroundRepeatKeyword::Repeat, BackgroundRepeatKeyword::NoRepeat) => {
98                dest.write_str("repeat-x")
99            },
100            (BackgroundRepeatKeyword::NoRepeat, BackgroundRepeatKeyword::Repeat) => {
101                dest.write_str("repeat-y")
102            },
103            (horizontal, vertical) => {
104                horizontal.to_css(dest)?;
105                if horizontal != vertical {
106                    dest.write_char(' ')?;
107                    vertical.to_css(dest)?;
108                }
109                Ok(())
110            },
111        }
112    }
113}
114
115impl ToTyped for BackgroundRepeat {
116    fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
117        match (self.0, self.1) {
118            (BackgroundRepeatKeyword::Repeat, BackgroundRepeatKeyword::NoRepeat) => {
119                dest.push(TypedValue::Keyword(KeywordValue(CssString::from(
120                    "repeat-x",
121                ))));
122                Ok(())
123            },
124            (BackgroundRepeatKeyword::NoRepeat, BackgroundRepeatKeyword::Repeat) => {
125                dest.push(TypedValue::Keyword(KeywordValue(CssString::from(
126                    "repeat-y",
127                ))));
128                Ok(())
129            },
130            (horizontal, vertical) if horizontal == vertical => {
131                ToTyped::to_typed(&horizontal, dest)
132            },
133            _ => Err(()),
134        }
135    }
136}
137
138impl Parse for BackgroundRepeat {
139    fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
140        let ident = input.expect_ident_cloned()?;
141
142        match_ignore_ascii_case! { &ident,
143            "repeat-x" => {
144                return Ok(BackgroundRepeat(BackgroundRepeatKeyword::Repeat, BackgroundRepeatKeyword::NoRepeat));
145            },
146            "repeat-y" => {
147                return Ok(BackgroundRepeat(BackgroundRepeatKeyword::NoRepeat, BackgroundRepeatKeyword::Repeat));
148            },
149            _ => {},
150        }
151
152        let horizontal = match BackgroundRepeatKeyword::from_ident(&ident) {
153            Ok(h) => h,
154            Err(()) => {
155                return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent));
156            },
157        };
158
159        let vertical = input.try_parse(BackgroundRepeatKeyword::parse).ok();
160        Ok(BackgroundRepeat(horizontal, vertical.unwrap_or(horizontal)))
161    }
162}
163
164fn background_clip_border_area_enabled(context: &ParserContext) -> bool {
165    context.chrome_rules_enabled()
166        || crate::pref!("layout.css.background-clip.border-area.enabled")
167}
168
169/// The specified value of the `background-clip` and `mask-clip` properties.
170///
171/// This is the union of the keywords both properties accept; each property restricts the set it
172/// actually allows during parsing (see `valid_for_background` / `valid_for_mask`).
173///
174/// https://drafts.csswg.org/css-backgrounds-4/#background-clip
175/// https://drafts.fxtf.org/css-masking-1/#propdef-mask-clip
176#[allow(missing_docs)]
177#[derive(
178    Clone,
179    Copy,
180    Debug,
181    Eq,
182    MallocSizeOf,
183    Parse,
184    PartialEq,
185    SpecifiedValueInfo,
186    ToComputedValue,
187    ToCss,
188    ToResolvedValue,
189    ToShmem,
190    ToTyped,
191)]
192#[repr(u8)]
193pub enum BackgroundClip {
194    BorderBox,
195    PaddingBox,
196    ContentBox,
197    // TODO(emilio): We should expose the svg values in SpecifiedValueInfo or so but only for
198    // mask-clip... Maybe we need a newtype thing, or to rejigger the painting code / storage
199    // further.
200    #[cfg(feature = "gecko")]
201    #[value_info(skip)]
202    FillBox,
203    #[cfg(feature = "gecko")]
204    #[value_info(skip)]
205    StrokeBox,
206    #[cfg(feature = "gecko")]
207    #[value_info(skip)]
208    ViewBox,
209    #[cfg(feature = "gecko")]
210    #[value_info(skip)]
211    NoClip,
212    // TODO: text and border-area are supposed to combine in backgrounds-4...
213    #[cfg(feature = "gecko")]
214    Text,
215    #[parse(condition = "background_clip_border_area_enabled")]
216    #[value_info(skip)]
217    BorderArea,
218}
219
220bitflags! {
221    /// Whether a value is valid for background-clip, mask-clip, or both.
222    #[derive(Clone, Copy)]
223    struct ClipValidity: u8 {
224        const BACKGROUND = 1 << 0;
225        const MASK = 1 << 1;
226        const BOTH = Self::BACKGROUND.bits() | Self::MASK.bits();
227    }
228}
229
230impl BackgroundClip {
231    fn validity(&self) -> ClipValidity {
232        match *self {
233            Self::BorderBox => ClipValidity::BOTH,
234            Self::PaddingBox => ClipValidity::BOTH,
235            Self::ContentBox => ClipValidity::BOTH,
236            #[cfg(feature = "gecko")]
237            Self::FillBox => ClipValidity::MASK,
238            #[cfg(feature = "gecko")]
239            Self::StrokeBox => ClipValidity::MASK,
240            #[cfg(feature = "gecko")]
241            Self::ViewBox => ClipValidity::MASK,
242            #[cfg(feature = "gecko")]
243            Self::NoClip => ClipValidity::MASK,
244            #[cfg(feature = "gecko")]
245            Self::Text => ClipValidity::BACKGROUND,
246            Self::BorderArea => ClipValidity::BACKGROUND,
247        }
248    }
249
250    /// Parse the value of the `background-clip` property.
251    pub fn parse_for_background(
252        context: &ParserContext,
253        input: &mut Parser,
254    ) -> Result<Self, ParseError> {
255        let clip = Self::parse(context, input)?;
256        if !clip.validity().intersects(ClipValidity::BACKGROUND) {
257            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
258        }
259        Ok(clip)
260    }
261
262    /// Parse the value of the `mask-clip` property.
263    pub fn parse_for_mask(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
264        let clip = Self::parse(context, input)?;
265        if !clip.validity().intersects(ClipValidity::MASK) {
266            return Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError));
267        }
268        Ok(clip)
269    }
270}