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