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