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