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 7 use crate::parser::{Parse, ParserContext}; 8 use crate::values::computed::{self, Context, ToComputedValue}; 9 use crate::values::generics::border::BorderCornerRadius as GenericBorderCornerRadius; 10 use crate::values::generics::border::BorderImageSideWidth as GenericBorderImageSideWidth; 11 use crate::values::generics::border::BorderImageSlice as GenericBorderImageSlice; 12 use crate::values::generics::border::BorderRadius as GenericBorderRadius; 13 use crate::values::generics::border::BorderSpacing as GenericBorderSpacing; 14 use crate::values::generics::rect::Rect; 15 use crate::values::generics::size::Size2D; 16 use crate::values::specified::length::{NonNegativeLength, NonNegativeLengthPercentage}; 17 use crate::values::specified::{AllowQuirks, NonNegativeNumber, NonNegativeNumberOrPercentage}; 18 use crate::Zero; 19 use cssparser::Parser; 20 use std::fmt::{self, Write}; 21 use style_traits::{CssWriter, ParseError, ToCss}; 22 23 /// A specified value for a single side of a `border-style` property. 24 /// 25 /// The order here corresponds to the integer values from the border conflict 26 /// resolution rules in CSS 2.1 § 17.6.2.1. Higher values override lower values. 27 #[allow(missing_docs)] 28 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] 29 #[derive( 30 Clone, 31 Copy, 32 Debug, 33 Eq, 34 FromPrimitive, 35 MallocSizeOf, 36 Ord, 37 Parse, 38 PartialEq, 39 PartialOrd, 40 SpecifiedValueInfo, 41 ToComputedValue, 42 ToCss, 43 ToResolvedValue, 44 ToShmem, 45 )] 46 #[repr(u8)] 47 pub enum BorderStyle { 48 Hidden, 49 None, 50 Inset, 51 Groove, 52 Outset, 53 Ridge, 54 Dotted, 55 Dashed, 56 Solid, 57 Double, 58 } 59 60 impl BorderStyle { 61 /// Whether this border style is either none or hidden. 62 #[inline] none_or_hidden(&self) -> bool63 pub fn none_or_hidden(&self) -> bool { 64 matches!(*self, BorderStyle::None | BorderStyle::Hidden) 65 } 66 } 67 68 /// A specified value for a single side of the `border-width` property. 69 #[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] 70 pub enum BorderSideWidth { 71 /// `thin` 72 Thin, 73 /// `medium` 74 Medium, 75 /// `thick` 76 Thick, 77 /// `<length>` 78 Length(NonNegativeLength), 79 } 80 81 /// A specified value for the `border-image-width` property. 82 pub type BorderImageWidth = Rect<BorderImageSideWidth>; 83 84 /// A specified value for a single side of a `border-image-width` property. 85 pub type BorderImageSideWidth = 86 GenericBorderImageSideWidth<NonNegativeLengthPercentage, NonNegativeNumber>; 87 88 /// A specified value for the `border-image-slice` property. 89 pub type BorderImageSlice = GenericBorderImageSlice<NonNegativeNumberOrPercentage>; 90 91 /// A specified value for the `border-radius` property. 92 pub type BorderRadius = GenericBorderRadius<NonNegativeLengthPercentage>; 93 94 /// A specified value for the `border-*-radius` longhand properties. 95 pub type BorderCornerRadius = GenericBorderCornerRadius<NonNegativeLengthPercentage>; 96 97 /// A specified value for the `border-spacing` longhand properties. 98 pub type BorderSpacing = GenericBorderSpacing<NonNegativeLength>; 99 100 impl BorderImageSlice { 101 /// Returns the `100%` value. 102 #[inline] hundred_percent() -> Self103 pub fn hundred_percent() -> Self { 104 GenericBorderImageSlice { 105 offsets: Rect::all(NonNegativeNumberOrPercentage::hundred_percent()), 106 fill: false, 107 } 108 } 109 } 110 111 impl BorderSideWidth { 112 /// Returns the `0px` value. 113 #[inline] zero() -> Self114 pub fn zero() -> Self { 115 BorderSideWidth::Length(NonNegativeLength::zero()) 116 } 117 118 /// Parses, with quirks. parse_quirky<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, allow_quirks: AllowQuirks, ) -> Result<Self, ParseError<'i>>119 pub fn parse_quirky<'i, 't>( 120 context: &ParserContext, 121 input: &mut Parser<'i, 't>, 122 allow_quirks: AllowQuirks, 123 ) -> Result<Self, ParseError<'i>> { 124 if let Ok(length) = 125 input.try_parse(|i| NonNegativeLength::parse_quirky(context, i, allow_quirks)) 126 { 127 return Ok(BorderSideWidth::Length(length)); 128 } 129 try_match_ident_ignore_ascii_case! { input, 130 "thin" => Ok(BorderSideWidth::Thin), 131 "medium" => Ok(BorderSideWidth::Medium), 132 "thick" => Ok(BorderSideWidth::Thick), 133 } 134 } 135 } 136 137 impl Parse for BorderSideWidth { parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>138 fn parse<'i, 't>( 139 context: &ParserContext, 140 input: &mut Parser<'i, 't>, 141 ) -> Result<Self, ParseError<'i>> { 142 Self::parse_quirky(context, input, AllowQuirks::No) 143 } 144 } 145 146 impl ToComputedValue for BorderSideWidth { 147 type ComputedValue = computed::NonNegativeLength; 148 149 #[inline] to_computed_value(&self, context: &Context) -> Self::ComputedValue150 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { 151 // We choose the pixel length of the keyword values the same as both spec and gecko. 152 // Spec: https://drafts.csswg.org/css-backgrounds-3/#line-width 153 // Gecko: https://bugzilla.mozilla.org/show_bug.cgi?id=1312155#c0 154 match *self { 155 BorderSideWidth::Thin => NonNegativeLength::from_px(1.).to_computed_value(context), 156 BorderSideWidth::Medium => NonNegativeLength::from_px(3.).to_computed_value(context), 157 BorderSideWidth::Thick => NonNegativeLength::from_px(5.).to_computed_value(context), 158 BorderSideWidth::Length(ref length) => length.to_computed_value(context), 159 } 160 .into() 161 } 162 163 #[inline] from_computed_value(computed: &Self::ComputedValue) -> Self164 fn from_computed_value(computed: &Self::ComputedValue) -> Self { 165 BorderSideWidth::Length(ToComputedValue::from_computed_value(computed)) 166 } 167 } 168 169 impl BorderImageSideWidth { 170 /// Returns `1`. 171 #[inline] one() -> Self172 pub fn one() -> Self { 173 GenericBorderImageSideWidth::Number(NonNegativeNumber::new(1.)) 174 } 175 } 176 177 impl Parse for BorderImageSlice { parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>178 fn parse<'i, 't>( 179 context: &ParserContext, 180 input: &mut Parser<'i, 't>, 181 ) -> Result<Self, ParseError<'i>> { 182 let mut fill = input.try_parse(|i| i.expect_ident_matching("fill")).is_ok(); 183 let offsets = Rect::parse_with(context, input, NonNegativeNumberOrPercentage::parse)?; 184 if !fill { 185 fill = input.try_parse(|i| i.expect_ident_matching("fill")).is_ok(); 186 } 187 Ok(GenericBorderImageSlice { offsets, fill }) 188 } 189 } 190 191 impl Parse for BorderRadius { parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>192 fn parse<'i, 't>( 193 context: &ParserContext, 194 input: &mut Parser<'i, 't>, 195 ) -> Result<Self, ParseError<'i>> { 196 let widths = Rect::parse_with(context, input, NonNegativeLengthPercentage::parse)?; 197 let heights = if input.try_parse(|i| i.expect_delim('/')).is_ok() { 198 Rect::parse_with(context, input, NonNegativeLengthPercentage::parse)? 199 } else { 200 widths.clone() 201 }; 202 203 Ok(GenericBorderRadius { 204 top_left: BorderCornerRadius::new(widths.0, heights.0), 205 top_right: BorderCornerRadius::new(widths.1, heights.1), 206 bottom_right: BorderCornerRadius::new(widths.2, heights.2), 207 bottom_left: BorderCornerRadius::new(widths.3, heights.3), 208 }) 209 } 210 } 211 212 impl Parse for BorderCornerRadius { parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>213 fn parse<'i, 't>( 214 context: &ParserContext, 215 input: &mut Parser<'i, 't>, 216 ) -> Result<Self, ParseError<'i>> { 217 Size2D::parse_with(context, input, NonNegativeLengthPercentage::parse) 218 .map(GenericBorderCornerRadius) 219 } 220 } 221 222 impl Parse for BorderSpacing { parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>223 fn parse<'i, 't>( 224 context: &ParserContext, 225 input: &mut Parser<'i, 't>, 226 ) -> Result<Self, ParseError<'i>> { 227 Size2D::parse_with(context, input, |context, input| { 228 NonNegativeLength::parse_quirky(context, input, AllowQuirks::Yes) 229 }) 230 .map(GenericBorderSpacing) 231 } 232 } 233 234 /// A single border-image-repeat keyword. 235 #[allow(missing_docs)] 236 #[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] 237 #[derive( 238 Clone, 239 Copy, 240 Debug, 241 Eq, 242 MallocSizeOf, 243 Parse, 244 PartialEq, 245 SpecifiedValueInfo, 246 ToComputedValue, 247 ToCss, 248 ToResolvedValue, 249 ToShmem, 250 )] 251 pub enum BorderImageRepeatKeyword { 252 Stretch, 253 Repeat, 254 Round, 255 Space, 256 } 257 258 /// The specified value for the `border-image-repeat` property. 259 /// 260 /// https://drafts.csswg.org/css-backgrounds/#the-border-image-repeat 261 #[derive( 262 Clone, 263 Copy, 264 Debug, 265 MallocSizeOf, 266 PartialEq, 267 SpecifiedValueInfo, 268 ToComputedValue, 269 ToResolvedValue, 270 ToShmem, 271 )] 272 pub struct BorderImageRepeat(pub BorderImageRepeatKeyword, pub BorderImageRepeatKeyword); 273 274 impl ToCss for BorderImageRepeat { to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write,275 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result 276 where 277 W: Write, 278 { 279 self.0.to_css(dest)?; 280 if self.0 != self.1 { 281 dest.write_str(" ")?; 282 self.1.to_css(dest)?; 283 } 284 Ok(()) 285 } 286 } 287 288 impl BorderImageRepeat { 289 /// Returns the `stretch` value. 290 #[inline] stretch() -> Self291 pub fn stretch() -> Self { 292 BorderImageRepeat( 293 BorderImageRepeatKeyword::Stretch, 294 BorderImageRepeatKeyword::Stretch, 295 ) 296 } 297 } 298 299 impl Parse for BorderImageRepeat { parse<'i, 't>( _context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result<Self, ParseError<'i>>300 fn parse<'i, 't>( 301 _context: &ParserContext, 302 input: &mut Parser<'i, 't>, 303 ) -> Result<Self, ParseError<'i>> { 304 let horizontal = BorderImageRepeatKeyword::parse(input)?; 305 let vertical = input.try_parse(BorderImageRepeatKeyword::parse).ok(); 306 Ok(BorderImageRepeat( 307 horizontal, 308 vertical.unwrap_or(horizontal), 309 )) 310 } 311 } 312