aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--components/layout/display_list/builder.rs7
-rw-r--r--components/style/cbindgen.toml3
-rw-r--r--components/style/custom_properties.rs60
-rw-r--r--components/style/gecko/conversions.rs35
-rw-r--r--components/style/properties/declaration_block.rs20
-rw-r--r--components/style/properties/gecko.mako.rs129
-rw-r--r--components/style/properties/helpers/animated_properties.mako.rs1741
-rw-r--r--components/style/properties/longhands/background.mako.rs3
-rw-r--r--components/style/properties/longhands/border.mako.rs4
-rw-r--r--components/style/properties/longhands/box.mako.rs14
-rw-r--r--components/style/properties/longhands/column.mako.rs8
-rw-r--r--components/style/properties/longhands/inherited_text.mako.rs1
-rw-r--r--components/style/properties/longhands/ui.mako.rs1
-rw-r--r--components/style/properties/properties.mako.rs29
-rw-r--r--components/style/properties/shorthands/box.mako.rs48
-rw-r--r--components/style/rule_tree/mod.rs1
-rw-r--r--components/style/values/animated/mod.rs11
-rw-r--r--components/style/values/animated/transform.rs1502
-rw-r--r--components/style/values/computed/transform.rs251
-rw-r--r--components/style/values/generics/transform.rs29
-rw-r--r--components/style/values/specified/border.rs42
-rw-r--r--components/style/values/specified/box.rs78
-rw-r--r--components/style/values/specified/mod.rs42
-rw-r--r--components/style/values/specified/outline.rs9
-rw-r--r--components/style/values/specified/ui.rs16
-rw-r--r--tests/wpt/metadata/css/css-variables/variable-exponential-blowup.html.ini2
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