Skip to main content

style/values/specified/
border.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 borders.
6
7use crate::derives::*;
8use crate::parser::{Parse, ParserContext};
9use crate::values::computed::border::BorderSideWidth as ComputedBorderSideWidth;
10use crate::values::computed::{Context, ToComputedValue};
11use crate::values::generics::border::{
12    GenericBorderCornerRadius, GenericBorderImageSideWidth, GenericBorderImageSlice,
13    GenericBorderRadius, GenericBorderSpacing,
14};
15use crate::values::generics::rect::Rect;
16use crate::values::generics::size::Size2D;
17use crate::values::specified::length::{Length, NonNegativeLength, NonNegativeLengthPercentage};
18use crate::values::specified::{AllowQuirks, NonNegativeNumber, NonNegativeNumberOrPercentage};
19use crate::Zero;
20use app_units::Au;
21use cssparser::Parser;
22use std::fmt::{self, Write};
23use style_traits::{CssWriter, ParseError, ToCss};
24
25/// A specified value for a single side of a `border-style` property.
26///
27/// The order here corresponds to the integer values from the border conflict
28/// resolution rules in CSS 2.1 ยง 17.6.2.1. Higher values override lower values.
29#[allow(missing_docs)]
30#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
31#[derive(
32    Clone,
33    Copy,
34    Debug,
35    Eq,
36    FromPrimitive,
37    MallocSizeOf,
38    Ord,
39    Parse,
40    PartialEq,
41    PartialOrd,
42    SpecifiedValueInfo,
43    ToComputedValue,
44    ToCss,
45    ToResolvedValue,
46    ToShmem,
47    ToTyped,
48)]
49#[repr(u8)]
50pub enum BorderStyle {
51    Hidden,
52    None,
53    Inset,
54    Groove,
55    Outset,
56    Ridge,
57    Dotted,
58    Dashed,
59    Solid,
60    Double,
61}
62
63impl BorderStyle {
64    /// Whether this border style is either none or hidden.
65    #[inline]
66    pub fn none_or_hidden(&self) -> bool {
67        matches!(*self, BorderStyle::None | BorderStyle::Hidden)
68    }
69}
70
71/// A specified value for the `border-image-width` property.
72pub type BorderImageWidth = Rect<BorderImageSideWidth>;
73
74/// A specified value for a single side of a `border-image-width` property.
75pub type BorderImageSideWidth =
76    GenericBorderImageSideWidth<NonNegativeLengthPercentage, NonNegativeNumber>;
77
78/// A specified value for the `border-image-slice` property.
79pub type BorderImageSlice = GenericBorderImageSlice<NonNegativeNumberOrPercentage>;
80
81/// A specified value for the `border-radius` property.
82pub type BorderRadius = GenericBorderRadius<NonNegativeLengthPercentage>;
83
84/// A specified value for the `border-*-radius` longhand properties.
85pub type BorderCornerRadius = GenericBorderCornerRadius<NonNegativeLengthPercentage>;
86
87/// A specified value for the `border-spacing` longhand properties.
88pub type BorderSpacing = GenericBorderSpacing<NonNegativeLength>;
89
90impl BorderImageSlice {
91    /// Returns the `100%` value.
92    #[inline]
93    pub fn hundred_percent() -> Self {
94        GenericBorderImageSlice {
95            offsets: Rect::all(NonNegativeNumberOrPercentage::hundred_percent()),
96            fill: false,
97        }
98    }
99}
100
101/// https://drafts.csswg.org/css-backgrounds-3/#typedef-line-width
102#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
103#[typed_value(derive_fields)]
104pub enum LineWidth {
105    /// `thin`
106    Thin,
107    /// `medium`
108    Medium,
109    /// `thick`
110    Thick,
111    /// `<length>`
112    Length(NonNegativeLength),
113}
114
115impl LineWidth {
116    /// Returns the `0px` value.
117    #[inline]
118    pub fn zero() -> Self {
119        Self::Length(NonNegativeLength::zero())
120    }
121
122    fn parse_quirky<'i, 't>(
123        context: &ParserContext,
124        input: &mut Parser<'i, 't>,
125        allow_quirks: AllowQuirks,
126    ) -> Result<Self, ParseError<'i>> {
127        if let Ok(length) =
128            input.try_parse(|i| NonNegativeLength::parse_quirky(context, i, allow_quirks))
129        {
130            return Ok(Self::Length(length));
131        }
132        Ok(try_match_ident_ignore_ascii_case! { input,
133            "thin" => Self::Thin,
134            "medium" => Self::Medium,
135            "thick" => Self::Thick,
136        })
137    }
138}
139
140impl Parse for LineWidth {
141    fn parse<'i>(
142        context: &ParserContext,
143        input: &mut Parser<'i, '_>,
144    ) -> Result<Self, ParseError<'i>> {
145        Self::parse_quirky(context, input, AllowQuirks::No)
146    }
147}
148
149impl ToComputedValue for LineWidth {
150    type ComputedValue = Au;
151
152    #[inline]
153    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
154        match *self {
155            // https://drafts.csswg.org/css-backgrounds-3/#line-width
156            Self::Thin => Au::from_px(1),
157            Self::Medium => Au::from_px(3),
158            Self::Thick => Au::from_px(5),
159            Self::Length(ref length) => Au::from_f32_px(length.to_computed_value(context).px()),
160        }
161    }
162
163    #[inline]
164    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
165        Self::Length(NonNegativeLength::from_px(computed.to_f32_px()))
166    }
167}
168
169/// A specified value for a single side of the `border-width` property. The difference between this
170/// and LineWidth is whether we snap to device pixels or not.
171#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
172#[typed_value(derive_fields)]
173pub struct BorderSideWidth(LineWidth);
174
175impl BorderSideWidth {
176    /// Returns the `medium` value.
177    pub fn medium() -> Self {
178        Self(LineWidth::Medium)
179    }
180
181    /// Returns a bare px value from the argument.
182    pub fn from_px(px: f32) -> Self {
183        Self(LineWidth::Length(Length::from_px(px).into()))
184    }
185
186    /// Parses, with quirks.
187    pub fn parse_quirky<'i, 't>(
188        context: &ParserContext,
189        input: &mut Parser<'i, 't>,
190        allow_quirks: AllowQuirks,
191    ) -> Result<Self, ParseError<'i>> {
192        Ok(Self(LineWidth::parse_quirky(context, input, allow_quirks)?))
193    }
194}
195
196impl Parse for BorderSideWidth {
197    fn parse<'i>(
198        context: &ParserContext,
199        input: &mut Parser<'i, '_>,
200    ) -> Result<Self, ParseError<'i>> {
201        Self::parse_quirky(context, input, AllowQuirks::No)
202    }
203}
204
205// https://drafts.csswg.org/css-values-4/#snap-a-length-as-a-border-width
206fn snap_as_border_width(len: Au, context: &Context) -> Au {
207    debug_assert!(len >= Au(0));
208
209    // Round `width` down to the nearest device pixel, but any non-zero value that would round
210    // down to zero is clamped to 1 device pixel.
211    if len == Au(0) {
212        return len;
213    }
214
215    let au_per_dev_px = context.device().app_units_per_device_pixel();
216    std::cmp::max(Au(au_per_dev_px), Au(len.0 / au_per_dev_px * au_per_dev_px))
217}
218
219impl ToComputedValue for BorderSideWidth {
220    type ComputedValue = ComputedBorderSideWidth;
221
222    #[inline]
223    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
224        ComputedBorderSideWidth(snap_as_border_width(
225            self.0.to_computed_value(context),
226            context,
227        ))
228    }
229
230    #[inline]
231    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
232        Self(LineWidth::from_computed_value(&computed.0))
233    }
234}
235
236/// A specified value for outline-offset.
237#[derive(
238    Clone, Debug, MallocSizeOf, PartialEq, Parse, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
239)]
240#[typed_value(derive_fields)]
241pub struct BorderSideOffset(Length);
242
243impl ToComputedValue for BorderSideOffset {
244    type ComputedValue = Au;
245
246    #[inline]
247    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
248        let offset = Au::from_f32_px(self.0.to_computed_value(context).px());
249        let should_snap = match static_prefs::pref!("layout.css.outline-offset.snapping") {
250            1 => true,
251            2 => context.device().chrome_rules_enabled_for_document(),
252            _ => false,
253        };
254        if !should_snap {
255            return offset;
256        }
257        if offset < Au(0) {
258            -snap_as_border_width(-offset, context)
259        } else {
260            snap_as_border_width(offset, context)
261        }
262    }
263
264    #[inline]
265    fn from_computed_value(computed: &Au) -> Self {
266        Self(Length::from_px(computed.to_f32_px()))
267    }
268}
269
270impl BorderImageSideWidth {
271    /// Returns `1`.
272    #[inline]
273    pub fn one() -> Self {
274        GenericBorderImageSideWidth::Number(NonNegativeNumber::new(1.))
275    }
276}
277
278impl Parse for BorderImageSlice {
279    fn parse<'i, 't>(
280        context: &ParserContext,
281        input: &mut Parser<'i, 't>,
282    ) -> Result<Self, ParseError<'i>> {
283        let mut fill = input.try_parse(|i| i.expect_ident_matching("fill")).is_ok();
284        let offsets = Rect::parse_with(context, input, NonNegativeNumberOrPercentage::parse)?;
285        if !fill {
286            fill = input.try_parse(|i| i.expect_ident_matching("fill")).is_ok();
287        }
288        Ok(GenericBorderImageSlice { offsets, fill })
289    }
290}
291
292impl Parse for BorderRadius {
293    fn parse<'i, 't>(
294        context: &ParserContext,
295        input: &mut Parser<'i, 't>,
296    ) -> Result<Self, ParseError<'i>> {
297        let widths = Rect::parse_with(context, input, NonNegativeLengthPercentage::parse)?;
298        let heights = if input.try_parse(|i| i.expect_delim('/')).is_ok() {
299            Rect::parse_with(context, input, NonNegativeLengthPercentage::parse)?
300        } else {
301            widths.clone()
302        };
303
304        Ok(GenericBorderRadius {
305            top_left: BorderCornerRadius::new(widths.0, heights.0),
306            top_right: BorderCornerRadius::new(widths.1, heights.1),
307            bottom_right: BorderCornerRadius::new(widths.2, heights.2),
308            bottom_left: BorderCornerRadius::new(widths.3, heights.3),
309        })
310    }
311}
312
313impl Parse for BorderCornerRadius {
314    fn parse<'i, 't>(
315        context: &ParserContext,
316        input: &mut Parser<'i, 't>,
317    ) -> Result<Self, ParseError<'i>> {
318        Size2D::parse_with(context, input, NonNegativeLengthPercentage::parse)
319            .map(GenericBorderCornerRadius)
320    }
321}
322
323impl Parse for BorderSpacing {
324    fn parse<'i, 't>(
325        context: &ParserContext,
326        input: &mut Parser<'i, 't>,
327    ) -> Result<Self, ParseError<'i>> {
328        Size2D::parse_with(context, input, |context, input| {
329            NonNegativeLength::parse_quirky(context, input, AllowQuirks::Yes)
330        })
331        .map(GenericBorderSpacing)
332    }
333}
334
335/// A single border-image-repeat keyword.
336#[allow(missing_docs)]
337#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
338#[derive(
339    Clone,
340    Copy,
341    Debug,
342    Eq,
343    MallocSizeOf,
344    Parse,
345    PartialEq,
346    SpecifiedValueInfo,
347    ToComputedValue,
348    ToCss,
349    ToResolvedValue,
350    ToShmem,
351)]
352#[repr(u8)]
353pub enum BorderImageRepeatKeyword {
354    Stretch,
355    Repeat,
356    Round,
357    Space,
358}
359
360/// The specified value for the `border-image-repeat` property.
361///
362/// https://drafts.csswg.org/css-backgrounds/#the-border-image-repeat
363#[derive(
364    Clone,
365    Copy,
366    Debug,
367    MallocSizeOf,
368    PartialEq,
369    SpecifiedValueInfo,
370    ToComputedValue,
371    ToResolvedValue,
372    ToShmem,
373    ToTyped,
374)]
375#[repr(C)]
376pub struct BorderImageRepeat(pub BorderImageRepeatKeyword, pub BorderImageRepeatKeyword);
377
378impl ToCss for BorderImageRepeat {
379    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
380    where
381        W: Write,
382    {
383        self.0.to_css(dest)?;
384        if self.0 != self.1 {
385            dest.write_char(' ')?;
386            self.1.to_css(dest)?;
387        }
388        Ok(())
389    }
390}
391
392impl BorderImageRepeat {
393    /// Returns the `stretch` value.
394    #[inline]
395    pub fn stretch() -> Self {
396        BorderImageRepeat(
397            BorderImageRepeatKeyword::Stretch,
398            BorderImageRepeatKeyword::Stretch,
399        )
400    }
401}
402
403impl Parse for BorderImageRepeat {
404    fn parse<'i, 't>(
405        _context: &ParserContext,
406        input: &mut Parser<'i, 't>,
407    ) -> Result<Self, ParseError<'i>> {
408        let horizontal = BorderImageRepeatKeyword::parse(input)?;
409        let vertical = input.try_parse(BorderImageRepeatKeyword::parse).ok();
410        Ok(BorderImageRepeat(
411            horizontal,
412            vertical.unwrap_or(horizontal),
413        ))
414    }
415}