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::Zero;
8use crate::derives::*;
9use crate::parser::{Parse, ParserContext};
10use crate::typed_om::{ToTyped, TypedValue};
11use crate::values::computed::border::BorderSideWidth as ComputedBorderSideWidth;
12use crate::values::computed::{Context, ToComputedValue};
13use crate::values::generics::border::{
14    GenericBorderCornerRadius, GenericBorderImageSideWidth, GenericBorderImageSlice,
15    GenericBorderRadius, GenericBorderSpacing,
16};
17use crate::values::generics::rect::Rect;
18use crate::values::generics::size::Size2D;
19use crate::values::specified::length::{Length, NonNegativeLength, NonNegativeLengthPercentage};
20use crate::values::specified::{AllowQuirks, NonNegativeNumber, NonNegativeNumberOrPercentage};
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(
159        context: &ParserContext,
160        input: &mut Parser,
161        allow_quirks: AllowQuirks,
162    ) -> Result<Self, ParseError> {
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(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
178        Self::parse_quirky(context, input, AllowQuirks::No)
179    }
180}
181
182impl ToComputedValue for LineWidth {
183    type ComputedValue = Au;
184
185    #[inline]
186    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
187        match *self {
188            // https://drafts.csswg.org/css-backgrounds-3/#line-width
189            Self::Thin => Au::from_px(1),
190            Self::Medium => Au::from_px(3),
191            Self::Thick => Au::from_px(5),
192            Self::Length(ref length) => Au::from_f32_px(length.to_computed_value(context).px()),
193        }
194    }
195
196    #[inline]
197    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
198        Self::Length(NonNegativeLength::from_px(computed.to_f32_px()))
199    }
200}
201
202/// A specified value for a single side of the `border-width` property. The difference between this
203/// and LineWidth is whether we snap to device pixels or not.
204#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
205pub struct BorderSideWidth(LineWidth);
206
207impl BorderSideWidth {
208    /// Returns the `medium` value.
209    pub fn medium() -> Self {
210        Self(LineWidth::Medium)
211    }
212
213    /// Returns a bare px value from the argument.
214    pub fn from_px(px: f32) -> Self {
215        Self(LineWidth::Length(Length::from_px(px).into()))
216    }
217
218    /// Parses, with quirks.
219    pub fn parse_quirky(
220        context: &ParserContext,
221        input: &mut Parser,
222        allow_quirks: AllowQuirks,
223    ) -> Result<Self, ParseError> {
224        Ok(Self(LineWidth::parse_quirky(context, input, allow_quirks)?))
225    }
226}
227
228impl Parse for BorderSideWidth {
229    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
230        Self::parse_quirky(context, input, AllowQuirks::No)
231    }
232}
233
234// https://drafts.csswg.org/css-values-4/#snap-a-length-as-a-border-width
235fn snap_as_border_width(len: Au, context: &Context) -> Au {
236    debug_assert!(len >= Au(0));
237
238    // Round `width` down to the nearest device pixel, but any non-zero value that would round
239    // down to zero is clamped to 1 device pixel.
240    if len == Au(0) {
241        return len;
242    }
243
244    let au_per_dev_px = context.device().app_units_per_device_pixel();
245    std::cmp::max(Au(au_per_dev_px), Au(len.0 / au_per_dev_px * au_per_dev_px))
246}
247
248impl ToComputedValue for BorderSideWidth {
249    type ComputedValue = ComputedBorderSideWidth;
250
251    #[inline]
252    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
253        ComputedBorderSideWidth(snap_as_border_width(
254            self.0.to_computed_value(context),
255            context,
256        ))
257    }
258
259    #[inline]
260    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
261        Self(LineWidth::from_computed_value(&computed.0))
262    }
263}
264
265/// A specified value for outline-offset.
266#[derive(
267    Clone, Debug, MallocSizeOf, PartialEq, Parse, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
268)]
269pub struct BorderSideOffset(Length);
270
271impl ToComputedValue for BorderSideOffset {
272    type ComputedValue = Au;
273
274    #[inline]
275    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
276        let offset = Au::from_f32_px(self.0.to_computed_value(context).px());
277        let should_snap = match crate::pref!("layout.css.outline-offset.snapping") {
278            1 => true,
279            2 => context.device().chrome_rules_enabled_for_document(),
280            _ => false,
281        };
282        if !should_snap {
283            return offset;
284        }
285        if offset < Au(0) {
286            -snap_as_border_width(-offset, context)
287        } else {
288            snap_as_border_width(offset, context)
289        }
290    }
291
292    #[inline]
293    fn from_computed_value(computed: &Au) -> Self {
294        Self(Length::from_px(computed.to_f32_px()))
295    }
296}
297
298impl BorderImageSideWidth {
299    /// Returns `1`.
300    #[inline]
301    pub fn one() -> Self {
302        GenericBorderImageSideWidth::Number(NonNegativeNumber::new(1.))
303    }
304}
305
306impl Parse for BorderImageSlice {
307    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
308        let mut fill = input.try_parse(|i| i.expect_ident_matching("fill")).is_ok();
309        let offsets = Rect::parse_with(context, input, NonNegativeNumberOrPercentage::parse)?;
310        if !fill {
311            fill = input.try_parse(|i| i.expect_ident_matching("fill")).is_ok();
312        }
313        Ok(GenericBorderImageSlice { offsets, fill })
314    }
315}
316
317impl Parse for BorderRadius {
318    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
319        let widths = Rect::parse_with(context, input, NonNegativeLengthPercentage::parse)?;
320        let heights = if input.try_parse(|i| i.expect_delim('/')).is_ok() {
321            Rect::parse_with(context, input, NonNegativeLengthPercentage::parse)?
322        } else {
323            widths.clone()
324        };
325
326        Ok(GenericBorderRadius {
327            top_left: BorderCornerRadius::new(widths.0, heights.0),
328            top_right: BorderCornerRadius::new(widths.1, heights.1),
329            bottom_right: BorderCornerRadius::new(widths.2, heights.2),
330            bottom_left: BorderCornerRadius::new(widths.3, heights.3),
331        })
332    }
333}
334
335impl Parse for BorderCornerRadius {
336    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
337        Size2D::parse_with(context, input, NonNegativeLengthPercentage::parse)
338            .map(GenericBorderCornerRadius)
339    }
340}
341
342impl Parse for BorderSpacing {
343    fn parse(context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
344        Size2D::parse_with(context, input, |context, input| {
345            NonNegativeLength::parse_quirky(context, input, AllowQuirks::Yes)
346        })
347        .map(GenericBorderSpacing)
348    }
349}
350
351/// A single border-image-repeat keyword.
352#[allow(missing_docs)]
353#[derive(
354    Clone,
355    Copy,
356    Debug,
357    Deserialize,
358    Eq,
359    MallocSizeOf,
360    Parse,
361    PartialEq,
362    Serialize,
363    SpecifiedValueInfo,
364    ToComputedValue,
365    ToCss,
366    ToResolvedValue,
367    ToShmem,
368)]
369#[repr(u8)]
370pub enum BorderImageRepeatKeyword {
371    Stretch,
372    Repeat,
373    Round,
374    Space,
375}
376
377/// The specified value for the `border-image-repeat` property.
378///
379/// https://drafts.csswg.org/css-backgrounds/#the-border-image-repeat
380#[derive(
381    Clone,
382    Copy,
383    Debug,
384    MallocSizeOf,
385    PartialEq,
386    SpecifiedValueInfo,
387    ToComputedValue,
388    ToResolvedValue,
389    ToShmem,
390    ToTyped,
391)]
392#[repr(C)]
393#[typed(todo_derive_fields)]
394pub struct BorderImageRepeat(pub BorderImageRepeatKeyword, pub BorderImageRepeatKeyword);
395
396impl ToCss for BorderImageRepeat {
397    fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
398    where
399        W: Write,
400    {
401        self.0.to_css(dest)?;
402        if self.0 != self.1 {
403            dest.write_char(' ')?;
404            self.1.to_css(dest)?;
405        }
406        Ok(())
407    }
408}
409
410impl BorderImageRepeat {
411    /// Returns the `stretch` value.
412    #[inline]
413    pub fn stretch() -> Self {
414        BorderImageRepeat(
415            BorderImageRepeatKeyword::Stretch,
416            BorderImageRepeatKeyword::Stretch,
417        )
418    }
419}
420
421impl Parse for BorderImageRepeat {
422    fn parse(_context: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
423        let horizontal = BorderImageRepeatKeyword::parse(input)?;
424        let vertical = input.try_parse(BorderImageRepeatKeyword::parse).ok();
425        Ok(BorderImageRepeat(
426            horizontal,
427            vertical.unwrap_or(horizontal),
428        ))
429    }
430}
431
432/// https://drafts.csswg.org/css-break/#propdef-box-decoration-break
433#[allow(missing_docs)]
434#[derive(
435    Clone,
436    Copy,
437    Debug,
438    Deserialize,
439    Eq,
440    FromPrimitive,
441    Hash,
442    MallocSizeOf,
443    Parse,
444    PartialEq,
445    Serialize,
446    SpecifiedValueInfo,
447    ToComputedValue,
448    ToCss,
449    ToResolvedValue,
450    ToShmem,
451    ToTyped,
452)]
453#[repr(u8)]
454pub enum BoxDecorationBreak {
455    Slice,
456    Clone,
457}
458
459/// Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-float-edge)
460#[allow(missing_docs)]
461#[derive(
462    Clone,
463    Copy,
464    Debug,
465    Deserialize,
466    Eq,
467    FromPrimitive,
468    Hash,
469    MallocSizeOf,
470    Parse,
471    PartialEq,
472    Serialize,
473    SpecifiedValueInfo,
474    ToComputedValue,
475    ToCss,
476    ToResolvedValue,
477    ToShmem,
478    ToTyped,
479)]
480#[repr(u8)]
481pub enum FloatEdge {
482    ContentBox,
483    MarginBox,
484}