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::{AllowQuirks, NonNegativeNumber, NonNegativeNumberOrPercentage};
19use crate::Zero;
20use app_units::Au;
21use cssparser::Parser;
22use std::fmt::{self, Write};
23use style_traits::{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)]
103#[typed_value(derive_fields)]
104pub enum LineWidth {
105 Thin,
107 Medium,
109 Thick,
111 Length(NonNegativeLength),
113}
114
115impl LineWidth {
116 #[inline]
118 pub fn zero() -> Self {
119 Self::Length(NonNegativeLength::zero())
120 }
121
122 fn parse_quirky<'i, 't>(
123 context: &ParserContext,
124 input: &mut Parser<'i, 't>,
125 allow_quirks: AllowQuirks,
126 ) -> Result<Self, ParseError<'i>> {
127 if let Ok(length) =
128 input.try_parse(|i| NonNegativeLength::parse_quirky(context, i, allow_quirks))
129 {
130 return Ok(Self::Length(length));
131 }
132 Ok(try_match_ident_ignore_ascii_case! { input,
133 "thin" => Self::Thin,
134 "medium" => Self::Medium,
135 "thick" => Self::Thick,
136 })
137 }
138}
139
140impl Parse for LineWidth {
141 fn parse<'i>(
142 context: &ParserContext,
143 input: &mut Parser<'i, '_>,
144 ) -> Result<Self, ParseError<'i>> {
145 Self::parse_quirky(context, input, AllowQuirks::No)
146 }
147}
148
149impl ToComputedValue for LineWidth {
150 type ComputedValue = Au;
151
152 #[inline]
153 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
154 match *self {
155 Self::Thin => Au::from_px(1),
157 Self::Medium => Au::from_px(3),
158 Self::Thick => Au::from_px(5),
159 Self::Length(ref length) => Au::from_f32_px(length.to_computed_value(context).px()),
160 }
161 }
162
163 #[inline]
164 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
165 Self::Length(NonNegativeLength::from_px(computed.to_f32_px()))
166 }
167}
168
169#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
172#[typed_value(derive_fields)]
173pub struct BorderSideWidth(LineWidth);
174
175impl BorderSideWidth {
176 pub fn medium() -> Self {
178 Self(LineWidth::Medium)
179 }
180
181 pub fn from_px(px: f32) -> Self {
183 Self(LineWidth::Length(Length::from_px(px).into()))
184 }
185
186 pub fn parse_quirky<'i, 't>(
188 context: &ParserContext,
189 input: &mut Parser<'i, 't>,
190 allow_quirks: AllowQuirks,
191 ) -> Result<Self, ParseError<'i>> {
192 Ok(Self(LineWidth::parse_quirky(context, input, allow_quirks)?))
193 }
194}
195
196impl Parse for BorderSideWidth {
197 fn parse<'i>(
198 context: &ParserContext,
199 input: &mut Parser<'i, '_>,
200 ) -> Result<Self, ParseError<'i>> {
201 Self::parse_quirky(context, input, AllowQuirks::No)
202 }
203}
204
205fn snap_as_border_width(len: Au, context: &Context) -> Au {
207 debug_assert!(len >= Au(0));
208
209 if len == Au(0) {
212 return len;
213 }
214
215 let au_per_dev_px = context.device().app_units_per_device_pixel();
216 std::cmp::max(Au(au_per_dev_px), Au(len.0 / au_per_dev_px * au_per_dev_px))
217}
218
219impl ToComputedValue for BorderSideWidth {
220 type ComputedValue = ComputedBorderSideWidth;
221
222 #[inline]
223 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
224 ComputedBorderSideWidth(snap_as_border_width(
225 self.0.to_computed_value(context),
226 context,
227 ))
228 }
229
230 #[inline]
231 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
232 Self(LineWidth::from_computed_value(&computed.0))
233 }
234}
235
236#[derive(
238 Clone, Debug, MallocSizeOf, PartialEq, Parse, SpecifiedValueInfo, ToCss, ToShmem, ToTyped,
239)]
240#[typed_value(derive_fields)]
241pub struct BorderSideOffset(Length);
242
243impl ToComputedValue for BorderSideOffset {
244 type ComputedValue = Au;
245
246 #[inline]
247 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
248 let offset = Au::from_f32_px(self.0.to_computed_value(context).px());
249 let should_snap = match static_prefs::pref!("layout.css.outline-offset.snapping") {
250 1 => true,
251 2 => context.device().chrome_rules_enabled_for_document(),
252 _ => false,
253 };
254 if !should_snap {
255 return offset;
256 }
257 if offset < Au(0) {
258 -snap_as_border_width(-offset, context)
259 } else {
260 snap_as_border_width(offset, context)
261 }
262 }
263
264 #[inline]
265 fn from_computed_value(computed: &Au) -> Self {
266 Self(Length::from_px(computed.to_f32_px()))
267 }
268}
269
270impl BorderImageSideWidth {
271 #[inline]
273 pub fn one() -> Self {
274 GenericBorderImageSideWidth::Number(NonNegativeNumber::new(1.))
275 }
276}
277
278impl Parse for BorderImageSlice {
279 fn parse<'i, 't>(
280 context: &ParserContext,
281 input: &mut Parser<'i, 't>,
282 ) -> Result<Self, ParseError<'i>> {
283 let mut fill = input.try_parse(|i| i.expect_ident_matching("fill")).is_ok();
284 let offsets = Rect::parse_with(context, input, NonNegativeNumberOrPercentage::parse)?;
285 if !fill {
286 fill = input.try_parse(|i| i.expect_ident_matching("fill")).is_ok();
287 }
288 Ok(GenericBorderImageSlice { offsets, fill })
289 }
290}
291
292impl Parse for BorderRadius {
293 fn parse<'i, 't>(
294 context: &ParserContext,
295 input: &mut Parser<'i, 't>,
296 ) -> Result<Self, ParseError<'i>> {
297 let widths = Rect::parse_with(context, input, NonNegativeLengthPercentage::parse)?;
298 let heights = if input.try_parse(|i| i.expect_delim('/')).is_ok() {
299 Rect::parse_with(context, input, NonNegativeLengthPercentage::parse)?
300 } else {
301 widths.clone()
302 };
303
304 Ok(GenericBorderRadius {
305 top_left: BorderCornerRadius::new(widths.0, heights.0),
306 top_right: BorderCornerRadius::new(widths.1, heights.1),
307 bottom_right: BorderCornerRadius::new(widths.2, heights.2),
308 bottom_left: BorderCornerRadius::new(widths.3, heights.3),
309 })
310 }
311}
312
313impl Parse for BorderCornerRadius {
314 fn parse<'i, 't>(
315 context: &ParserContext,
316 input: &mut Parser<'i, 't>,
317 ) -> Result<Self, ParseError<'i>> {
318 Size2D::parse_with(context, input, NonNegativeLengthPercentage::parse)
319 .map(GenericBorderCornerRadius)
320 }
321}
322
323impl Parse for BorderSpacing {
324 fn parse<'i, 't>(
325 context: &ParserContext,
326 input: &mut Parser<'i, 't>,
327 ) -> Result<Self, ParseError<'i>> {
328 Size2D::parse_with(context, input, |context, input| {
329 NonNegativeLength::parse_quirky(context, input, AllowQuirks::Yes)
330 })
331 .map(GenericBorderSpacing)
332 }
333}
334
335#[allow(missing_docs)]
337#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
338#[derive(
339 Clone,
340 Copy,
341 Debug,
342 Eq,
343 MallocSizeOf,
344 Parse,
345 PartialEq,
346 SpecifiedValueInfo,
347 ToComputedValue,
348 ToCss,
349 ToResolvedValue,
350 ToShmem,
351)]
352#[repr(u8)]
353pub enum BorderImageRepeatKeyword {
354 Stretch,
355 Repeat,
356 Round,
357 Space,
358}
359
360#[derive(
364 Clone,
365 Copy,
366 Debug,
367 MallocSizeOf,
368 PartialEq,
369 SpecifiedValueInfo,
370 ToComputedValue,
371 ToResolvedValue,
372 ToShmem,
373 ToTyped,
374)]
375#[repr(C)]
376pub struct BorderImageRepeat(pub BorderImageRepeatKeyword, pub BorderImageRepeatKeyword);
377
378impl ToCss for BorderImageRepeat {
379 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
380 where
381 W: Write,
382 {
383 self.0.to_css(dest)?;
384 if self.0 != self.1 {
385 dest.write_char(' ')?;
386 self.1.to_css(dest)?;
387 }
388 Ok(())
389 }
390}
391
392impl BorderImageRepeat {
393 #[inline]
395 pub fn stretch() -> Self {
396 BorderImageRepeat(
397 BorderImageRepeatKeyword::Stretch,
398 BorderImageRepeatKeyword::Stretch,
399 )
400 }
401}
402
403impl Parse for BorderImageRepeat {
404 fn parse<'i, 't>(
405 _context: &ParserContext,
406 input: &mut Parser<'i, 't>,
407 ) -> Result<Self, ParseError<'i>> {
408 let horizontal = BorderImageRepeatKeyword::parse(input)?;
409 let vertical = input.try_parse(BorderImageRepeatKeyword::parse).ok();
410 Ok(BorderImageRepeat(
411 horizontal,
412 vertical.unwrap_or(horizontal),
413 ))
414 }
415}