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