/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! Specified types for CSS values that are related to motion path. use crate::parser::{Parse, ParserContext}; use crate::values::computed::motion::OffsetRotate as ComputedOffsetRotate; use crate::values::computed::{Context, ToComputedValue}; use crate::values::generics::motion as generics; use crate::values::specified::basic_shape::BasicShape; use crate::values::specified::position::{HorizontalPosition, VerticalPosition}; use crate::values::specified::url::SpecifiedUrl; use crate::values::specified::{Angle, Position}; use crate::Zero; use cssparser::Parser; use style_traits::{ParseError, StyleParseErrorKind}; /// The specified value of ray() function. pub type RayFunction = generics::GenericRayFunction; /// The specified value of . pub type OffsetPathFunction = generics::GenericOffsetPathFunction; /// The specified value of `offset-path`. pub type OffsetPath = generics::GenericOffsetPath; /// The specified value of `offset-position`. pub type OffsetPosition = generics::GenericOffsetPosition; /// The value, which defines the box that the sizes into. /// https://drafts.fxtf.org/motion-1/#valdef-offset-path-coord-box /// /// = content-box | padding-box | border-box | fill-box | stroke-box | view-box /// https://drafts.csswg.org/css-box-4/#typedef-coord-box #[allow(missing_docs)] #[derive( Animate, Clone, ComputeSquaredDistance, Copy, Debug, Deserialize, MallocSizeOf, Parse, PartialEq, Serialize, SpecifiedValueInfo, ToAnimatedValue, ToComputedValue, ToCss, ToResolvedValue, ToShmem, )] #[repr(u8)] pub enum CoordBox { ContentBox, PaddingBox, BorderBox, FillBox, StrokeBox, ViewBox, } impl CoordBox { /// Returns true if it is default value, border-box. #[inline] pub fn is_default(&self) -> bool { matches!(*self, Self::BorderBox) } } impl Parse for RayFunction { fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { if !static_prefs::pref!("layout.css.motion-path-ray.enabled") { return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); } input.expect_function_matching("ray")?; input.parse_nested_block(|i| Self::parse_function_arguments(context, i)) } } impl RayFunction { /// Parse the inner arguments of a `ray` function. fn parse_function_arguments<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { use crate::values::specified::PositionOrAuto; let mut angle = None; let mut size = None; let mut contain = false; let mut position = None; loop { if angle.is_none() { angle = input.try_parse(|i| Angle::parse(context, i)).ok(); } if size.is_none() { size = input.try_parse(generics::RaySize::parse).ok(); if size.is_some() { continue; } } if !contain { contain = input .try_parse(|i| i.expect_ident_matching("contain")) .is_ok(); if contain { continue; } } if position.is_none() { if input.try_parse(|i| i.expect_ident_matching("at")).is_ok() { let pos = Position::parse(context, input)?; position = Some(PositionOrAuto::Position(pos)); } if position.is_some() { continue; } } break; } if angle.is_none() { return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); } Ok(RayFunction { angle: angle.unwrap(), // If no is specified it defaults to closest-side. size: size.unwrap_or(generics::RaySize::ClosestSide), contain, position: position.unwrap_or(PositionOrAuto::auto()), }) } } impl Parse for OffsetPathFunction { fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { use crate::values::specified::basic_shape::{AllowedBasicShapes, ShapeType}; // = | | // https://drafts.fxtf.org/motion-1/#typedef-offset-path if static_prefs::pref!("layout.css.motion-path-ray.enabled") { if let Ok(ray) = input.try_parse(|i| RayFunction::parse(context, i)) { return Ok(OffsetPathFunction::Ray(ray)); } } if static_prefs::pref!("layout.css.motion-path-url.enabled") { if let Ok(url) = input.try_parse(|i| SpecifiedUrl::parse(context, i)) { return Ok(OffsetPathFunction::Url(url)); } } let allowed_shapes = if static_prefs::pref!("layout.css.motion-path-basic-shapes.enabled") { AllowedBasicShapes::ALL } else { AllowedBasicShapes::PATH }; BasicShape::parse(context, input, allowed_shapes, ShapeType::Outline) .map(OffsetPathFunction::Shape) } } impl Parse for OffsetPath { fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { // Parse none. if input.try_parse(|i| i.expect_ident_matching("none")).is_ok() { return Ok(OffsetPath::none()); } let mut path = None; let mut coord_box = None; loop { if path.is_none() { path = input .try_parse(|i| OffsetPathFunction::parse(context, i)) .ok(); } if static_prefs::pref!("layout.css.motion-path-coord-box.enabled") && coord_box.is_none() { coord_box = input.try_parse(CoordBox::parse).ok(); if coord_box.is_some() { continue; } } break; } if let Some(p) = path { return Ok(OffsetPath::OffsetPath { path: Box::new(p), coord_box: coord_box.unwrap_or(CoordBox::BorderBox), }); } match coord_box { Some(c) => Ok(OffsetPath::CoordBox(c)), None => Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)), } } } /// The direction of offset-rotate. #[derive( Clone, Copy, Debug, MallocSizeOf, Parse, PartialEq, SpecifiedValueInfo, ToCss, ToShmem, )] #[repr(u8)] pub enum OffsetRotateDirection { /// Unspecified direction keyword. #[css(skip)] None, /// 0deg offset (face forward). Auto, /// 180deg offset (face backward). Reverse, } impl OffsetRotateDirection { /// Returns true if it is none (i.e. the keyword is not specified). #[inline] fn is_none(&self) -> bool { *self == OffsetRotateDirection::None } } #[inline] fn direction_specified_and_angle_is_zero(direction: &OffsetRotateDirection, angle: &Angle) -> bool { !direction.is_none() && angle.is_zero() } /// The specified offset-rotate. /// The syntax is: "[ auto | reverse ] || " /// /// https://drafts.fxtf.org/motion-1/#offset-rotate-property #[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] pub struct OffsetRotate { /// [auto | reverse]. #[css(skip_if = "OffsetRotateDirection::is_none")] direction: OffsetRotateDirection, /// . /// If direction is None, this is a fixed angle which indicates a /// constant clockwise rotation transformation applied to it by this /// specified rotation angle. Otherwise, the angle will be added to /// the angle of the direction in layout. #[css(contextual_skip_if = "direction_specified_and_angle_is_zero")] angle: Angle, } impl OffsetRotate { /// Returns the initial value, auto. #[inline] pub fn auto() -> Self { OffsetRotate { direction: OffsetRotateDirection::Auto, angle: Angle::zero(), } } /// Returns true if self is auto 0deg. #[inline] pub fn is_auto(&self) -> bool { self.direction == OffsetRotateDirection::Auto && self.angle.is_zero() } } impl Parse for OffsetRotate { fn parse<'i, 't>( context: &ParserContext, input: &mut Parser<'i, 't>, ) -> Result> { let location = input.current_source_location(); let mut direction = input.try_parse(OffsetRotateDirection::parse); let angle = input.try_parse(|i| Angle::parse(context, i)); if direction.is_err() { // The direction and angle could be any order, so give it a change to parse // direction again. direction = input.try_parse(OffsetRotateDirection::parse); } if direction.is_err() && angle.is_err() { return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); } Ok(OffsetRotate { direction: direction.unwrap_or(OffsetRotateDirection::None), angle: angle.unwrap_or(Zero::zero()), }) } } impl ToComputedValue for OffsetRotate { type ComputedValue = ComputedOffsetRotate; #[inline] fn to_computed_value(&self, context: &Context) -> Self::ComputedValue { use crate::values::computed::Angle as ComputedAngle; ComputedOffsetRotate { auto: !self.direction.is_none(), angle: if self.direction == OffsetRotateDirection::Reverse { // The computed value should always convert "reverse" into "auto". // e.g. "reverse calc(20deg + 10deg)" => "auto 210deg" self.angle.to_computed_value(context) + ComputedAngle::from_degrees(180.0) } else { self.angle.to_computed_value(context) }, } } #[inline] fn from_computed_value(computed: &Self::ComputedValue) -> Self { OffsetRotate { direction: if computed.auto { OffsetRotateDirection::Auto } else { OffsetRotateDirection::None }, angle: ToComputedValue::from_computed_value(&computed.angle), } } }