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 rustc_hash::FxHashMap;
25use std::fmt::{self, Write};
26use std::ops::Add;
27use style_traits::values::specified::AllowedNumericType;
28use style_traits::{
29 CssString, CssWriter, NumericValue, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss,
30 ToTyped, TypedValue, UnitValue,
31};
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::AnchorFunction;
89pub use self::position::AnchorName;
90pub use self::position::AnchorNameIdent;
91pub use self::position::AspectRatio;
92pub use self::position::Inset;
93pub use self::position::PositionAnchor;
94pub use self::position::PositionAnchorKeyword;
95pub use self::position::PositionTryFallbacks;
96pub use self::position::PositionTryOrder;
97pub use self::position::PositionVisibility;
98pub use self::position::ScopedName;
99pub use self::position::ScopedNameKeyword;
100pub use self::position::{GridAutoFlow, GridTemplateAreas, Position, PositionOrAuto};
101pub use self::position::{MasonryAutoFlow, MasonryItemOrder, MasonryPlacement};
102pub use self::position::{PositionArea, PositionAreaKeyword};
103pub use self::position::{PositionComponent, ZIndex};
104pub use self::ratio::Ratio;
105pub use self::rect::NonNegativeLengthOrNumberRect;
106pub use self::resolution::Resolution;
107pub use self::svg::{DProperty, MozContextProperties};
108pub use self::svg::{SVGLength, SVGOpacity, SVGPaint};
109pub use self::svg::{SVGPaintOrder, SVGStrokeDashArray, SVGWidth, VectorEffect};
110pub use self::svg_path::SVGPathData;
111pub use self::text::RubyPosition;
112pub use self::text::{HyphenateCharacter, HyphenateLimitChars};
113pub use self::text::{InitialLetter, LetterSpacing, LineBreak, TextAlign, TextIndent};
114pub use self::text::{OverflowWrap, TextEmphasisPosition, TextEmphasisStyle, WordBreak};
115pub use self::text::{TextAlignKeyword, TextDecorationLine, TextOverflow, WordSpacing};
116pub use self::text::{TextAlignLast, TextAutospace, TextUnderlinePosition};
117pub use self::text::{TextBoxEdge, TextBoxTrim};
118pub use self::text::{
119 TextDecorationInset, TextDecorationLength, TextDecorationSkipInk, TextJustify, TextTransform,
120};
121pub use self::time::Time;
122pub use self::transform::{Rotate, Scale, Transform};
123pub use self::transform::{TransformBox, TransformOrigin, TransformStyle, Translate};
124#[cfg(feature = "gecko")]
125pub use self::ui::CursorImage;
126pub use self::ui::{
127 BoolInteger, Cursor, Inert, MozTheme, PointerEvents, ScrollbarColor, UserFocus, UserSelect,
128};
129pub use super::generics::grid::GridTemplateComponent as GenericGridTemplateComponent;
130
131pub mod align;
132pub mod angle;
133pub mod animation;
134pub mod background;
135pub mod basic_shape;
136pub mod border;
137#[path = "box.rs"]
138pub mod box_;
139pub mod calc;
140pub mod color;
141pub mod column;
142pub mod counters;
143pub mod easing;
144pub mod effects;
145pub mod flex;
146pub mod font;
147pub mod grid;
148pub mod image;
149pub mod intersection_observer;
150pub mod length;
151pub mod list;
152pub mod motion;
153pub mod outline;
154pub mod page;
155pub mod percentage;
156pub mod position;
157pub mod ratio;
158pub mod rect;
159pub mod resolution;
160pub mod source_size_list;
161pub mod svg;
162pub mod svg_path;
163pub mod table;
164pub mod text;
165pub mod time;
166pub mod transform;
167pub mod ui;
168pub mod url;
169
170#[allow(missing_docs)]
173#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
174pub enum AngleOrPercentage {
175 Percentage(Percentage),
176 Angle(Angle),
177}
178
179impl AngleOrPercentage {
180 fn parse_internal<'i, 't>(
181 context: &ParserContext,
182 input: &mut Parser<'i, 't>,
183 allow_unitless_zero: AllowUnitlessZeroAngle,
184 ) -> Result<Self, ParseError<'i>> {
185 if let Ok(per) = input.try_parse(|i| Percentage::parse(context, i)) {
186 return Ok(AngleOrPercentage::Percentage(per));
187 }
188
189 Angle::parse_internal(context, input, allow_unitless_zero).map(AngleOrPercentage::Angle)
190 }
191
192 pub fn parse_with_unitless<'i, 't>(
195 context: &ParserContext,
196 input: &mut Parser<'i, 't>,
197 ) -> Result<Self, ParseError<'i>> {
198 AngleOrPercentage::parse_internal(context, input, AllowUnitlessZeroAngle::Yes)
199 }
200}
201
202impl Parse for AngleOrPercentage {
203 fn parse<'i, 't>(
204 context: &ParserContext,
205 input: &mut Parser<'i, 't>,
206 ) -> Result<Self, ParseError<'i>> {
207 AngleOrPercentage::parse_internal(context, input, AllowUnitlessZeroAngle::No)
208 }
209}
210
211fn parse_number_with_clamping_mode<'i, 't>(
213 context: &ParserContext,
214 input: &mut Parser<'i, 't>,
215 clamping_mode: AllowedNumericType,
216) -> Result<Number, ParseError<'i>> {
217 let location = input.current_source_location();
218 match *input.next()? {
219 Token::Number { value, .. } if clamping_mode.is_ok(context.parsing_mode, value) => {
220 Ok(Number {
221 value,
222 calc_clamping_mode: None,
223 })
224 },
225 Token::Function(ref name) => {
226 let function = CalcNode::math_function(context, name, location)?;
227 let value = CalcNode::parse_number(context, input, function)?;
228 Ok(Number {
229 value,
230 calc_clamping_mode: Some(clamping_mode),
231 })
232 },
233 ref t => Err(location.new_unexpected_token_error(t.clone())),
234 }
235}
236
237#[derive(Clone, Copy, Debug, MallocSizeOf, PartialOrd, ToShmem)]
241pub struct Number {
242 value: CSSFloat,
244 calc_clamping_mode: Option<AllowedNumericType>,
247}
248
249impl Parse for Number {
250 fn parse<'i, 't>(
251 context: &ParserContext,
252 input: &mut Parser<'i, 't>,
253 ) -> Result<Self, ParseError<'i>> {
254 parse_number_with_clamping_mode(context, input, AllowedNumericType::All)
255 }
256}
257
258impl PartialEq<Number> for Number {
259 fn eq(&self, other: &Number) -> bool {
260 if self.calc_clamping_mode != other.calc_clamping_mode {
261 return false;
262 }
263
264 self.value == other.value || (self.value.is_nan() && other.value.is_nan())
265 }
266}
267
268impl Number {
269 #[inline]
271 fn new_with_clamping_mode(
272 value: CSSFloat,
273 calc_clamping_mode: Option<AllowedNumericType>,
274 ) -> Self {
275 Self {
276 value,
277 calc_clamping_mode,
278 }
279 }
280
281 pub fn to_percentage(&self) -> Percentage {
283 Percentage::new_with_clamping_mode(self.value, self.calc_clamping_mode)
284 }
285
286 #[inline]
288 pub fn new(val: CSSFloat) -> Self {
289 Self::new_with_clamping_mode(val, None)
290 }
291
292 #[inline]
294 pub fn was_calc(&self) -> bool {
295 self.calc_clamping_mode.is_some()
296 }
297
298 #[inline]
300 pub fn get(&self) -> f32 {
301 crate::values::normalize(
302 self.calc_clamping_mode
303 .map_or(self.value, |mode| mode.clamp(self.value)),
304 )
305 .min(f32::MAX)
306 .max(f32::MIN)
307 }
308
309 pub fn unit(&self) -> &'static str {
311 "number"
312 }
313
314 pub fn canonical_unit(&self) -> Option<&'static str> {
316 None
317 }
318
319 pub fn to(&self, unit: &str) -> Result<Self, ()> {
322 if !unit.eq_ignore_ascii_case("number") {
323 return Err(());
324 }
325 Ok(Self {
326 value: self.value,
327 calc_clamping_mode: self.calc_clamping_mode,
328 })
329 }
330
331 #[allow(missing_docs)]
332 pub fn parse_non_negative<'i, 't>(
333 context: &ParserContext,
334 input: &mut Parser<'i, 't>,
335 ) -> Result<Number, ParseError<'i>> {
336 parse_number_with_clamping_mode(context, input, AllowedNumericType::NonNegative)
337 }
338
339 #[allow(missing_docs)]
340 pub fn parse_at_least_one<'i, 't>(
341 context: &ParserContext,
342 input: &mut Parser<'i, 't>,
343 ) -> Result<Number, ParseError<'i>> {
344 parse_number_with_clamping_mode(context, input, AllowedNumericType::AtLeastOne)
345 }
346
347 #[inline]
349 pub fn clamp_to_one(self) -> Self {
350 Number {
351 value: self.value.min(1.),
352 calc_clamping_mode: self.calc_clamping_mode,
353 }
354 }
355}
356
357impl ToComputedValue for Number {
358 type ComputedValue = CSSFloat;
359
360 #[inline]
361 fn to_computed_value(&self, _: &Context) -> CSSFloat {
362 self.get()
363 }
364
365 #[inline]
366 fn from_computed_value(computed: &CSSFloat) -> Self {
367 Number {
368 value: *computed,
369 calc_clamping_mode: None,
370 }
371 }
372}
373
374impl ToCss for Number {
375 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
376 where
377 W: Write,
378 {
379 serialize_number(self.value, self.calc_clamping_mode.is_some(), dest)
380 }
381}
382
383impl ToTyped for Number {
384 fn to_typed(&self) -> Option<TypedValue> {
385 let value = self.value;
386 let unit = CssString::from("number");
387 Some(TypedValue::Numeric(NumericValue::Unit(UnitValue {
388 value,
389 unit,
390 })))
391 }
392}
393
394impl IsParallelTo for (Number, Number, Number) {
395 fn is_parallel_to(&self, vector: &DirectionVector) -> bool {
396 use euclid::approxeq::ApproxEq;
397 let self_vector = DirectionVector::new(self.0.get(), self.1.get(), self.2.get());
400 self_vector
401 .cross(*vector)
402 .square_length()
403 .approx_eq(&0.0f32)
404 }
405}
406
407impl SpecifiedValueInfo for Number {}
408
409impl Add for Number {
410 type Output = Self;
411
412 fn add(self, other: Self) -> Self {
413 Self::new(self.get() + other.get())
414 }
415}
416
417impl Zero for Number {
418 #[inline]
419 fn zero() -> Self {
420 Self::new(0.)
421 }
422
423 #[inline]
424 fn is_zero(&self) -> bool {
425 self.get() == 0.
426 }
427}
428
429impl From<Number> for f32 {
430 #[inline]
431 fn from(n: Number) -> Self {
432 n.get()
433 }
434}
435
436impl From<Number> for f64 {
437 #[inline]
438 fn from(n: Number) -> Self {
439 n.get() as f64
440 }
441}
442
443pub type NonNegativeNumber = NonNegative<Number>;
445
446impl Parse for NonNegativeNumber {
447 fn parse<'i, 't>(
448 context: &ParserContext,
449 input: &mut Parser<'i, 't>,
450 ) -> Result<Self, ParseError<'i>> {
451 parse_number_with_clamping_mode(context, input, AllowedNumericType::NonNegative)
452 .map(NonNegative::<Number>)
453 }
454}
455
456impl One for NonNegativeNumber {
457 #[inline]
458 fn one() -> Self {
459 NonNegativeNumber::new(1.0)
460 }
461
462 #[inline]
463 fn is_one(&self) -> bool {
464 self.get() == 1.0
465 }
466}
467
468impl NonNegativeNumber {
469 pub fn new(val: CSSFloat) -> Self {
471 NonNegative::<Number>(Number::new(val.max(0.)))
472 }
473
474 #[inline]
476 pub fn get(&self) -> f32 {
477 self.0.get()
478 }
479}
480
481pub type NonNegativeInteger = NonNegative<Integer>;
483
484impl Parse for NonNegativeInteger {
485 fn parse<'i, 't>(
486 context: &ParserContext,
487 input: &mut Parser<'i, 't>,
488 ) -> Result<Self, ParseError<'i>> {
489 Ok(NonNegative(Integer::parse_non_negative(context, input)?))
490 }
491}
492
493pub type GreaterThanOrEqualToOneNumber = GreaterThanOrEqualToOne<Number>;
495
496impl Parse for GreaterThanOrEqualToOneNumber {
497 fn parse<'i, 't>(
498 context: &ParserContext,
499 input: &mut Parser<'i, 't>,
500 ) -> Result<Self, ParseError<'i>> {
501 parse_number_with_clamping_mode(context, input, AllowedNumericType::AtLeastOne)
502 .map(GreaterThanOrEqualToOne::<Number>)
503 }
504}
505
506#[allow(missing_docs)]
510#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
511pub enum NumberOrPercentage {
512 Percentage(Percentage),
513 Number(Number),
514}
515
516impl NumberOrPercentage {
517 fn parse_with_clamping_mode<'i, 't>(
518 context: &ParserContext,
519 input: &mut Parser<'i, 't>,
520 type_: AllowedNumericType,
521 ) -> Result<Self, ParseError<'i>> {
522 if let Ok(per) =
523 input.try_parse(|i| Percentage::parse_with_clamping_mode(context, i, type_))
524 {
525 return Ok(NumberOrPercentage::Percentage(per));
526 }
527
528 parse_number_with_clamping_mode(context, input, type_).map(NumberOrPercentage::Number)
529 }
530
531 pub fn parse_non_negative<'i, 't>(
533 context: &ParserContext,
534 input: &mut Parser<'i, 't>,
535 ) -> Result<Self, ParseError<'i>> {
536 Self::parse_with_clamping_mode(context, input, AllowedNumericType::NonNegative)
537 }
538
539 pub fn to_percentage(self) -> Percentage {
541 match self {
542 Self::Percentage(p) => p,
543 Self::Number(n) => n.to_percentage(),
544 }
545 }
546
547 pub fn to_number(self) -> Number {
549 match self {
550 Self::Percentage(p) => p.to_number(),
551 Self::Number(n) => n,
552 }
553 }
554}
555
556impl Parse for NumberOrPercentage {
557 fn parse<'i, 't>(
558 context: &ParserContext,
559 input: &mut Parser<'i, 't>,
560 ) -> Result<Self, ParseError<'i>> {
561 Self::parse_with_clamping_mode(context, input, AllowedNumericType::All)
562 }
563}
564
565pub type NonNegativeNumberOrPercentage = NonNegative<NumberOrPercentage>;
567
568impl NonNegativeNumberOrPercentage {
569 #[inline]
571 pub fn hundred_percent() -> Self {
572 NonNegative(NumberOrPercentage::Percentage(Percentage::hundred()))
573 }
574
575 #[inline]
577 pub fn new_number(n: f32) -> Self {
578 NonNegative(NumberOrPercentage::Number(Number::new(n)))
579 }
580}
581
582impl Parse for NonNegativeNumberOrPercentage {
583 fn parse<'i, 't>(
584 context: &ParserContext,
585 input: &mut Parser<'i, 't>,
586 ) -> Result<Self, ParseError<'i>> {
587 Ok(NonNegative(NumberOrPercentage::parse_non_negative(
588 context, input,
589 )?))
590 }
591}
592
593#[derive(
597 Clone,
598 Copy,
599 Debug,
600 MallocSizeOf,
601 PartialEq,
602 PartialOrd,
603 SpecifiedValueInfo,
604 ToCss,
605 ToShmem,
606 ToTyped,
607)]
608pub struct Opacity(Number);
609
610impl Parse for Opacity {
611 fn parse<'i, 't>(
615 context: &ParserContext,
616 input: &mut Parser<'i, 't>,
617 ) -> Result<Self, ParseError<'i>> {
618 let number = NumberOrPercentage::parse(context, input)?.to_number();
619 Ok(Opacity(number))
620 }
621}
622
623impl ToComputedValue for Opacity {
624 type ComputedValue = CSSFloat;
625
626 #[inline]
627 fn to_computed_value(&self, context: &Context) -> CSSFloat {
628 let value = self.0.to_computed_value(context);
629 if context.for_smil_animation {
630 value
633 } else {
634 value.min(1.0).max(0.0)
635 }
636 }
637
638 #[inline]
639 fn from_computed_value(computed: &CSSFloat) -> Self {
640 Opacity(Number::from_computed_value(computed))
641 }
642}
643
644#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, PartialOrd, ToShmem, ToTyped)]
650pub enum Integer {
651 Literal(CSSInteger),
653 Calc(CSSFloat),
655}
656
657impl Zero for Integer {
658 #[inline]
659 fn zero() -> Self {
660 Self::new(0)
661 }
662
663 #[inline]
664 fn is_zero(&self) -> bool {
665 *self == 0
666 }
667}
668
669impl One for Integer {
670 #[inline]
671 fn one() -> Self {
672 Self::new(1)
673 }
674
675 #[inline]
676 fn is_one(&self) -> bool {
677 *self == 1
678 }
679}
680
681impl PartialEq<i32> for Integer {
682 fn eq(&self, value: &i32) -> bool {
683 self.value() == *value
684 }
685}
686
687impl Integer {
688 pub fn new(val: CSSInteger) -> Self {
690 Self::Literal(val)
691 }
692
693 pub fn value(&self) -> CSSInteger {
695 match *self {
696 Self::Literal(i) => i,
697 Self::Calc(n) => (n + 0.5).floor() as CSSInteger,
698 }
699 }
700
701 fn from_calc(val: CSSFloat) -> Self {
703 Self::Calc(val)
704 }
705}
706
707impl Parse for Integer {
708 fn parse<'i, 't>(
709 context: &ParserContext,
710 input: &mut Parser<'i, 't>,
711 ) -> Result<Self, ParseError<'i>> {
712 let location = input.current_source_location();
713 match *input.next()? {
714 Token::Number {
715 int_value: Some(v), ..
716 } => Ok(Integer::new(v)),
717 Token::Function(ref name) => {
718 let function = CalcNode::math_function(context, name, location)?;
719 let result = CalcNode::parse_number(context, input, function)?;
720 Ok(Integer::from_calc(result))
721 },
722 ref t => Err(location.new_unexpected_token_error(t.clone())),
723 }
724 }
725}
726
727impl Integer {
728 pub fn parse_with_minimum<'i, 't>(
730 context: &ParserContext,
731 input: &mut Parser<'i, 't>,
732 min: i32,
733 ) -> Result<Integer, ParseError<'i>> {
734 let value = Integer::parse(context, input)?;
735 if value.value() < min {
741 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
742 }
743 Ok(value)
744 }
745
746 pub fn parse_non_negative<'i, 't>(
748 context: &ParserContext,
749 input: &mut Parser<'i, 't>,
750 ) -> Result<Integer, ParseError<'i>> {
751 Integer::parse_with_minimum(context, input, 0)
752 }
753
754 pub fn parse_positive<'i, 't>(
756 context: &ParserContext,
757 input: &mut Parser<'i, 't>,
758 ) -> Result<Integer, ParseError<'i>> {
759 Integer::parse_with_minimum(context, input, 1)
760 }
761}
762
763impl ToComputedValue for Integer {
764 type ComputedValue = i32;
765
766 #[inline]
767 fn to_computed_value(&self, _: &Context) -> i32 {
768 self.value()
769 }
770
771 #[inline]
772 fn from_computed_value(computed: &i32) -> Self {
773 Integer::new(*computed)
774 }
775}
776
777impl ToCss for Integer {
778 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
779 where
780 W: Write,
781 {
782 match *self {
783 Integer::Literal(i) => i.to_css(dest),
784 Integer::Calc(n) => {
785 dest.write_str("calc(")?;
786 n.to_css(dest)?;
787 dest.write_char(')')
788 },
789 }
790 }
791}
792
793impl SpecifiedValueInfo for Integer {}
794
795pub type PositiveInteger = GreaterThanOrEqualToOne<Integer>;
797
798impl Parse for PositiveInteger {
799 #[inline]
800 fn parse<'i, 't>(
801 context: &ParserContext,
802 input: &mut Parser<'i, 't>,
803 ) -> Result<Self, ParseError<'i>> {
804 Integer::parse_positive(context, input).map(GreaterThanOrEqualToOne)
805 }
806}
807
808pub type TrackBreadth = GenericTrackBreadth<LengthPercentage>;
810
811pub type TrackSize = GenericTrackSize<LengthPercentage>;
813
814pub type ImplicitGridTracks = GenericImplicitGridTracks<TrackSize>;
816
817pub type TrackList = GenericTrackList<LengthPercentage, Integer>;
820
821pub type GridLine = GenericGridLine<Integer>;
823
824pub type GridTemplateComponent = GenericGridTemplateComponent<LengthPercentage, Integer>;
826
827pub type ClipRect = generics::GenericClipRect<LengthOrAuto>;
829
830impl Parse for ClipRect {
831 fn parse<'i, 't>(
832 context: &ParserContext,
833 input: &mut Parser<'i, 't>,
834 ) -> Result<Self, ParseError<'i>> {
835 Self::parse_quirky(context, input, AllowQuirks::No)
836 }
837}
838
839impl ClipRect {
840 fn parse_quirky<'i, 't>(
842 context: &ParserContext,
843 input: &mut Parser<'i, 't>,
844 allow_quirks: AllowQuirks,
845 ) -> Result<Self, ParseError<'i>> {
846 input.expect_function_matching("rect")?;
847
848 fn parse_argument<'i, 't>(
849 context: &ParserContext,
850 input: &mut Parser<'i, 't>,
851 allow_quirks: AllowQuirks,
852 ) -> Result<LengthOrAuto, ParseError<'i>> {
853 LengthOrAuto::parse_quirky(context, input, allow_quirks)
854 }
855
856 input.parse_nested_block(|input| {
857 let top = parse_argument(context, input, allow_quirks)?;
858 let right;
859 let bottom;
860 let left;
861
862 if input.try_parse(|input| input.expect_comma()).is_ok() {
863 right = parse_argument(context, input, allow_quirks)?;
864 input.expect_comma()?;
865 bottom = parse_argument(context, input, allow_quirks)?;
866 input.expect_comma()?;
867 left = parse_argument(context, input, allow_quirks)?;
868 } else {
869 right = parse_argument(context, input, allow_quirks)?;
870 bottom = parse_argument(context, input, allow_quirks)?;
871 left = parse_argument(context, input, allow_quirks)?;
872 }
873
874 Ok(ClipRect {
875 top,
876 right,
877 bottom,
878 left,
879 })
880 })
881 }
882}
883
884pub type ClipRectOrAuto = generics::GenericClipRectOrAuto<ClipRect>;
886
887impl ClipRectOrAuto {
888 pub fn parse_quirky<'i, 't>(
890 context: &ParserContext,
891 input: &mut Parser<'i, 't>,
892 allow_quirks: AllowQuirks,
893 ) -> Result<Self, ParseError<'i>> {
894 if let Ok(v) = input.try_parse(|i| ClipRect::parse_quirky(context, i, allow_quirks)) {
895 return Ok(generics::GenericClipRectOrAuto::Rect(v));
896 }
897 input.expect_ident_matching("auto")?;
898 Ok(generics::GenericClipRectOrAuto::Auto)
899 }
900}
901
902#[derive(Clone, Copy, PartialEq)]
904pub enum AllowQuirks {
905 No,
907 Yes,
909 Always,
911}
912
913impl AllowQuirks {
914 pub fn allowed(self, quirks_mode: QuirksMode) -> bool {
916 match self {
917 AllowQuirks::Always => true,
918 AllowQuirks::No => false,
919 AllowQuirks::Yes => quirks_mode == QuirksMode::Quirks,
920 }
921 }
922}
923
924#[derive(Clone, Debug, PartialEq, MallocSizeOf, ToShmem)]
925pub enum ParsedNamespace {
927 Unknown,
929 Known(Namespace),
931}
932
933impl ParsedNamespace {
934 pub fn parse<'i, 't>(
937 namespaces: &FxHashMap<Prefix, Namespace>,
938 input: &mut Parser<'i, 't>,
939 ) -> Result<Self, ParseError<'i>> {
940 parse_namespace(namespaces, input, true)
945 .map(|(_prefix, namespace)| namespace)
946 }
947}
948
949impl Default for ParsedNamespace {
950 fn default() -> Self {
951 Self::Known(Namespace::default())
952 }
953}
954
955#[derive(
959 Clone,
960 Debug,
961 Eq,
962 MallocSizeOf,
963 PartialEq,
964 SpecifiedValueInfo,
965 ToComputedValue,
966 ToResolvedValue,
967 ToShmem,
968)]
969#[css(function)]
970#[repr(C)]
971pub struct Attr {
972 pub namespace_prefix: Prefix,
974 pub namespace_url: Namespace,
976 pub attribute: Atom,
978 pub fallback: AtomString,
980}
981
982impl Parse for Attr {
983 fn parse<'i, 't>(
984 context: &ParserContext,
985 input: &mut Parser<'i, 't>,
986 ) -> Result<Attr, ParseError<'i>> {
987 input.expect_function_matching("attr")?;
988 input.parse_nested_block(|i| Attr::parse_function(context, i))
989 }
990}
991
992pub fn parse_namespace<'i, 't>(
994 namespaces: &FxHashMap<Prefix, Namespace>,
995 input: &mut Parser<'i, 't>,
996 allow_non_registered: bool,
998) -> Result<(Prefix, ParsedNamespace), ParseError<'i>> {
999 let ns_prefix = match input.next()? {
1000 Token::Ident(ref prefix) => Some(Prefix::from(prefix.as_ref())),
1001 Token::Delim('|') => None,
1002 _ => return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
1003 };
1004
1005 if ns_prefix.is_some() && !matches!(*input.next_including_whitespace()?, Token::Delim('|')) {
1006 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1007 }
1008
1009 if let Some(prefix) = ns_prefix {
1010 let ns = match namespaces.get(&prefix).cloned() {
1011 Some(ns) => ParsedNamespace::Known(ns),
1012 None => {
1013 if allow_non_registered {
1014 ParsedNamespace::Unknown
1015 } else {
1016 return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError));
1017 }
1018 },
1019 };
1020 Ok((prefix, ns))
1021 } else {
1022 Ok((Prefix::default(), ParsedNamespace::default()))
1023 }
1024}
1025
1026impl Attr {
1027 pub fn parse_function<'i, 't>(
1030 context: &ParserContext,
1031 input: &mut Parser<'i, 't>,
1032 ) -> Result<Attr, ParseError<'i>> {
1033 let namespace = input
1035 .try_parse(|input| {
1036 parse_namespace(
1037 &context.namespaces.prefixes,
1038 input,
1039 false,
1040 )
1041 })
1042 .ok();
1043 let namespace_is_some = namespace.is_some();
1044 let (namespace_prefix, namespace_url) = namespace.unwrap_or_default();
1045 let ParsedNamespace::Known(namespace_url) = namespace_url else {
1046 unreachable!("Non-registered url not allowed (see parse namespace flag).")
1047 };
1048
1049 let attribute = Atom::from(if namespace_is_some {
1051 let location = input.current_source_location();
1052 match *input.next_including_whitespace()? {
1053 Token::Ident(ref ident) => ident.as_ref(),
1054 ref t => return Err(location.new_unexpected_token_error(t.clone())),
1055 }
1056 } else {
1057 input.expect_ident()?.as_ref()
1058 });
1059
1060 let fallback = input
1063 .try_parse(|input| -> Result<AtomString, ParseError<'i>> {
1064 input.expect_comma()?;
1065 Ok(input.expect_string()?.as_ref().into())
1066 })
1067 .unwrap_or_default();
1068
1069 Ok(Attr {
1070 namespace_prefix,
1071 namespace_url,
1072 attribute,
1073 fallback,
1074 })
1075 }
1076}
1077
1078impl ToCss for Attr {
1079 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
1080 where
1081 W: Write,
1082 {
1083 dest.write_str("attr(")?;
1084 if !self.namespace_prefix.is_empty() {
1085 serialize_atom_identifier(&self.namespace_prefix, dest)?;
1086 dest.write_char('|')?;
1087 }
1088 serialize_atom_identifier(&self.attribute, dest)?;
1089
1090 if !self.fallback.is_empty() {
1091 dest.write_str(", ")?;
1092 self.fallback.to_css(dest)?;
1093 }
1094
1095 dest.write_char(')')
1096 }
1097}