border.rs (12919B)
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::derives::*; 8 use crate::parser::{Parse, ParserContext}; 9 use crate::values::computed::border::BorderSideWidth as ComputedBorderSideWidth; 10 use crate::values::computed::{Context, ToComputedValue}; 11 use crate::values::generics::border::{ 12 GenericBorderCornerRadius, GenericBorderImageSideWidth, GenericBorderImageSlice, 13 GenericBorderRadius, GenericBorderSpacing, 14 }; 15 use crate::values::generics::rect::Rect; 16 use crate::values::generics::size::Size2D; 17 use crate::values::specified::length::{Length, NonNegativeLength, NonNegativeLengthPercentage}; 18 use crate::values::specified::Color; 19 use crate::values::specified::{AllowQuirks, NonNegativeNumber, NonNegativeNumberOrPercentage}; 20 use crate::Zero; 21 use app_units::Au; 22 use cssparser::Parser; 23 use std::fmt::{self, Write}; 24 use 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)] 51 pub enum BorderStyle { 52 Hidden, 53 None, 54 Inset, 55 Groove, 56 Outset, 57 Ridge, 58 Dotted, 59 Dashed, 60 Solid, 61 Double, 62 } 63 64 impl 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. 73 pub type BorderImageWidth = Rect<BorderImageSideWidth>; 74 75 /// A specified value for a single side of a `border-image-width` property. 76 pub type BorderImageSideWidth = 77 GenericBorderImageSideWidth<NonNegativeLengthPercentage, NonNegativeNumber>; 78 79 /// A specified value for the `border-image-slice` property. 80 pub type BorderImageSlice = GenericBorderImageSlice<NonNegativeNumberOrPercentage>; 81 82 /// A specified value for the `border-radius` property. 83 pub type BorderRadius = GenericBorderRadius<NonNegativeLengthPercentage>; 84 85 /// A specified value for the `border-*-radius` longhand properties. 86 pub type BorderCornerRadius = GenericBorderCornerRadius<NonNegativeLengthPercentage>; 87 88 /// A specified value for the `border-spacing` longhand properties. 89 pub type BorderSpacing = GenericBorderSpacing<NonNegativeLength>; 90 91 impl 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)] 105 pub enum LineWidth { 106 /// `thin` 107 Thin, 108 /// `medium` 109 Medium, 110 /// `thick` 111 Thick, 112 /// `<length>` 113 Length(NonNegativeLength), 114 } 115 116 impl 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 141 impl 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 150 impl 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)] 174 pub struct BorderSideWidth(LineWidth); 175 176 impl 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 197 impl 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 207 fn 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 220 impl 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)] 242 pub struct BorderSideOffset(Length); 243 244 impl 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 271 impl BorderImageSideWidth { 272 /// Returns `1`. 273 #[inline] 274 pub fn one() -> Self { 275 GenericBorderImageSideWidth::Number(NonNegativeNumber::new(1.)) 276 } 277 } 278 279 impl 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 293 impl 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 314 impl 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 324 impl 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)] 354 pub 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)] 377 pub struct BorderImageRepeat(pub BorderImageRepeatKeyword, pub BorderImageRepeatKeyword); 378 379 impl 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 393 impl 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 404 impl 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. 419 pub fn serialize_directional_border<W>( 420 dest: &mut CssWriter<W>, 421 width: &BorderSideWidth, 422 style: &BorderStyle, 423 color: &Color, 424 ) -> fmt::Result 425 where 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 }