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