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