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