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