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 = 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
202fn snap_as_border_width(len: Au, context: &Context) -> Au {
204 debug_assert!(len >= Au(0));
205
206 if len == Au(0) {
209 return len;
210 }
211
212 let au_per_dev_px = context.device().app_units_per_device_pixel();
213 std::cmp::max(
214 Au(au_per_dev_px),
215 Au(len.0 / au_per_dev_px * au_per_dev_px),
216 )
217}
218
219impl ToComputedValue for BorderSideWidth {
220 type ComputedValue = Au;
221
222 #[inline]
223 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
224 snap_as_border_width(self.0.to_computed_value(context), context)
225 }
226
227 #[inline]
228 fn from_computed_value(computed: &Self::ComputedValue) -> Self {
229 Self(LineWidth::from_computed_value(computed))
230 }
231}
232
233#[derive(Clone, Debug, MallocSizeOf, PartialEq, Parse, SpecifiedValueInfo, ToCss, ToShmem, ToTyped)]
235pub struct BorderSideOffset(Length);
236
237impl ToComputedValue for BorderSideOffset {
238 type ComputedValue = Au;
239
240 #[inline]
241 fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
242 let offset = Au::from_f32_px(self.0.to_computed_value(context).px());
243 let should_snap = match static_prefs::pref!("layout.css.outline-offset.snapping") {
244 1 => true,
245 2 => context.device().chrome_rules_enabled_for_document(),
246 _ => false,
247 };
248 if !should_snap {
249 return offset;
250 }
251 if offset < Au(0) {
252 -snap_as_border_width(-offset, context)
253 } else {
254 snap_as_border_width(offset, context)
255 }
256 }
257
258 #[inline]
259 fn from_computed_value(computed: &Au) -> Self {
260 Self(Length::from_px(computed.to_f32_px()))
261 }
262}
263
264impl BorderImageSideWidth {
265 #[inline]
267 pub fn one() -> Self {
268 GenericBorderImageSideWidth::Number(NonNegativeNumber::new(1.))
269 }
270}
271
272impl Parse for BorderImageSlice {
273 fn parse<'i, 't>(
274 context: &ParserContext,
275 input: &mut Parser<'i, 't>,
276 ) -> Result<Self, ParseError<'i>> {
277 let mut fill = input.try_parse(|i| i.expect_ident_matching("fill")).is_ok();
278 let offsets = Rect::parse_with(context, input, NonNegativeNumberOrPercentage::parse)?;
279 if !fill {
280 fill = input.try_parse(|i| i.expect_ident_matching("fill")).is_ok();
281 }
282 Ok(GenericBorderImageSlice { offsets, fill })
283 }
284}
285
286impl Parse for BorderRadius {
287 fn parse<'i, 't>(
288 context: &ParserContext,
289 input: &mut Parser<'i, 't>,
290 ) -> Result<Self, ParseError<'i>> {
291 let widths = Rect::parse_with(context, input, NonNegativeLengthPercentage::parse)?;
292 let heights = if input.try_parse(|i| i.expect_delim('/')).is_ok() {
293 Rect::parse_with(context, input, NonNegativeLengthPercentage::parse)?
294 } else {
295 widths.clone()
296 };
297
298 Ok(GenericBorderRadius {
299 top_left: BorderCornerRadius::new(widths.0, heights.0),
300 top_right: BorderCornerRadius::new(widths.1, heights.1),
301 bottom_right: BorderCornerRadius::new(widths.2, heights.2),
302 bottom_left: BorderCornerRadius::new(widths.3, heights.3),
303 })
304 }
305}
306
307impl Parse for BorderCornerRadius {
308 fn parse<'i, 't>(
309 context: &ParserContext,
310 input: &mut Parser<'i, 't>,
311 ) -> Result<Self, ParseError<'i>> {
312 Size2D::parse_with(context, input, NonNegativeLengthPercentage::parse)
313 .map(GenericBorderCornerRadius)
314 }
315}
316
317impl Parse for BorderSpacing {
318 fn parse<'i, 't>(
319 context: &ParserContext,
320 input: &mut Parser<'i, 't>,
321 ) -> Result<Self, ParseError<'i>> {
322 Size2D::parse_with(context, input, |context, input| {
323 NonNegativeLength::parse_quirky(context, input, AllowQuirks::Yes)
324 })
325 .map(GenericBorderSpacing)
326 }
327}
328
329#[allow(missing_docs)]
331#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))]
332#[derive(
333 Clone,
334 Copy,
335 Debug,
336 Eq,
337 MallocSizeOf,
338 Parse,
339 PartialEq,
340 SpecifiedValueInfo,
341 ToComputedValue,
342 ToCss,
343 ToResolvedValue,
344 ToShmem,
345)]
346#[repr(u8)]
347pub enum BorderImageRepeatKeyword {
348 Stretch,
349 Repeat,
350 Round,
351 Space,
352}
353
354#[derive(
358 Clone,
359 Copy,
360 Debug,
361 MallocSizeOf,
362 PartialEq,
363 SpecifiedValueInfo,
364 ToComputedValue,
365 ToResolvedValue,
366 ToShmem,
367 ToTyped,
368)]
369#[repr(C)]
370pub struct BorderImageRepeat(pub BorderImageRepeatKeyword, pub BorderImageRepeatKeyword);
371
372impl ToCss for BorderImageRepeat {
373 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
374 where
375 W: Write,
376 {
377 self.0.to_css(dest)?;
378 if self.0 != self.1 {
379 dest.write_char(' ')?;
380 self.1.to_css(dest)?;
381 }
382 Ok(())
383 }
384}
385
386impl BorderImageRepeat {
387 #[inline]
389 pub fn stretch() -> Self {
390 BorderImageRepeat(
391 BorderImageRepeatKeyword::Stretch,
392 BorderImageRepeatKeyword::Stretch,
393 )
394 }
395}
396
397impl Parse for BorderImageRepeat {
398 fn parse<'i, 't>(
399 _context: &ParserContext,
400 input: &mut Parser<'i, 't>,
401 ) -> Result<Self, ParseError<'i>> {
402 let horizontal = BorderImageRepeatKeyword::parse(input)?;
403 let vertical = input.try_parse(BorderImageRepeatKeyword::parse).ok();
404 Ok(BorderImageRepeat(
405 horizontal,
406 vertical.unwrap_or(horizontal),
407 ))
408 }
409}
410
411pub fn serialize_directional_border<W>(
413 dest: &mut CssWriter<W>,
414 width: &BorderSideWidth,
415 style: &BorderStyle,
416 color: &Color,
417) -> fmt::Result
418where
419 W: Write,
420{
421 let has_style = *style != BorderStyle::None;
422 let has_color = *color != Color::CurrentColor;
423 let has_width = *width != BorderSideWidth::medium();
424 if !has_style && !has_color && !has_width {
425 return width.to_css(dest);
426 }
427 let mut writer = SequenceWriter::new(dest, " ");
428 if has_width {
429 writer.item(width)?;
430 }
431 if has_style {
432 writer.item(style)?;
433 }
434 if has_color {
435 writer.item(color)?;
436 }
437 Ok(())
438}