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