diff options
26 files changed, 2101 insertions, 1985 deletions
diff --git a/components/layout/display_list/builder.rs b/components/layout/display_list/builder.rs index c68853b9149..3f474af6f1e 100644 --- a/components/layout/display_list/builder.rs +++ b/components/layout/display_list/builder.rs @@ -1499,8 +1499,11 @@ impl FragmentDisplayListBuilding for Fragment { let outline_style = match style.get_outline().outline_style { OutlineStyle::Auto => BorderStyle::Solid, - OutlineStyle::Other(BorderStyle::None) => return, - OutlineStyle::Other(border_style) => border_style, + // FIXME(emilio): I don't think this border-style check is + // necessary, since border-style: none implies an outline-width of + // zero at computed value time. + OutlineStyle::BorderStyle(BorderStyle::None) => return, + OutlineStyle::BorderStyle(s) => s, }; // Outlines are not accounted for in the dimensions of the border box, so adjust the diff --git a/components/style/cbindgen.toml b/components/style/cbindgen.toml index bbd5838c7b7..e93d2c35c35 100644 --- a/components/style/cbindgen.toml +++ b/components/style/cbindgen.toml @@ -31,6 +31,7 @@ include = ["cssparser"] [struct] derive_eq = true +derive_neq = true [enum] derive_helper_methods = true @@ -41,6 +42,8 @@ include = [ "Appearance", "BreakBetween", "BreakWithin", + "BorderStyle", + "OutlineStyle", "ComputedFontStretchRange", "ComputedFontStyleDescriptor", "ComputedFontWeightRange", diff --git a/components/style/custom_properties.rs b/components/style/custom_properties.rs index b70834b81c9..130c92b0e9b 100644 --- a/components/style/custom_properties.rs +++ b/components/style/custom_properties.rs @@ -283,18 +283,32 @@ impl VariableValue { } } - fn push( + fn push<'i>( &mut self, + input: &Parser<'i, '_>, css: &str, css_first_token_type: TokenSerializationType, css_last_token_type: TokenSerializationType, - ) { + ) -> Result<(), ParseError<'i>> { + /// Prevent values from getting terribly big since you can use custom + /// properties exponentially. + /// + /// This number (1MB) is somewhat arbitrary, but silly enough that no + /// sane page would hit it. We could limit by number of total + /// substitutions, but that was very easy to work around in practice + /// (just choose a larger initial value and boom). + const MAX_VALUE_LENGTH_IN_BYTES: usize = 1024 * 1024; + + if self.css.len() + css.len() > MAX_VALUE_LENGTH_IN_BYTES { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + // This happens e.g. between two subsequent var() functions: // `var(--a)var(--b)`. // // In that case, css_*_token_type is nonsensical. if css.is_empty() { - return; + return Ok(()); } self.first_token_type.set_if_nothing(css_first_token_type); @@ -307,21 +321,32 @@ impl VariableValue { self.css.push_str("/**/") } self.css.push_str(css); - self.last_token_type = css_last_token_type + self.last_token_type = css_last_token_type; + Ok(()) } - fn push_from( + fn push_from<'i>( &mut self, + input: &Parser<'i, '_>, position: (SourcePosition, TokenSerializationType), - input: &Parser, last_token_type: TokenSerializationType, - ) { - self.push(input.slice_from(position.0), position.1, last_token_type) + ) -> Result<(), ParseError<'i>> { + self.push( + input, + input.slice_from(position.0), + position.1, + last_token_type, + ) } - fn push_variable(&mut self, variable: &ComputedValue) { + fn push_variable<'i>( + &mut self, + input: &Parser<'i, '_>, + variable: &ComputedValue, + ) -> Result<(), ParseError<'i>> { debug_assert!(variable.references.is_empty()); self.push( + input, &variable.css, variable.first_token_type, variable.last_token_type, @@ -727,6 +752,7 @@ fn substitute_all(custom_properties_map: &mut CustomPropertiesMap, environment: // title=Tarjan%27s_strongly_connected_components_algorithm&oldid=801728495 /// Struct recording necessary information for each variable. + #[derive(Debug)] struct VarInfo { /// The name of the variable. It will be taken to save addref /// when the corresponding variable is popped from the stack. @@ -741,6 +767,7 @@ fn substitute_all(custom_properties_map: &mut CustomPropertiesMap, environment: } /// Context struct for traversing the variable graph, so that we can /// avoid referencing all the fields multiple times. + #[derive(Debug)] struct Context<'a> { /// Number of variables visited. This is used as the order index /// when we visit a new unresolved variable. @@ -941,7 +968,7 @@ fn substitute_references_in_value<'i>( environment, )?; - computed_value.push_from(position, &input, last_token_type); + computed_value.push_from(&input, position, last_token_type)?; Ok(computed_value) } @@ -955,8 +982,8 @@ fn substitute_references_in_value<'i>( /// /// Return `Err(())` if `input` is invalid at computed-value time. /// or `Ok(last_token_type that was pushed to partial_computed_value)` otherwise. -fn substitute_block<'i, 't>( - input: &mut Parser<'i, 't>, +fn substitute_block<'i>( + input: &mut Parser<'i, '_>, position: &mut (SourcePosition, TokenSerializationType), partial_computed_value: &mut ComputedValue, custom_properties: &CustomPropertiesMap, @@ -991,10 +1018,11 @@ fn substitute_block<'i, 't>( let is_env = name.eq_ignore_ascii_case("env"); partial_computed_value.push( + input, input.slice(position.0..before_this_token), position.1, last_token_type, - ); + )?; input.parse_nested_block(|input| { // parse_var_function() / parse_env_function() ensure neither .unwrap() will fail. let name = { @@ -1014,7 +1042,7 @@ fn substitute_block<'i, 't>( if let Some(v) = value { last_token_type = v.last_token_type; - partial_computed_value.push_variable(v); + partial_computed_value.push_variable(input, v)?; // Skip over the fallback, as `parse_nested_block` would return `Err` // if we don't consume all of `input`. // FIXME: Add a specialized method to cssparser to do this with less work. @@ -1036,7 +1064,7 @@ fn substitute_block<'i, 't>( custom_properties, env, )?; - partial_computed_value.push_from(position, input, last_token_type); + partial_computed_value.push_from(input, position, last_token_type)?; } Ok(()) })?; @@ -1096,6 +1124,6 @@ pub fn substitute<'i>( &custom_properties, env, )?; - substituted.push_from(position, &input, last_token_type); + substituted.push_from(&input, position, last_token_type)?; Ok(substituted.css) } diff --git a/components/style/gecko/conversions.rs b/components/style/gecko/conversions.rs index 59d80e32cfc..426d8c30ae9 100644 --- a/components/style/gecko/conversions.rs +++ b/components/style/gecko/conversions.rs @@ -11,11 +11,13 @@ use app_units::Au; use crate::gecko::values::GeckoStyleCoordConvertible; use crate::gecko_bindings::bindings; +use crate::gecko_bindings::structs::RawGeckoGfxMatrix4x4; use crate::gecko_bindings::structs::{self, nsStyleCoord_CalcValue}; use crate::gecko_bindings::structs::{nsStyleImage, nsresult, SheetType}; use crate::gecko_bindings::sugar::ns_style_coord::{CoordData, CoordDataMut, CoordDataValue}; use crate::stylesheets::{Origin, RulesMutateError}; use crate::values::computed::image::LineDirection; +use crate::values::computed::transform::Matrix3D; use crate::values::computed::url::ComputedImageUrl; use crate::values::computed::{Angle, CalcLengthOrPercentage, Gradient, Image}; use crate::values::computed::{Integer, LengthOrPercentage}; @@ -1142,3 +1144,36 @@ pub unsafe fn string_from_chars_pointer(p: *const u16) -> String { let char_vec = slice::from_raw_parts(p, length as usize); String::from_utf16_lossy(char_vec) } + +impl<'a> From<&'a RawGeckoGfxMatrix4x4> for Matrix3D { + fn from(m: &'a RawGeckoGfxMatrix4x4) -> Matrix3D { + Matrix3D { + m11: m[0], + m12: m[1], + m13: m[2], + m14: m[3], + m21: m[4], + m22: m[5], + m23: m[6], + m24: m[7], + m31: m[8], + m32: m[9], + m33: m[10], + m34: m[11], + m41: m[12], + m42: m[13], + m43: m[14], + m44: m[15], + } + } +} + +impl From<Matrix3D> for RawGeckoGfxMatrix4x4 { + fn from(matrix: Matrix3D) -> RawGeckoGfxMatrix4x4 { + [ + matrix.m11, matrix.m12, matrix.m13, matrix.m14, matrix.m21, matrix.m22, matrix.m23, + matrix.m24, matrix.m31, matrix.m32, matrix.m33, matrix.m34, matrix.m41, matrix.m42, + matrix.m43, matrix.m44, + ] + } +} diff --git a/components/style/properties/declaration_block.rs b/components/style/properties/declaration_block.rs index 28a3e9ecfaa..d7058d99635 100644 --- a/components/style/properties/declaration_block.rs +++ b/components/style/properties/declaration_block.rs @@ -462,22 +462,6 @@ impl PropertyDeclarationBlock { return false; } - // As a compatibility hack, specially on Android, - // don't allow to override a prefixed webkit display - // value with an unprefixed version from parsing - // code. - // - // TODO(emilio): Unship. - if let PropertyDeclaration::Display(old_display) = *slot { - use crate::properties::longhands::display::computed_value::T as display; - - if let PropertyDeclaration::Display(new_display) = declaration { - if display::should_ignore_parsed_value(old_display, new_display) { - return false; - } - } - } - index_to_remove = Some(i); break; } @@ -933,6 +917,10 @@ impl PropertyDeclarationBlock { } already_serialized.insert(shorthand.into()); + if shorthand.is_legacy_shorthand() { + continue; + } + // Substep 2 & 3 let mut current_longhands = SmallVec::<[_; 10]>::new(); let mut important_count = 0; diff --git a/components/style/properties/gecko.mako.rs b/components/style/properties/gecko.mako.rs index 81c2f690cc2..de6a84cccf2 100644 --- a/components/style/properties/gecko.mako.rs +++ b/components/style/properties/gecko.mako.rs @@ -58,15 +58,14 @@ use std::mem::{forget, uninitialized, transmute, zeroed}; use std::{cmp, ops, ptr}; use crate::values::{self, CustomIdent, Either, KeyframesName, None_}; use crate::values::computed::{NonNegativeLength, Percentage, TransitionProperty}; +use crate::values::computed::BorderStyle; use crate::values::computed::font::FontSize; use crate::values::computed::effects::{BoxShadow, Filter, SimpleShadow}; -use crate::values::computed::outline::OutlineStyle; use crate::values::generics::column::ColumnCount; use crate::values::generics::position::ZIndex; use crate::values::generics::text::MozTabSize; use crate::values::generics::transform::TransformStyle; use crate::values::generics::url::UrlOrNone; -use crate::computed_values::border_style; pub mod style_structs { % for style_struct in data.style_structs: @@ -333,13 +332,10 @@ impl ${style_struct.gecko_struct_name} { } </%def> -<%def name="impl_simple_copy(ident, gecko_ffi_name, on_set=None, *kwargs)"> +<%def name="impl_simple_copy(ident, gecko_ffi_name, *kwargs)"> #[allow(non_snake_case)] pub fn copy_${ident}_from(&mut self, other: &Self) { self.gecko.${gecko_ffi_name} = other.gecko.${gecko_ffi_name}; - % if on_set: - self.${on_set}(); - % endif } #[allow(non_snake_case)] @@ -368,7 +364,7 @@ def set_gecko_property(ffi_name, expr): return "self.gecko.%s = %s;" % (ffi_name, expr) %> -<%def name="impl_keyword_setter(ident, gecko_ffi_name, keyword, cast_type='u8', on_set=None)"> +<%def name="impl_keyword_setter(ident, gecko_ffi_name, keyword, cast_type='u8')"> #[allow(non_snake_case)] pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) { use crate::properties::longhands::${ident}::computed_value::T as Keyword; @@ -380,9 +376,6 @@ def set_gecko_property(ffi_name, expr): % endfor }; ${set_gecko_property(gecko_ffi_name, "result")} - % if on_set: - self.${on_set}(); - % endif } </%def> @@ -1515,9 +1508,6 @@ fn static_assert() { } -<% border_style_keyword = Keyword("border-style", - "none solid double dotted dashed hidden groove ridge inset outset") %> - <% skip_border_longhands = " ".join(["border-{0}-{1}".format(x.ident, y) for x in SIDES for y in ["color", "style", "width"]] + @@ -1528,41 +1518,55 @@ fn static_assert() { skip_longhands="${skip_border_longhands} border-image-source border-image-outset border-image-repeat border-image-width border-image-slice"> % for side in SIDES: - <% impl_keyword("border_%s_style" % side.ident, - "mBorderStyle[%s]" % side.index, - border_style_keyword, - on_set="update_border_%s" % side.ident) %> - - // This is needed because the initial mComputedBorder value is set to zero. - // - // In order to compute stuff, we start from the initial struct, and keep - // going down the tree applying properties. - // - // That means, effectively, that when we set border-style to something - // non-hidden, we should use the initial border instead. - // - // Servo stores the initial border-width in the initial struct, and then - // adjusts as needed in the fixup phase. This means that the initial struct - // is technically not valid without fixups, and that you lose pretty much - // any sharing of the initial struct, which is kind of unfortunate. - // - // Gecko has two fields for this, one that stores the "specified" border, - // and other that stores the actual computed one. That means that when we - // set border-style, border-width may change and we need to sync back to the - // specified one. This is what this function does. - // - // Note that this doesn't impose any dependency in the order of computation - // of the properties. This is only relevant if border-style is specified, - // but border-width isn't. If border-width is specified at some point, the - // two mBorder and mComputedBorder fields would be the same already. - // - // Once we're here, we know that we'll run style fixups, so it's fine to - // just copy the specified border here, we'll adjust it if it's incorrect - // later. - fn update_border_${side.ident}(&mut self) { + pub fn set_border_${side.ident}_style(&mut self, v: BorderStyle) { + self.gecko.mBorderStyle[${side.index}] = v; + + // This is needed because the initial mComputedBorder value is set to + // zero. + // + // In order to compute stuff, we start from the initial struct, and keep + // going down the tree applying properties. + // + // That means, effectively, that when we set border-style to something + // non-hidden, we should use the initial border instead. + // + // Servo stores the initial border-width in the initial struct, and then + // adjusts as needed in the fixup phase. This means that the initial + // struct is technically not valid without fixups, and that you lose + // pretty much any sharing of the initial struct, which is kind of + // unfortunate. + // + // Gecko has two fields for this, one that stores the "specified" + // border, and other that stores the actual computed one. That means + // that when we set border-style, border-width may change and we need to + // sync back to the specified one. This is what this function does. + // + // Note that this doesn't impose any dependency in the order of + // computation of the properties. This is only relevant if border-style + // is specified, but border-width isn't. If border-width is specified at + // some point, the two mBorder and mComputedBorder fields would be the + // same already. + // + // Once we're here, we know that we'll run style fixups, so it's fine to + // just copy the specified border here, we'll adjust it if it's + // incorrect later. + self.gecko.mComputedBorder.${side.ident} = self.gecko.mBorder.${side.ident}; + } + + pub fn copy_border_${side.ident}_style_from(&mut self, other: &Self) { + self.gecko.mBorderStyle[${side.index}] = other.gecko.mBorderStyle[${side.index}]; self.gecko.mComputedBorder.${side.ident} = self.gecko.mBorder.${side.ident}; } + pub fn reset_border_${side.ident}_style(&mut self, other: &Self) { + self.copy_border_${side.ident}_style_from(other); + } + + #[inline] + pub fn clone_border_${side.ident}_style(&self) -> BorderStyle { + self.gecko.mBorderStyle[${side.index}] + } + <% impl_color("border_%s_color" % side.ident, "mBorder%sColor" % side.name) %> <% impl_non_negative_length("border_%s_width" % side.ident, @@ -2170,50 +2174,26 @@ fn static_assert() { <%self:impl_trait style_struct_name="Outline" skip_longhands="${skip_outline_longhands}"> - #[allow(non_snake_case)] pub fn set_outline_style(&mut self, v: longhands::outline_style::computed_value::T) { - // FIXME(bholley): Align binary representations and ditch |match| for - // cast + static_asserts - let result = match v { - % for value in border_style_keyword.values_for('gecko'): - OutlineStyle::Other(border_style::T::${to_camel_case(value)}) => - structs::${border_style_keyword.gecko_constant(value)} ${border_style_keyword.maybe_cast("u8")}, - % endfor - OutlineStyle::Auto => - structs::${border_style_keyword.gecko_constant('auto')} ${border_style_keyword.maybe_cast("u8")}, - }; - ${set_gecko_property("mOutlineStyle", "result")} - + self.gecko.mOutlineStyle = v; // NB: This is needed to correctly handling the initial value of // outline-width when outline-style changes, see the // update_border_${side.ident} comment for more details. self.gecko.mActualOutlineWidth = self.gecko.mOutlineWidth; } - #[allow(non_snake_case)] pub fn copy_outline_style_from(&mut self, other: &Self) { + // FIXME(emilio): Why doesn't this need to reset mActualOutlineWidth? + // Looks fishy. self.gecko.mOutlineStyle = other.gecko.mOutlineStyle; } - #[allow(non_snake_case)] pub fn reset_outline_style(&mut self, other: &Self) { self.copy_outline_style_from(other) } - #[allow(non_snake_case)] pub fn clone_outline_style(&self) -> longhands::outline_style::computed_value::T { - // FIXME(bholley): Align binary representations and ditch |match| for cast + static_asserts - match ${get_gecko_property("mOutlineStyle")} ${border_style_keyword.maybe_cast("u32")} { - % for value in border_style_keyword.values_for('gecko'): - structs::${border_style_keyword.gecko_constant(value)} => { - OutlineStyle::Other(border_style::T::${to_camel_case(value)}) - }, - % endfor - structs::${border_style_keyword.gecko_constant('auto')} => OutlineStyle::Auto, - % if border_style_keyword.gecko_inexhaustive: - _ => panic!("Found unexpected value in style struct for outline_style property"), - % endif - } + self.gecko.mOutlineStyle.clone() } <% impl_non_negative_length("outline_width", "mActualOutlineWidth", @@ -5410,7 +5390,7 @@ clip-path </%self:impl_trait> <%self:impl_trait style_struct_name="Column" - skip_longhands="column-count column-rule-width"> + skip_longhands="column-count column-rule-width column-rule-style"> #[allow(unused_unsafe)] pub fn set_column_count(&mut self, v: longhands::column_count::computed_value::T) { @@ -5439,6 +5419,7 @@ clip-path <% impl_non_negative_length("column_rule_width", "mColumnRuleWidth", round_to_pixels=True) %> + ${impl_simple('column_rule_style', 'mColumnRuleStyle')} </%self:impl_trait> <%self:impl_trait style_struct_name="Counters" diff --git a/components/style/properties/helpers/animated_properties.mako.rs b/components/style/properties/helpers/animated_properties.mako.rs index 4234b3ff729..fcc6e48b41b 100644 --- a/components/style/properties/helpers/animated_properties.mako.rs +++ b/components/style/properties/helpers/animated_properties.mako.rs @@ -10,37 +10,24 @@ %> #[cfg(feature = "gecko")] use crate::gecko_bindings::structs::RawServoAnimationValueMap; -#[cfg(feature = "gecko")] use crate::gecko_bindings::structs::RawGeckoGfxMatrix4x4; #[cfg(feature = "gecko")] use crate::gecko_bindings::structs::nsCSSPropertyID; #[cfg(feature = "gecko")] use crate::gecko_bindings::sugar::ownership::{HasFFI, HasSimpleFFI}; use itertools::{EitherOrBoth, Itertools}; -use num_traits::Zero; use crate::properties::{CSSWideKeyword, PropertyDeclaration}; use crate::properties::longhands; use crate::properties::longhands::visibility::computed_value::T as Visibility; use crate::properties::LonghandId; use servo_arc::Arc; use smallvec::SmallVec; -use std::{cmp, ptr}; +use std::ptr; use std::mem::{self, ManuallyDrop}; use crate::hash::FxHashMap; use super::ComputedValues; -use crate::values::CSSFloat; use crate::values::animated::{Animate, Procedure, ToAnimatedValue, ToAnimatedZero}; use crate::values::animated::effects::Filter as AnimatedFilter; #[cfg(feature = "gecko")] use crate::values::computed::TransitionProperty; -use crate::values::computed::Angle; use crate::values::computed::{ClipRect, Context}; -use crate::values::computed::{Length, LengthOrPercentage}; -use crate::values::computed::{Number, Percentage}; use crate::values::computed::ToComputedValue; -use crate::values::computed::transform::{DirectionVector, Matrix, Matrix3D}; -use crate::values::computed::transform::TransformOperation as ComputedTransformOperation; -use crate::values::computed::transform::Transform as ComputedTransform; -use crate::values::computed::transform::Rotate as ComputedRotate; -use crate::values::computed::transform::Translate as ComputedTranslate; -use crate::values::computed::transform::Scale as ComputedScale; -use crate::values::generics::transform::{self, Rotate, Translate, Scale, Transform, TransformOperation}; use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; use crate::values::generics::effects::Filter; use void::{self, Void}; @@ -838,1731 +825,6 @@ impl ToAnimatedZero for ClipRect { fn to_animated_zero(&self) -> Result<Self, ()> { Err(()) } } -fn animate_multiplicative_factor( - this: CSSFloat, - other: CSSFloat, - procedure: Procedure, -) -> Result<CSSFloat, ()> { - Ok((this - 1.).animate(&(other - 1.), procedure)? + 1.) -} - -/// <http://dev.w3.org/csswg/css-transforms/#interpolation-of-transforms> -impl Animate for ComputedTransformOperation { - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - match (self, other) { - ( - &TransformOperation::Matrix3D(ref this), - &TransformOperation::Matrix3D(ref other), - ) => { - Ok(TransformOperation::Matrix3D( - this.animate(other, procedure)?, - )) - }, - ( - &TransformOperation::Matrix(ref this), - &TransformOperation::Matrix(ref other), - ) => { - Ok(TransformOperation::Matrix( - this.animate(other, procedure)?, - )) - }, - ( - &TransformOperation::Skew(ref fx, None), - &TransformOperation::Skew(ref tx, None), - ) => { - Ok(TransformOperation::Skew( - fx.animate(tx, procedure)?, - None, - )) - }, - ( - &TransformOperation::Skew(ref fx, ref fy), - &TransformOperation::Skew(ref tx, ref ty), - ) => { - Ok(TransformOperation::Skew( - fx.animate(tx, procedure)?, - Some(fy.unwrap_or(Angle::zero()).animate(&ty.unwrap_or(Angle::zero()), procedure)?) - )) - }, - ( - &TransformOperation::SkewX(ref f), - &TransformOperation::SkewX(ref t), - ) => { - Ok(TransformOperation::SkewX( - f.animate(t, procedure)?, - )) - }, - ( - &TransformOperation::SkewY(ref f), - &TransformOperation::SkewY(ref t), - ) => { - Ok(TransformOperation::SkewY( - f.animate(t, procedure)?, - )) - }, - ( - &TransformOperation::Translate3D(ref fx, ref fy, ref fz), - &TransformOperation::Translate3D(ref tx, ref ty, ref tz), - ) => { - Ok(TransformOperation::Translate3D( - fx.animate(tx, procedure)?, - fy.animate(ty, procedure)?, - fz.animate(tz, procedure)?, - )) - }, - ( - &TransformOperation::Translate(ref fx, None), - &TransformOperation::Translate(ref tx, None), - ) => { - Ok(TransformOperation::Translate( - fx.animate(tx, procedure)?, - None - )) - }, - ( - &TransformOperation::Translate(ref fx, ref fy), - &TransformOperation::Translate(ref tx, ref ty), - ) => { - Ok(TransformOperation::Translate( - fx.animate(tx, procedure)?, - Some(fy.unwrap_or(LengthOrPercentage::zero()) - .animate(&ty.unwrap_or(LengthOrPercentage::zero()), procedure)?) - )) - }, - ( - &TransformOperation::TranslateX(ref f), - &TransformOperation::TranslateX(ref t), - ) => { - Ok(TransformOperation::TranslateX( - f.animate(t, procedure)? - )) - }, - ( - &TransformOperation::TranslateY(ref f), - &TransformOperation::TranslateY(ref t), - ) => { - Ok(TransformOperation::TranslateY( - f.animate(t, procedure)? - )) - }, - ( - &TransformOperation::TranslateZ(ref f), - &TransformOperation::TranslateZ(ref t), - ) => { - Ok(TransformOperation::TranslateZ( - f.animate(t, procedure)? - )) - }, - ( - &TransformOperation::Scale3D(ref fx, ref fy, ref fz), - &TransformOperation::Scale3D(ref tx, ref ty, ref tz), - ) => { - Ok(TransformOperation::Scale3D( - animate_multiplicative_factor(*fx, *tx, procedure)?, - animate_multiplicative_factor(*fy, *ty, procedure)?, - animate_multiplicative_factor(*fz, *tz, procedure)?, - )) - }, - ( - &TransformOperation::ScaleX(ref f), - &TransformOperation::ScaleX(ref t), - ) => { - Ok(TransformOperation::ScaleX( - animate_multiplicative_factor(*f, *t, procedure)? - )) - }, - ( - &TransformOperation::ScaleY(ref f), - &TransformOperation::ScaleY(ref t), - ) => { - Ok(TransformOperation::ScaleY( - animate_multiplicative_factor(*f, *t, procedure)? - )) - }, - ( - &TransformOperation::ScaleZ(ref f), - &TransformOperation::ScaleZ(ref t), - ) => { - Ok(TransformOperation::ScaleZ( - animate_multiplicative_factor(*f, *t, procedure)? - )) - }, - ( - &TransformOperation::Scale(ref f, None), - &TransformOperation::Scale(ref t, None), - ) => { - Ok(TransformOperation::Scale( - animate_multiplicative_factor(*f, *t, procedure)?, - None - )) - }, - ( - &TransformOperation::Scale(ref fx, ref fy), - &TransformOperation::Scale(ref tx, ref ty), - ) => { - Ok(TransformOperation::Scale( - animate_multiplicative_factor(*fx, *tx, procedure)?, - Some(animate_multiplicative_factor( - fy.unwrap_or(*fx), - ty.unwrap_or(*tx), - procedure - )?), - )) - }, - ( - &TransformOperation::Rotate3D(fx, fy, fz, fa), - &TransformOperation::Rotate3D(tx, ty, tz, ta), - ) => { - let animated = Rotate::Rotate3D(fx, fy, fz, fa) - .animate(&Rotate::Rotate3D(tx, ty, tz, ta), procedure)?; - let (fx, fy, fz, fa) = ComputedRotate::resolve(&animated); - Ok(TransformOperation::Rotate3D(fx, fy, fz, fa)) - }, - ( - &TransformOperation::RotateX(fa), - &TransformOperation::RotateX(ta), - ) => { - Ok(TransformOperation::RotateX( - fa.animate(&ta, procedure)? - )) - }, - ( - &TransformOperation::RotateY(fa), - &TransformOperation::RotateY(ta), - ) => { - Ok(TransformOperation::RotateY( - fa.animate(&ta, procedure)? - )) - }, - ( - &TransformOperation::RotateZ(fa), - &TransformOperation::RotateZ(ta), - ) => { - Ok(TransformOperation::RotateZ( - fa.animate(&ta, procedure)? - )) - }, - ( - &TransformOperation::Rotate(fa), - &TransformOperation::Rotate(ta), - ) => { - Ok(TransformOperation::Rotate( - fa.animate(&ta, procedure)? - )) - }, - ( - &TransformOperation::Rotate(fa), - &TransformOperation::RotateZ(ta), - ) => { - Ok(TransformOperation::Rotate( - fa.animate(&ta, procedure)? - )) - }, - ( - &TransformOperation::RotateZ(fa), - &TransformOperation::Rotate(ta), - ) => { - Ok(TransformOperation::Rotate( - fa.animate(&ta, procedure)? - )) - }, - ( - &TransformOperation::Perspective(ref fd), - &TransformOperation::Perspective(ref td), - ) => { - use crate::values::computed::CSSPixelLength; - use crate::values::generics::transform::create_perspective_matrix; - - // From https://drafts.csswg.org/css-transforms-2/#interpolation-of-transform-functions: - // - // The transform functions matrix(), matrix3d() and - // perspective() get converted into 4x4 matrices first and - // interpolated as defined in section Interpolation of - // Matrices afterwards. - // - let from = create_perspective_matrix(fd.px()); - let to = create_perspective_matrix(td.px()); - - let interpolated = - Matrix3D::from(from).animate(&Matrix3D::from(to), procedure)?; - - let decomposed = decompose_3d_matrix(interpolated)?; - let perspective_z = decomposed.perspective.2; - let used_value = - if perspective_z == 0. { 0. } else { -1. / perspective_z }; - Ok(TransformOperation::Perspective(CSSPixelLength::new(used_value))) - }, - _ if self.is_translate() && other.is_translate() => { - self.to_translate_3d().animate(&other.to_translate_3d(), procedure) - } - _ if self.is_scale() && other.is_scale() => { - self.to_scale_3d().animate(&other.to_scale_3d(), procedure) - } - _ if self.is_rotate() && other.is_rotate() => { - self.to_rotate_3d().animate(&other.to_rotate_3d(), procedure) - } - _ => Err(()), - } - } -} - -fn is_matched_operation(first: &ComputedTransformOperation, second: &ComputedTransformOperation) -> bool { - match (first, second) { - (&TransformOperation::Matrix(..), - &TransformOperation::Matrix(..)) | - (&TransformOperation::Matrix3D(..), - &TransformOperation::Matrix3D(..)) | - (&TransformOperation::Skew(..), - &TransformOperation::Skew(..)) | - (&TransformOperation::SkewX(..), - &TransformOperation::SkewX(..)) | - (&TransformOperation::SkewY(..), - &TransformOperation::SkewY(..)) | - (&TransformOperation::Rotate(..), - &TransformOperation::Rotate(..)) | - (&TransformOperation::Rotate3D(..), - &TransformOperation::Rotate3D(..)) | - (&TransformOperation::RotateX(..), - &TransformOperation::RotateX(..)) | - (&TransformOperation::RotateY(..), - &TransformOperation::RotateY(..)) | - (&TransformOperation::RotateZ(..), - &TransformOperation::RotateZ(..)) | - (&TransformOperation::Perspective(..), - &TransformOperation::Perspective(..)) => true, - // Match functions that have the same primitive transform function - (a, b) if a.is_translate() && b.is_translate() => true, - (a, b) if a.is_scale() && b.is_scale() => true, - (a, b) if a.is_rotate() && b.is_rotate() => true, - // InterpolateMatrix and AccumulateMatrix are for mismatched transforms - _ => false - } -} - -/// A 2d matrix for interpolation. -#[derive(Clone, ComputeSquaredDistance, Copy, Debug)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -#[allow(missing_docs)] -// FIXME: We use custom derive for ComputeSquaredDistance. However, If possible, we should convert -// the InnerMatrix2D into types with physical meaning. This custom derive computes the squared -// distance from each matrix item, and this makes the result different from that in Gecko if we -// have skew factor in the Matrix3D. -pub struct InnerMatrix2D { - pub m11: CSSFloat, pub m12: CSSFloat, - pub m21: CSSFloat, pub m22: CSSFloat, -} - -/// A 2d translation function. -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -#[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug)] -pub struct Translate2D(f32, f32); - -/// A 2d scale function. -#[derive(Clone, ComputeSquaredDistance, Copy, Debug)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub struct Scale2D(f32, f32); - -/// A decomposed 2d matrix. -#[derive(Clone, Copy, Debug)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub struct MatrixDecomposed2D { - /// The translation function. - pub translate: Translate2D, - /// The scale function. - pub scale: Scale2D, - /// The rotation angle. - pub angle: f32, - /// The inner matrix. - pub matrix: InnerMatrix2D, -} - -impl Animate for InnerMatrix2D { - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - Ok(InnerMatrix2D { - m11: animate_multiplicative_factor(self.m11, other.m11, procedure)?, - m12: self.m12.animate(&other.m12, procedure)?, - m21: self.m21.animate(&other.m21, procedure)?, - m22: animate_multiplicative_factor(self.m22, other.m22, procedure)?, - }) - } -} - -impl Animate for Scale2D { - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - Ok(Scale2D( - animate_multiplicative_factor(self.0, other.0, procedure)?, - animate_multiplicative_factor(self.1, other.1, procedure)?, - )) - } -} - -impl Animate for MatrixDecomposed2D { - /// <https://drafts.csswg.org/css-transforms/#interpolation-of-decomposed-2d-matrix-values> - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - // If x-axis of one is flipped, and y-axis of the other, - // convert to an unflipped rotation. - let mut scale = self.scale; - let mut angle = self.angle; - let mut other_angle = other.angle; - if (scale.0 < 0.0 && other.scale.1 < 0.0) || (scale.1 < 0.0 && other.scale.0 < 0.0) { - scale.0 = -scale.0; - scale.1 = -scale.1; - angle += if angle < 0.0 {180.} else {-180.}; - } - - // Don't rotate the long way around. - if angle == 0.0 { - angle = 360. - } - if other_angle == 0.0 { - other_angle = 360. - } - - if (angle - other_angle).abs() > 180. { - if angle > other_angle { - angle -= 360. - } - else{ - other_angle -= 360. - } - } - - // Interpolate all values. - let translate = self.translate.animate(&other.translate, procedure)?; - let scale = scale.animate(&other.scale, procedure)?; - let angle = angle.animate(&other_angle, procedure)?; - let matrix = self.matrix.animate(&other.matrix, procedure)?; - - Ok(MatrixDecomposed2D { - translate, - scale, - angle, - matrix, - }) - } -} - -impl ComputeSquaredDistance for MatrixDecomposed2D { - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - // Use Radian to compute the distance. - const RAD_PER_DEG: f64 = std::f64::consts::PI / 180.0; - let angle1 = self.angle as f64 * RAD_PER_DEG; - let angle2 = other.angle as f64 * RAD_PER_DEG; - Ok(self.translate.compute_squared_distance(&other.translate)? + - self.scale.compute_squared_distance(&other.scale)? + - angle1.compute_squared_distance(&angle2)? + - self.matrix.compute_squared_distance(&other.matrix)?) - } -} - -impl Animate for Matrix3D { - #[cfg(feature = "servo")] - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - if self.is_3d() || other.is_3d() { - let decomposed_from = decompose_3d_matrix(*self); - let decomposed_to = decompose_3d_matrix(*other); - match (decomposed_from, decomposed_to) { - (Ok(this), Ok(other)) => { - Ok(Matrix3D::from(this.animate(&other, procedure)?)) - }, - // Matrices can be undecomposable due to couple reasons, e.g., - // non-invertible matrices. In this case, we should report Err - // here, and let the caller do the fallback procedure. - _ => Err(()) - } - } else { - let this = MatrixDecomposed2D::from(*self); - let other = MatrixDecomposed2D::from(*other); - Ok(Matrix3D::from(this.animate(&other, procedure)?)) - } - } - - #[cfg(feature = "gecko")] - // Gecko doesn't exactly follow the spec here; we use a different procedure - // to match it - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - let (from, to) = if self.is_3d() || other.is_3d() { - (decompose_3d_matrix(*self), decompose_3d_matrix(*other)) - } else { - (decompose_2d_matrix(self), decompose_2d_matrix(other)) - }; - match (from, to) { - (Ok(from), Ok(to)) => { - Ok(Matrix3D::from(from.animate(&to, procedure)?)) - }, - // Matrices can be undecomposable due to couple reasons, e.g., - // non-invertible matrices. In this case, we should report Err here, - // and let the caller do the fallback procedure. - _ => Err(()) - } - } -} - -impl Animate for Matrix { - #[cfg(feature = "servo")] - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - let this = Matrix3D::from(*self); - let other = Matrix3D::from(*other); - let this = MatrixDecomposed2D::from(this); - let other = MatrixDecomposed2D::from(other); - Ok(Matrix3D::from(this.animate(&other, procedure)?).into_2d()?) - } - - #[cfg(feature = "gecko")] - // Gecko doesn't exactly follow the spec here; we use a different procedure - // to match it - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - let from = decompose_2d_matrix(&(*self).into()); - let to = decompose_2d_matrix(&(*other).into()); - match (from, to) { - (Ok(from), Ok(to)) => { - Matrix3D::from(from.animate(&to, procedure)?).into_2d() - }, - // Matrices can be undecomposable due to couple reasons, e.g., - // non-invertible matrices. In this case, we should report Err here, - // and let the caller do the fallback procedure. - _ => Err(()) - } - } -} - -impl ComputeSquaredDistance for Matrix3D { - #[inline] - #[cfg(feature = "servo")] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - if self.is_3d() || other.is_3d() { - let from = decompose_3d_matrix(*self)?; - let to = decompose_3d_matrix(*other)?; - from.compute_squared_distance(&to) - } else { - let from = MatrixDecomposed2D::from(*self); - let to = MatrixDecomposed2D::from(*other); - from.compute_squared_distance(&to) - } - } - - #[inline] - #[cfg(feature = "gecko")] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - let (from, to) = if self.is_3d() || other.is_3d() { - (decompose_3d_matrix(*self)?, decompose_3d_matrix(*other)?) - } else { - (decompose_2d_matrix(self)?, decompose_2d_matrix(other)?) - }; - from.compute_squared_distance(&to) - } -} - -impl From<Matrix3D> for MatrixDecomposed2D { - /// Decompose a 2D matrix. - /// <https://drafts.csswg.org/css-transforms/#decomposing-a-2d-matrix> - fn from(matrix: Matrix3D) -> MatrixDecomposed2D { - let mut row0x = matrix.m11; - let mut row0y = matrix.m12; - let mut row1x = matrix.m21; - let mut row1y = matrix.m22; - - let translate = Translate2D(matrix.m41, matrix.m42); - let mut scale = Scale2D((row0x * row0x + row0y * row0y).sqrt(), - (row1x * row1x + row1y * row1y).sqrt()); - - // If determinant is negative, one axis was flipped. - let determinant = row0x * row1y - row0y * row1x; - if determinant < 0. { - if row0x < row1y { - scale.0 = -scale.0; - } else { - scale.1 = -scale.1; - } - } - - // Renormalize matrix to remove scale. - if scale.0 != 0.0 { - row0x *= 1. / scale.0; - row0y *= 1. / scale.0; - } - if scale.1 != 0.0 { - row1x *= 1. / scale.1; - row1y *= 1. / scale.1; - } - - // Compute rotation and renormalize matrix. - let mut angle = row0y.atan2(row0x); - if angle != 0.0 { - let sn = -row0y; - let cs = row0x; - let m11 = row0x; - let m12 = row0y; - let m21 = row1x; - let m22 = row1y; - row0x = cs * m11 + sn * m21; - row0y = cs * m12 + sn * m22; - row1x = -sn * m11 + cs * m21; - row1y = -sn * m12 + cs * m22; - } - - let m = InnerMatrix2D { - m11: row0x, m12: row0y, - m21: row1x, m22: row1y, - }; - - // Convert into degrees because our rotation functions expect it. - angle = angle.to_degrees(); - MatrixDecomposed2D { - translate: translate, - scale: scale, - angle: angle, - matrix: m, - } - } -} - -impl From<MatrixDecomposed2D> for Matrix3D { - /// Recompose a 2D matrix. - /// <https://drafts.csswg.org/css-transforms/#recomposing-to-a-2d-matrix> - fn from(decomposed: MatrixDecomposed2D) -> Matrix3D { - let mut computed_matrix = Matrix3D::identity(); - computed_matrix.m11 = decomposed.matrix.m11; - computed_matrix.m12 = decomposed.matrix.m12; - computed_matrix.m21 = decomposed.matrix.m21; - computed_matrix.m22 = decomposed.matrix.m22; - - // Translate matrix. - computed_matrix.m41 = decomposed.translate.0; - computed_matrix.m42 = decomposed.translate.1; - - // Rotate matrix. - let angle = decomposed.angle.to_radians(); - let cos_angle = angle.cos(); - let sin_angle = angle.sin(); - - let mut rotate_matrix = Matrix3D::identity(); - rotate_matrix.m11 = cos_angle; - rotate_matrix.m12 = sin_angle; - rotate_matrix.m21 = -sin_angle; - rotate_matrix.m22 = cos_angle; - - // Multiplication of computed_matrix and rotate_matrix - computed_matrix = multiply(rotate_matrix, computed_matrix); - - // Scale matrix. - computed_matrix.m11 *= decomposed.scale.0; - computed_matrix.m12 *= decomposed.scale.0; - computed_matrix.m21 *= decomposed.scale.1; - computed_matrix.m22 *= decomposed.scale.1; - computed_matrix - } -} - -#[cfg(feature = "gecko")] -impl<'a> From< &'a RawGeckoGfxMatrix4x4> for Matrix3D { - fn from(m: &'a RawGeckoGfxMatrix4x4) -> Matrix3D { - Matrix3D { - m11: m[0], m12: m[1], m13: m[2], m14: m[3], - m21: m[4], m22: m[5], m23: m[6], m24: m[7], - m31: m[8], m32: m[9], m33: m[10], m34: m[11], - m41: m[12], m42: m[13], m43: m[14], m44: m[15], - } - } -} - -#[cfg(feature = "gecko")] -impl From<Matrix3D> for RawGeckoGfxMatrix4x4 { - fn from(matrix: Matrix3D) -> RawGeckoGfxMatrix4x4 { - [ matrix.m11, matrix.m12, matrix.m13, matrix.m14, - matrix.m21, matrix.m22, matrix.m23, matrix.m24, - matrix.m31, matrix.m32, matrix.m33, matrix.m34, - matrix.m41, matrix.m42, matrix.m43, matrix.m44 ] - } -} - -/// A 3d translation. -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -#[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug)] -pub struct Translate3D(f32, f32, f32); - -/// A 3d scale function. -#[derive(Clone, ComputeSquaredDistance, Copy, Debug)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub struct Scale3D(f32, f32, f32); - -/// A 3d skew function. -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -#[derive(Animate, Clone, Copy, Debug)] -pub struct Skew(f32, f32, f32); - -/// A 3d perspective transformation. -#[derive(Clone, ComputeSquaredDistance, Copy, Debug)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub struct Perspective(f32, f32, f32, f32); - -/// A quaternion used to represent a rotation. -#[derive(Clone, Copy, Debug)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub struct Quaternion(f64, f64, f64, f64); - -/// A decomposed 3d matrix. -#[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug)] -#[cfg_attr(feature = "servo", derive(MallocSizeOf))] -pub struct MatrixDecomposed3D { - /// A translation function. - pub translate: Translate3D, - /// A scale function. - pub scale: Scale3D, - /// The skew component of the transformation. - pub skew: Skew, - /// The perspective component of the transformation. - pub perspective: Perspective, - /// The quaternion used to represent the rotation. - pub quaternion: Quaternion, -} - -impl Quaternion { - /// Return a quaternion from a unit direction vector and angle (unit: radian). - #[inline] - fn from_direction_and_angle(vector: &DirectionVector, angle: f64) -> Self { - debug_assert!((vector.length() - 1.).abs() < 0.0001, - "Only accept an unit direction vector to create a quaternion"); - // Reference: - // https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation - // - // if the direction axis is (x, y, z) = xi + yj + zk, - // and the angle is |theta|, this formula can be done using - // an extension of Euler's formula: - // q = cos(theta/2) + (xi + yj + zk)(sin(theta/2)) - // = cos(theta/2) + - // x*sin(theta/2)i + y*sin(theta/2)j + z*sin(theta/2)k - Quaternion(vector.x as f64 * (angle / 2.).sin(), - vector.y as f64 * (angle / 2.).sin(), - vector.z as f64 * (angle / 2.).sin(), - (angle / 2.).cos()) - } - - /// Calculate the dot product. - #[inline] - fn dot(&self, other: &Self) -> f64 { - self.0 * other.0 + self.1 * other.1 + self.2 * other.2 + self.3 * other.3 - } -} - -impl Animate for Quaternion { - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - use std::f64; - - let (this_weight, other_weight) = procedure.weights(); - debug_assert!( - // Doule EPSILON since both this_weight and other_weght have calculation errors - // which are approximately equal to EPSILON. - (this_weight + other_weight - 1.0f64).abs() <= f64::EPSILON * 2.0 || - other_weight == 1.0f64 || other_weight == 0.0f64, - "animate should only be used for interpolating or accumulating transforms" - ); - - // We take a specialized code path for accumulation (where other_weight - // is 1). - if let Procedure::Accumulate { .. } = procedure { - debug_assert_eq!(other_weight, 1.0); - if this_weight == 0.0 { - return Ok(*other); - } - - let clamped_w = self.3.min(1.0).max(-1.0); - - // Determine the scale factor. - let mut theta = clamped_w.acos(); - let mut scale = if theta == 0.0 { 0.0 } else { 1.0 / theta.sin() }; - theta *= this_weight; - scale *= theta.sin(); - - // Scale the self matrix by this_weight. - let mut scaled_self = *self; - % for i in range(3): - scaled_self.${i} *= scale; - % endfor - scaled_self.3 = theta.cos(); - - // Multiply scaled-self by other. - let a = &scaled_self; - let b = other; - return Ok(Quaternion( - a.3 * b.0 + a.0 * b.3 + a.1 * b.2 - a.2 * b.1, - a.3 * b.1 - a.0 * b.2 + a.1 * b.3 + a.2 * b.0, - a.3 * b.2 + a.0 * b.1 - a.1 * b.0 + a.2 * b.3, - a.3 * b.3 - a.0 * b.0 - a.1 * b.1 - a.2 * b.2, - )); - } - - // Straight from gfxQuaternion::Slerp. - // - // Dot product, clamped between -1 and 1. - let dot = - (self.0 * other.0 + - self.1 * other.1 + - self.2 * other.2 + - self.3 * other.3) - .min(1.0).max(-1.0); - - if dot.abs() == 1.0 { - return Ok(*self); - } - - let theta = dot.acos(); - let rsintheta = 1.0 / (1.0 - dot * dot).sqrt(); - - let right_weight = (other_weight * theta).sin() * rsintheta; - let left_weight = (other_weight * theta).cos() - dot * right_weight; - - let mut left = *self; - let mut right = *other; - % for i in range(4): - left.${i} *= left_weight; - right.${i} *= right_weight; - % endfor - - Ok(Quaternion( - left.0 + right.0, - left.1 + right.1, - left.2 + right.2, - left.3 + right.3, - )) - } -} - -impl ComputeSquaredDistance for Quaternion { - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - // Use quaternion vectors to get the angle difference. Both q1 and q2 are unit vectors, - // so we can get their angle difference by: - // cos(theta/2) = (q1 dot q2) / (|q1| * |q2|) = q1 dot q2. - let distance = self.dot(other).max(-1.0).min(1.0).acos() * 2.0; - Ok(SquaredDistance::from_sqrt(distance)) - } -} - -/// Decompose a 3D matrix. -/// https://drafts.csswg.org/css-transforms-2/#decomposing-a-3d-matrix -/// http://www.realtimerendering.com/resources/GraphicsGems/gemsii/unmatrix.c -fn decompose_3d_matrix(mut matrix: Matrix3D) -> Result<MatrixDecomposed3D, ()> { - // Normalize the matrix. - if matrix.m44 == 0.0 { - return Err(()); - } - - let scaling_factor = matrix.m44; - - // Normalize the matrix. - % for i in range(1, 5): - % for j in range(1, 5): - matrix.m${i}${j} /= scaling_factor; - % endfor - % endfor - - // perspective_matrix is used to solve for perspective, but it also provides - // an easy way to test for singularity of the upper 3x3 component. - let mut perspective_matrix = matrix; - - perspective_matrix.m14 = 0.0; - perspective_matrix.m24 = 0.0; - perspective_matrix.m34 = 0.0; - perspective_matrix.m44 = 1.0; - - if perspective_matrix.determinant() == 0.0 { - return Err(()); - } - - // First, isolate perspective. - let perspective = if matrix.m14 != 0.0 || matrix.m24 != 0.0 || matrix.m34 != 0.0 { - let right_hand_side: [f32; 4] = [ - matrix.m14, - matrix.m24, - matrix.m34, - matrix.m44 - ]; - - perspective_matrix = perspective_matrix.inverse().unwrap().transpose(); - let perspective = perspective_matrix.pre_mul_point4(&right_hand_side); - // NOTE(emilio): Even though the reference algorithm clears the - // fourth column here (matrix.m14..matrix.m44), they're not used below - // so it's not really needed. - Perspective(perspective[0], perspective[1], perspective[2], perspective[3]) - } else { - Perspective(0.0, 0.0, 0.0, 1.0) - }; - - // Next take care of translation (easy). - let translate = Translate3D(matrix.m41, matrix.m42, matrix.m43); - - // Now get scale and shear. 'row' is a 3 element array of 3 component vectors - let mut row: [[f32; 3]; 3] = [[0.0; 3]; 3]; - % for i in range(1, 4): - row[${i - 1}][0] = matrix.m${i}1; - row[${i - 1}][1] = matrix.m${i}2; - row[${i - 1}][2] = matrix.m${i}3; - % endfor - - // Compute X scale factor and normalize first row. - let row0len = (row[0][0] * row[0][0] + row[0][1] * row[0][1] + row[0][2] * row[0][2]).sqrt(); - let mut scale = Scale3D(row0len, 0.0, 0.0); - row[0] = [row[0][0] / row0len, row[0][1] / row0len, row[0][2] / row0len]; - - // Compute XY shear factor and make 2nd row orthogonal to 1st. - let mut skew = Skew(dot(row[0], row[1]), 0.0, 0.0); - row[1] = combine(row[1], row[0], 1.0, -skew.0); - - // Now, compute Y scale and normalize 2nd row. - let row1len = (row[1][0] * row[1][0] + row[1][1] * row[1][1] + row[1][2] * row[1][2]).sqrt(); - scale.1 = row1len; - row[1] = [row[1][0] / row1len, row[1][1] / row1len, row[1][2] / row1len]; - skew.0 /= scale.1; - - // Compute XZ and YZ shears, orthogonalize 3rd row - skew.1 = dot(row[0], row[2]); - row[2] = combine(row[2], row[0], 1.0, -skew.1); - skew.2 = dot(row[1], row[2]); - row[2] = combine(row[2], row[1], 1.0, -skew.2); - - // Next, get Z scale and normalize 3rd row. - let row2len = (row[2][0] * row[2][0] + row[2][1] * row[2][1] + row[2][2] * row[2][2]).sqrt(); - scale.2 = row2len; - row[2] = [row[2][0] / row2len, row[2][1] / row2len, row[2][2] / row2len]; - skew.1 /= scale.2; - skew.2 /= scale.2; - - // At this point, the matrix (in rows) is orthonormal. - // Check for a coordinate system flip. If the determinant - // is -1, then negate the matrix and the scaling factors. - if dot(row[0], cross(row[1], row[2])) < 0.0 { - % for i in range(3): - scale.${i} *= -1.0; - row[${i}][0] *= -1.0; - row[${i}][1] *= -1.0; - row[${i}][2] *= -1.0; - % endfor - } - - // Now, get the rotations out. - let mut quaternion = Quaternion( - 0.5 * ((1.0 + row[0][0] - row[1][1] - row[2][2]).max(0.0) as f64).sqrt(), - 0.5 * ((1.0 - row[0][0] + row[1][1] - row[2][2]).max(0.0) as f64).sqrt(), - 0.5 * ((1.0 - row[0][0] - row[1][1] + row[2][2]).max(0.0) as f64).sqrt(), - 0.5 * ((1.0 + row[0][0] + row[1][1] + row[2][2]).max(0.0) as f64).sqrt() - ); - - if row[2][1] > row[1][2] { - quaternion.0 = -quaternion.0 - } - if row[0][2] > row[2][0] { - quaternion.1 = -quaternion.1 - } - if row[1][0] > row[0][1] { - quaternion.2 = -quaternion.2 - } - - Ok(MatrixDecomposed3D { - translate, - scale, - skew, - perspective, - quaternion, - }) -} - -/// Decompose a 2D matrix for Gecko. -// Use the algorithm from nsStyleTransformMatrix::Decompose2DMatrix() in Gecko. -#[cfg(feature = "gecko")] -fn decompose_2d_matrix(matrix: &Matrix3D) -> Result<MatrixDecomposed3D, ()> { - // The index is column-major, so the equivalent transform matrix is: - // | m11 m21 0 m41 | => | m11 m21 | and translate(m41, m42) - // | m12 m22 0 m42 | | m12 m22 | - // | 0 0 1 0 | - // | 0 0 0 1 | - let (mut m11, mut m12) = (matrix.m11, matrix.m12); - let (mut m21, mut m22) = (matrix.m21, matrix.m22); - // Check if this is a singular matrix. - if m11 * m22 == m12 * m21 { - return Err(()); - } - - let mut scale_x = (m11 * m11 + m12 * m12).sqrt(); - m11 /= scale_x; - m12 /= scale_x; - - let mut shear_xy = m11 * m21 + m12 * m22; - m21 -= m11 * shear_xy; - m22 -= m12 * shear_xy; - - let scale_y = (m21 * m21 + m22 * m22).sqrt(); - m21 /= scale_y; - m22 /= scale_y; - shear_xy /= scale_y; - - let determinant = m11 * m22 - m12 * m21; - // Determinant should now be 1 or -1. - if 0.99 > determinant.abs() || determinant.abs() > 1.01 { - return Err(()); - } - - if determinant < 0. { - m11 = -m11; - m12 = -m12; - shear_xy = -shear_xy; - scale_x = -scale_x; - } - - Ok(MatrixDecomposed3D { - translate: Translate3D(matrix.m41, matrix.m42, 0.), - scale: Scale3D(scale_x, scale_y, 1.), - skew: Skew(shear_xy, 0., 0.), - perspective: Perspective(0., 0., 0., 1.), - quaternion: Quaternion::from_direction_and_angle(&DirectionVector::new(0., 0., 1.), - m12.atan2(m11) as f64) - }) -} - -// Combine 2 point. -fn combine(a: [f32; 3], b: [f32; 3], ascl: f32, bscl: f32) -> [f32; 3] { - [ - (ascl * a[0]) + (bscl * b[0]), - (ascl * a[1]) + (bscl * b[1]), - (ascl * a[2]) + (bscl * b[2]) - ] -} - -// Dot product. -fn dot(a: [f32; 3], b: [f32; 3]) -> f32 { - a[0] * b[0] + a[1] * b[1] + a[2] * b[2] -} - -// Cross product. -fn cross(row1: [f32; 3], row2: [f32; 3]) -> [f32; 3] { - [ - row1[1] * row2[2] - row1[2] * row2[1], - row1[2] * row2[0] - row1[0] * row2[2], - row1[0] * row2[1] - row1[1] * row2[0] - ] -} - -impl Animate for Scale3D { - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - Ok(Scale3D( - animate_multiplicative_factor(self.0, other.0, procedure)?, - animate_multiplicative_factor(self.1, other.1, procedure)?, - animate_multiplicative_factor(self.2, other.2, procedure)?, - )) - } -} - -impl ComputeSquaredDistance for Skew { - // We have to use atan() to convert the skew factors into skew angles, so implement - // ComputeSquaredDistance manually. - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - Ok(self.0.atan().compute_squared_distance(&other.0.atan())? + - self.1.atan().compute_squared_distance(&other.1.atan())? + - self.2.atan().compute_squared_distance(&other.2.atan())?) - } -} - -impl Animate for Perspective { - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - Ok(Perspective( - self.0.animate(&other.0, procedure)?, - self.1.animate(&other.1, procedure)?, - self.2.animate(&other.2, procedure)?, - animate_multiplicative_factor(self.3, other.3, procedure)?, - )) - } -} - -impl From<MatrixDecomposed3D> for Matrix3D { - /// Recompose a 3D matrix. - /// <https://drafts.csswg.org/css-transforms/#recomposing-to-a-3d-matrix> - fn from(decomposed: MatrixDecomposed3D) -> Matrix3D { - let mut matrix = Matrix3D::identity(); - - // Apply perspective - % for i in range(1, 5): - matrix.m${i}4 = decomposed.perspective.${i - 1}; - % endfor - - // Apply translation - % for i in range(1, 5): - % for j in range(1, 4): - matrix.m4${i} += decomposed.translate.${j - 1} * matrix.m${j}${i}; - % endfor - % endfor - - // Apply rotation - { - let x = decomposed.quaternion.0; - let y = decomposed.quaternion.1; - let z = decomposed.quaternion.2; - let w = decomposed.quaternion.3; - - // Construct a composite rotation matrix from the quaternion values - // rotationMatrix is a identity 4x4 matrix initially - let mut rotation_matrix = Matrix3D::identity(); - rotation_matrix.m11 = 1.0 - 2.0 * (y * y + z * z) as f32; - rotation_matrix.m12 = 2.0 * (x * y + z * w) as f32; - rotation_matrix.m13 = 2.0 * (x * z - y * w) as f32; - rotation_matrix.m21 = 2.0 * (x * y - z * w) as f32; - rotation_matrix.m22 = 1.0 - 2.0 * (x * x + z * z) as f32; - rotation_matrix.m23 = 2.0 * (y * z + x * w) as f32; - rotation_matrix.m31 = 2.0 * (x * z + y * w) as f32; - rotation_matrix.m32 = 2.0 * (y * z - x * w) as f32; - rotation_matrix.m33 = 1.0 - 2.0 * (x * x + y * y) as f32; - - matrix = multiply(rotation_matrix, matrix); - } - - // Apply skew - { - let mut temp = Matrix3D::identity(); - if decomposed.skew.2 != 0.0 { - temp.m32 = decomposed.skew.2; - matrix = multiply(temp, matrix); - temp.m32 = 0.0; - } - - if decomposed.skew.1 != 0.0 { - temp.m31 = decomposed.skew.1; - matrix = multiply(temp, matrix); - temp.m31 = 0.0; - } - - if decomposed.skew.0 != 0.0 { - temp.m21 = decomposed.skew.0; - matrix = multiply(temp, matrix); - } - } - - // Apply scale - % for i in range(1, 4): - % for j in range(1, 5): - matrix.m${i}${j} *= decomposed.scale.${i - 1}; - % endfor - % endfor - - matrix - } -} - -// Multiplication of two 4x4 matrices. -fn multiply(a: Matrix3D, b: Matrix3D) -> Matrix3D { - Matrix3D { - % for i in range(1, 5): - % for j in range(1, 5): - m${i}${j}: - a.m${i}1 * b.m1${j} + - a.m${i}2 * b.m2${j} + - a.m${i}3 * b.m3${j} + - a.m${i}4 * b.m4${j}, - % endfor - % endfor - } -} - -impl Matrix3D { - fn is_3d(&self) -> bool { - self.m13 != 0.0 || self.m14 != 0.0 || - self.m23 != 0.0 || self.m24 != 0.0 || - self.m31 != 0.0 || self.m32 != 0.0 || self.m33 != 1.0 || self.m34 != 0.0 || - self.m43 != 0.0 || self.m44 != 1.0 - } - - fn determinant(&self) -> CSSFloat { - self.m14 * self.m23 * self.m32 * self.m41 - - self.m13 * self.m24 * self.m32 * self.m41 - - self.m14 * self.m22 * self.m33 * self.m41 + - self.m12 * self.m24 * self.m33 * self.m41 + - self.m13 * self.m22 * self.m34 * self.m41 - - self.m12 * self.m23 * self.m34 * self.m41 - - self.m14 * self.m23 * self.m31 * self.m42 + - self.m13 * self.m24 * self.m31 * self.m42 + - self.m14 * self.m21 * self.m33 * self.m42 - - self.m11 * self.m24 * self.m33 * self.m42 - - self.m13 * self.m21 * self.m34 * self.m42 + - self.m11 * self.m23 * self.m34 * self.m42 + - self.m14 * self.m22 * self.m31 * self.m43 - - self.m12 * self.m24 * self.m31 * self.m43 - - self.m14 * self.m21 * self.m32 * self.m43 + - self.m11 * self.m24 * self.m32 * self.m43 + - self.m12 * self.m21 * self.m34 * self.m43 - - self.m11 * self.m22 * self.m34 * self.m43 - - self.m13 * self.m22 * self.m31 * self.m44 + - self.m12 * self.m23 * self.m31 * self.m44 + - self.m13 * self.m21 * self.m32 * self.m44 - - self.m11 * self.m23 * self.m32 * self.m44 - - self.m12 * self.m21 * self.m33 * self.m44 + - self.m11 * self.m22 * self.m33 * self.m44 - } - - /// Transpose a matrix. - fn transpose(&self) -> Self { - Self { - % for i in range(1, 5): - % for j in range(1, 5): - m${i}${j}: self.m${j}${i}, - % endfor - % endfor - } - } - - fn inverse(&self) -> Result<Matrix3D, ()> { - let mut det = self.determinant(); - - if det == 0.0 { - return Err(()); - } - - det = 1.0 / det; - let x = Matrix3D { - m11: det * - (self.m23*self.m34*self.m42 - self.m24*self.m33*self.m42 + - self.m24*self.m32*self.m43 - self.m22*self.m34*self.m43 - - self.m23*self.m32*self.m44 + self.m22*self.m33*self.m44), - m12: det * - (self.m14*self.m33*self.m42 - self.m13*self.m34*self.m42 - - self.m14*self.m32*self.m43 + self.m12*self.m34*self.m43 + - self.m13*self.m32*self.m44 - self.m12*self.m33*self.m44), - m13: det * - (self.m13*self.m24*self.m42 - self.m14*self.m23*self.m42 + - self.m14*self.m22*self.m43 - self.m12*self.m24*self.m43 - - self.m13*self.m22*self.m44 + self.m12*self.m23*self.m44), - m14: det * - (self.m14*self.m23*self.m32 - self.m13*self.m24*self.m32 - - self.m14*self.m22*self.m33 + self.m12*self.m24*self.m33 + - self.m13*self.m22*self.m34 - self.m12*self.m23*self.m34), - m21: det * - (self.m24*self.m33*self.m41 - self.m23*self.m34*self.m41 - - self.m24*self.m31*self.m43 + self.m21*self.m34*self.m43 + - self.m23*self.m31*self.m44 - self.m21*self.m33*self.m44), - m22: det * - (self.m13*self.m34*self.m41 - self.m14*self.m33*self.m41 + - self.m14*self.m31*self.m43 - self.m11*self.m34*self.m43 - - self.m13*self.m31*self.m44 + self.m11*self.m33*self.m44), - m23: det * - (self.m14*self.m23*self.m41 - self.m13*self.m24*self.m41 - - self.m14*self.m21*self.m43 + self.m11*self.m24*self.m43 + - self.m13*self.m21*self.m44 - self.m11*self.m23*self.m44), - m24: det * - (self.m13*self.m24*self.m31 - self.m14*self.m23*self.m31 + - self.m14*self.m21*self.m33 - self.m11*self.m24*self.m33 - - self.m13*self.m21*self.m34 + self.m11*self.m23*self.m34), - m31: det * - (self.m22*self.m34*self.m41 - self.m24*self.m32*self.m41 + - self.m24*self.m31*self.m42 - self.m21*self.m34*self.m42 - - self.m22*self.m31*self.m44 + self.m21*self.m32*self.m44), - m32: det * - (self.m14*self.m32*self.m41 - self.m12*self.m34*self.m41 - - self.m14*self.m31*self.m42 + self.m11*self.m34*self.m42 + - self.m12*self.m31*self.m44 - self.m11*self.m32*self.m44), - m33: det * - (self.m12*self.m24*self.m41 - self.m14*self.m22*self.m41 + - self.m14*self.m21*self.m42 - self.m11*self.m24*self.m42 - - self.m12*self.m21*self.m44 + self.m11*self.m22*self.m44), - m34: det * - (self.m14*self.m22*self.m31 - self.m12*self.m24*self.m31 - - self.m14*self.m21*self.m32 + self.m11*self.m24*self.m32 + - self.m12*self.m21*self.m34 - self.m11*self.m22*self.m34), - m41: det * - (self.m23*self.m32*self.m41 - self.m22*self.m33*self.m41 - - self.m23*self.m31*self.m42 + self.m21*self.m33*self.m42 + - self.m22*self.m31*self.m43 - self.m21*self.m32*self.m43), - m42: det * - (self.m12*self.m33*self.m41 - self.m13*self.m32*self.m41 + - self.m13*self.m31*self.m42 - self.m11*self.m33*self.m42 - - self.m12*self.m31*self.m43 + self.m11*self.m32*self.m43), - m43: det * - (self.m13*self.m22*self.m41 - self.m12*self.m23*self.m41 - - self.m13*self.m21*self.m42 + self.m11*self.m23*self.m42 + - self.m12*self.m21*self.m43 - self.m11*self.m22*self.m43), - m44: det * - (self.m12*self.m23*self.m31 - self.m13*self.m22*self.m31 + - self.m13*self.m21*self.m32 - self.m11*self.m23*self.m32 - - self.m12*self.m21*self.m33 + self.m11*self.m22*self.m33), - }; - - Ok(x) - } - - /// Multiplies `pin * self`. - fn pre_mul_point4(&self, pin: &[f32; 4]) -> [f32; 4] { - [ - % for i in range(1, 5): - pin[0] * self.m1${i} + - pin[1] * self.m2${i} + - pin[2] * self.m3${i} + - pin[3] * self.m4${i}, - % endfor - ] - } -} - -/// <https://drafts.csswg.org/css-transforms-2/#propdef-rotate> -impl ComputedRotate { - fn resolve(&self) -> (Number, Number, Number, Angle) { - // According to the spec: - // https://drafts.csswg.org/css-transforms-2/#individual-transforms - // - // If the axis is unspecified, it defaults to "0 0 1" - match *self { - Rotate::None => unreachable!("None is handled by the caller"), - Rotate::Rotate3D(rx, ry, rz, angle) => (rx, ry, rz, angle), - Rotate::Rotate(angle) => (0., 0., 1., angle), - } - } -} - -impl Animate for ComputedRotate { - #[inline] - fn animate( - &self, - other: &Self, - procedure: Procedure, - ) -> Result<Self, ()> { - match (self, other) { - (&Rotate::None, &Rotate::None) => Ok(Rotate::None), - (&Rotate::Rotate3D(fx, fy, fz, fa), &Rotate::None) => { - // No need to normalize `none`, so animate angle directly. - Ok(Rotate::Rotate3D(fx, fy, fz, fa.animate(&Angle::zero(), procedure)?)) - }, - (&Rotate::None, &Rotate::Rotate3D(tx, ty, tz, ta)) => { - // No need to normalize `none`, so animate angle directly. - Ok(Rotate::Rotate3D(tx, ty, tz, Angle::zero().animate(&ta, procedure)?)) - }, - (&Rotate::Rotate3D(_, ..), _) | (_, &Rotate::Rotate3D(_, ..)) => { - let (from, to) = (self.resolve(), other.resolve()); - let (mut fx, mut fy, mut fz, fa) = - transform::get_normalized_vector_and_angle(from.0, from.1, from.2, from.3); - let (mut tx, mut ty, mut tz, ta) = - transform::get_normalized_vector_and_angle(to.0, to.1, to.2, to.3); - - if fa == Angle::from_degrees(0.) { - fx = tx; - fy = ty; - fz = tz; - } else if ta == Angle::from_degrees(0.) { - tx = fx; - ty = fy; - tz = fz; - } - - if (fx, fy, fz) == (tx, ty, tz) { - return Ok(Rotate::Rotate3D(fx, fy, fz, fa.animate(&ta, procedure)?)); - } - - let fv = DirectionVector::new(fx, fy, fz); - let tv = DirectionVector::new(tx, ty, tz); - let fq = Quaternion::from_direction_and_angle(&fv, fa.radians64()); - let tq = Quaternion::from_direction_and_angle(&tv, ta.radians64()); - - let rq = Quaternion::animate(&fq, &tq, procedure)?; - let (x, y, z, angle) = transform::get_normalized_vector_and_angle( - rq.0 as f32, - rq.1 as f32, - rq.2 as f32, - rq.3.acos() as f32 * 2.0, - ); - - Ok(Rotate::Rotate3D(x, y, z, Angle::from_radians(angle))) - }, - (&Rotate::Rotate(_), _) | (_, &Rotate::Rotate(_)) => { - // If this is a 2D rotation, we just animate the <angle> - let (from, to) = (self.resolve().3, other.resolve().3); - Ok(Rotate::Rotate(from.animate(&to, procedure)?)) - }, - } - } -} - -/// <https://drafts.csswg.org/css-transforms-2/#propdef-translate> -impl ComputedTranslate { - fn resolve(&self) -> (LengthOrPercentage, LengthOrPercentage, Length) { - // According to the spec: - // https://drafts.csswg.org/css-transforms-2/#individual-transforms - // - // Unspecified translations default to 0px - match *self { - Translate::None => { - (LengthOrPercentage::zero(), LengthOrPercentage::zero(), Length::zero()) - }, - Translate::Translate3D(tx, ty, tz) => (tx, ty, tz), - Translate::Translate(tx, ty) => (tx, ty, Length::zero()), - } - } -} - -impl Animate for ComputedTranslate { - #[inline] - fn animate( - &self, - other: &Self, - procedure: Procedure, - ) -> Result<Self, ()> { - match (self, other) { - (&Translate::None, &Translate::None) => Ok(Translate::None), - (&Translate::Translate3D(_, ..), _) | (_, &Translate::Translate3D(_, ..)) => { - let (from, to) = (self.resolve(), other.resolve()); - Ok(Translate::Translate3D(from.0.animate(&to.0, procedure)?, - from.1.animate(&to.1, procedure)?, - from.2.animate(&to.2, procedure)?)) - }, - (&Translate::Translate(_, ..), _) | (_, &Translate::Translate(_, ..)) => { - let (from, to) = (self.resolve(), other.resolve()); - Ok(Translate::Translate(from.0.animate(&to.0, procedure)?, - from.1.animate(&to.1, procedure)?)) - }, - } - } -} - -/// <https://drafts.csswg.org/css-transforms-2/#propdef-scale> -impl ComputedScale { - fn resolve(&self) -> (Number, Number, Number) { - // According to the spec: - // https://drafts.csswg.org/css-transforms-2/#individual-transforms - // - // Unspecified scales default to 1 - match *self { - Scale::None => (1.0, 1.0, 1.0), - Scale::Scale3D(sx, sy, sz) => (sx, sy, sz), - Scale::Scale(sx, sy) => (sx, sy, 1.), - } - } -} - -impl Animate for ComputedScale { - #[inline] - fn animate( - &self, - other: &Self, - procedure: Procedure, - ) -> Result<Self, ()> { - match (self, other) { - (&Scale::None, &Scale::None) => Ok(Scale::None), - (&Scale::Scale3D(_, ..), _) | (_, &Scale::Scale3D(_, ..)) => { - let (from, to) = (self.resolve(), other.resolve()); - // FIXME(emilio, bug 1464791): why does this do something different than - // Scale3D / TransformOperation::Scale3D? - if procedure == Procedure::Add { - // scale(x1,y1,z1)*scale(x2,y2,z2) = scale(x1*x2, y1*y2, z1*z2) - return Ok(Scale::Scale3D(from.0 * to.0, from.1 * to.1, from.2 * to.2)); - } - Ok(Scale::Scale3D( - animate_multiplicative_factor(from.0, to.0, procedure)?, - animate_multiplicative_factor(from.1, to.1, procedure)?, - animate_multiplicative_factor(from.2, to.2, procedure)?, - )) - }, - (&Scale::Scale(_, ..), _) | (_, &Scale::Scale(_, ..)) => { - let (from, to) = (self.resolve(), other.resolve()); - // FIXME(emilio, bug 1464791): why does this do something different than - // Scale / TransformOperation::Scale? - if procedure == Procedure::Add { - // scale(x1,y1)*scale(x2,y2) = scale(x1*x2, y1*y2) - return Ok(Scale::Scale(from.0 * to.0, from.1 * to.1)); - } - Ok(Scale::Scale( - animate_multiplicative_factor(from.0, to.0, procedure)?, - animate_multiplicative_factor(from.1, to.1, procedure)?, - )) - }, - } - } -} - -/// <https://drafts.csswg.org/css-transforms/#interpolation-of-transforms> -impl Animate for ComputedTransform { - #[inline] - fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { - use std::borrow::Cow; - - if procedure == Procedure::Add { - let result = self.0.iter().chain(&other.0).cloned().collect::<Vec<_>>(); - return Ok(Transform(result)); - } - - let this = Cow::Borrowed(&self.0); - let other = Cow::Borrowed(&other.0); - - // Interpolate the common prefix - let mut result = this - .iter() - .zip(other.iter()) - .take_while(|(this, other)| is_matched_operation(this, other)) - .map(|(this, other)| this.animate(other, procedure)) - .collect::<Result<Vec<_>, _>>()?; - - // Deal with the remainders - let this_remainder = if this.len() > result.len() { - Some(&this[result.len()..]) - } else { - None - }; - let other_remainder = if other.len() > result.len() { - Some(&other[result.len()..]) - } else { - None - }; - - match (this_remainder, other_remainder) { - // If there is a remainder from *both* lists we must have had mismatched functions. - // => Add the remainders to a suitable ___Matrix function. - (Some(this_remainder), Some(other_remainder)) => match procedure { - Procedure::Add => { - debug_assert!(false, "Should have already dealt with add by the point"); - return Err(()); - } - Procedure::Interpolate { progress } => { - result.push(TransformOperation::InterpolateMatrix { - from_list: Transform(this_remainder.to_vec()), - to_list: Transform(other_remainder.to_vec()), - progress: Percentage(progress as f32), - }); - } - Procedure::Accumulate { count } => { - result.push(TransformOperation::AccumulateMatrix { - from_list: Transform(this_remainder.to_vec()), - to_list: Transform(other_remainder.to_vec()), - count: cmp::min(count, i32::max_value() as u64) as i32, - }); - } - }, - // If there is a remainder from just one list, then one list must be shorter but - // completely match the type of the corresponding functions in the longer list. - // => Interpolate the remainder with identity transforms. - (Some(remainder), None) | (None, Some(remainder)) => { - let fill_right = this_remainder.is_some(); - result.append( - &mut remainder - .iter() - .map(|transform| { - let identity = transform.to_animated_zero().unwrap(); - - match transform { - // We can't interpolate/accumulate ___Matrix types directly with a - // matrix. Instead we need to wrap it in another ___Matrix type. - TransformOperation::AccumulateMatrix { .. } - | TransformOperation::InterpolateMatrix { .. } => { - let transform_list = Transform(vec![transform.clone()]); - let identity_list = Transform(vec![identity]); - let (from_list, to_list) = if fill_right { - (transform_list, identity_list) - } else { - (identity_list, transform_list) - }; - - match procedure { - Procedure::Add => Err(()), - Procedure::Interpolate { progress } => { - Ok(TransformOperation::InterpolateMatrix { - from_list, - to_list, - progress: Percentage(progress as f32), - }) - } - Procedure::Accumulate { count } => { - Ok(TransformOperation::AccumulateMatrix { - from_list, - to_list, - count: cmp::min(count, i32::max_value() as u64) - as i32, - }) - } - } - } - _ => { - let (lhs, rhs) = if fill_right { - (transform, &identity) - } else { - (&identity, transform) - }; - lhs.animate(rhs, procedure) - } - } - }) - .collect::<Result<Vec<_>, _>>()?, - ); - } - (None, None) => {} - } - - Ok(Transform(result)) - } -} - -// This might not be the most useful definition of distance. It might be better, for example, -// to trace the distance travelled by a point as its transform is interpolated between the two -// lists. That, however, proves to be quite complicated so we take a simple approach for now. -// See https://bugzilla.mozilla.org/show_bug.cgi?id=1318591#c0. -impl ComputeSquaredDistance for ComputedTransformOperation { - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - // For translate, We don't want to require doing layout in order to calculate the result, so - // drop the percentage part. However, dropping percentage makes us impossible to - // compute the distance for the percentage-percentage case, but Gecko uses the - // same formula, so it's fine for now. - // Note: We use pixel value to compute the distance for translate, so we have to - // convert Au into px. - let extract_pixel_length = |lop: &LengthOrPercentage| { - match *lop { - LengthOrPercentage::Length(px) => px.px(), - LengthOrPercentage::Percentage(_) => 0., - LengthOrPercentage::Calc(calc) => calc.length().px(), - } - }; - match (self, other) { - ( - &TransformOperation::Matrix3D(ref this), - &TransformOperation::Matrix3D(ref other), - ) => { - this.compute_squared_distance(other) - }, - ( - &TransformOperation::Matrix(ref this), - &TransformOperation::Matrix(ref other), - ) => { - let this: Matrix3D = (*this).into(); - let other: Matrix3D = (*other).into(); - this.compute_squared_distance(&other) - }, - - - ( - &TransformOperation::Skew(ref fx, ref fy), - &TransformOperation::Skew(ref tx, ref ty), - ) => { - Ok( - fx.compute_squared_distance(&tx)? + - fy.compute_squared_distance(&ty)?, - ) - }, - ( - &TransformOperation::SkewX(ref f), - &TransformOperation::SkewX(ref t), - ) | ( - &TransformOperation::SkewY(ref f), - &TransformOperation::SkewY(ref t), - ) => { - f.compute_squared_distance(&t) - }, - ( - &TransformOperation::Translate3D(ref fx, ref fy, ref fz), - &TransformOperation::Translate3D(ref tx, ref ty, ref tz), - ) => { - let fx = extract_pixel_length(&fx); - let fy = extract_pixel_length(&fy); - let tx = extract_pixel_length(&tx); - let ty = extract_pixel_length(&ty); - - Ok( - fx.compute_squared_distance(&tx)? + - fy.compute_squared_distance(&ty)? + - fz.compute_squared_distance(&tz)?, - ) - }, - ( - &TransformOperation::Scale3D(ref fx, ref fy, ref fz), - &TransformOperation::Scale3D(ref tx, ref ty, ref tz), - ) => { - Ok( - fx.compute_squared_distance(&tx)? + - fy.compute_squared_distance(&ty)? + - fz.compute_squared_distance(&tz)?, - ) - }, - ( - &TransformOperation::Rotate3D(fx, fy, fz, fa), - &TransformOperation::Rotate3D(tx, ty, tz, ta), - ) => { - let (fx, fy, fz, angle1) = - transform::get_normalized_vector_and_angle(fx, fy, fz, fa); - let (tx, ty, tz, angle2) = - transform::get_normalized_vector_and_angle(tx, ty, tz, ta); - if (fx, fy, fz) == (tx, ty, tz) { - angle1.compute_squared_distance(&angle2) - } else { - let v1 = DirectionVector::new(fx, fy, fz); - let v2 = DirectionVector::new(tx, ty, tz); - let q1 = Quaternion::from_direction_and_angle(&v1, angle1.radians64()); - let q2 = Quaternion::from_direction_and_angle(&v2, angle2.radians64()); - q1.compute_squared_distance(&q2) - } - } - ( - &TransformOperation::RotateX(fa), - &TransformOperation::RotateX(ta), - ) | - ( - &TransformOperation::RotateY(fa), - &TransformOperation::RotateY(ta), - ) | - ( - &TransformOperation::RotateZ(fa), - &TransformOperation::RotateZ(ta), - ) | - ( - &TransformOperation::Rotate(fa), - &TransformOperation::Rotate(ta), - ) => { - fa.compute_squared_distance(&ta) - } - ( - &TransformOperation::Perspective(ref fd), - &TransformOperation::Perspective(ref td), - ) => { - fd.compute_squared_distance(td) - } - ( - &TransformOperation::Perspective(ref p), - &TransformOperation::Matrix3D(ref m), - ) | ( - &TransformOperation::Matrix3D(ref m), - &TransformOperation::Perspective(ref p), - ) => { - // FIXME(emilio): Is this right? Why interpolating this with - // Perspective but not with anything else? - let mut p_matrix = Matrix3D::identity(); - if p.px() > 0. { - p_matrix.m34 = -1. / p.px(); - } - p_matrix.compute_squared_distance(&m) - } - // Gecko cross-interpolates amongst all translate and all scale - // functions (See ToPrimitive in layout/style/StyleAnimationValue.cpp) - // without falling back to InterpolateMatrix - _ if self.is_translate() && other.is_translate() => { - self.to_translate_3d().compute_squared_distance(&other.to_translate_3d()) - } - _ if self.is_scale() && other.is_scale() => { - self.to_scale_3d().compute_squared_distance(&other.to_scale_3d()) - } - _ if self.is_rotate() && other.is_rotate() => { - self.to_rotate_3d().compute_squared_distance(&other.to_rotate_3d()) - } - _ => Err(()), - } - } -} - -impl ComputeSquaredDistance for ComputedTransform { - #[inline] - fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { - let squared_dist = self.0.squared_distance_with_zero(&other.0); - - // Roll back to matrix interpolation if there is any Err(()) in the - // transform lists, such as mismatched transform functions. - if squared_dist.is_err() { - let matrix1: Matrix3D = self.to_transform_3d_matrix(None)?.0.into(); - let matrix2: Matrix3D = other.to_transform_3d_matrix(None)?.0.into(); - return matrix1.compute_squared_distance(&matrix2); - } - - squared_dist - } -} - <% FILTER_FUNCTIONS = [ 'Blur', 'Brightness', 'Contrast', 'Grayscale', 'HueRotate', 'Invert', 'Opacity', 'Saturate', @@ -2576,6 +838,7 @@ impl Animate for AnimatedFilter { other: &Self, procedure: Procedure, ) -> Result<Self, ()> { + use crate::values::animated::animate_multiplicative_factor; match (self, other) { % for func in ['Blur', 'Grayscale', 'HueRotate', 'Invert', 'Sepia']: (&Filter::${func}(ref this), &Filter::${func}(ref other)) => { diff --git a/components/style/properties/longhands/background.mako.rs b/components/style/properties/longhands/background.mako.rs index aa44e1889de..934814567ba 100644 --- a/components/style/properties/longhands/background.mako.rs +++ b/components/style/properties/longhands/background.mako.rs @@ -15,7 +15,8 @@ ${helpers.predefined_type( animation_value_type="AnimatedColor", ignored_when_colors_disabled=True, allow_quirks=True, - flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER", + flags="APPLIES_TO_FIRST_LETTER APPLIES_TO_FIRST_LINE APPLIES_TO_PLACEHOLDER \ + CAN_ANIMATE_ON_COMPOSITOR", )} ${helpers.predefined_type( diff --git a/components/style/properties/longhands/border.mako.rs b/components/style/properties/longhands/border.mako.rs index 204c710625a..5a5cdbeb552 100644 --- a/components/style/properties/longhands/border.mako.rs +++ b/components/style/properties/longhands/border.mako.rs @@ -63,7 +63,9 @@ ${helpers.gecko_keyword_conversion( Keyword('border-style', - "none solid double dotted dashed hidden groove ridge inset outset"), + "none solid double dotted dashed hidden groove ridge inset outset", + gecko_enum_prefix="StyleBorderStyle", + gecko_inexhaustive=True), type="crate::values::specified::BorderStyle", )} diff --git a/components/style/properties/longhands/box.mako.rs b/components/style/properties/longhands/box.mako.rs index d560c4a1ad0..5a0f893cc63 100644 --- a/components/style/properties/longhands/box.mako.rs +++ b/components/style/properties/longhands/box.mako.rs @@ -438,33 +438,33 @@ ${helpers.single_keyword( )} ${helpers.predefined_type( - "page-break-after", + "break-after", "BreakBetween", "computed::BreakBetween::Auto", needs_context=False, products="gecko", - spec="https://drafts.csswg.org/css2/page.html#propdef-page-break-after", + spec="https://drafts.csswg.org/css-break/#propdef-break-after", animation_value_type="discrete", )} ${helpers.predefined_type( - "page-break-before", + "break-before", "BreakBetween", "computed::BreakBetween::Auto", needs_context=False, products="gecko", - spec="https://drafts.csswg.org/css2/page.html#propdef-page-break-before", + spec="https://drafts.csswg.org/css-break/#propdef-break-before", animation_value_type="discrete", )} ${helpers.predefined_type( - "page-break-inside", + "break-inside", "BreakWithin", "computed::BreakWithin::Auto", - gecko_ffi_name="mBreakInside", needs_context=False, products="gecko", - spec="https://drafts.csswg.org/css2/page.html#propdef-page-break-inside", + alias="page-break-inside", + spec="https://drafts.csswg.org/css-break/#propdef-break-inside", animation_value_type="discrete", )} diff --git a/components/style/properties/longhands/column.mako.rs b/components/style/properties/longhands/column.mako.rs index 193c13f2aa6..90c37a9710f 100644 --- a/components/style/properties/longhands/column.mako.rs +++ b/components/style/properties/longhands/column.mako.rs @@ -78,12 +78,14 @@ ${helpers.single_keyword( extra_prefixes="moz:layout.css.column-span.enabled", )} -${helpers.single_keyword( +${helpers.predefined_type( "column-rule-style", - "none hidden dotted dashed solid double groove ridge inset outset", + "BorderStyle", + "computed::BorderStyle::None", + needs_context=False, + initial_specified_value="specified::BorderStyle::None", products="gecko", extra_prefixes="moz", - gecko_constant_prefix="NS_STYLE_BORDER_STYLE", animation_value_type="discrete", spec="https://drafts.csswg.org/css-multicol/#propdef-column-rule-style", )} diff --git a/components/style/properties/longhands/inherited_text.mako.rs b/components/style/properties/longhands/inherited_text.mako.rs index a5bbe5ac72d..f751b29ae68 100644 --- a/components/style/properties/longhands/inherited_text.mako.rs +++ b/components/style/properties/longhands/inherited_text.mako.rs @@ -337,6 +337,7 @@ ${helpers.single_keyword( ${helpers.single_keyword( "text-rendering", "auto optimizespeed optimizelegibility geometricprecision", + gecko_enum_prefix="StyleTextRendering", animation_value_type="discrete", spec="https://www.w3.org/TR/SVG11/painting.html#TextRenderingProperty", servo_restyle_damage="rebuild_and_reflow", diff --git a/components/style/properties/longhands/ui.mako.rs b/components/style/properties/longhands/ui.mako.rs index 549327739e5..c54582e2584 100644 --- a/components/style/properties/longhands/ui.mako.rs +++ b/components/style/properties/longhands/ui.mako.rs @@ -39,6 +39,7 @@ ${helpers.predefined_type( gecko_ffi_name="mUserSelect", alias="-webkit-user-select", animation_value_type="discrete", + needs_context=False, spec="https://drafts.csswg.org/css-ui-4/#propdef-user-select", )} diff --git a/components/style/properties/properties.mako.rs b/components/style/properties/properties.mako.rs index 16fad869427..494bdc7706d 100644 --- a/components/style/properties/properties.mako.rs +++ b/components/style/properties/properties.mako.rs @@ -948,6 +948,10 @@ bitflags! { /// This property's getComputedStyle implementation requires layout /// to be flushed. const GETCS_NEEDS_LAYOUT_FLUSH = 1 << 6; + /// This property is a legacy shorthand. + /// + /// https://drafts.csswg.org/css-cascade/#legacy-shorthand + const IS_LEGACY_SHORTHAND = 1 << 7; /* The following flags are currently not used in Rust code, they * only need to be listed in corresponding properties so that @@ -1461,17 +1465,24 @@ impl ShorthandId { None } - /// Returns PropertyFlags for given shorthand property. - pub fn flags(&self) -> PropertyFlags { - match *self { + /// Returns PropertyFlags for the given shorthand property. + #[inline] + pub fn flags(self) -> PropertyFlags { + const FLAGS: [u8; ${len(data.shorthands)}] = [ % for property in data.shorthands: - ShorthandId::${property.camel_case} => - % for flag in property.flags: - PropertyFlags::${flag} | - % endfor - PropertyFlags::empty(), + % for flag in property.flags: + PropertyFlags::${flag}.bits | + % endfor + 0, % endfor - } + ]; + PropertyFlags::from_bits_truncate(FLAGS[self as usize]) + } + + /// Returns whether this property is a legacy shorthand. + #[inline] + pub fn is_legacy_shorthand(self) -> bool { + self.flags().contains(PropertyFlags::IS_LEGACY_SHORTHAND) } /// Returns the order in which this property appears relative to other diff --git a/components/style/properties/shorthands/box.mako.rs b/components/style/properties/shorthands/box.mako.rs index 95c31453a04..02763207cfa 100644 --- a/components/style/properties/shorthands/box.mako.rs +++ b/components/style/properties/shorthands/box.mako.rs @@ -447,3 +447,51 @@ macro_rules! try_parse_one { } } </%helpers:shorthand> + +<%helpers:shorthand + name="page-break-before" + products="gecko" + flags="SHORTHAND_IN_GETCS IS_LEGACY_SHORTHAND" + sub_properties="break-before" + spec="https://drafts.csswg.org/css2/page.html#propdef-page-break-before" +> + pub fn parse_value<'i>( + _: &ParserContext, + input: &mut Parser<'i, '_>, + ) -> Result<Longhands, ParseError<'i>> { + use crate::values::specified::box_::BreakBetween; + Ok(expanded! { + break_before: BreakBetween::parse_legacy(input)?, + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + self.break_before.to_css_legacy(dest) + } + } +</%helpers:shorthand> + +<%helpers:shorthand + name="page-break-after" + products="gecko" + flags="SHORTHAND_IN_GETCS IS_LEGACY_SHORTHAND" + sub_properties="break-after" + spec="https://drafts.csswg.org/css2/page.html#propdef-page-break-after" +> + pub fn parse_value<'i>( + _: &ParserContext, + input: &mut Parser<'i, '_>, + ) -> Result<Longhands, ParseError<'i>> { + use crate::values::specified::box_::BreakBetween; + Ok(expanded! { + break_after: BreakBetween::parse_legacy(input)?, + }) + } + + impl<'a> ToCss for LonghandsToSerialize<'a> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: fmt::Write { + self.break_after.to_css_legacy(dest) + } + } +</%helpers:shorthand> diff --git a/components/style/rule_tree/mod.rs b/components/style/rule_tree/mod.rs index fdd563d2372..a2caba6f9f7 100644 --- a/components/style/rule_tree/mod.rs +++ b/components/style/rule_tree/mod.rs @@ -453,6 +453,7 @@ impl RuleTree { StyleSource::from_declarations(pdb.clone_arc()), level, ); + *important_rules_changed = true; } } else { if pdb.read_with(level.guard(guards)).any_normal() { diff --git a/components/style/values/animated/mod.rs b/components/style/values/animated/mod.rs index 1adc3459970..6711dcf3c83 100644 --- a/components/style/values/animated/mod.rs +++ b/components/style/values/animated/mod.rs @@ -14,6 +14,7 @@ use crate::values::computed::length::CalcLengthOrPercentage; use crate::values::computed::url::ComputedUrl; use crate::values::computed::Angle as ComputedAngle; use crate::values::computed::BorderCornerRadius as ComputedBorderCornerRadius; +use crate::values::CSSFloat; use euclid::{Point2D, Size2D}; use smallvec::SmallVec; use std::cmp; @@ -23,6 +24,7 @@ pub mod effects; mod font; mod length; mod svg; +pub mod transform; /// The category a property falls into for ordering purposes. /// @@ -86,6 +88,15 @@ pub fn compare_property_priority(a: &PropertyId, b: &PropertyId) -> cmp::Orderin .then_with(|| a.idl_name_sort_order().cmp(&b.idl_name_sort_order())) } +/// A helper function to animate two multiplicative factor. +pub fn animate_multiplicative_factor( + this: CSSFloat, + other: CSSFloat, + procedure: Procedure, +) -> Result<CSSFloat, ()> { + Ok((this - 1.).animate(&(other - 1.), procedure)? + 1.) +} + /// Animate from one value to another. /// /// This trait is derivable with `#[derive(Animate)]`. The derived diff --git a/components/style/values/animated/transform.rs b/components/style/values/animated/transform.rs new file mode 100644 index 00000000000..5223a195075 --- /dev/null +++ b/components/style/values/animated/transform.rs @@ -0,0 +1,1502 @@ +/* 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/. */ + +//! Animated types for transform. +// There are still some implementation on Matrix3D in animated_properties.mako.rs +// because they still need mako to generate the code. + +use super::animate_multiplicative_factor; +use super::{Animate, Procedure, ToAnimatedZero}; +use crate::properties::animated_properties::ListAnimation; +use crate::values::computed::transform::Rotate as ComputedRotate; +use crate::values::computed::transform::Scale as ComputedScale; +use crate::values::computed::transform::Transform as ComputedTransform; +use crate::values::computed::transform::TransformOperation as ComputedTransformOperation; +use crate::values::computed::transform::Translate as ComputedTranslate; +use crate::values::computed::transform::{DirectionVector, Matrix, Matrix3D}; +use crate::values::computed::Angle; +use crate::values::computed::{Length, LengthOrPercentage}; +use crate::values::computed::{Number, Percentage}; +use crate::values::distance::{ComputeSquaredDistance, SquaredDistance}; +use crate::values::generics::transform::{self, Transform, TransformOperation}; +use crate::values::generics::transform::{Rotate, Scale, Translate}; +use crate::values::CSSFloat; +use num_traits::Zero; +use std::cmp; + +// ------------------------------------ +// Animations for Matrix/Matrix3D. +// ------------------------------------ +/// A 2d matrix for interpolation. +#[derive(Clone, ComputeSquaredDistance, Copy, Debug)] +#[cfg_attr(feature = "servo", derive(MallocSizeOf))] +#[allow(missing_docs)] +// FIXME: We use custom derive for ComputeSquaredDistance. However, If possible, we should convert +// the InnerMatrix2D into types with physical meaning. This custom derive computes the squared +// distance from each matrix item, and this makes the result different from that in Gecko if we +// have skew factor in the Matrix3D. +pub struct InnerMatrix2D { + pub m11: CSSFloat, + pub m12: CSSFloat, + pub m21: CSSFloat, + pub m22: CSSFloat, +} + +impl Animate for InnerMatrix2D { + fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { + Ok(InnerMatrix2D { + m11: animate_multiplicative_factor(self.m11, other.m11, procedure)?, + m12: self.m12.animate(&other.m12, procedure)?, + m21: self.m21.animate(&other.m21, procedure)?, + m22: animate_multiplicative_factor(self.m22, other.m22, procedure)?, + }) + } +} + +/// A 2d translation function. +#[cfg_attr(feature = "servo", derive(MallocSizeOf))] +#[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug)] +pub struct Translate2D(f32, f32); + +/// A 2d scale function. +#[derive(Clone, ComputeSquaredDistance, Copy, Debug)] +#[cfg_attr(feature = "servo", derive(MallocSizeOf))] +pub struct Scale2D(f32, f32); + +impl Animate for Scale2D { + fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { + Ok(Scale2D( + animate_multiplicative_factor(self.0, other.0, procedure)?, + animate_multiplicative_factor(self.1, other.1, procedure)?, + )) + } +} + +/// A decomposed 2d matrix. +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "servo", derive(MallocSizeOf))] +pub struct MatrixDecomposed2D { + /// The translation function. + pub translate: Translate2D, + /// The scale function. + pub scale: Scale2D, + /// The rotation angle. + pub angle: f32, + /// The inner matrix. + pub matrix: InnerMatrix2D, +} + +impl Animate for MatrixDecomposed2D { + /// <https://drafts.csswg.org/css-transforms/#interpolation-of-decomposed-2d-matrix-values> + fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { + // If x-axis of one is flipped, and y-axis of the other, + // convert to an unflipped rotation. + let mut scale = self.scale; + let mut angle = self.angle; + let mut other_angle = other.angle; + if (scale.0 < 0.0 && other.scale.1 < 0.0) || (scale.1 < 0.0 && other.scale.0 < 0.0) { + scale.0 = -scale.0; + scale.1 = -scale.1; + angle += if angle < 0.0 { 180. } else { -180. }; + } + + // Don't rotate the long way around. + if angle == 0.0 { + angle = 360. + } + if other_angle == 0.0 { + other_angle = 360. + } + + if (angle - other_angle).abs() > 180. { + if angle > other_angle { + angle -= 360. + } else { + other_angle -= 360. + } + } + + // Interpolate all values. + let translate = self.translate.animate(&other.translate, procedure)?; + let scale = scale.animate(&other.scale, procedure)?; + let angle = angle.animate(&other_angle, procedure)?; + let matrix = self.matrix.animate(&other.matrix, procedure)?; + + Ok(MatrixDecomposed2D { + translate, + scale, + angle, + matrix, + }) + } +} + +impl ComputeSquaredDistance for MatrixDecomposed2D { + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { + // Use Radian to compute the distance. + const RAD_PER_DEG: f64 = std::f64::consts::PI / 180.0; + let angle1 = self.angle as f64 * RAD_PER_DEG; + let angle2 = other.angle as f64 * RAD_PER_DEG; + Ok(self.translate.compute_squared_distance(&other.translate)? + + self.scale.compute_squared_distance(&other.scale)? + + angle1.compute_squared_distance(&angle2)? + + self.matrix.compute_squared_distance(&other.matrix)?) + } +} + +impl From<Matrix3D> for MatrixDecomposed2D { + /// Decompose a 2D matrix. + /// <https://drafts.csswg.org/css-transforms/#decomposing-a-2d-matrix> + fn from(matrix: Matrix3D) -> MatrixDecomposed2D { + let mut row0x = matrix.m11; + let mut row0y = matrix.m12; + let mut row1x = matrix.m21; + let mut row1y = matrix.m22; + + let translate = Translate2D(matrix.m41, matrix.m42); + let mut scale = Scale2D( + (row0x * row0x + row0y * row0y).sqrt(), + (row1x * row1x + row1y * row1y).sqrt(), + ); + + // If determinant is negative, one axis was flipped. + let determinant = row0x * row1y - row0y * row1x; + if determinant < 0. { + if row0x < row1y { + scale.0 = -scale.0; + } else { + scale.1 = -scale.1; + } + } + + // Renormalize matrix to remove scale. + if scale.0 != 0.0 { + row0x *= 1. / scale.0; + row0y *= 1. / scale.0; + } + if scale.1 != 0.0 { + row1x *= 1. / scale.1; + row1y *= 1. / scale.1; + } + + // Compute rotation and renormalize matrix. + let mut angle = row0y.atan2(row0x); + if angle != 0.0 { + let sn = -row0y; + let cs = row0x; + let m11 = row0x; + let m12 = row0y; + let m21 = row1x; + let m22 = row1y; + row0x = cs * m11 + sn * m21; + row0y = cs * m12 + sn * m22; + row1x = -sn * m11 + cs * m21; + row1y = -sn * m12 + cs * m22; + } + + let m = InnerMatrix2D { + m11: row0x, + m12: row0y, + m21: row1x, + m22: row1y, + }; + + // Convert into degrees because our rotation functions expect it. + angle = angle.to_degrees(); + MatrixDecomposed2D { + translate: translate, + scale: scale, + angle: angle, + matrix: m, + } + } +} + +impl From<MatrixDecomposed2D> for Matrix3D { + /// Recompose a 2D matrix. + /// <https://drafts.csswg.org/css-transforms/#recomposing-to-a-2d-matrix> + fn from(decomposed: MatrixDecomposed2D) -> Matrix3D { + let mut computed_matrix = Matrix3D::identity(); + computed_matrix.m11 = decomposed.matrix.m11; + computed_matrix.m12 = decomposed.matrix.m12; + computed_matrix.m21 = decomposed.matrix.m21; + computed_matrix.m22 = decomposed.matrix.m22; + + // Translate matrix. + computed_matrix.m41 = decomposed.translate.0; + computed_matrix.m42 = decomposed.translate.1; + + // Rotate matrix. + let angle = decomposed.angle.to_radians(); + let cos_angle = angle.cos(); + let sin_angle = angle.sin(); + + let mut rotate_matrix = Matrix3D::identity(); + rotate_matrix.m11 = cos_angle; + rotate_matrix.m12 = sin_angle; + rotate_matrix.m21 = -sin_angle; + rotate_matrix.m22 = cos_angle; + + // Multiplication of computed_matrix and rotate_matrix + computed_matrix = rotate_matrix.multiply(&computed_matrix); + + // Scale matrix. + computed_matrix.m11 *= decomposed.scale.0; + computed_matrix.m12 *= decomposed.scale.0; + computed_matrix.m21 *= decomposed.scale.1; + computed_matrix.m22 *= decomposed.scale.1; + computed_matrix + } +} + +impl Animate for Matrix { + #[cfg(feature = "servo")] + fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { + let this = Matrix3D::from(*self); + let other = Matrix3D::from(*other); + let this = MatrixDecomposed2D::from(this); + let other = MatrixDecomposed2D::from(other); + Ok(Matrix3D::from(this.animate(&other, procedure)?).into_2d()?) + } + + #[cfg(feature = "gecko")] + // Gecko doesn't exactly follow the spec here; we use a different procedure + // to match it + fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { + let from = decompose_2d_matrix(&(*self).into()); + let to = decompose_2d_matrix(&(*other).into()); + match (from, to) { + (Ok(from), Ok(to)) => Matrix3D::from(from.animate(&to, procedure)?).into_2d(), + // Matrices can be undecomposable due to couple reasons, e.g., + // non-invertible matrices. In this case, we should report Err here, + // and let the caller do the fallback procedure. + _ => Err(()), + } + } +} + +/// A 3d translation. +#[cfg_attr(feature = "servo", derive(MallocSizeOf))] +#[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug)] +pub struct Translate3D(pub f32, pub f32, pub f32); + +/// A 3d scale function. +#[derive(Clone, ComputeSquaredDistance, Copy, Debug)] +#[cfg_attr(feature = "servo", derive(MallocSizeOf))] +pub struct Scale3D(pub f32, pub f32, pub f32); + +impl Scale3D { + /// Negate self. + fn negate(&mut self) { + self.0 *= -1.0; + self.1 *= -1.0; + self.2 *= -1.0; + } +} + +impl Animate for Scale3D { + fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { + Ok(Scale3D( + animate_multiplicative_factor(self.0, other.0, procedure)?, + animate_multiplicative_factor(self.1, other.1, procedure)?, + animate_multiplicative_factor(self.2, other.2, procedure)?, + )) + } +} + +/// A 3d skew function. +#[cfg_attr(feature = "servo", derive(MallocSizeOf))] +#[derive(Animate, Clone, Copy, Debug)] +pub struct Skew(f32, f32, f32); + +impl ComputeSquaredDistance for Skew { + // We have to use atan() to convert the skew factors into skew angles, so implement + // ComputeSquaredDistance manually. + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { + Ok(self.0.atan().compute_squared_distance(&other.0.atan())? + + self.1.atan().compute_squared_distance(&other.1.atan())? + + self.2.atan().compute_squared_distance(&other.2.atan())?) + } +} + +/// A 3d perspective transformation. +#[derive(Clone, ComputeSquaredDistance, Copy, Debug)] +#[cfg_attr(feature = "servo", derive(MallocSizeOf))] +pub struct Perspective(pub f32, pub f32, pub f32, pub f32); + +impl Animate for Perspective { + fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { + Ok(Perspective( + self.0.animate(&other.0, procedure)?, + self.1.animate(&other.1, procedure)?, + self.2.animate(&other.2, procedure)?, + animate_multiplicative_factor(self.3, other.3, procedure)?, + )) + } +} + +/// A quaternion used to represent a rotation. +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "servo", derive(MallocSizeOf))] +pub struct Quaternion(f64, f64, f64, f64); + +impl Quaternion { + /// Return a quaternion from a unit direction vector and angle (unit: radian). + #[inline] + fn from_direction_and_angle(vector: &DirectionVector, angle: f64) -> Self { + debug_assert!( + (vector.length() - 1.).abs() < 0.0001, + "Only accept an unit direction vector to create a quaternion" + ); + // Reference: + // https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation + // + // if the direction axis is (x, y, z) = xi + yj + zk, + // and the angle is |theta|, this formula can be done using + // an extension of Euler's formula: + // q = cos(theta/2) + (xi + yj + zk)(sin(theta/2)) + // = cos(theta/2) + + // x*sin(theta/2)i + y*sin(theta/2)j + z*sin(theta/2)k + Quaternion( + vector.x as f64 * (angle / 2.).sin(), + vector.y as f64 * (angle / 2.).sin(), + vector.z as f64 * (angle / 2.).sin(), + (angle / 2.).cos(), + ) + } + + /// Calculate the dot product. + #[inline] + fn dot(&self, other: &Self) -> f64 { + self.0 * other.0 + self.1 * other.1 + self.2 * other.2 + self.3 * other.3 + } + + /// Return the scaled quaternion by a factor. + #[inline] + fn scale(&self, factor: f64) -> Self { + Quaternion( + self.0 * factor, + self.1 * factor, + self.2 * factor, + self.3 * factor, + ) + } +} + +impl Animate for Quaternion { + fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { + use std::f64; + + let (this_weight, other_weight) = procedure.weights(); + debug_assert!( + // Doule EPSILON since both this_weight and other_weght have calculation errors + // which are approximately equal to EPSILON. + (this_weight + other_weight - 1.0f64).abs() <= f64::EPSILON * 2.0 || + other_weight == 1.0f64 || + other_weight == 0.0f64, + "animate should only be used for interpolating or accumulating transforms" + ); + + // We take a specialized code path for accumulation (where other_weight + // is 1). + if let Procedure::Accumulate { .. } = procedure { + debug_assert_eq!(other_weight, 1.0); + if this_weight == 0.0 { + return Ok(*other); + } + + let clamped_w = self.3.min(1.0).max(-1.0); + + // Determine the scale factor. + let mut theta = clamped_w.acos(); + let mut scale = if theta == 0.0 { 0.0 } else { 1.0 / theta.sin() }; + theta *= this_weight; + scale *= theta.sin(); + + // Scale the self matrix by this_weight. + let mut scaled_self = *self; + scaled_self.0 *= scale; + scaled_self.1 *= scale; + scaled_self.2 *= scale; + scaled_self.3 = theta.cos(); + + // Multiply scaled-self by other. + let a = &scaled_self; + let b = other; + return Ok(Quaternion( + a.3 * b.0 + a.0 * b.3 + a.1 * b.2 - a.2 * b.1, + a.3 * b.1 - a.0 * b.2 + a.1 * b.3 + a.2 * b.0, + a.3 * b.2 + a.0 * b.1 - a.1 * b.0 + a.2 * b.3, + a.3 * b.3 - a.0 * b.0 - a.1 * b.1 - a.2 * b.2, + )); + } + + // Straight from gfxQuaternion::Slerp. + // + // Dot product, clamped between -1 and 1. + let dot = (self.0 * other.0 + self.1 * other.1 + self.2 * other.2 + self.3 * other.3) + .min(1.0) + .max(-1.0); + + if dot.abs() == 1.0 { + return Ok(*self); + } + + let theta = dot.acos(); + let rsintheta = 1.0 / (1.0 - dot * dot).sqrt(); + + let right_weight = (other_weight * theta).sin() * rsintheta; + let left_weight = (other_weight * theta).cos() - dot * right_weight; + + let left = self.scale(left_weight); + let right = other.scale(right_weight); + + Ok(Quaternion( + left.0 + right.0, + left.1 + right.1, + left.2 + right.2, + left.3 + right.3, + )) + } +} + +impl ComputeSquaredDistance for Quaternion { + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { + // Use quaternion vectors to get the angle difference. Both q1 and q2 are unit vectors, + // so we can get their angle difference by: + // cos(theta/2) = (q1 dot q2) / (|q1| * |q2|) = q1 dot q2. + let distance = self.dot(other).max(-1.0).min(1.0).acos() * 2.0; + Ok(SquaredDistance::from_sqrt(distance)) + } +} + +/// A decomposed 3d matrix. +#[derive(Animate, Clone, ComputeSquaredDistance, Copy, Debug)] +#[cfg_attr(feature = "servo", derive(MallocSizeOf))] +pub struct MatrixDecomposed3D { + /// A translation function. + pub translate: Translate3D, + /// A scale function. + pub scale: Scale3D, + /// The skew component of the transformation. + pub skew: Skew, + /// The perspective component of the transformation. + pub perspective: Perspective, + /// The quaternion used to represent the rotation. + pub quaternion: Quaternion, +} + +impl From<MatrixDecomposed3D> for Matrix3D { + /// Recompose a 3D matrix. + /// <https://drafts.csswg.org/css-transforms/#recomposing-to-a-3d-matrix> + fn from(decomposed: MatrixDecomposed3D) -> Matrix3D { + let mut matrix = Matrix3D::identity(); + + // Apply perspective + matrix.set_perspective(&decomposed.perspective); + + // Apply translation + matrix.apply_translate(&decomposed.translate); + + // Apply rotation + { + let x = decomposed.quaternion.0; + let y = decomposed.quaternion.1; + let z = decomposed.quaternion.2; + let w = decomposed.quaternion.3; + + // Construct a composite rotation matrix from the quaternion values + // rotationMatrix is a identity 4x4 matrix initially + let mut rotation_matrix = Matrix3D::identity(); + rotation_matrix.m11 = 1.0 - 2.0 * (y * y + z * z) as f32; + rotation_matrix.m12 = 2.0 * (x * y + z * w) as f32; + rotation_matrix.m13 = 2.0 * (x * z - y * w) as f32; + rotation_matrix.m21 = 2.0 * (x * y - z * w) as f32; + rotation_matrix.m22 = 1.0 - 2.0 * (x * x + z * z) as f32; + rotation_matrix.m23 = 2.0 * (y * z + x * w) as f32; + rotation_matrix.m31 = 2.0 * (x * z + y * w) as f32; + rotation_matrix.m32 = 2.0 * (y * z - x * w) as f32; + rotation_matrix.m33 = 1.0 - 2.0 * (x * x + y * y) as f32; + + matrix = rotation_matrix.multiply(&matrix); + } + + // Apply skew + { + let mut temp = Matrix3D::identity(); + if decomposed.skew.2 != 0.0 { + temp.m32 = decomposed.skew.2; + matrix = temp.multiply(&matrix); + temp.m32 = 0.0; + } + + if decomposed.skew.1 != 0.0 { + temp.m31 = decomposed.skew.1; + matrix = temp.multiply(&matrix); + temp.m31 = 0.0; + } + + if decomposed.skew.0 != 0.0 { + temp.m21 = decomposed.skew.0; + matrix = temp.multiply(&matrix); + } + } + + // Apply scale + matrix.apply_scale(&decomposed.scale); + + matrix + } +} + +/// Decompose a 3D matrix. +/// https://drafts.csswg.org/css-transforms-2/#decomposing-a-3d-matrix +/// http://www.realtimerendering.com/resources/GraphicsGems/gemsii/unmatrix.c +fn decompose_3d_matrix(mut matrix: Matrix3D) -> Result<MatrixDecomposed3D, ()> { + // Combine 2 point. + let combine = |a: [f32; 3], b: [f32; 3], ascl: f32, bscl: f32| { + [ + (ascl * a[0]) + (bscl * b[0]), + (ascl * a[1]) + (bscl * b[1]), + (ascl * a[2]) + (bscl * b[2]), + ] + }; + // Dot product. + let dot = |a: [f32; 3], b: [f32; 3]| a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + // Cross product. + let cross = |row1: [f32; 3], row2: [f32; 3]| { + [ + row1[1] * row2[2] - row1[2] * row2[1], + row1[2] * row2[0] - row1[0] * row2[2], + row1[0] * row2[1] - row1[1] * row2[0], + ] + }; + + if matrix.m44 == 0.0 { + return Err(()); + } + + let scaling_factor = matrix.m44; + + // Normalize the matrix. + matrix.scale_by_factor(1.0 / scaling_factor); + + // perspective_matrix is used to solve for perspective, but it also provides + // an easy way to test for singularity of the upper 3x3 component. + let mut perspective_matrix = matrix; + + perspective_matrix.m14 = 0.0; + perspective_matrix.m24 = 0.0; + perspective_matrix.m34 = 0.0; + perspective_matrix.m44 = 1.0; + + if perspective_matrix.determinant() == 0.0 { + return Err(()); + } + + // First, isolate perspective. + let perspective = if matrix.m14 != 0.0 || matrix.m24 != 0.0 || matrix.m34 != 0.0 { + let right_hand_side: [f32; 4] = [matrix.m14, matrix.m24, matrix.m34, matrix.m44]; + + perspective_matrix = perspective_matrix.inverse().unwrap().transpose(); + let perspective = perspective_matrix.pre_mul_point4(&right_hand_side); + // NOTE(emilio): Even though the reference algorithm clears the + // fourth column here (matrix.m14..matrix.m44), they're not used below + // so it's not really needed. + Perspective( + perspective[0], + perspective[1], + perspective[2], + perspective[3], + ) + } else { + Perspective(0.0, 0.0, 0.0, 1.0) + }; + + // Next take care of translation (easy). + let translate = Translate3D(matrix.m41, matrix.m42, matrix.m43); + + // Now get scale and shear. 'row' is a 3 element array of 3 component vectors + let mut row = matrix.get_matrix_3x3_part(); + + // Compute X scale factor and normalize first row. + let row0len = (row[0][0] * row[0][0] + row[0][1] * row[0][1] + row[0][2] * row[0][2]).sqrt(); + let mut scale = Scale3D(row0len, 0.0, 0.0); + row[0] = [ + row[0][0] / row0len, + row[0][1] / row0len, + row[0][2] / row0len, + ]; + + // Compute XY shear factor and make 2nd row orthogonal to 1st. + let mut skew = Skew(dot(row[0], row[1]), 0.0, 0.0); + row[1] = combine(row[1], row[0], 1.0, -skew.0); + + // Now, compute Y scale and normalize 2nd row. + let row1len = (row[1][0] * row[1][0] + row[1][1] * row[1][1] + row[1][2] * row[1][2]).sqrt(); + scale.1 = row1len; + row[1] = [ + row[1][0] / row1len, + row[1][1] / row1len, + row[1][2] / row1len, + ]; + skew.0 /= scale.1; + + // Compute XZ and YZ shears, orthogonalize 3rd row + skew.1 = dot(row[0], row[2]); + row[2] = combine(row[2], row[0], 1.0, -skew.1); + skew.2 = dot(row[1], row[2]); + row[2] = combine(row[2], row[1], 1.0, -skew.2); + + // Next, get Z scale and normalize 3rd row. + let row2len = (row[2][0] * row[2][0] + row[2][1] * row[2][1] + row[2][2] * row[2][2]).sqrt(); + scale.2 = row2len; + row[2] = [ + row[2][0] / row2len, + row[2][1] / row2len, + row[2][2] / row2len, + ]; + skew.1 /= scale.2; + skew.2 /= scale.2; + + // At this point, the matrix (in rows) is orthonormal. + // Check for a coordinate system flip. If the determinant + // is -1, then negate the matrix and the scaling factors. + if dot(row[0], cross(row[1], row[2])) < 0.0 { + scale.negate(); + for i in 0..3 { + row[i][0] *= -1.0; + row[i][1] *= -1.0; + row[i][2] *= -1.0; + } + } + + // Now, get the rotations out. + let mut quaternion = Quaternion( + 0.5 * ((1.0 + row[0][0] - row[1][1] - row[2][2]).max(0.0) as f64).sqrt(), + 0.5 * ((1.0 - row[0][0] + row[1][1] - row[2][2]).max(0.0) as f64).sqrt(), + 0.5 * ((1.0 - row[0][0] - row[1][1] + row[2][2]).max(0.0) as f64).sqrt(), + 0.5 * ((1.0 + row[0][0] + row[1][1] + row[2][2]).max(0.0) as f64).sqrt(), + ); + + if row[2][1] > row[1][2] { + quaternion.0 = -quaternion.0 + } + if row[0][2] > row[2][0] { + quaternion.1 = -quaternion.1 + } + if row[1][0] > row[0][1] { + quaternion.2 = -quaternion.2 + } + + Ok(MatrixDecomposed3D { + translate, + scale, + skew, + perspective, + quaternion, + }) +} + +/// Decompose a 2D matrix for Gecko. +// Use the algorithm from nsStyleTransformMatrix::Decompose2DMatrix() in Gecko. +#[cfg(feature = "gecko")] +fn decompose_2d_matrix(matrix: &Matrix3D) -> Result<MatrixDecomposed3D, ()> { + // The index is column-major, so the equivalent transform matrix is: + // | m11 m21 0 m41 | => | m11 m21 | and translate(m41, m42) + // | m12 m22 0 m42 | | m12 m22 | + // | 0 0 1 0 | + // | 0 0 0 1 | + let (mut m11, mut m12) = (matrix.m11, matrix.m12); + let (mut m21, mut m22) = (matrix.m21, matrix.m22); + // Check if this is a singular matrix. + if m11 * m22 == m12 * m21 { + return Err(()); + } + + let mut scale_x = (m11 * m11 + m12 * m12).sqrt(); + m11 /= scale_x; + m12 /= scale_x; + + let mut shear_xy = m11 * m21 + m12 * m22; + m21 -= m11 * shear_xy; + m22 -= m12 * shear_xy; + + let scale_y = (m21 * m21 + m22 * m22).sqrt(); + m21 /= scale_y; + m22 /= scale_y; + shear_xy /= scale_y; + + let determinant = m11 * m22 - m12 * m21; + // Determinant should now be 1 or -1. + if 0.99 > determinant.abs() || determinant.abs() > 1.01 { + return Err(()); + } + + if determinant < 0. { + m11 = -m11; + m12 = -m12; + shear_xy = -shear_xy; + scale_x = -scale_x; + } + + Ok(MatrixDecomposed3D { + translate: Translate3D(matrix.m41, matrix.m42, 0.), + scale: Scale3D(scale_x, scale_y, 1.), + skew: Skew(shear_xy, 0., 0.), + perspective: Perspective(0., 0., 0., 1.), + quaternion: Quaternion::from_direction_and_angle( + &DirectionVector::new(0., 0., 1.), + m12.atan2(m11) as f64, + ), + }) +} + +impl Animate for Matrix3D { + #[cfg(feature = "servo")] + fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { + if self.is_3d() || other.is_3d() { + let decomposed_from = decompose_3d_matrix(*self); + let decomposed_to = decompose_3d_matrix(*other); + match (decomposed_from, decomposed_to) { + (Ok(this), Ok(other)) => Ok(Matrix3D::from(this.animate(&other, procedure)?)), + // Matrices can be undecomposable due to couple reasons, e.g., + // non-invertible matrices. In this case, we should report Err + // here, and let the caller do the fallback procedure. + _ => Err(()), + } + } else { + let this = MatrixDecomposed2D::from(*self); + let other = MatrixDecomposed2D::from(*other); + Ok(Matrix3D::from(this.animate(&other, procedure)?)) + } + } + + #[cfg(feature = "gecko")] + // Gecko doesn't exactly follow the spec here; we use a different procedure + // to match it + fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { + let (from, to) = if self.is_3d() || other.is_3d() { + (decompose_3d_matrix(*self), decompose_3d_matrix(*other)) + } else { + (decompose_2d_matrix(self), decompose_2d_matrix(other)) + }; + match (from, to) { + (Ok(from), Ok(to)) => Ok(Matrix3D::from(from.animate(&to, procedure)?)), + // Matrices can be undecomposable due to couple reasons, e.g., + // non-invertible matrices. In this case, we should report Err here, + // and let the caller do the fallback procedure. + _ => Err(()), + } + } +} + +impl ComputeSquaredDistance for Matrix3D { + #[inline] + #[cfg(feature = "servo")] + fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { + if self.is_3d() || other.is_3d() { + let from = decompose_3d_matrix(*self)?; + let to = decompose_3d_matrix(*other)?; + from.compute_squared_distance(&to) + } else { + let from = MatrixDecomposed2D::from(*self); + let to = MatrixDecomposed2D::from(*other); + from.compute_squared_distance(&to) + } + } + + #[inline] + #[cfg(feature = "gecko")] + fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { + let (from, to) = if self.is_3d() || other.is_3d() { + (decompose_3d_matrix(*self)?, decompose_3d_matrix(*other)?) + } else { + (decompose_2d_matrix(self)?, decompose_2d_matrix(other)?) + }; + from.compute_squared_distance(&to) + } +} + +// ------------------------------------ +// Animation for Transform list. +// ------------------------------------ +fn is_matched_operation( + first: &ComputedTransformOperation, + second: &ComputedTransformOperation, +) -> bool { + match (first, second) { + (&TransformOperation::Matrix(..), &TransformOperation::Matrix(..)) | + (&TransformOperation::Matrix3D(..), &TransformOperation::Matrix3D(..)) | + (&TransformOperation::Skew(..), &TransformOperation::Skew(..)) | + (&TransformOperation::SkewX(..), &TransformOperation::SkewX(..)) | + (&TransformOperation::SkewY(..), &TransformOperation::SkewY(..)) | + (&TransformOperation::Rotate(..), &TransformOperation::Rotate(..)) | + (&TransformOperation::Rotate3D(..), &TransformOperation::Rotate3D(..)) | + (&TransformOperation::RotateX(..), &TransformOperation::RotateX(..)) | + (&TransformOperation::RotateY(..), &TransformOperation::RotateY(..)) | + (&TransformOperation::RotateZ(..), &TransformOperation::RotateZ(..)) | + (&TransformOperation::Perspective(..), &TransformOperation::Perspective(..)) => true, + // Match functions that have the same primitive transform function + (a, b) if a.is_translate() && b.is_translate() => true, + (a, b) if a.is_scale() && b.is_scale() => true, + (a, b) if a.is_rotate() && b.is_rotate() => true, + // InterpolateMatrix and AccumulateMatrix are for mismatched transforms + _ => false, + } +} + +/// <https://drafts.csswg.org/css-transforms/#interpolation-of-transforms> +impl Animate for ComputedTransform { + #[inline] + fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { + use std::borrow::Cow; + + // Addition for transforms simply means appending to the list of + // transform functions. This is different to how we handle the other + // animation procedures so we treat it separately here rather than + // handling it in TransformOperation. + if procedure == Procedure::Add { + let result = self.0.iter().chain(&other.0).cloned().collect::<Vec<_>>(); + return Ok(Transform(result)); + } + + let this = Cow::Borrowed(&self.0); + let other = Cow::Borrowed(&other.0); + + // Interpolate the common prefix + let mut result = this + .iter() + .zip(other.iter()) + .take_while(|(this, other)| is_matched_operation(this, other)) + .map(|(this, other)| this.animate(other, procedure)) + .collect::<Result<Vec<_>, _>>()?; + + // Deal with the remainders + let this_remainder = if this.len() > result.len() { + Some(&this[result.len()..]) + } else { + None + }; + let other_remainder = if other.len() > result.len() { + Some(&other[result.len()..]) + } else { + None + }; + + match (this_remainder, other_remainder) { + // If there is a remainder from *both* lists we must have had mismatched functions. + // => Add the remainders to a suitable ___Matrix function. + (Some(this_remainder), Some(other_remainder)) => match procedure { + Procedure::Add => { + debug_assert!(false, "Should have already dealt with add by the point"); + return Err(()); + }, + Procedure::Interpolate { progress } => { + result.push(TransformOperation::InterpolateMatrix { + from_list: Transform(this_remainder.to_vec()), + to_list: Transform(other_remainder.to_vec()), + progress: Percentage(progress as f32), + }); + }, + Procedure::Accumulate { count } => { + result.push(TransformOperation::AccumulateMatrix { + from_list: Transform(this_remainder.to_vec()), + to_list: Transform(other_remainder.to_vec()), + count: cmp::min(count, i32::max_value() as u64) as i32, + }); + }, + }, + // If there is a remainder from just one list, then one list must be shorter but + // completely match the type of the corresponding functions in the longer list. + // => Interpolate the remainder with identity transforms. + (Some(remainder), None) | (None, Some(remainder)) => { + let fill_right = this_remainder.is_some(); + result.append( + &mut remainder + .iter() + .map(|transform| { + let identity = transform.to_animated_zero().unwrap(); + + match transform { + // We can't interpolate/accumulate ___Matrix types directly with a + // matrix. Instead we need to wrap it in another ___Matrix type. + TransformOperation::AccumulateMatrix { .. } | + TransformOperation::InterpolateMatrix { .. } => { + let transform_list = Transform(vec![transform.clone()]); + let identity_list = Transform(vec![identity]); + let (from_list, to_list) = if fill_right { + (transform_list, identity_list) + } else { + (identity_list, transform_list) + }; + + match procedure { + Procedure::Add => Err(()), + Procedure::Interpolate { progress } => { + Ok(TransformOperation::InterpolateMatrix { + from_list, + to_list, + progress: Percentage(progress as f32), + }) + }, + Procedure::Accumulate { count } => { + Ok(TransformOperation::AccumulateMatrix { + from_list, + to_list, + count: cmp::min(count, i32::max_value() as u64) + as i32, + }) + }, + } + }, + _ => { + let (lhs, rhs) = if fill_right { + (transform, &identity) + } else { + (&identity, transform) + }; + lhs.animate(rhs, procedure) + }, + } + }) + .collect::<Result<Vec<_>, _>>()?, + ); + }, + (None, None) => {}, + } + + Ok(Transform(result)) + } +} + +impl ComputeSquaredDistance for ComputedTransform { + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { + let squared_dist = self.0.squared_distance_with_zero(&other.0); + + // Roll back to matrix interpolation if there is any Err(()) in the + // transform lists, such as mismatched transform functions. + if squared_dist.is_err() { + let matrix1: Matrix3D = self.to_transform_3d_matrix(None)?.0.into(); + let matrix2: Matrix3D = other.to_transform_3d_matrix(None)?.0.into(); + return matrix1.compute_squared_distance(&matrix2); + } + + squared_dist + } +} + +/// <http://dev.w3.org/csswg/css-transforms/#interpolation-of-transforms> +impl Animate for ComputedTransformOperation { + fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { + match (self, other) { + (&TransformOperation::Matrix3D(ref this), &TransformOperation::Matrix3D(ref other)) => { + Ok(TransformOperation::Matrix3D( + this.animate(other, procedure)?, + )) + }, + (&TransformOperation::Matrix(ref this), &TransformOperation::Matrix(ref other)) => { + Ok(TransformOperation::Matrix(this.animate(other, procedure)?)) + }, + (&TransformOperation::Skew(ref fx, None), &TransformOperation::Skew(ref tx, None)) => { + Ok(TransformOperation::Skew(fx.animate(tx, procedure)?, None)) + }, + ( + &TransformOperation::Skew(ref fx, ref fy), + &TransformOperation::Skew(ref tx, ref ty), + ) => Ok(TransformOperation::Skew( + fx.animate(tx, procedure)?, + Some( + fy.unwrap_or(Angle::zero()) + .animate(&ty.unwrap_or(Angle::zero()), procedure)?, + ), + )), + (&TransformOperation::SkewX(ref f), &TransformOperation::SkewX(ref t)) => { + Ok(TransformOperation::SkewX(f.animate(t, procedure)?)) + }, + (&TransformOperation::SkewY(ref f), &TransformOperation::SkewY(ref t)) => { + Ok(TransformOperation::SkewY(f.animate(t, procedure)?)) + }, + ( + &TransformOperation::Translate3D(ref fx, ref fy, ref fz), + &TransformOperation::Translate3D(ref tx, ref ty, ref tz), + ) => Ok(TransformOperation::Translate3D( + fx.animate(tx, procedure)?, + fy.animate(ty, procedure)?, + fz.animate(tz, procedure)?, + )), + ( + &TransformOperation::Translate(ref fx, None), + &TransformOperation::Translate(ref tx, None), + ) => Ok(TransformOperation::Translate( + fx.animate(tx, procedure)?, + None, + )), + ( + &TransformOperation::Translate(ref fx, ref fy), + &TransformOperation::Translate(ref tx, ref ty), + ) => Ok(TransformOperation::Translate( + fx.animate(tx, procedure)?, + Some( + fy.unwrap_or(LengthOrPercentage::zero()) + .animate(&ty.unwrap_or(LengthOrPercentage::zero()), procedure)?, + ), + )), + (&TransformOperation::TranslateX(ref f), &TransformOperation::TranslateX(ref t)) => { + Ok(TransformOperation::TranslateX(f.animate(t, procedure)?)) + }, + (&TransformOperation::TranslateY(ref f), &TransformOperation::TranslateY(ref t)) => { + Ok(TransformOperation::TranslateY(f.animate(t, procedure)?)) + }, + (&TransformOperation::TranslateZ(ref f), &TransformOperation::TranslateZ(ref t)) => { + Ok(TransformOperation::TranslateZ(f.animate(t, procedure)?)) + }, + ( + &TransformOperation::Scale3D(ref fx, ref fy, ref fz), + &TransformOperation::Scale3D(ref tx, ref ty, ref tz), + ) => Ok(TransformOperation::Scale3D( + animate_multiplicative_factor(*fx, *tx, procedure)?, + animate_multiplicative_factor(*fy, *ty, procedure)?, + animate_multiplicative_factor(*fz, *tz, procedure)?, + )), + (&TransformOperation::ScaleX(ref f), &TransformOperation::ScaleX(ref t)) => Ok( + TransformOperation::ScaleX(animate_multiplicative_factor(*f, *t, procedure)?), + ), + (&TransformOperation::ScaleY(ref f), &TransformOperation::ScaleY(ref t)) => Ok( + TransformOperation::ScaleY(animate_multiplicative_factor(*f, *t, procedure)?), + ), + (&TransformOperation::ScaleZ(ref f), &TransformOperation::ScaleZ(ref t)) => Ok( + TransformOperation::ScaleZ(animate_multiplicative_factor(*f, *t, procedure)?), + ), + (&TransformOperation::Scale(ref f, None), &TransformOperation::Scale(ref t, None)) => { + Ok(TransformOperation::Scale( + animate_multiplicative_factor(*f, *t, procedure)?, + None, + )) + }, + ( + &TransformOperation::Scale(ref fx, ref fy), + &TransformOperation::Scale(ref tx, ref ty), + ) => Ok(TransformOperation::Scale( + animate_multiplicative_factor(*fx, *tx, procedure)?, + Some(animate_multiplicative_factor( + fy.unwrap_or(*fx), + ty.unwrap_or(*tx), + procedure, + )?), + )), + ( + &TransformOperation::Rotate3D(fx, fy, fz, fa), + &TransformOperation::Rotate3D(tx, ty, tz, ta), + ) => { + let animated = Rotate::Rotate3D(fx, fy, fz, fa) + .animate(&Rotate::Rotate3D(tx, ty, tz, ta), procedure)?; + let (fx, fy, fz, fa) = ComputedRotate::resolve(&animated); + Ok(TransformOperation::Rotate3D(fx, fy, fz, fa)) + }, + (&TransformOperation::RotateX(fa), &TransformOperation::RotateX(ta)) => { + Ok(TransformOperation::RotateX(fa.animate(&ta, procedure)?)) + }, + (&TransformOperation::RotateY(fa), &TransformOperation::RotateY(ta)) => { + Ok(TransformOperation::RotateY(fa.animate(&ta, procedure)?)) + }, + (&TransformOperation::RotateZ(fa), &TransformOperation::RotateZ(ta)) => { + Ok(TransformOperation::RotateZ(fa.animate(&ta, procedure)?)) + }, + (&TransformOperation::Rotate(fa), &TransformOperation::Rotate(ta)) => { + Ok(TransformOperation::Rotate(fa.animate(&ta, procedure)?)) + }, + (&TransformOperation::Rotate(fa), &TransformOperation::RotateZ(ta)) => { + Ok(TransformOperation::Rotate(fa.animate(&ta, procedure)?)) + }, + (&TransformOperation::RotateZ(fa), &TransformOperation::Rotate(ta)) => { + Ok(TransformOperation::Rotate(fa.animate(&ta, procedure)?)) + }, + ( + &TransformOperation::Perspective(ref fd), + &TransformOperation::Perspective(ref td), + ) => { + use crate::values::computed::CSSPixelLength; + use crate::values::generics::transform::create_perspective_matrix; + + // From https://drafts.csswg.org/css-transforms-2/#interpolation-of-transform-functions: + // + // The transform functions matrix(), matrix3d() and + // perspective() get converted into 4x4 matrices first and + // interpolated as defined in section Interpolation of + // Matrices afterwards. + // + let from = create_perspective_matrix(fd.px()); + let to = create_perspective_matrix(td.px()); + + let interpolated = Matrix3D::from(from).animate(&Matrix3D::from(to), procedure)?; + + let decomposed = decompose_3d_matrix(interpolated)?; + let perspective_z = decomposed.perspective.2; + let used_value = if perspective_z == 0. { + 0. + } else { + -1. / perspective_z + }; + Ok(TransformOperation::Perspective(CSSPixelLength::new( + used_value, + ))) + }, + _ if self.is_translate() && other.is_translate() => self + .to_translate_3d() + .animate(&other.to_translate_3d(), procedure), + _ if self.is_scale() && other.is_scale() => { + self.to_scale_3d().animate(&other.to_scale_3d(), procedure) + }, + _ if self.is_rotate() && other.is_rotate() => self + .to_rotate_3d() + .animate(&other.to_rotate_3d(), procedure), + _ => Err(()), + } + } +} + +// This might not be the most useful definition of distance. It might be better, for example, +// to trace the distance travelled by a point as its transform is interpolated between the two +// lists. That, however, proves to be quite complicated so we take a simple approach for now. +// See https://bugzilla.mozilla.org/show_bug.cgi?id=1318591#c0. +impl ComputeSquaredDistance for ComputedTransformOperation { + fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { + // For translate, We don't want to require doing layout in order to calculate the result, so + // drop the percentage part. However, dropping percentage makes us impossible to + // compute the distance for the percentage-percentage case, but Gecko uses the + // same formula, so it's fine for now. + // Note: We use pixel value to compute the distance for translate, so we have to + // convert Au into px. + let extract_pixel_length = |lop: &LengthOrPercentage| match *lop { + LengthOrPercentage::Length(px) => px.px(), + LengthOrPercentage::Percentage(_) => 0., + LengthOrPercentage::Calc(calc) => calc.length().px(), + }; + match (self, other) { + (&TransformOperation::Matrix3D(ref this), &TransformOperation::Matrix3D(ref other)) => { + this.compute_squared_distance(other) + }, + (&TransformOperation::Matrix(ref this), &TransformOperation::Matrix(ref other)) => { + let this: Matrix3D = (*this).into(); + let other: Matrix3D = (*other).into(); + this.compute_squared_distance(&other) + }, + ( + &TransformOperation::Skew(ref fx, ref fy), + &TransformOperation::Skew(ref tx, ref ty), + ) => Ok(fx.compute_squared_distance(&tx)? + fy.compute_squared_distance(&ty)?), + (&TransformOperation::SkewX(ref f), &TransformOperation::SkewX(ref t)) | + (&TransformOperation::SkewY(ref f), &TransformOperation::SkewY(ref t)) => { + f.compute_squared_distance(&t) + }, + ( + &TransformOperation::Translate3D(ref fx, ref fy, ref fz), + &TransformOperation::Translate3D(ref tx, ref ty, ref tz), + ) => { + let fx = extract_pixel_length(&fx); + let fy = extract_pixel_length(&fy); + let tx = extract_pixel_length(&tx); + let ty = extract_pixel_length(&ty); + + Ok(fx.compute_squared_distance(&tx)? + + fy.compute_squared_distance(&ty)? + + fz.compute_squared_distance(&tz)?) + }, + ( + &TransformOperation::Scale3D(ref fx, ref fy, ref fz), + &TransformOperation::Scale3D(ref tx, ref ty, ref tz), + ) => Ok(fx.compute_squared_distance(&tx)? + + fy.compute_squared_distance(&ty)? + + fz.compute_squared_distance(&tz)?), + ( + &TransformOperation::Rotate3D(fx, fy, fz, fa), + &TransformOperation::Rotate3D(tx, ty, tz, ta), + ) => Rotate::Rotate3D(fx, fy, fz, fa) + .compute_squared_distance(&Rotate::Rotate3D(tx, ty, tz, ta)), + (&TransformOperation::RotateX(fa), &TransformOperation::RotateX(ta)) | + (&TransformOperation::RotateY(fa), &TransformOperation::RotateY(ta)) | + (&TransformOperation::RotateZ(fa), &TransformOperation::RotateZ(ta)) | + (&TransformOperation::Rotate(fa), &TransformOperation::Rotate(ta)) => { + fa.compute_squared_distance(&ta) + }, + ( + &TransformOperation::Perspective(ref fd), + &TransformOperation::Perspective(ref td), + ) => fd.compute_squared_distance(td), + (&TransformOperation::Perspective(ref p), &TransformOperation::Matrix3D(ref m)) | + (&TransformOperation::Matrix3D(ref m), &TransformOperation::Perspective(ref p)) => { + // FIXME(emilio): Is this right? Why interpolating this with + // Perspective but not with anything else? + let mut p_matrix = Matrix3D::identity(); + if p.px() > 0. { + p_matrix.m34 = -1. / p.px(); + } + p_matrix.compute_squared_distance(&m) + }, + // Gecko cross-interpolates amongst all translate and all scale + // functions (See ToPrimitive in layout/style/StyleAnimationValue.cpp) + // without falling back to InterpolateMatrix + _ if self.is_translate() && other.is_translate() => self + .to_translate_3d() + .compute_squared_distance(&other.to_translate_3d()), + _ if self.is_scale() && other.is_scale() => self + .to_scale_3d() + .compute_squared_distance(&other.to_scale_3d()), + _ if self.is_rotate() && other.is_rotate() => self + .to_rotate_3d() + .compute_squared_distance(&other.to_rotate_3d()), + _ => Err(()), + } + } +} + +// ------------------------------------ +// Individual transforms. +// ------------------------------------ +/// <https://drafts.csswg.org/css-transforms-2/#propdef-rotate> +impl ComputedRotate { + fn resolve(&self) -> (Number, Number, Number, Angle) { + // According to the spec: + // https://drafts.csswg.org/css-transforms-2/#individual-transforms + // + // If the axis is unspecified, it defaults to "0 0 1" + match *self { + Rotate::None => (0., 0., 1., Angle::zero()), + Rotate::Rotate3D(rx, ry, rz, angle) => (rx, ry, rz, angle), + Rotate::Rotate(angle) => (0., 0., 1., angle), + } + } +} + +impl Animate for ComputedRotate { + #[inline] + fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { + match (self, other) { + (&Rotate::None, &Rotate::None) => Ok(Rotate::None), + (&Rotate::Rotate3D(fx, fy, fz, fa), &Rotate::None) => { + // No need to normalize `none`, so animate angle directly. + Ok(Rotate::Rotate3D( + fx, + fy, + fz, + fa.animate(&Angle::zero(), procedure)?, + )) + }, + (&Rotate::None, &Rotate::Rotate3D(tx, ty, tz, ta)) => { + // No need to normalize `none`, so animate angle directly. + Ok(Rotate::Rotate3D( + tx, + ty, + tz, + Angle::zero().animate(&ta, procedure)?, + )) + }, + (&Rotate::Rotate3D(_, ..), _) | (_, &Rotate::Rotate3D(_, ..)) => { + let (from, to) = (self.resolve(), other.resolve()); + let (mut fx, mut fy, mut fz, fa) = + transform::get_normalized_vector_and_angle(from.0, from.1, from.2, from.3); + let (mut tx, mut ty, mut tz, ta) = + transform::get_normalized_vector_and_angle(to.0, to.1, to.2, to.3); + + if fa == Angle::from_degrees(0.) { + fx = tx; + fy = ty; + fz = tz; + } else if ta == Angle::from_degrees(0.) { + tx = fx; + ty = fy; + tz = fz; + } + + if (fx, fy, fz) == (tx, ty, tz) { + return Ok(Rotate::Rotate3D(fx, fy, fz, fa.animate(&ta, procedure)?)); + } + + let fv = DirectionVector::new(fx, fy, fz); + let tv = DirectionVector::new(tx, ty, tz); + let fq = Quaternion::from_direction_and_angle(&fv, fa.radians64()); + let tq = Quaternion::from_direction_and_angle(&tv, ta.radians64()); + + let rq = Quaternion::animate(&fq, &tq, procedure)?; + let (x, y, z, angle) = transform::get_normalized_vector_and_angle( + rq.0 as f32, + rq.1 as f32, + rq.2 as f32, + rq.3.acos() as f32 * 2.0, + ); + + Ok(Rotate::Rotate3D(x, y, z, Angle::from_radians(angle))) + }, + (&Rotate::Rotate(_), _) | (_, &Rotate::Rotate(_)) => { + // If this is a 2D rotation, we just animate the <angle> + let (from, to) = (self.resolve().3, other.resolve().3); + Ok(Rotate::Rotate(from.animate(&to, procedure)?)) + }, + } + } +} + +impl ComputeSquaredDistance for ComputedRotate { + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { + match (self, other) { + (&Rotate::None, &Rotate::None) => Ok(SquaredDistance::from_sqrt(0.)), + (&Rotate::Rotate3D(_, _, _, a), &Rotate::None) | + (&Rotate::None, &Rotate::Rotate3D(_, _, _, a)) => { + a.compute_squared_distance(&Angle::zero()) + }, + (&Rotate::Rotate3D(_, ..), _) | (_, &Rotate::Rotate3D(_, ..)) => { + let (from, to) = (self.resolve(), other.resolve()); + let (mut fx, mut fy, mut fz, angle1) = + transform::get_normalized_vector_and_angle(from.0, from.1, from.2, from.3); + let (mut tx, mut ty, mut tz, angle2) = + transform::get_normalized_vector_and_angle(to.0, to.1, to.2, to.3); + + if angle1 == Angle::zero() { + fx = tx; + fy = ty; + fz = tz; + } else if angle2 == Angle::zero() { + tx = fx; + ty = fy; + tz = fz; + } + + if (fx, fy, fz) == (tx, ty, tz) { + angle1.compute_squared_distance(&angle2) + } else { + let v1 = DirectionVector::new(fx, fy, fz); + let v2 = DirectionVector::new(tx, ty, tz); + let q1 = Quaternion::from_direction_and_angle(&v1, angle1.radians64()); + let q2 = Quaternion::from_direction_and_angle(&v2, angle2.radians64()); + q1.compute_squared_distance(&q2) + } + }, + (&Rotate::Rotate(_), _) | (_, &Rotate::Rotate(_)) => self + .resolve() + .3 + .compute_squared_distance(&other.resolve().3), + } + } +} + +/// <https://drafts.csswg.org/css-transforms-2/#propdef-translate> +impl ComputedTranslate { + fn resolve(&self) -> (LengthOrPercentage, LengthOrPercentage, Length) { + // According to the spec: + // https://drafts.csswg.org/css-transforms-2/#individual-transforms + // + // Unspecified translations default to 0px + match *self { + Translate::None => ( + LengthOrPercentage::zero(), + LengthOrPercentage::zero(), + Length::zero(), + ), + Translate::Translate3D(tx, ty, tz) => (tx, ty, tz), + Translate::Translate(tx, ty) => (tx, ty, Length::zero()), + } + } +} + +impl Animate for ComputedTranslate { + #[inline] + fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { + match (self, other) { + (&Translate::None, &Translate::None) => Ok(Translate::None), + (&Translate::Translate3D(_, ..), _) | (_, &Translate::Translate3D(_, ..)) => { + let (from, to) = (self.resolve(), other.resolve()); + Ok(Translate::Translate3D( + from.0.animate(&to.0, procedure)?, + from.1.animate(&to.1, procedure)?, + from.2.animate(&to.2, procedure)?, + )) + }, + (&Translate::Translate(_, ..), _) | (_, &Translate::Translate(_, ..)) => { + let (from, to) = (self.resolve(), other.resolve()); + Ok(Translate::Translate( + from.0.animate(&to.0, procedure)?, + from.1.animate(&to.1, procedure)?, + )) + }, + } + } +} + +impl ComputeSquaredDistance for ComputedTranslate { + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { + let (from, to) = (self.resolve(), other.resolve()); + Ok(from.0.compute_squared_distance(&to.0)? + + from.1.compute_squared_distance(&to.1)? + + from.2.compute_squared_distance(&to.2)?) + } +} + +/// <https://drafts.csswg.org/css-transforms-2/#propdef-scale> +impl ComputedScale { + fn resolve(&self) -> (Number, Number, Number) { + // According to the spec: + // https://drafts.csswg.org/css-transforms-2/#individual-transforms + // + // Unspecified scales default to 1 + match *self { + Scale::None => (1.0, 1.0, 1.0), + Scale::Scale3D(sx, sy, sz) => (sx, sy, sz), + Scale::Scale(sx, sy) => (sx, sy, 1.), + } + } +} + +impl Animate for ComputedScale { + #[inline] + fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> { + match (self, other) { + (&Scale::None, &Scale::None) => Ok(Scale::None), + (&Scale::Scale3D(_, ..), _) | (_, &Scale::Scale3D(_, ..)) => { + let (from, to) = (self.resolve(), other.resolve()); + // For transform lists, we add by appending to the list of + // transform functions. However, ComputedScale cannot be + // simply concatenated, so we have to calculate the additive + // result here. + if procedure == Procedure::Add { + // scale(x1,y1,z1)*scale(x2,y2,z2) = scale(x1*x2, y1*y2, z1*z2) + return Ok(Scale::Scale3D(from.0 * to.0, from.1 * to.1, from.2 * to.2)); + } + Ok(Scale::Scale3D( + animate_multiplicative_factor(from.0, to.0, procedure)?, + animate_multiplicative_factor(from.1, to.1, procedure)?, + animate_multiplicative_factor(from.2, to.2, procedure)?, + )) + }, + (&Scale::Scale(_, ..), _) | (_, &Scale::Scale(_, ..)) => { + let (from, to) = (self.resolve(), other.resolve()); + // As with Scale3D, addition needs special handling. + if procedure == Procedure::Add { + // scale(x1,y1)*scale(x2,y2) = scale(x1*x2, y1*y2) + return Ok(Scale::Scale(from.0 * to.0, from.1 * to.1)); + } + Ok(Scale::Scale( + animate_multiplicative_factor(from.0, to.0, procedure)?, + animate_multiplicative_factor(from.1, to.1, procedure)?, + )) + }, + } + } +} + +impl ComputeSquaredDistance for ComputedScale { + #[inline] + fn compute_squared_distance(&self, other: &Self) -> Result<SquaredDistance, ()> { + let (from, to) = (self.resolve(), other.resolve()); + Ok(from.0.compute_squared_distance(&to.0)? + + from.1.compute_squared_distance(&to.1)? + + from.2.compute_squared_distance(&to.2)?) + } +} diff --git a/components/style/values/computed/transform.rs b/components/style/values/computed/transform.rs index d0ea167f4e1..e3493131544 100644 --- a/components/style/values/computed/transform.rs +++ b/components/style/values/computed/transform.rs @@ -5,6 +5,7 @@ //! Computed types for CSS values that are related to transformations. use super::CSSFloat; +use crate::values::animated::transform::{Perspective, Scale3D, Translate3D}; use crate::values::animated::ToAnimatedZero; use crate::values::computed::{Angle, Integer, Length, LengthOrPercentage, Number, Percentage}; use crate::values::generics::transform as generic; @@ -47,8 +48,8 @@ pub type Matrix = generic::Matrix<Number>; // matrices instead of being split across lines #[cfg_attr(rustfmt, rustfmt_skip)] impl Matrix3D { - #[inline] /// Get an identity matrix + #[inline] pub fn identity() -> Self { Self { m11: 1.0, m12: 0.0, m13: 0.0, m14: 0.0, @@ -59,6 +60,7 @@ impl Matrix3D { } /// Convert to a 2D Matrix + #[inline] pub fn into_2d(self) -> Result<Matrix, ()> { if self.m13 == 0. && self.m23 == 0. && self.m31 == 0. && self.m32 == 0. && @@ -73,6 +75,253 @@ impl Matrix3D { Err(()) } } + + /// Return true if this has 3D components. + #[inline] + pub fn is_3d(&self) -> bool { + self.m13 != 0.0 || self.m14 != 0.0 || + self.m23 != 0.0 || self.m24 != 0.0 || + self.m31 != 0.0 || self.m32 != 0.0 || + self.m33 != 1.0 || self.m34 != 0.0 || + self.m43 != 0.0 || self.m44 != 1.0 + } + + /// Return determinant value. + #[inline] + pub fn determinant(&self) -> CSSFloat { + self.m14 * self.m23 * self.m32 * self.m41 - + self.m13 * self.m24 * self.m32 * self.m41 - + self.m14 * self.m22 * self.m33 * self.m41 + + self.m12 * self.m24 * self.m33 * self.m41 + + self.m13 * self.m22 * self.m34 * self.m41 - + self.m12 * self.m23 * self.m34 * self.m41 - + self.m14 * self.m23 * self.m31 * self.m42 + + self.m13 * self.m24 * self.m31 * self.m42 + + self.m14 * self.m21 * self.m33 * self.m42 - + self.m11 * self.m24 * self.m33 * self.m42 - + self.m13 * self.m21 * self.m34 * self.m42 + + self.m11 * self.m23 * self.m34 * self.m42 + + self.m14 * self.m22 * self.m31 * self.m43 - + self.m12 * self.m24 * self.m31 * self.m43 - + self.m14 * self.m21 * self.m32 * self.m43 + + self.m11 * self.m24 * self.m32 * self.m43 + + self.m12 * self.m21 * self.m34 * self.m43 - + self.m11 * self.m22 * self.m34 * self.m43 - + self.m13 * self.m22 * self.m31 * self.m44 + + self.m12 * self.m23 * self.m31 * self.m44 + + self.m13 * self.m21 * self.m32 * self.m44 - + self.m11 * self.m23 * self.m32 * self.m44 - + self.m12 * self.m21 * self.m33 * self.m44 + + self.m11 * self.m22 * self.m33 * self.m44 + } + + /// Transpose a matrix. + #[inline] + pub fn transpose(&self) -> Self { + Self { + m11: self.m11, m12: self.m21, m13: self.m31, m14: self.m41, + m21: self.m12, m22: self.m22, m23: self.m32, m24: self.m42, + m31: self.m13, m32: self.m23, m33: self.m33, m34: self.m43, + m41: self.m14, m42: self.m24, m43: self.m34, m44: self.m44, + } + } + + /// Return inverse matrix. + pub fn inverse(&self) -> Result<Matrix3D, ()> { + let mut det = self.determinant(); + + if det == 0.0 { + return Err(()); + } + + det = 1.0 / det; + let x = Matrix3D { + m11: det * + (self.m23 * self.m34 * self.m42 - self.m24 * self.m33 * self.m42 + + self.m24 * self.m32 * self.m43 - self.m22 * self.m34 * self.m43 - + self.m23 * self.m32 * self.m44 + self.m22 * self.m33 * self.m44), + m12: det * + (self.m14 * self.m33 * self.m42 - self.m13 * self.m34 * self.m42 - + self.m14 * self.m32 * self.m43 + self.m12 * self.m34 * self.m43 + + self.m13 * self.m32 * self.m44 - self.m12 * self.m33 * self.m44), + m13: det * + (self.m13 * self.m24 * self.m42 - self.m14 * self.m23 * self.m42 + + self.m14 * self.m22 * self.m43 - self.m12 * self.m24 * self.m43 - + self.m13 * self.m22 * self.m44 + self.m12 * self.m23 * self.m44), + m14: det * + (self.m14 * self.m23 * self.m32 - self.m13 * self.m24 * self.m32 - + self.m14 * self.m22 * self.m33 + self.m12 * self.m24 * self.m33 + + self.m13 * self.m22 * self.m34 - self.m12 * self.m23 * self.m34), + m21: det * + (self.m24 * self.m33 * self.m41 - self.m23 * self.m34 * self.m41 - + self.m24 * self.m31 * self.m43 + self.m21 * self.m34 * self.m43 + + self.m23 * self.m31 * self.m44 - self.m21 * self.m33 * self.m44), + m22: det * + (self.m13 * self.m34 * self.m41 - self.m14 * self.m33 * self.m41 + + self.m14 * self.m31 * self.m43 - self.m11 * self.m34 * self.m43 - + self.m13 * self.m31 * self.m44 + self.m11 * self.m33 * self.m44), + m23: det * + (self.m14 * self.m23 * self.m41 - self.m13 * self.m24 * self.m41 - + self.m14 * self.m21 * self.m43 + self.m11 * self.m24 * self.m43 + + self.m13 * self.m21 * self.m44 - self.m11 * self.m23 * self.m44), + m24: det * + (self.m13 * self.m24 * self.m31 - self.m14 * self.m23 * self.m31 + + self.m14 * self.m21 * self.m33 - self.m11 * self.m24 * self.m33 - + self.m13 * self.m21 * self.m34 + self.m11 * self.m23 * self.m34), + m31: det * + (self.m22 * self.m34 * self.m41 - self.m24 * self.m32 * self.m41 + + self.m24 * self.m31 * self.m42 - self.m21 * self.m34 * self.m42 - + self.m22 * self.m31 * self.m44 + self.m21 * self.m32 * self.m44), + m32: det * + (self.m14 * self.m32 * self.m41 - self.m12 * self.m34 * self.m41 - + self.m14 * self.m31 * self.m42 + self.m11 * self.m34 * self.m42 + + self.m12 * self.m31 * self.m44 - self.m11 * self.m32 * self.m44), + m33: det * + (self.m12 * self.m24 * self.m41 - self.m14 * self.m22 * self.m41 + + self.m14 * self.m21 * self.m42 - self.m11 * self.m24 * self.m42 - + self.m12 * self.m21 * self.m44 + self.m11 * self.m22 * self.m44), + m34: det * + (self.m14 * self.m22 * self.m31 - self.m12 * self.m24 * self.m31 - + self.m14 * self.m21 * self.m32 + self.m11 * self.m24 * self.m32 + + self.m12 * self.m21 * self.m34 - self.m11 * self.m22 * self.m34), + m41: det * + (self.m23 * self.m32 * self.m41 - self.m22 * self.m33 * self.m41 - + self.m23 * self.m31 * self.m42 + self.m21 * self.m33 * self.m42 + + self.m22 * self.m31 * self.m43 - self.m21 * self.m32 * self.m43), + m42: det * + (self.m12 * self.m33 * self.m41 - self.m13 * self.m32 * self.m41 + + self.m13 * self.m31 * self.m42 - self.m11 * self.m33 * self.m42 - + self.m12 * self.m31 * self.m43 + self.m11 * self.m32 * self.m43), + m43: det * + (self.m13 * self.m22 * self.m41 - self.m12 * self.m23 * self.m41 - + self.m13 * self.m21 * self.m42 + self.m11 * self.m23 * self.m42 + + self.m12 * self.m21 * self.m43 - self.m11 * self.m22 * self.m43), + m44: det * + (self.m12 * self.m23 * self.m31 - self.m13 * self.m22 * self.m31 + + self.m13 * self.m21 * self.m32 - self.m11 * self.m23 * self.m32 - + self.m12 * self.m21 * self.m33 + self.m11 * self.m22 * self.m33), + }; + + Ok(x) + } + + /// Multiply `pin * self`. + #[inline] + pub fn pre_mul_point4(&self, pin: &[f32; 4]) -> [f32; 4] { + [ + pin[0] * self.m11 + pin[1] * self.m21 + pin[2] * self.m31 + pin[3] * self.m41, + pin[0] * self.m12 + pin[1] * self.m22 + pin[2] * self.m32 + pin[3] * self.m42, + pin[0] * self.m13 + pin[1] * self.m23 + pin[2] * self.m33 + pin[3] * self.m43, + pin[0] * self.m14 + pin[1] * self.m24 + pin[2] * self.m34 + pin[3] * self.m44, + ] + } + + /// Return the multiplication of two 4x4 matrices. + #[inline] + pub fn multiply(&self, other: &Self) -> Self { + Matrix3D { + m11: self.m11 * other.m11 + self.m12 * other.m21 + + self.m13 * other.m31 + self.m14 * other.m41, + m12: self.m11 * other.m12 + self.m12 * other.m22 + + self.m13 * other.m32 + self.m14 * other.m42, + m13: self.m11 * other.m13 + self.m12 * other.m23 + + self.m13 * other.m33 + self.m14 * other.m43, + m14: self.m11 * other.m14 + self.m12 * other.m24 + + self.m13 * other.m34 + self.m14 * other.m44, + m21: self.m21 * other.m11 + self.m22 * other.m21 + + self.m23 * other.m31 + self.m24 * other.m41, + m22: self.m21 * other.m12 + self.m22 * other.m22 + + self.m23 * other.m32 + self.m24 * other.m42, + m23: self.m21 * other.m13 + self.m22 * other.m23 + + self.m23 * other.m33 + self.m24 * other.m43, + m24: self.m21 * other.m14 + self.m22 * other.m24 + + self.m23 * other.m34 + self.m24 * other.m44, + m31: self.m31 * other.m11 + self.m32 * other.m21 + + self.m33 * other.m31 + self.m34 * other.m41, + m32: self.m31 * other.m12 + self.m32 * other.m22 + + self.m33 * other.m32 + self.m34 * other.m42, + m33: self.m31 * other.m13 + self.m32 * other.m23 + + self.m33 * other.m33 + self.m34 * other.m43, + m34: self.m31 * other.m14 + self.m32 * other.m24 + + self.m33 * other.m34 + self.m34 * other.m44, + m41: self.m41 * other.m11 + self.m42 * other.m21 + + self.m43 * other.m31 + self.m44 * other.m41, + m42: self.m41 * other.m12 + self.m42 * other.m22 + + self.m43 * other.m32 + self.m44 * other.m42, + m43: self.m41 * other.m13 + self.m42 * other.m23 + + self.m43 * other.m33 + self.m44 * other.m43, + m44: self.m41 * other.m14 + self.m42 * other.m24 + + self.m43 * other.m34 + self.m44 * other.m44, + } + } + + /// Scale the matrix by a factor. + #[inline] + pub fn scale_by_factor(&mut self, scaling_factor: CSSFloat) { + self.m11 *= scaling_factor; + self.m12 *= scaling_factor; + self.m13 *= scaling_factor; + self.m14 *= scaling_factor; + self.m21 *= scaling_factor; + self.m22 *= scaling_factor; + self.m23 *= scaling_factor; + self.m24 *= scaling_factor; + self.m31 *= scaling_factor; + self.m32 *= scaling_factor; + self.m33 *= scaling_factor; + self.m34 *= scaling_factor; + self.m41 *= scaling_factor; + self.m42 *= scaling_factor; + self.m43 *= scaling_factor; + self.m44 *= scaling_factor; + } + + /// Return the matrix 3x3 part (top-left corner). + /// This is used by retrieving the scale and shear factors + /// during decomposing a 3d matrix. + #[inline] + pub fn get_matrix_3x3_part(&self) -> [[f32; 3]; 3] { + [ + [ self.m11, self.m12, self.m13 ], + [ self.m21, self.m22, self.m23 ], + [ self.m31, self.m32, self.m33 ], + ] + } + + /// Set perspective on the matrix. + #[inline] + pub fn set_perspective(&mut self, perspective: &Perspective) { + self.m14 = perspective.0; + self.m24 = perspective.1; + self.m34 = perspective.2; + self.m44 = perspective.3; + } + + /// Apply translate on the matrix. + #[inline] + pub fn apply_translate(&mut self, translate: &Translate3D) { + self.m41 += translate.0 * self.m11 + translate.1 * self.m21 + translate.2 * self.m31; + self.m42 += translate.0 * self.m12 + translate.1 * self.m22 + translate.2 * self.m32; + self.m43 += translate.0 * self.m13 + translate.1 * self.m23 + translate.2 * self.m33; + self.m44 += translate.0 * self.m14 + translate.1 * self.m24 + translate.2 * self.m34; + } + + /// Apply scale on the matrix. + #[inline] + pub fn apply_scale(&mut self, scale: &Scale3D) { + self.m11 *= scale.0; + self.m12 *= scale.0; + self.m13 *= scale.0; + self.m14 *= scale.0; + self.m21 *= scale.1; + self.m22 *= scale.1; + self.m23 *= scale.1; + self.m24 *= scale.1; + self.m31 *= scale.2; + self.m32 *= scale.2; + self.m33 *= scale.2; + self.m34 *= scale.2; + } } #[cfg_attr(rustfmt, rustfmt_skip)] diff --git a/components/style/values/generics/transform.rs b/components/style/values/generics/transform.rs index 0cf8f490bb3..91af1355a27 100644 --- a/components/style/values/generics/transform.rs +++ b/components/style/values/generics/transform.rs @@ -529,15 +529,7 @@ pub fn get_normalized_vector_and_angle<T: Zero>( } #[derive( - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedZero, - ToComputedValue, + Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToAnimatedZero, ToComputedValue, )] /// A value of the `Rotate` property /// @@ -599,15 +591,7 @@ where } #[derive( - Clone, - ComputeSquaredDistance, - Copy, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedZero, - ToComputedValue, + Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToAnimatedZero, ToComputedValue, )] /// A value of the `Scale` property /// @@ -648,14 +632,7 @@ impl<Number: ToCss + PartialEq> ToCss for Scale<Number> { } #[derive( - Clone, - ComputeSquaredDistance, - Debug, - MallocSizeOf, - PartialEq, - SpecifiedValueInfo, - ToAnimatedZero, - ToComputedValue, + Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToAnimatedZero, ToComputedValue, )] /// A value of the `Translate` property /// diff --git a/components/style/values/specified/border.rs b/components/style/values/specified/border.rs index 139e424ffd1..cf60c91b329 100644 --- a/components/style/values/specified/border.rs +++ b/components/style/values/specified/border.rs @@ -19,6 +19,48 @@ use cssparser::Parser; use std::fmt::{self, Write}; use style_traits::{CssWriter, ParseError, ToCss}; +/// A specified value for a single side of a `border-style` property. +/// +/// The order here corresponds to the integer values from the border conflict +/// resolution rules in CSS 2.1 § 17.6.2.1. Higher values override lower values. +#[allow(missing_docs)] +#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] +#[derive( + Clone, + Copy, + Debug, + Eq, + MallocSizeOf, + Ord, + Parse, + PartialEq, + PartialOrd, + SpecifiedValueInfo, + ToComputedValue, + ToCss, +)] +#[repr(u8)] +pub enum BorderStyle { + Hidden, + None, + Inset, + Groove, + Outset, + Ridge, + Dotted, + Dashed, + Solid, + Double, +} + +impl BorderStyle { + /// Whether this border style is either none or hidden. + #[inline] + pub fn none_or_hidden(&self) -> bool { + matches!(*self, BorderStyle::None | BorderStyle::Hidden) + } +} + /// A specified value for a single side of the `border-width` property. #[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss)] pub enum BorderSideWidth { diff --git a/components/style/values/specified/box.rs b/components/style/values/specified/box.rs index 3bd89efd20e..78f29ccf6de 100644 --- a/components/style/values/specified/box.rs +++ b/components/style/values/specified/box.rs @@ -178,30 +178,6 @@ impl Display { } } - /// Whether `new_display` should be ignored, given a previous - /// `old_display` value. - /// - /// This is used to ignore `display: -moz-box` declarations after an - /// equivalent `display: -webkit-box` declaration, since the former - /// has a vastly different meaning. See bug 1107378 and bug 1407701. - /// - /// FIXME(emilio): This is a pretty decent hack, we should try to - /// remove it. - pub fn should_ignore_parsed_value(_old_display: Self, _new_display: Self) -> bool { - #[cfg(feature = "gecko")] - { - match (_old_display, _new_display) { - (Display::WebkitBox, Display::MozBox) | - (Display::WebkitInlineBox, Display::MozInlineBox) => { - return true; - }, - _ => {}, - } - } - - return false; - } - /// Returns whether this "display" value is one of the types for /// ruby. #[cfg(feature = "gecko")] @@ -1155,10 +1131,11 @@ pub enum Appearance { TabScrollArrowBack, #[parse(condition = "in_ua_or_chrome_sheet")] TabScrollArrowForward, - /// A textfield or text area. + /// A multi-line text field, e.g. HTML <textarea>. + #[parse(aliases = "textfield-multiline")] + Textarea, + /// A single-line text field, e.g. HTML <input type=text>. Textfield, - /// A multiline text field. - TextfieldMultiline, /// A toolbar in an application window. #[parse(condition = "in_ua_or_chrome_sheet")] Toolbar, @@ -1309,13 +1286,58 @@ pub enum Appearance { )] #[repr(u8)] pub enum BreakBetween { - Auto, Always, + Auto, + Page, Avoid, Left, Right, } +impl BreakBetween { + /// Parse a legacy break-between value for `page-break-*`. + /// + /// See https://drafts.csswg.org/css-break/#page-break-properties. + #[inline] + pub fn parse_legacy<'i>(input: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> { + let location = input.current_source_location(); + let ident = input.expect_ident()?; + let break_value = match BreakBetween::from_ident(ident) { + Ok(v) => v, + Err(()) => { + return Err(location + .new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone()))) + }, + }; + match break_value { + BreakBetween::Always => Ok(BreakBetween::Page), + BreakBetween::Auto | BreakBetween::Avoid | BreakBetween::Left | BreakBetween::Right => { + Ok(break_value) + }, + BreakBetween::Page => { + Err(location + .new_custom_error(SelectorParseErrorKind::UnexpectedIdent(ident.clone()))) + }, + } + } + + /// Serialize a legacy break-between value for `page-break-*`. + /// + /// See https://drafts.csswg.org/css-break/#page-break-properties. + pub fn to_css_legacy<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + match *self { + BreakBetween::Auto | BreakBetween::Avoid | BreakBetween::Left | BreakBetween::Right => { + self.to_css(dest) + }, + BreakBetween::Page => dest.write_str("always"), + BreakBetween::Always => Ok(()), + } + } +} + /// A kind of break within a box. /// /// https://drafts.csswg.org/css-break/#break-within diff --git a/components/style/values/specified/mod.rs b/components/style/values/specified/mod.rs index 64f0255762d..c185dce80d0 100644 --- a/components/style/values/specified/mod.rs +++ b/components/style/values/specified/mod.rs @@ -34,7 +34,7 @@ pub use self::background::{BackgroundRepeat, BackgroundSize}; pub use self::basic_shape::FillRule; pub use self::border::{BorderCornerRadius, BorderImageSlice, BorderImageWidth}; pub use self::border::{BorderImageRepeat, BorderImageSideWidth}; -pub use self::border::{BorderRadius, BorderSideWidth, BorderSpacing}; +pub use self::border::{BorderRadius, BorderSideWidth, BorderSpacing, BorderStyle}; pub use self::box_::{AnimationIterationCount, AnimationName, Contain, Display}; pub use self::box_::{Appearance, BreakBetween, BreakWithin, Clear, Float}; pub use self::box_::{OverflowClipBox, OverscrollBehavior, Perspective, Resize}; @@ -152,46 +152,6 @@ fn parse_number_with_clamping_mode<'i, 't>( }) } -// The integer values here correspond to the border conflict resolution rules in CSS 2.1 § -// 17.6.2.1. Higher values override lower values. -// -// FIXME(emilio): Should move to border.rs -#[allow(missing_docs)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Ord, - Parse, - PartialEq, - PartialOrd, - SpecifiedValueInfo, - ToComputedValue, - ToCss, -)] -pub enum BorderStyle { - None = -1, - Solid = 6, - Double = 7, - Dotted = 4, - Dashed = 5, - Hidden = -2, - Groove = 1, - Ridge = 3, - Inset = 0, - Outset = 2, -} - -impl BorderStyle { - /// Whether this border style is either none or hidden. - pub fn none_or_hidden(&self) -> bool { - matches!(*self, BorderStyle::None | BorderStyle::Hidden) - } -} - /// A CSS `<number>` specified value. /// /// https://drafts.csswg.org/css-values-3/#number-value diff --git a/components/style/values/specified/outline.rs b/components/style/values/specified/outline.rs index b85e690a2ba..398315cbf93 100644 --- a/components/style/values/specified/outline.rs +++ b/components/style/values/specified/outline.rs @@ -23,19 +23,20 @@ use style_traits::ParseError; ToComputedValue, ToCss, )] +#[repr(C, u8)] /// <https://drafts.csswg.org/css-ui/#propdef-outline-style> pub enum OutlineStyle { /// auto Auto, /// <border-style> - Other(BorderStyle), + BorderStyle(BorderStyle), } impl OutlineStyle { #[inline] /// Get default value as None pub fn none() -> OutlineStyle { - OutlineStyle::Other(BorderStyle::None) + OutlineStyle::BorderStyle(BorderStyle::None) } #[inline] @@ -43,7 +44,7 @@ impl OutlineStyle { pub fn none_or_hidden(&self) -> bool { match *self { OutlineStyle::Auto => false, - OutlineStyle::Other(ref border_style) => border_style.none_or_hidden(), + OutlineStyle::BorderStyle(ref style) => style.none_or_hidden(), } } } @@ -59,7 +60,7 @@ impl Parse for OutlineStyle { .new_custom_error(SelectorParseErrorKind::UnexpectedIdent("hidden".into()))); } - return Ok(OutlineStyle::Other(border_style)); + return Ok(OutlineStyle::BorderStyle(border_style)); } input.expect_ident_matching("auto")?; diff --git a/components/style/values/specified/ui.rs b/components/style/values/specified/ui.rs index 200bff54032..4095bdfa89b 100644 --- a/components/style/values/specified/ui.rs +++ b/components/style/values/specified/ui.rs @@ -141,11 +141,6 @@ impl Parse for ScrollbarColor { } } -fn in_ua_sheet(context: &ParserContext) -> bool { - use crate::stylesheets::Origin; - context.stylesheet_origin == Origin::UserAgent -} - /// The specified value for the `user-select` property. /// /// https://drafts.csswg.org/css-ui-4/#propdef-user-select @@ -168,15 +163,6 @@ pub enum UserSelect { Text, #[parse(aliases = "-moz-none")] None, - /// Force selection of all children, unless an ancestor has `none` set. + /// Force selection of all children. All, - /// Like `text`, except that it won't get overridden by ancestors having - /// `all`. - /// - /// FIXME(emilio): This only has one use in contenteditable.css, can we find - /// a better way to do this? - /// - /// See bug 1181130. - #[parse(condition = "in_ua_sheet")] - MozText, } diff --git a/tests/wpt/metadata/css/css-variables/variable-exponential-blowup.html.ini b/tests/wpt/metadata/css/css-variables/variable-exponential-blowup.html.ini deleted file mode 100644 index e7be20cdb5b..00000000000 --- a/tests/wpt/metadata/css/css-variables/variable-exponential-blowup.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[variable-exponential-blowup.html] - expected: TIMEOUT |