1use 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#[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 #[inline]
67 pub fn none_or_hidden(&self) -> bool {
68 matches!(*self, BorderStyle::None | BorderStyle::Hidden)
69 }
70}
71
72pub type BorderImageWidth = Rect<BorderImageSideWidth>;
74
75pub type BorderImageSideWidth =
77 GenericBorderImageSideWidth<NonNegativeLengthPercentage, NonNegativeNumber>;
78
79pub type BorderImageSlice = GenericBorderImageSlice<NonNegativeNumberOrPercentage>;
81
82pub type BorderRadius = GenericBorderRadius<NonNegativeLengthPercentage>;
84
85pub type BorderCornerRadius = GenericBorderCornerRadius<NonNegativeLengthPercentage>;
87
88pub type BorderSpacing = GenericBorderSpacing<NonNegativeLength>;
90
91impl BorderImageSlice {
92 #[inline]
94 pub fn hundred_percent() -> Self {
95 GenericBorderImageSlice {
96 offsets: Rect::all(NonNegativeNumberOrPercentage::hundred_percent()),
97 fill: false,
98 }
99 }
100}
101
102#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
104#[typed_value(derive_fields)]
105pub enum LineWidth {
106 Thin,
108 Medium,
110 Thick,
112 Length(NonNegativeLength),
114}
115
116impl LineWidth {
117 #[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 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#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
173#[typed_value(derive_fields)]
174pub struct BorderSideWidth(LineWidth);
175
176impl BorderSideWidth {
177 pub fn medium() -> Self {
179 Self(LineWidth::Medium)
180 }
181
182 pub fn from_px(px: f32) -> Self {
184 Self(LineWidth::Length(Length::from_px(px).into()))
185 }
186
187 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
206fn snap_as_border_width(len: Au, context: &Context) -> Au {
208 debug_assert!(len >= Au(0));
209
210 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#[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 #[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#[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#[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 #[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
418pub 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}