1use super::computed::transform::DirectionVector;
10use super::computed::{Context, ToComputedValue};
11use super::generics::grid::ImplicitGridTracks as GenericImplicitGridTracks;
12use super::generics::grid::{GridLine as GenericGridLine, TrackBreadth as GenericTrackBreadth};
13use super::generics::grid::{TrackList as GenericTrackList, TrackSize as GenericTrackSize};
14use super::generics::transform::IsParallelTo;
15use super::generics::{self, GreaterThanOrEqualToOne, NonNegative};
16use super::{CSSFloat, CSSInteger};
17use crate::context::QuirksMode;
18use crate::derives::*;
19use crate::parser::{Parse, ParserContext};
20use crate::values::specified::calc::CalcNode;
21use crate::values::{serialize_atom_identifier, serialize_number, AtomString};
22use crate::{Atom, Namespace, One, Prefix, Zero};
23use cssparser::{Parser, Token};
24use std::fmt::{self, Write};
25use std::ops::Add;
26use style_traits::values::specified::AllowedNumericType;
27use style_traits::{
28 CssString, CssWriter, NumericValue, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss,
29 ToTyped, TypedValue,
30};
31
32pub use self::align::{ContentDistribution, ItemPlacement, JustifyItems, SelfAlignment};
33pub use self::angle::{AllowUnitlessZeroAngle, Angle};
34pub use self::animation::{
35 AnimationComposition, AnimationDirection, AnimationDuration, AnimationFillMode,
36 AnimationIterationCount, AnimationName, AnimationPlayState, AnimationTimeline, ScrollAxis,
37 TimelineName, TransitionBehavior, TransitionProperty, ViewTimelineInset, ViewTransitionClass,
38 ViewTransitionName,
39};
40pub use self::background::{BackgroundRepeat, BackgroundSize};
41pub use self::basic_shape::FillRule;
42pub use self::border::{
43 BorderCornerRadius, BorderImageRepeat, BorderImageSideWidth, BorderImageSlice,
44 BorderImageWidth, BorderRadius, BorderSideOffset, BorderSideWidth, BorderSpacing, BorderStyle,
45 LineWidth,
46};
47pub use self::box_::{
48 AlignmentBaseline, Appearance, BaselineShift, BaselineSource, BreakBetween, BreakWithin, Clear,
49 Contain, ContainIntrinsicSize, ContainerName, ContainerType, ContentVisibility, Display, Float,
50 LineClamp, Overflow, OverflowAnchor, OverflowClipMargin, OverscrollBehavior, Perspective,
51 PositionProperty, Resize, ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStop,
52 ScrollSnapStrictness, ScrollSnapType, ScrollbarGutter, TouchAction, WillChange, WillChangeBits,
53 WritingModeProperty, Zoom,
54};
55pub use self::color::{
56 Color, ColorOrAuto, ColorPropertyValue, ColorScheme, ForcedColorAdjust, PrintColorAdjust,
57};
58pub use self::column::ColumnCount;
59pub use self::counters::{Content, ContentItem, CounterIncrement, CounterReset, CounterSet};
60pub use self::easing::TimingFunction;
61pub use self::effects::{BoxShadow, Filter, SimpleShadow};
62pub use self::flex::FlexBasis;
63pub use self::font::{FontFamily, FontLanguageOverride, FontPalette, FontStyle};
64pub use self::font::{FontFeatureSettings, FontVariantLigatures, FontVariantNumeric};
65pub use self::font::{
66 FontSize, FontSizeAdjust, FontSizeAdjustFactor, FontSizeKeyword, FontStretch, FontSynthesis,
67 FontSynthesisStyle,
68};
69pub use self::font::{FontVariantAlternates, FontWeight};
70pub use self::font::{FontVariantEastAsian, FontVariationSettings, LineHeight};
71pub use self::font::{MathDepth, MozScriptMinSize, MozScriptSizeMultiplier, XLang, XTextScale};
72pub use self::image::{EndingShape as GradientEndingShape, Gradient, Image, ImageRendering};
73pub use self::length::{AbsoluteLength, CalcLengthPercentage, CharacterWidth};
74pub use self::length::{FontRelativeLength, Length, LengthOrNumber, NonNegativeLengthOrNumber};
75pub use self::length::{LengthOrAuto, LengthPercentage, LengthPercentageOrAuto};
76pub use self::length::{Margin, MaxSize, Size};
77pub use self::length::{NoCalcLength, ViewportPercentageLength, ViewportVariant};
78pub use self::length::{
79 NonNegativeLength, NonNegativeLengthPercentage, NonNegativeLengthPercentageOrAuto,
80};
81pub use self::list::ListStyleType;
82pub use self::list::Quotes;
83pub use self::motion::{OffsetPath, OffsetPosition, OffsetRotate};
84pub use self::outline::OutlineStyle;
85pub use self::page::{PageName, PageOrientation, PageSize, PageSizeOrientation, PaperSize};
86pub use self::percentage::{NonNegativePercentage, Percentage};
87pub use self::position::AnchorFunction;
88pub use self::position::AnchorName;
89pub use self::position::AnchorNameIdent;
90pub use self::position::AnchorScope;
91pub use self::position::AnchorScopeKeyword;
92pub use self::position::AspectRatio;
93pub use self::position::Inset;
94pub use self::position::PositionAnchor;
95pub use self::position::PositionAnchorKeyword;
96pub use self::position::PositionTryFallbacks;
97pub use self::position::PositionTryOrder;
98pub use self::position::PositionVisibility;
99pub use self::position::{GridAutoFlow, GridTemplateAreas, Position, PositionOrAuto};
100pub use self::position::{MasonryAutoFlow, MasonryItemOrder, MasonryPlacement};
101pub use self::position::{PositionArea, PositionAreaKeyword};
102pub use self::position::{PositionComponent, ZIndex};
103pub use self::ratio::Ratio;
104pub use self::rect::NonNegativeLengthOrNumberRect;
105pub use self::resolution::Resolution;
106pub use self::svg::{DProperty, MozContextProperties};
107pub use self::svg::{SVGLength, SVGOpacity, SVGPaint};
108pub use self::svg::{SVGPaintOrder, SVGStrokeDashArray, SVGWidth, VectorEffect};
109pub use self::svg_path::SVGPathData;
110pub use self::text::RubyPosition;
111pub use self::text::{HyphenateCharacter, HyphenateLimitChars};
112pub use self::text::{InitialLetter, LetterSpacing, LineBreak, TextAlign, TextIndent};
113pub use self::text::{OverflowWrap, TextEmphasisPosition, TextEmphasisStyle, WordBreak};
114pub use self::text::{TextAlignKeyword, TextDecorationLine, TextOverflow, WordSpacing};
115pub use self::text::{TextAlignLast, TextAutospace, TextUnderlinePosition};
116pub use self::text::{
117 TextDecorationInset, TextDecorationLength, TextDecorationSkipInk, TextJustify, TextTransform,
118};
119pub use self::time::Time;
120pub use self::transform::{Rotate, Scale, Transform};
121pub use self::transform::{TransformBox, TransformOrigin, TransformStyle, Translate};
122#[cfg(feature = "gecko")]
123pub use self::ui::CursorImage;
124pub use self::ui::{
125 BoolInteger, Cursor, Inert, MozTheme, PointerEvents, ScrollbarColor, UserFocus, UserSelect,
126};
127pub use super::generics::grid::GridTemplateComponent as GenericGridTemplateComponent;
128
129pub mod align;
130pub mod angle;
131pub mod animation;
132pub mod background;
133pub mod basic_shape;
134pub mod border;
135#[path = "box.rs"]
136pub mod box_;
137pub mod calc;
138pub mod color;
139pub mod column;
140pub mod counters;
141pub mod easing;
142pub mod effects;
143pub mod flex;
144pub mod font;
145pub mod grid;
146pub mod image;
147pub mod intersection_observer;
148pub mod length;
149pub mod list;
150pub mod motion;
151pub mod outline;
152pub mod page;
153pub mod percentage;
154pub mod position;
155pub mod ratio;
156pub mod rect;
157pub mod resolution;
158pub mod source_size_list;
159pub mod svg;
160pub mod svg_path;
161pub mod table;
162pub mod text;
163pub mod time;
164pub mod transform;
165pub mod ui;
166pub mod url;
167
168#[allow(missing_docs)]
171#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
172pub enum AngleOrPercentage {
173 Percentage(Percentage),
174 Angle(Angle),
175}
176
177impl AngleOrPercentage {
178 fn parse_internal<'i, 't>(
179 context: &ParserContext,
180 input: &mut Parser<'i, 't>,
181 allow_unitless_zero: AllowUnitlessZeroAngle,
182 ) -> Result<Self, ParseError<'i>> {
183 if let Ok(per) = input.try_parse(|i| Percentage::parse(context, i)) {
184 return Ok(AngleOrPercentage::Percentage(per));
185 }
186
187 Angle::parse_internal(context, input, allow_unitless_zero).map(AngleOrPercentage::Angle)
188 }
189
190 pub fn parse_with_unitless<'i, 't>(
193 context: &ParserContext,
194 input: &mut Parser<'i, 't>,
195 ) -> Result<Self, ParseError<'i>> {
196 AngleOrPercentage::parse_internal(context, input, AllowUnitlessZeroAngle::Yes)
197 }
198}
199
200impl Parse for AngleOrPercentage {
201 fn parse<'i, 't>(
202 context: &ParserContext,
203 input: &mut Parser<'i, 't>,
204 ) -> Result<Self, ParseError<'i>> {
205 AngleOrPercentage::parse_internal(context, input, AllowUnitlessZeroAngle::No)
206 }
207}
208
209fn parse_number_with_clamping_mode<'i, 't>(
211 context: &ParserContext,
212 input: &mut Parser<'i, 't>,
213 clamping_mode: AllowedNumericType,
214) -> Result<Number, ParseError<'i>> {
215 let location = input.current_source_location();
216 match *input.next()? {
217 Token::Number { value, .. } if clamping_mode.is_ok(context.parsing_mode, value) => {
218 Ok(Number {
219 value,
220 calc_clamping_mode: None,
221 })
222 },
223 Token::Function(ref name) => {
224 let function = CalcNode::math_function(context, name, location)?;
225 let value = CalcNode::parse_number(context, input, function)?;
226 Ok(Number {
227 value,
228 calc_clamping_mode: Some(clamping_mode),
229 })
230 },
231 ref t => Err(location.new_unexpected_token_error(t.clone())),
232 }
233}
234
235#[derive(Clone, Copy, Debug, MallocSizeOf, PartialOrd, ToShmem)]
239pub struct Number {
240 value: CSSFloat,
242 calc_clamping_mode: Option<AllowedNumericType>,
245}
246
247impl Parse for Number {
248 fn parse<'i, 't>(
249 context: &ParserContext,
250 input: &mut Parser<'i, 't>,
251 ) -> Result<Self, ParseError<'i>> {
252 parse_number_with_clamping_mode(context, input, AllowedNumericType::All)
253 }
254}
255
256impl PartialEq<Number> for Number {
257 fn eq(&self, other: &Number) -> bool {
258 if self.calc_clamping_mode != other.calc_clamping_mode {
259 return false;
260 }
261
262 self.value == other.value || (self.value.is_nan() && other.value.is_nan())
263 }
264}
265
266impl Number {
267 #[inline]
269 fn new_with_clamping_mode(
270 value: CSSFloat,
271 calc_clamping_mode: Option<AllowedNumericType>,
272 ) -> Self {
273 Self {
274 value,
275 calc_clamping_mode,
276 }
277 }
278
279 pub fn to_percentage(&self) -> Percentage {
281 Percentage::new_with_clamping_mode(self.value, self.calc_clamping_mode)
282 }
283
284 #[inline]
286 pub fn new(val: CSSFloat) -> Self {
287 Self::new_with_clamping_mode(val, None)
288 }
289
290 #[inline]
292 pub fn was_calc(&self) -> bool {
293 self.calc_clamping_mode.is_some()
294 }
295
296 #[inline]
298 pub fn get(&self) -> f32 {
299 crate::values::normalize(
300 self.calc_clamping_mode
301 .map_or(self.value, |mode| mode.clamp(self.value)),
302 )
303 .min(f32::MAX)
304 .max(f32::MIN)
305 }
306
307 #[allow(missing_docs)]
308 pub fn parse_non_negative<'i, 't>(
309 context: &ParserContext,
310 input: &mut Parser<'i, 't>,
311 ) -> Result<Number, ParseError<'i>> {
312 parse_number_with_clamping_mode(context, input, AllowedNumericType::NonNegative)
313 }
314
315 #[allow(missing_docs)]
316 pub fn parse_at_least_one<'i, 't>(
317 context: &ParserContext,
318 input: &mut Parser<'i, 't>,
319 ) -> Result<Number, ParseError<'i>> {
320 parse_number_with_clamping_mode(context, input, AllowedNumericType::AtLeastOne)
321 }
322
323 #[inline]
325 pub fn clamp_to_one(self) -> Self {
326 Number {
327 value: self.value.min(1.),
328 calc_clamping_mode: self.calc_clamping_mode,
329 }
330 }
331}
332
333impl ToComputedValue for Number {
334 type ComputedValue = CSSFloat;
335
336 #[inline]
337 fn to_computed_value(&self, _: &Context) -> CSSFloat {
338 self.get()
339 }
340
341 #[inline]
342 fn from_computed_value(computed: &CSSFloat) -> Self {
343 Number {
344 value: *computed,
345 calc_clamping_mode: None,
346 }
347 }
348}
349
350impl ToCss for Number {
351 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
352 where
353 W: Write,
354 {
355 serialize_number(self.value, self.calc_clamping_mode.is_some(), dest)
356 }
357}
358
359impl ToTyped for Number {
360 fn to_typed(&self) -> Option<TypedValue> {
361 let value = self.value;
362 let unit = CssString::from("number");
363 Some(TypedValue::Numeric(NumericValue::Unit { value, unit }))
364 }
365}
366
367impl IsParallelTo for (Number, Number, Number) {
368 fn is_parallel_to(&self, vector: &DirectionVector) -> bool {
369 use euclid::approxeq::ApproxEq;
370 let self_vector = DirectionVector::new(self.0.get(), self.1.get(), self.2.get());
373 self_vector
374 .cross(*vector)
375 .square_length()
376 .approx_eq(&0.0f32)
377 }
378}
379
380impl SpecifiedValueInfo for Number {}
381
382impl Add for Number {
383 type Output = Self;
384
385 fn add(self, other: Self) -> Self {
386 Self::new(self.get() + other.get())
387 }
388}
389
390impl Zero for Number {
391 #[inline]
392 fn zero() -> Self {
393 Self::new(0.)
394 }
395
396 #[inline]
397 fn is_zero(&self) -> bool {
398 self.get() == 0.
399 }
400}
401
402impl From<Number> for f32 {
403 #[inline]
404 fn from(n: Number) -> Self {
405 n.get()
406 }
407}
408
409impl From<Number> for f64 {
410 #[inline]
411 fn from(n: Number) -> Self {
412 n.get() as f64
413 }
414}
415
416pub type NonNegativeNumber = NonNegative<Number>;
418
419impl Parse for NonNegativeNumber {
420 fn parse<'i, 't>(
421 context: &ParserContext,
422 input: &mut Parser<'i, 't>,
423 ) -> Result<Self, ParseError<'i>> {
424 parse_number_with_clamping_mode(context, input, AllowedNumericType::NonNegative)
425 .map(NonNegative::<Number>)
426 }
427}
428
429impl One for NonNegativeNumber {
430 #[inline]
431 fn one() -> Self {
432 NonNegativeNumber::new(1.0)
433 }
434
435 #[inline]
436 fn is_one(&self) -> bool {
437 self.get() == 1.0
438 }
439}
440
441impl NonNegativeNumber {
442 pub fn new(val: CSSFloat) -> Self {
444 NonNegative::<Number>(Number::new(val.max(0.)))
445 }
446
447 #[inline]
449 pub fn get(&self) -> f32 {
450 self.0.get()
451 }
452}
453
454pub type NonNegativeInteger = NonNegative<Integer>;
456
457impl Parse for NonNegativeInteger {
458 fn parse<'i, 't>(
459 context: &ParserContext,
460 input: &mut Parser<'i, 't>,
461 ) -> Result<Self, ParseError<'i>> {
462 Ok(NonNegative(Integer::parse_non_negative(context, input)?))
463 }
464}
465
466pub type GreaterThanOrEqualToOneNumber = GreaterThanOrEqualToOne<Number>;
468
469impl Parse for GreaterThanOrEqualToOneNumber {
470 fn parse<'i, 't>(
471 context: &ParserContext,
472 input: &mut Parser<'i, 't>,
473 ) -> Result<Self, ParseError<'i>> {
474 parse_number_with_clamping_mode(context, input, AllowedNumericType::AtLeastOne)
475 .map(GreaterThanOrEqualToOne::<Number>)
476 }
477}
478
479#[allow(missing_docs)]
483#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
484pub enum NumberOrPercentage {
485 Percentage(Percentage),
486 Number(Number),
487}
488
489impl NumberOrPercentage {
490 fn parse_with_clamping_mode<'i, 't>(
491 context: &ParserContext,
492 input: &mut Parser<'i, 't>,
493 type_: AllowedNumericType,
494 ) -> Result<Self, ParseError<'i>> {
495 if let Ok(per) =
496 input.try_parse(|i| Percentage::parse_with_clamping_mode(context, i, type_))
497 {
498 return Ok(NumberOrPercentage::Percentage(per));
499 }
500
501 parse_number_with_clamping_mode(context, input, type_).map(NumberOrPercentage::Number)
502 }
503
504 pub fn parse_non_negative<'i, 't>(
506 context: &ParserContext,
507 input: &mut Parser<'i, 't>,
508 ) -> Result<Self, ParseError<'i>> {
509 Self::parse_with_clamping_mode(context, input, AllowedNumericType::NonNegative)
510 }
511
512 pub fn to_percentage(self) -> Percentage {
514 match self {
515 Self::Percentage(p) => p,
516 Self::Number(n) => n.to_percentage(),
517 }
518 }
519
520 pub fn to_number(self) -> Number {
522 match self {
523 Self::Percentage(p) => p.to_number(),
524 Self::Number(n) => n,
525 }
526 }
527}
528
529impl Parse for NumberOrPercentage {
530 fn parse<'i, 't>(
531 context: &ParserContext,
532 input: &mut Parser<'i, 't>,
533 ) -> Result<Self, ParseError<'i>> {
534 Self::parse_with_clamping_mode(context, input, AllowedNumericType::All)
535 }
536}
537
538pub type NonNegativeNumberOrPercentage = NonNegative<NumberOrPercentage>;
540
541impl NonNegativeNumberOrPercentage {
542 #[inline]
544 pub fn hundred_percent() -> Self {
545 NonNegative(NumberOrPercentage::Percentage(Percentage::hundred()))
546 }
547
548 #[inline]
550 pub fn new_number(n: f32) -> Self {
551 NonNegative(NumberOrPercentage::Number(Number::new(n)))
552 }
553}
554
555impl Parse for NonNegativeNumberOrPercentage {
556 fn parse<'i, 't>(
557 context: &ParserContext,
558 input: &mut Parser<'i, 't>,
559 ) -> Result<Self, ParseError<'i>> {
560 Ok(NonNegative(NumberOrPercentage::parse_non_negative(
561 context, input,
562 )?))
563 }
564}
565
566#[derive(
570 Clone,
571 Copy,
572 Debug,
573 MallocSizeOf,
574 PartialEq,
575 PartialOrd,
576 SpecifiedValueInfo,
577 ToCss,
578 ToShmem,
579 ToTyped,
580)]
581pub struct Opacity(Number);
582
583impl Parse for Opacity {
584 fn parse<'i, 't>(
588 context: &ParserContext,
589 input: &mut Parser<'i, 't>,
590 ) -> Result<Self, ParseError<'i>> {
591 let number = NumberOrPercentage::parse(context, input)?.to_number();
592 Ok(Opacity(number))
593 }
594}
595
596impl ToComputedValue for Opacity {
597 type ComputedValue = CSSFloat;
598
599 #[inline]
600 fn to_computed_value(&self, context: &Context) -> CSSFloat {
601 let value = self.0.to_computed_value(context);
602 if context.for_smil_animation {
603 value
606 } else {
607 value.min(1.0).max(0.0)
608 }
609 }
610
611 #[inline]
612 fn from_computed_value(computed: &CSSFloat) -> Self {
613 Opacity(Number::from_computed_value(computed))
614 }
615}
616
617#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd, ToShmem, ToTyped)]
623pub enum Integer {
624 Literal(CSSInteger),
626 Calc(CSSFloat),
628}
629
630impl Zero for Integer {
631 #[inline]
632 fn zero() -> Self {
633 Self::new(0)
634 }
635
636 #[inline]
637 fn is_zero(&self) -> bool {
638 *self == 0
639 }
640}
641
642impl One for Integer {
643 #[inline]
644 fn one() -> Self {
645 Self::new(1)
646 }
647
648 #[inline]
649 fn is_one(&self) -> bool {
650 *self == 1
651 }
652}
653
654impl PartialEq<i32> for Integer {
655 fn eq(&self, value: &i32) -> bool {
656 self.value() == *value
657 }
658}
659
660impl Integer {
661 pub fn new(val: CSSInteger) -> Self {
663 Self::Literal(val)
664 }
665
666 pub fn value(&self) -> CSSInteger {
668 match *self {
669 Self::Literal(i) => i,
670 Self::Calc(n) => (n + 0.5).floor() as CSSInteger,
671 }
672 }
673
674 fn from_calc(val: CSSFloat) -> Self {
676 Self::Calc(val)
677 }
678}
679
680impl Parse for Integer {
681 fn parse<'i, 't>(
682 context: &ParserContext,
683 input: &mut Parser<'i, 't>,
684 ) -> Result<Self, ParseError<'i>> {
685 let location = input.current_source_location();
686 match *input.next()? {
687 Token::Number {
688 int_value: Some(v), ..
689 } => Ok(Integer::new(v)),
690 Token::Function(ref name) => {
691 let function = CalcNode::math_function(context, name, location)?;
692 let result = CalcNode::parse_number(context, input, function)?;
693 Ok(Integer::from_calc(result))
694 },
695 ref t => Err(location.new_unexpected_token_error(t.clone())),
696 }
697 }
698}
699
700impl Integer {
701 pub fn parse_with_minimum<'i, 't>(
703 context: &ParserContext,
704 input: &mut Parser<'i, 't>,
705 min: i32,
706 ) -> Result<Integer, ParseError<'i>> {
707 let value = Integer::parse(context, input)?;
708 if value.value() < min {
714 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
715 }
716 Ok(value)
717 }
718
719 pub fn parse_non_negative<'i, 't>(
721 context: &ParserContext,
722 input: &mut Parser<'i, 't>,
723 ) -> Result<Integer, ParseError<'i>> {
724 Integer::parse_with_minimum(context, input, 0)
725 }
726
727 pub fn parse_positive<'i, 't>(
729 context: &ParserContext,
730 input: &mut Parser<'i, 't>,
731 ) -> Result<Integer, ParseError<'i>> {
732 Integer::parse_with_minimum(context, input, 1)
733 }
734}
735
736impl ToComputedValue for Integer {
737 type ComputedValue = i32;
738
739 #[inline]
740 fn to_computed_value(&self, _: &Context) -> i32 {
741 self.value()
742 }
743
744 #[inline]
745 fn from_computed_value(computed: &i32) -> Self {
746 Integer::new(*computed)
747 }
748}
749
750impl ToCss for Integer {
751 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
752 where
753 W: Write,
754 {
755 match *self {
756 Integer::Literal(i) => i.to_css(dest),
757 Integer::Calc(n) => {
758 dest.write_str("calc(")?;
759 n.to_css(dest)?;
760 dest.write_char(')')
761 },
762 }
763 }
764}
765
766impl SpecifiedValueInfo for Integer {}
767
768pub type PositiveInteger = GreaterThanOrEqualToOne<Integer>;
770
771impl Parse for PositiveInteger {
772 #[inline]
773 fn parse<'i, 't>(
774 context: &ParserContext,
775 input: &mut Parser<'i, 't>,
776 ) -> Result<Self, ParseError<'i>> {
777 Integer::parse_positive(context, input).map(GreaterThanOrEqualToOne)
778 }
779}
780
781pub type TrackBreadth = GenericTrackBreadth<LengthPercentage>;
783
784pub type TrackSize = GenericTrackSize<LengthPercentage>;
786
787pub type ImplicitGridTracks = GenericImplicitGridTracks<TrackSize>;
789
790pub type TrackList = GenericTrackList<LengthPercentage, Integer>;
793
794pub type GridLine = GenericGridLine<Integer>;
796
797pub type GridTemplateComponent = GenericGridTemplateComponent<LengthPercentage, Integer>;
799
800pub type ClipRect = generics::GenericClipRect<LengthOrAuto>;
802
803impl Parse for ClipRect {
804 fn parse<'i, 't>(
805 context: &ParserContext,
806 input: &mut Parser<'i, 't>,
807 ) -> Result<Self, ParseError<'i>> {
808 Self::parse_quirky(context, input, AllowQuirks::No)
809 }
810}
811
812impl ClipRect {
813 fn parse_quirky<'i, 't>(
815 context: &ParserContext,
816 input: &mut Parser<'i, 't>,
817 allow_quirks: AllowQuirks,
818 ) -> Result<Self, ParseError<'i>> {
819 input.expect_function_matching("rect")?;
820
821 fn parse_argument<'i, 't>(
822 context: &ParserContext,
823 input: &mut Parser<'i, 't>,
824 allow_quirks: AllowQuirks,
825 ) -> Result<LengthOrAuto, ParseError<'i>> {
826 LengthOrAuto::parse_quirky(context, input, allow_quirks)
827 }
828
829 input.parse_nested_block(|input| {
830 let top = parse_argument(context, input, allow_quirks)?;
831 let right;
832 let bottom;
833 let left;
834
835 if input.try_parse(|input| input.expect_comma()).is_ok() {
836 right = parse_argument(context, input, allow_quirks)?;
837 input.expect_comma()?;
838 bottom = parse_argument(context, input, allow_quirks)?;
839 input.expect_comma()?;
840 left = parse_argument(context, input, allow_quirks)?;
841 } else {
842 right = parse_argument(context, input, allow_quirks)?;
843 bottom = parse_argument(context, input, allow_quirks)?;
844 left = parse_argument(context, input, allow_quirks)?;
845 }
846
847 Ok(ClipRect {
848 top,
849 right,
850 bottom,
851 left,
852 })
853 })
854 }
855}
856
857pub type ClipRectOrAuto = generics::GenericClipRectOrAuto<ClipRect>;
859
860impl ClipRectOrAuto {
861 pub fn parse_quirky<'i, 't>(
863 context: &ParserContext,
864 input: &mut Parser<'i, 't>,
865 allow_quirks: AllowQuirks,
866 ) -> Result<Self, ParseError<'i>> {
867 if let Ok(v) = input.try_parse(|i| ClipRect::parse_quirky(context, i, allow_quirks)) {
868 return Ok(generics::GenericClipRectOrAuto::Rect(v));
869 }
870 input.expect_ident_matching("auto")?;
871 Ok(generics::GenericClipRectOrAuto::Auto)
872 }
873}
874
875#[derive(Clone, Copy, PartialEq)]
877pub enum AllowQuirks {
878 No,
880 Yes,
882 Always,
884}
885
886impl AllowQuirks {
887 pub fn allowed(self, quirks_mode: QuirksMode) -> bool {
889 match self {
890 AllowQuirks::Always => true,
891 AllowQuirks::No => false,
892 AllowQuirks::Yes => quirks_mode == QuirksMode::Quirks,
893 }
894 }
895}
896
897#[derive(
901 Clone,
902 Debug,
903 Eq,
904 MallocSizeOf,
905 PartialEq,
906 SpecifiedValueInfo,
907 ToComputedValue,
908 ToResolvedValue,
909 ToShmem,
910)]
911#[css(function)]
912#[repr(C)]
913pub struct Attr {
914 pub namespace_prefix: Prefix,
916 pub namespace_url: Namespace,
918 pub attribute: Atom,
920 pub fallback: AtomString,
922}
923
924impl Parse for Attr {
925 fn parse<'i, 't>(
926 context: &ParserContext,
927 input: &mut Parser<'i, 't>,
928 ) -> Result<Attr, ParseError<'i>> {
929 input.expect_function_matching("attr")?;
930 input.parse_nested_block(|i| Attr::parse_function(context, i))
931 }
932}
933
934fn get_namespace_for_prefix(prefix: &Prefix, context: &ParserContext) -> Option<Namespace> {
936 context.namespaces.prefixes.get(prefix).cloned()
937}
938
939fn parse_namespace<'i, 't>(
941 context: &ParserContext,
942 input: &mut Parser<'i, 't>,
943) -> Result<(Prefix, Namespace), ParseError<'i>> {
944 let ns_prefix = match input.next()? {
945 Token::Ident(ref prefix) => Some(Prefix::from(prefix.as_ref())),
946 Token::Delim('|') => None,
947 _ => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
948 };
949
950 if ns_prefix.is_some() && !matches!(*input.next_including_whitespace()?, Token::Delim('|')) {
951 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
952 }
953
954 if let Some(prefix) = ns_prefix {
955 let ns = match get_namespace_for_prefix(&prefix, context) {
956 Some(ns) => ns,
957 None => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
958 };
959 Ok((prefix, ns))
960 } else {
961 Ok((Prefix::default(), Namespace::default()))
962 }
963}
964
965impl Attr {
966 pub fn parse_function<'i, 't>(
969 context: &ParserContext,
970 input: &mut Parser<'i, 't>,
971 ) -> Result<Attr, ParseError<'i>> {
972 let namespace = input
974 .try_parse(|input| parse_namespace(context, input))
975 .ok();
976 let namespace_is_some = namespace.is_some();
977 let (namespace_prefix, namespace_url) = namespace.unwrap_or_default();
978
979 let attribute = Atom::from(if namespace_is_some {
981 let location = input.current_source_location();
982 match *input.next_including_whitespace()? {
983 Token::Ident(ref ident) => ident.as_ref(),
984 ref t => return Err(location.new_unexpected_token_error(t.clone())),
985 }
986 } else {
987 input.expect_ident()?.as_ref()
988 });
989
990 let fallback = input
993 .try_parse(|input| -> Result<AtomString, ParseError<'i>> {
994 input.expect_comma()?;
995 Ok(input.expect_string()?.as_ref().into())
996 })
997 .unwrap_or_default();
998
999 Ok(Attr {
1000 namespace_prefix,
1001 namespace_url,
1002 attribute,
1003 fallback,
1004 })
1005 }
1006}
1007
1008impl ToCss for Attr {
1009 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1010 where
1011 W: Write,
1012 {
1013 dest.write_str("attr(")?;
1014 if !self.namespace_prefix.is_empty() {
1015 serialize_atom_identifier(&self.namespace_prefix, dest)?;
1016 dest.write_char('|')?;
1017 }
1018 serialize_atom_identifier(&self.attribute, dest)?;
1019
1020 if !self.fallback.is_empty() {
1021 dest.write_str(", ")?;
1022 self.fallback.to_css(dest)?;
1023 }
1024
1025 dest.write_char(')')
1026 }
1027}