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