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